Skip to content

Commit dfb3295

Browse files
Egor EgerevEgor Egerev
authored andcommitted
fix: shield cleanup operations from cancel scope conflicts
Apply CancelScope(shield=True) to prevent RuntimeError when cleaning up HTTP MCP clients. This fixes the 'Attempted to exit cancel scope in a different task' error that occurs with asyncio. Changes: - session.py: Shield cleanup in _receive_loop finally block - streamable_http.py: Shield terminate_session and stream cleanup Based on v1.26.0 to maintain compatibility with openai-agents SDK. Related to: modelcontextprotocol#577 Based on: modelcontextprotocol#1817
1 parent 3d9d345 commit dfb3295

2 files changed

Lines changed: 19 additions & 13 deletions

File tree

src/mcp/client/streamable_http.py

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -673,12 +673,16 @@ def start_get_stream() -> None:
673673
transport.get_session_id,
674674
)
675675
finally:
676-
if transport.session_id and terminate_on_close:
677-
await transport.terminate_session(client)
676+
# Shield cleanup from cancellation to prevent cancel scope conflicts
677+
with anyio.CancelScope(shield=True):
678+
if transport.session_id and terminate_on_close:
679+
await transport.terminate_session(client)
678680
tg.cancel_scope.cancel()
679681
finally:
680-
await read_stream_writer.aclose()
681-
await write_stream.aclose()
682+
# Shield stream cleanup from cancellation
683+
with anyio.CancelScope(shield=True):
684+
await read_stream_writer.aclose()
685+
await write_stream.aclose()
682686

683687

684688
@asynccontextmanager

src/mcp/shared/session.py

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -445,15 +445,17 @@ async def _receive_loop(self) -> None:
445445
finally:
446446
# after the read stream is closed, we need to send errors
447447
# to any pending requests
448-
for id, stream in self._response_streams.items():
449-
error = ErrorData(code=CONNECTION_CLOSED, message="Connection closed")
450-
try:
451-
await stream.send(JSONRPCError(jsonrpc="2.0", id=id, error=error))
452-
await stream.aclose()
453-
except Exception: # pragma: no cover
454-
# Stream might already be closed
455-
pass
456-
self._response_streams.clear()
448+
# Shield cleanup from cancellation to prevent cancel scope conflicts
449+
with anyio.CancelScope(shield=True):
450+
for id, stream in self._response_streams.items():
451+
error = ErrorData(code=CONNECTION_CLOSED, message="Connection closed")
452+
try:
453+
await stream.send(JSONRPCError(jsonrpc="2.0", id=id, error=error))
454+
await stream.aclose()
455+
except Exception: # pragma: no cover
456+
# Stream might already be closed
457+
pass
458+
self._response_streams.clear()
457459

458460
def _normalize_request_id(self, response_id: RequestId) -> RequestId:
459461
"""

0 commit comments

Comments
 (0)