Skip to content

Exception in ClientSessionGroup if streamable_http MCP server is not available #915

@hzeus

Description

@hzeus

Describe the bug
When I use the ClientSessionGroup for a remote MCP server via remote_http for a remote server that is not running, I get exception RuntimeError: Attempted to exit cancel scope in a different task than it was entered in

(complete output collapsed)
an error occurred during closing of asynchronous generator <async_generator object streamablehttp_client at 0xffffa53755d0>
asyncgen: <async_generator object streamablehttp_client at 0xffffa53755d0>
  + Exception Group Traceback (most recent call last):
  |   File "/workspaces/test/.venv/lib/python3.13/site-packages/anyio/_backends/_asyncio.py", line 772, in __aexit__
  |     raise BaseExceptionGroup(
  |         "unhandled errors in a TaskGroup", self._exceptions
  |     ) from None
  | BaseExceptionGroup: unhandled errors in a TaskGroup (2 sub-exceptions)
  +-+---------------- 1 ----------------
    | Traceback (most recent call last):
    |   File "/workspaces/test/.venv/lib/python3.13/site-packages/httpx/_transports/default.py", line 101, in map_httpcore_exceptions
    |     yield
    |   File "/workspaces/test/.venv/lib/python3.13/site-packages/httpx/_transports/default.py", line 394, in handle_async_request
    |     resp = await self._pool.handle_async_request(req)
    |            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    |   File "/workspaces/test/.venv/lib/python3.13/site-packages/httpcore/_async/connection_pool.py", line 256, in handle_async_request
    |     raise exc from None
    |   File "/workspaces/test/.venv/lib/python3.13/site-packages/httpcore/_async/connection_pool.py", line 236, in handle_async_request
    |     response = await connection.handle_async_request(
    |                ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    |         pool_request.request
    |         ^^^^^^^^^^^^^^^^^^^^
    |     )
    |     ^
    |   File "/workspaces/test/.venv/lib/python3.13/site-packages/httpcore/_async/connection.py", line 101, in handle_async_request
    |     raise exc
    |   File "/workspaces/test/.venv/lib/python3.13/site-packages/httpcore/_async/connection.py", line 78, in handle_async_request
    |     stream = await self._connect(request)
    |              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    |   File "/workspaces/test/.venv/lib/python3.13/site-packages/httpcore/_async/connection.py", line 124, in _connect
    |     stream = await self._network_backend.connect_tcp(**kwargs)
    |              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    |   File "/workspaces/test/.venv/lib/python3.13/site-packages/httpcore/_backends/auto.py", line 31, in connect_tcp
    |     return await self._backend.connect_tcp(
    |            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    |     ...<5 lines>...
    |     )
    |     ^
    |   File "/workspaces/test/.venv/lib/python3.13/site-packages/httpcore/_backends/anyio.py", line 113, in connect_tcp
    |     with map_exceptions(exc_map):
    |          ~~~~~~~~~~~~~~^^^^^^^^^
    |   File "/usr/local/lib/python3.13/contextlib.py", line 162, in __exit__
    |     self.gen.throw(value)
    |     ~~~~~~~~~~~~~~^^^^^^^
    |   File "/workspaces/test/.venv/lib/python3.13/site-packages/httpcore/_exceptions.py", line 14, in map_exceptions
    |     raise to_exc(exc) from exc
    | httpcore.ConnectError: All connection attempts failed
    | 
    | The above exception was the direct cause of the following exception:
    | 
    | Traceback (most recent call last):
    |   File "/workspaces/test/.venv/lib/python3.13/site-packages/mcp/client/streamable_http.py", line 391, in handle_request_async
    |     await self._handle_post_request(ctx)
    |   File "/workspaces/test/.venv/lib/python3.13/site-packages/mcp/client/streamable_http.py", line 251, in _handle_post_request
    |     async with ctx.client.stream(
    |                ~~~~~~~~~~~~~~~~~^
    |         "POST",
    |         ^^^^^^^
    |     ...<2 lines>...
    |         headers=headers,
    |         ^^^^^^^^^^^^^^^^
    |     ) as response:
    |     ^
    |   File "/usr/local/lib/python3.13/contextlib.py", line 214, in __aenter__
    |     return await anext(self.gen)
    |            ^^^^^^^^^^^^^^^^^^^^^
    |   File "/workspaces/test/.venv/lib/python3.13/site-packages/httpx/_client.py", line 1583, in stream
    |     response = await self.send(
    |                ^^^^^^^^^^^^^^^^
    |     ...<4 lines>...
    |     )
    |     ^
    |   File "/workspaces/test/.venv/lib/python3.13/site-packages/httpx/_client.py", line 1629, in send
    |     response = await self._send_handling_auth(
    |                ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    |     ...<4 lines>...
    |     )
    |     ^
    |   File "/workspaces/test/.venv/lib/python3.13/site-packages/httpx/_client.py", line 1657, in _send_handling_auth
    |     response = await self._send_handling_redirects(
    |                ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    |     ...<3 lines>...
    |     )
    |     ^
    |   File "/workspaces/test/.venv/lib/python3.13/site-packages/httpx/_client.py", line 1694, in _send_handling_redirects
    |     response = await self._send_single_request(request)
    |                ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    |   File "/workspaces/test/.venv/lib/python3.13/site-packages/httpx/_client.py", line 1730, in _send_single_request
    |     response = await transport.handle_async_request(request)
    |                ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    |   File "/workspaces/test/.venv/lib/python3.13/site-packages/httpx/_transports/default.py", line 393, in handle_async_request
    |     with map_httpcore_exceptions():
    |          ~~~~~~~~~~~~~~~~~~~~~~~^^
    |   File "/usr/local/lib/python3.13/contextlib.py", line 162, in __exit__
    |     self.gen.throw(value)
    |     ~~~~~~~~~~~~~~^^^^^^^
    |   File "/workspaces/test/.venv/lib/python3.13/site-packages/httpx/_transports/default.py", line 118, in map_httpcore_exceptions
    |     raise mapped_exc(message) from exc
    | httpx.ConnectError: All connection attempts failed
    +---------------- 2 ----------------
    | Traceback (most recent call last):
    |   File "/workspaces/test/.venv/lib/python3.13/site-packages/mcp/client/streamable_http.py", line 492, in streamablehttp_client
    |     yield (
    |     ...<3 lines>...
    |     )
    | GeneratorExit
    +------------------------------------

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/workspaces/test/.venv/lib/python3.13/site-packages/mcp/client/streamable_http.py", line 464, in streamablehttp_client
    async with anyio.create_task_group() as tg:
               ~~~~~~~~~~~~~~~~~~~~~~~^^
  File "/workspaces/test/.venv/lib/python3.13/site-packages/anyio/_backends/_asyncio.py", line 778, in __aexit__
    if self.cancel_scope.__exit__(type(exc), exc, exc.__traceback__):
       ~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/workspaces/test/.venv/lib/python3.13/site-packages/anyio/_backends/_asyncio.py", line 457, in __exit__
    raise RuntimeError(
    ...<2 lines>...
    )
RuntimeError: Attempted to exit cancel scope in a different task than it was entered in
Traceback (most recent call last):
  File "/workspaces/test/.venv/lib/python3.13/site-packages/anyio/streams/memory.py", line 111, in receive
    return self.receive_nowait()
           ~~~~~~~~~~~~~~~~~~~^^
  File "/workspaces/test/.venv/lib/python3.13/site-packages/anyio/streams/memory.py", line 106, in receive_nowait
    raise WouldBlock
anyio.WouldBlock

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/workspaces/test.py", line 14, in <module>
    asyncio.run(main())
    ~~~~~~~~~~~^^^^^^^^
  File "/usr/local/lib/python3.13/asyncio/runners.py", line 195, in run
    return runner.run(main)
           ~~~~~~~~~~^^^^^^
  File "/usr/local/lib/python3.13/asyncio/runners.py", line 118, in run
    return self._loop.run_until_complete(task)
           ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^
  File "/usr/local/lib/python3.13/asyncio/base_events.py", line 719, in run_until_complete
    return future.result()
           ~~~~~~~~~~~~~^^
  File "/workspaces/test.py", line 10, in main
    await client_session_group.connect_to_server(server_params)
  File "/workspaces/test/.venv/lib/python3.13/site-packages/mcp/client/session_group.py", line 232, in connect_to_server
    server_info, session = await self._establish_session(server_params)
                           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/workspaces/test/.venv/lib/python3.13/site-packages/mcp/client/session_group.py", line 267, in _establish_session
    result = await session.initialize()
             ^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/workspaces/test/.venv/lib/python3.13/site-packages/mcp/client/session.py", line 133, in initialize
    result = await self.send_request(
             ^^^^^^^^^^^^^^^^^^^^^^^^
    ...<15 lines>...
    )
    ^
  File "/workspaces/test/.venv/lib/python3.13/site-packages/mcp/shared/session.py", line 283, in send_request
    response_or_error = await response_stream_reader.receive()
                        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/workspaces/test/.venv/lib/python3.13/site-packages/anyio/streams/memory.py", line 119, in receive
    await receive_event.wait()
  File "/workspaces/test/.venv/lib/python3.13/site-packages/anyio/_backends/_asyncio.py", line 1774, in wait
    await self._event.wait()
  File "/usr/local/lib/python3.13/asyncio/locks.py", line 213, in wait
    await fut
asyncio.exceptions.CancelledError: Cancelled by cancel scope ffffa51c4050

To Reproduce

I tested this with this script:

import asyncio

from mcp.client.session_group import ClientSessionGroup, StreamableHttpParameters


async def main():
    async with ClientSessionGroup() as client_session_group:
        server_params = StreamableHttpParameters(url="http://localhost:3001/mcp/")
        try:
            await client_session_group.connect_to_server(server_params)
        except Exception:
            print("this is never reached")


if __name__ == "__main__":
    asyncio.run(main())

Expected behavior

I would like to be able to catch any error that happens during connect_to_server and handle it within my application code - in my case I am using the ClientSessionGroup for multiple MCP servers and want to provide the tools of the running MCP servers to the LLM.

Additional context

  • I am using version 1.9.3

Metadata

Metadata

Assignees

No one assigned

    Labels

    P1Significant bug affecting many users, highly requested featurebugSomething isn't workingfix proposedBot has a verified fix diff in the commentready for workEnough information for someone to start working on

    Type

    No type
    No fields configured for issues without a type.

    Projects

    Status

    To triage

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions