From 049531095e50fde45cdbd8c0be74bf968a71086f Mon Sep 17 00:00:00 2001 From: Bortlesboat Date: Thu, 19 Mar 2026 22:24:12 -0400 Subject: [PATCH] fix: handle unbound response_or_error in send_request timeout Initialize response_or_error to None before the try block and add an explicit None check after. This prevents an UnboundLocalError when anyio's fail_after cancel scope suppresses the timeout exception at the exact deadline boundary without raising, leaving the variable unassigned (see agronholm/anyio#589). Github-Issue: #1717 Reported-by: maxisbey --- src/mcp/shared/session.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/mcp/shared/session.py b/src/mcp/shared/session.py index 9364abb73..755da6af0 100644 --- a/src/mcp/shared/session.py +++ b/src/mcp/shared/session.py @@ -270,6 +270,7 @@ async def send_request( # request read timeout takes precedence over session read timeout timeout = request_read_timeout_seconds or self._session_read_timeout_seconds + response_or_error: JSONRPCResponse | JSONRPCError | None = None try: with anyio.fail_after(timeout): response_or_error = await response_stream_reader.receive() @@ -278,6 +279,15 @@ async def send_request( message = f"Timed out while waiting for response to {class_name}. Waited {timeout} seconds." raise MCPError(code=REQUEST_TIMEOUT, message=message) + if response_or_error is None: + raise MCPError( + code=REQUEST_TIMEOUT, + message=( + f"Response not received for {request.__class__.__name__}. " + "This may indicate a race condition in the cancel scope." + ), + ) + if isinstance(response_or_error, JSONRPCError): raise MCPError.from_jsonrpc_error(response_or_error) else: