ClientSessionGroup: 'Attempted to exit cancel scope in a different task' when streamable_http server is unreachable
When a streamable HTTP MCP server is not running, using ClientSessionGroup with StreamableHttpParameters(url="http://localhost:1/mcp/") raises 'RuntimeError: Attempted to exit cancel scope in a different task than it was entered in' at teardown instead of a catchable connection error. The same failure also occurs mid-connection on a transient 502 with mcp 1.26.0, anyio 4.12.1, httpx 0.28.1, Python 3.12.12. How can I make the failure catchable at connect_to_server and keep other servers alive?
Originally asked on GitHub: https://github.com/modelcontextprotocol/python-sdk/issues/915
4 answers
Status: still open. No fix has been merged into the python-sdk main branch as of the latest comments; the closest thing to a fix is an unmerged fork branch with passing tests.
Root cause, from comment 7: in streamable_http.py, handle_request_async runs in a child task via tg.start_soon in post_writer. When _handle_post_request raises an httpx.HTTPError such as httpx.ConnectError (for an unreachable server), the exception escapes into the transport's task group and unwinds its cancel scope from a different task than the one that entered it. The result is the RuntimeError surfacing during teardown, so a try/except around connect_to_server never sees the ConnectError. A later commenter confirmed the same teardown failure on an already-established connection when the upstream returned 502 Bad Gateway, and in a long-lived proxy this left a coroutine spinning one CPU core at 100% until the process was killed.
Workarounds that worked in the thread:
- Patch handle_request_async locally to wrap the resumption/post dispatch in try/except Exception and forward the exception with await read_stream_writer.send(e). The commenter says it works, while noting it is not an ideal fix.
- Remove tg.start_soon and call await handle_request_async() directly; the commenter reports the issue no longer appears.
- Use the fork branch fix/915-streamable-http-unreachable-server: it catches httpx.HTTPError in post_writer and completes the pending JSON-RPC waiter; its test suite and ruff checks pass, but the upstream PR was not opened.
General advice: if you need other servers to stay up when one fails, isolate each connection in its own scope rather than relying on a single shared task group.
Did this work for you?What @syf2211 tried on GitHub (Worked)
Reproduced on current main and pushed a verified fix on fork branch fix/915-streamable-http-unreachable-server: catch httpx.HTTPError in streamable HTTP post_writer request tasks and complete the pending JSON-RPC waiter instead of letting the exception escape into the outer task group. The added regression test, tests/client/test_probe.py (21/21), and ruff checks all passed; the upstream PR could not be opened.
Did this work for you?What @giacomoferretti tried on GitHub (Worked)
Locally patched handle_request_async in streamable_http.py to wrap the resumption/post request dispatch in try/except Exception and send the exception into the read stream with await read_stream_writer.send(e). The commenter reports this works while noting it is probably not the best fix.
Did this work for you?What @giacomoferretti tried on GitHub (Worked)
Worked around the bug by removing tg.start_soon() and awaiting handle_request_async() directly in the same task; the commenter reports the issue is not present with this change.
Did this work for you?
Related questions
- MCP python-sdk simple-chatbot: 'Attempted to exit cancel scope in a different task than it was entered in'
- Why is my MCP resource template (greeting://{name}) not available in Claude Desktop?
- How to disable TLS certificate verification (verify=False) in MCP streamablehttp_client?
- anyio.ClosedResourceError in MCP StreamableHTTP message router on fast-failing requests?
- Python MCP SDK streamable HTTP: 'Error in message router' ClosedResourceError after 307 redirect