Skip to content

Multiple bugs in mcp library related to async context management #1095

Closed
@PsychArch

Description

@PsychArch

Initial Checks

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

  1. Create a system with two agents where one agent (a "director") can call another agent (a "worker") as a tool.
  2. The director agent is invoked, and it in turn calls the worker agent.
  3. This creates a nested MCP client/server interaction.
  4. 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:

  1. Suppressed RuntimeError: In mcp/shared/session.py, the _receive_loop method uses a broad except Exception block. This catches the RuntimeError raised by RequestResponder's synchronous __enter__ method and logs it as a non-fatal warning, hiding the true nature of the problem.

  2. Incorrect RequestResponder Usage: In mcp/server/session.py, the _received_request method handles InitializeRequest by using with responder:. Since RequestResponder is an async context manager, it must be used with async with responder:.

  3. Incorrect anyio.CancelScope Usage: In mcp/shared/session.py, the RequestResponder's __aenter__ and __aexit__ methods incorrectly await the __enter__ and __exit__ methods of an anyio.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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions