modelcontextprotocol / modelcontextprotocol/python-sdk

Exception in `ClientSessionGroup` if `streamable_http` MCP server is not available

Đang mở
#915 12 bình luận 7 reaction 0 người được giao Xem trên GitHub

Chưa có ai nhận issue này.

bug fix proposed P1 ready for work
Ngôn ngữ chính
Python
Star
24.3k
Fork
4k
Merge trung bình
1 ngày 1 giờ
Pull request đã merge (30 ngày)
31

Mô tả

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

Hướng dẫn đóng góp

Mở hướng dẫn đóng góp

Bắt đầu từ đâu

  1. Đọc hết issue, rồi đọc hướng dẫn đóng góp của dự án.
  2. Bình luận trên issue rằng bạn sẽ nhận — tránh hai người làm cùng một việc.
  3. Fork repository và làm thay đổi trên một nhánh.
  4. Mở pull request có tham chiếu số hiệu của issue.

Hướng nghiên cứu

Bắt đầu tại ClientSessionGroup.connect_to_server và _establish_session, sau đó theo dõi session.initialize và vòng đời của streamablehttp_client được hiển thị trong traceback. Tái hiện bằng script được cung cấp trong khi server tại localhost:3001/mcp/ không khả dụng; hoàn tất có nghĩa là connect_to_server phát sinh lỗi kết nối có thể bắt được mà không có cancel-scope hoặc async-generator cleanup exception.

Do mô hình lập chỉ mục viết ra từ nội dung của issue.

Đánh giá

Công nghệ
python
Lĩnh vực
backend, networking
Loại issue
Lỗi
Độ khó
4/5
Thời gian dự kiến
3-5 ngày
Mức độ hoạt động
Ít trao đổi
Độ rõ ràng
Khá rõ ràng
Mức phù hợp với người mới
48/100

Nhận issue mới trong hộp thư của bạn

Bản tóm tắt ngắn những issue GitHub phù hợp với người mới.