-
Notifications
You must be signed in to change notification settings - Fork 2.9k
Description
Description
When attempting to stop an SSE client connection created with mcp.client.sse.sse_client(), the application throws a RuntimeError related to AnyIO cancel scopes being exited in different tasks than they were entered in. This prevents proper application shutdown and can lead to resource leaks.
Steps to Reproduce
Set up an SSE client using the mcp.client.sse.sse_client() function
Establish a connection using the aenter() method
Attempt to close the connection using the aexit() method during application shutdown
The issue appears to be that the sse_client function in mcp.client.sse module creates AnyIO task groups or cancel scopes that are entered in one task but exited in another during cleanup. AnyIO requires that cancel scopes must be entered and exited in the same task context.
The problem is likely in the implementation of the async generator pattern in the sse_client function. When the connection is closed or an error occurs, the task group's __aexit__ method is being called in a different task than the one where __aenter__ was called, violating AnyIO's constraints.
Expected Behavior
The SSE connection should close cleanly without errors.
Actual Behavior
The application throws the following error:
RuntimeError: Attempted to exit cancel scope in a different task than it was entered in
Code Example
from mcp.client.sse import sse_client
async def connect_and_disconnect():
# Set up the client
streams_context = sse_client(url="http://example.com/sse", headers={"Authorization": "Bearer token"})
streams = await streams_context.__aenter__()
# Use the streams...
# When shutting down - this throws the RuntimeError
await streams_context.__aexit__(None, None, None)
Environment
- Python version: 3.10
- mcp package version: 1.6.0