Description
Initial Checks
- I confirm that I'm using the latest version of MCP Python SDK
- I confirm that I searched for my issue in https://github.com/modelcontextprotocol/python-sdk/issues before opening this issue
Description
Note the following is generated automatically by Gemini 2.5 Pro. Its descriptions of the scenario is accurate but I don't quite understand its fix.
Bug Report: Multiple bugs in mcp
library related to async context management
Summary
The mcp
library has a series of bugs related to asynchronous context management that cause unexpected warnings and errors, particularly in scenarios involving nested agent calls. The initial symptom is a RequestResponder must be used with 'async with'
warning, which masks a RuntimeError
. Fixing this issue reveals further bugs related to the incorrect usage of anyio.CancelScope
.
Environment
- mcp version: 1.10.1
- fastmcp version: 2.10.2
- Python version: 3.13
Steps to Reproduce
- Create a system with two agents where one agent (a "director") can call another agent (a "worker") as a tool.
- The director agent is invoked, and it in turn calls the worker agent.
- This creates a nested MCP client/server interaction.
- The bug is triggered when the nested server instance handles an
InitializeRequest
.
Expected Behavior
The nested agent interaction should complete successfully without any warnings or errors.
Actual Behavior
The following sequence of errors occurs. Initially, a warning is logged:
WARNING - Failed to validate request: RequestResponder must be used with 'async with'
This warning is the result of a suppressed RuntimeError
. After patching the library to expose the error, the following traceback is generated:
Traceback (most recent call last):
File ".../mcp/shared/session.py", line 369, in _receive_loop
await self._received_request(responder)
File ".../mcp/server/session.py", line 148, in _received_request
with responder:
File ".../mcp/shared/session.py", line 117, in __enter__
raise RuntimeError("RequestResponder must be used with 'async with'")
RuntimeError: RequestResponder must be used with 'async with'
After fixing the RuntimeError
, a new error appears:
Traceback (most recent call last):
File ".../mcp/shared/session.py", line 369, in _receive_loop
await self._received_request(responder)
File ".../mcp/server/session.py", line 148, in _received_request
async with responder:
File ".../mcp/shared/session.py", line 97, in __aenter__
await self._cancel_scope.__aenter__()
AttributeError: 'CancelScope' object has no attribute '__aenter__'. Did you mean: '__enter__'?
Root Cause Analysis
There are three distinct bugs in the mcp
library:
-
Suppressed
RuntimeError
: Inmcp/shared/session.py
, the_receive_loop
method uses a broadexcept Exception
block. This catches theRuntimeError
raised byRequestResponder
's synchronous__enter__
method and logs it as a non-fatal warning, hiding the true nature of the problem. -
Incorrect
RequestResponder
Usage: Inmcp/server/session.py
, the_received_request
method handlesInitializeRequest
by usingwith responder:
. SinceRequestResponder
is an async context manager, it must be used withasync with responder:
. -
Incorrect
anyio.CancelScope
Usage: Inmcp/shared/session.py
, theRequestResponder
's__aenter__
and__aexit__
methods incorrectlyawait
the__enter__
and__exit__
methods of ananyio.CancelScope
object.anyio.CancelScope
is a synchronous context manager, and its methods should not be awaited.
Solution and Patches
The following patches resolve the issues.
Patch 1: mcp/shared/session.py
- Narrow Exception Scope
This change exposes the underlying RuntimeError
.
--- a/.venv/lib/python3.13/site-packages/mcp/shared/session.py
+++ b/.venv/lib/python3.13/site-packages/mcp/shared/session.py
@@ -10,7 +10,7 @@
import anyio
import httpx
from anyio.streams.memory import MemoryObjectReceiveStream, MemoryObjectSendStream
-from pydantic import BaseModel
+from pydantic import BaseModel, ValidationError
from typing_extensions import Self
from mcp.shared.exceptions import McpError
@@ -382,7 +382,7 @@
if not responder._completed: # type: ignore[reportPrivateUsage]
await self._handle_incoming(responder)
- except Exception as e:
+ except ValidationError as e:
# For request validation errors, send a proper JSON-RPC error
# response instead of crashing the server
logging.warning(f"Failed to validate request: {e}")
Patch 2: mcp/server/session.py
- Use async with
This change correctly uses the RequestResponder
as an async context manager.
--- a/.venv/lib/python3.13/site-packages/mcp/server/session.py
+++ b/.venv/lib/python3.13/site-packages/mcp/server/session.py
@@ -155,7 +155,7 @@
requested_version = params.protocolVersion
self._initialization_state = InitializationState.Initializing
self._client_params = params
- with responder:
+ async with responder:
await responder.respond(
types.ServerResult(
types.InitializeResult(
Patch 3: mcp/shared/session.py
- Fix anyio.CancelScope
Usage
This change corrects the usage of the synchronous anyio.CancelScope
context manager within the async RequestResponder
.
--- a/.venv/lib/python3.13/site-packages/mcp/shared/session.py
+++ b/.venv/lib/python3.13/site-packages/mcp/shared/session.py
@@ -94,18 +94,18 @@
async def __aenter__(self) -> "RequestResponder[ReceiveRequestT, SendResultT]":
"""Enter the context manager asynchronously."""
self._entered = True
self._cancel_scope = anyio.CancelScope()
- await self._cancel_scope.__aenter__()
+ self._cancel_scope.__enter__()
return self
async def __aexit__(
self,
exc_type: type[BaseException] | None,
exc_val: BaseException | None,
exc_tb: TracebackType | None,
) -> None:
"""Exit the context manager asynchronously."""
try:
if self._completed:
self._on_complete(self)
finally:
self._entered = False
if self._cancel_scope:
- await self._cancel_scope.__aexit__(exc_type, exc_val, exc_tb)
+ self._cancel_scope.__exit__(exc_type, exc_val, exc_tb)
Example Code
Python & MCP Python SDK
- **mcp version:** 1.10.1
- **fastmcp version:** 2.10.2
- **Python version:** 3.13