1717from mcp .client .sse import sse_client
1818from mcp .client .streamable_http import streamable_http_client
1919
20+ # Establishing the connection is not a tool call, so it keeps its own short budget.
21+ # Matches the connect timeout the MCP SDK's own SSE transport defaults to.
22+ CONNECT_TIMEOUT_SECONDS = 5.0
23+
2024
2125class MCPBaseClient (ABC ):
2226 """Base client for creating an MCP transport session and connecting to an MCP server.
@@ -120,7 +124,7 @@ async def connect_to_server(self):
120124 url = self ._url ,
121125 headers = self ._headers if self ._headers else None ,
122126 ) as (read , write ),
123- ClientSession (read , write ) as session ,
127+ ClientSession (read , write , read_timeout_seconds = self . _tool_call_timeout ) as session ,
124128 ):
125129 await session .initialize ()
126130 yield session
@@ -197,7 +201,7 @@ async def connect_to_server(self):
197201 )
198202 async with (
199203 stdio_client (server_params ) as (read , write ),
200- ClientSession (read , write ) as session ,
204+ ClientSession (read , write , read_timeout_seconds = self . _tool_call_timeout ) as session ,
201205 ):
202206 await session .initialize ()
203207 yield session
@@ -273,9 +277,19 @@ async def connect_to_server(self):
273277 httpx.HTTPStatusError: If server returns HTTP error (e.g., 401 Unauthorized, 500)
274278 RuntimeError: If session initialization fails (MCP protocol error)
275279 """
276- # Create httpx client with custom headers
277- # streamable_http_client expects a pre-configured httpx.AsyncClient for headers
278- http_client = httpx .AsyncClient (headers = self ._headers if self ._headers else None )
280+ # Create httpx client with custom headers.
281+ # streamable_http_client expects a pre-configured httpx.AsyncClient, so this
282+ # client's timeouts are the only ones that apply: the transport has no timeout
283+ # arguments of its own to fall back on. Reading the response has to be allowed
284+ # to take as long as a tool call may take, or a slow tool's reply arrives on a
285+ # stream httpx already abandoned and the caller waits forever.
286+ http_client = httpx .AsyncClient (
287+ headers = self ._headers if self ._headers else None ,
288+ timeout = httpx .Timeout (
289+ self ._tool_call_timeout .total_seconds (),
290+ connect = CONNECT_TIMEOUT_SECONDS ,
291+ ),
292+ )
279293
280294 try :
281295 async with (
@@ -288,7 +302,9 @@ async def connect_to_server(self):
288302 ):
289303 # Store the session ID callback for later retrieval
290304 self ._get_mcp_session_id = get_session_id
291- async with ClientSession (read , write ) as session :
305+ async with ClientSession (
306+ read , write , read_timeout_seconds = self ._tool_call_timeout
307+ ) as session :
292308 await session .initialize ()
293309 yield session
294310 finally :
@@ -303,6 +319,7 @@ def create_mcp_client(
303319 args : list [str ] | None = None ,
304320 env : dict [str , str ] | None = None ,
305321 headers : dict [str , str ] | None = None ,
322+ tool_call_timeout : timedelta = timedelta (seconds = 60 ),
306323) -> MCPBaseClient :
307324 """Create an MCP client based on the transport type and configuration.
308325
@@ -313,6 +330,7 @@ def create_mcp_client(
313330 args: Command arguments (optional, for stdio transport)
314331 env: Environment variables for the server process (optional, for stdio transport)
315332 headers: Optional custom HTTP headers to include in requests (for HTTP transports)
333+ tool_call_timeout: How long one tool call may take before it fails
316334
317335 Returns:
318336 An MCPBaseClient instance configured for the specified transport
@@ -328,15 +346,19 @@ def create_mcp_client(
328346 case "stdio" :
329347 if command is None :
330348 raise ValueError ("command must be provided for stdio transport" )
331- return MCPStdioClient (command = command , args = args , env = env )
349+ return MCPStdioClient (
350+ command = command , args = args , env = env , tool_call_timeout = tool_call_timeout
351+ )
332352 case "sse" :
333353 if url is None :
334354 raise ValueError ("url must be provided for sse transport" )
335- return MCPSSEClient (url = url , headers = headers )
355+ return MCPSSEClient (url = url , headers = headers , tool_call_timeout = tool_call_timeout )
336356 case "streamable-http" :
337357 if url is None :
338358 raise ValueError ("url must be provided for streamable-http transport" )
339- return MCPStreamableHTTPClient (url = url , headers = headers )
359+ return MCPStreamableHTTPClient (
360+ url = url , headers = headers , tool_call_timeout = tool_call_timeout
361+ )
340362 case _:
341363 raise ValueError (
342364 f"Unsupported transport type: { transport } . Use 'stdio', 'sse', or 'streamable-http'"
0 commit comments