Skip to content

adding multiple servers using the ClientSessionGroup, an error occurs when closing the connection #788

Closed

Description

@gaojun1212

Describe the bug
When adding multiple servers using the Client Session Group, an error occurs when closing the connection

No problem when adding only one server

code

async def test_connect_to_server():
    async with ClientSessionGroup() as clinet_session_group:
        for server in ["stream_law", "sse_kb"]:
            server_param = server_config_group.get_server_param(server)
            print(f"connect to server: {server_param}")
            print("----------------------------------------")
            await clinet_session_group.connect_to_server(server_param)

        await asyncio.sleep(3)
        print("*****************list tools********************")

        for name, tool in clinet_session_group.tools.items():
            print(f"tool_name: {name}")
            print("----------------------------------------")

        await asyncio.sleep(3)
        print("*****************disconnect********************")

        for session in clinet_session_group.sessions:
            print(f"disconnect session: {session}")
            await clinet_session_group.disconnect_from_server(session)
            await asyncio.sleep(2)

logs

connect to server: url='http://x.x.x.x:8080/mcp' headers={} timeout=datetime.timedelta(seconds=30) sse_read_timeout=datetime.timedelta(seconds=300) terminate_on_close=True
----------------------------------------
connect to server: url='http://x.x.x.x:9000/sse/' headers={} timeout=30.0 sse_read_timeout=300.0
----------------------------------------
*****************list tools********************
tool_name: search_ft
----------------------------------------
tool_name: search_fg
----------------------------------------
tool_name: search_law_xl
----------------------------------------
tool_name: writ_search
----------------------------------------
tool_name: law_search
----------------------------------------
tool_name: paper_search
----------------------------------------
tool_name: web_search
----------------------------------------
*****************disconnect********************
disconnect session: <mcp.client.session.ClientSession object at 0x0000022AAAF1C050>
  + Exception Group Traceback (most recent call last):
  |   File "C:\project\python\work\mcp\mcp-client\main.py", line 107, in <module>
  |     asyncio.run(test_connect_to_server())
  |   File "C:\Users\gaojun\AppData\Local\Programs\Python\Python312\Lib\asyncio\runners.py", line 195, in run
  |     return runner.run(main)
  |            ^^^^^^^^^^^^^^^^
  |   File "C:\Users\gaojun\AppData\Local\Programs\Python\Python312\Lib\asyncio\runners.py", line 118, in run
  |     return self._loop.run_until_complete(task)
  |            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  |   File "C:\Users\gaojun\AppData\Local\Programs\Python\Python312\Lib\asyncio\base_events.py", line 691, in run_until_complete
  |     return future.result()
  |            ^^^^^^^^^^^^^^^
  |   File "C:\project\python\work\mcp\mcp-client\main.py", line 31, in test_connect_to_server
  |     async with ClientSessionGroup() as clinet_session_group:
  |                ^^^^^^^^^^^^^^^^^^^^
  |   File "C:\project\python\work\mcp\mcp-client\.venv\Lib\site-packages\mcp\client\session_group.py", line 149, in __aexit__
  |     async with anyio.create_task_group() as tg:
  |                ^^^^^^^^^^^^^^^^^^^^^^^^^
  |   File "C:\project\python\work\mcp\mcp-client\.venv\Lib\site-packages\anyio\_backends\_asyncio.py", line 772, in __aexit__
  |     raise BaseExceptionGroup(
  | ExceptionGroup: unhandled errors in a TaskGroup (1 sub-exception)
  +-+---------------- 1 ----------------
    | Exception Group Traceback (most recent call last):
    |   File "C:\project\python\work\mcp\mcp-client\.venv\Lib\site-packages\anyio\_backends\_asyncio.py", line 772, in __aexit__
    |     raise BaseExceptionGroup(
    | ExceptionGroup: unhandled errors in a TaskGroup (1 sub-exception)
    +-+---------------- 1 ----------------
      | Traceback (most recent call last):
      |   File "C:\project\python\work\mcp\mcp-client\.venv\Lib\site-packages\mcp\client\sse.py", line 154, in sse_client
      |     yield read_stream, write_stream
      |   File "C:\Users\gaojun\AppData\Local\Programs\Python\Python312\Lib\contextlib.py", line 737, in __aexit__
      |     cb_suppress = await cb(*exc_details)
      |                   ^^^^^^^^^^^^^^^^^^^^^^
      |   File "C:\project\python\work\mcp\mcp-client\.venv\Lib\site-packages\mcp\shared\session.py", line 220, in __aexit__
      |     return await self._task_group.__aexit__(exc_type, exc_val, exc_tb)
      |            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
      |   File "C:\project\python\work\mcp\mcp-client\.venv\Lib\site-packages\anyio\_backends\_asyncio.py", line 783, in __aexit__
      |     return self.cancel_scope.__exit__(exc_type, exc_val, exc_tb)
      |            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
      |   File "C:\project\python\work\mcp\mcp-client\.venv\Lib\site-packages\anyio\_backends\_asyncio.py", line 457, in __exit__
      |     raise RuntimeError(
      | RuntimeError: Attempted to exit cancel scope in a different task than it was entered in
      +------------------------------------
    | 
    | During handling of the above exception, another exception occurred:
    | 
    | Traceback (most recent call last):
    |   File "C:\Users\gaojun\AppData\Local\Programs\Python\Python312\Lib\contextlib.py", line 696, in aclose
    |     await self.__aexit__(None, None, None)
    |   File "C:\Users\gaojun\AppData\Local\Programs\Python\Python312\Lib\contextlib.py", line 754, in __aexit__
    |     raise exc_details[1]
    |   File "C:\Users\gaojun\AppData\Local\Programs\Python\Python312\Lib\contextlib.py", line 737, in __aexit__
    |     cb_suppress = await cb(*exc_details)
    |                   ^^^^^^^^^^^^^^^^^^^^^^
    |   File "C:\Users\gaojun\AppData\Local\Programs\Python\Python312\Lib\contextlib.py", line 231, in __aexit__
    |     await self.gen.athrow(value)
    |   File "C:\project\python\work\mcp\mcp-client\.venv\Lib\site-packages\mcp\client\sse.py", line 53, in sse_client
    |     async with anyio.create_task_group() as tg:
    |                ^^^^^^^^^^^^^^^^^^^^^^^^^
    |   File "C:\project\python\work\mcp\mcp-client\.venv\Lib\site-packages\anyio\_backends\_asyncio.py", line 778, in __aexit__
    |     if self.cancel_scope.__exit__(type(exc), exc, exc.__traceback__):
    |        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    |   File "C:\project\python\work\mcp\mcp-client\.venv\Lib\site-packages\anyio\_backends\_asyncio.py", line 457, in __exit__
    |     raise RuntimeError(
    | RuntimeError: Attempted to exit cancel scope in a different task than it was entered in
    +------------------------------------

进程已结束,退出代码为 1

·
Only one server is OK

code

async def test_connect_to_server():
    async with ClientSessionGroup() as clinet_session_group:
        for server in ["stream_law"]:
            server_param = server_config_group.get_server_param(server)
            print(f"connect to server: {server_param}")
            print("----------------------------------------")
            await clinet_session_group.connect_to_server(server_param)

        await asyncio.sleep(3)
        print("*****************list tools********************")

        for name, tool in clinet_session_group.tools.items():
            print(f"tool_name: {name}")
            print("----------------------------------------")

        await asyncio.sleep(3)
        print("*****************disconnect********************")

        for session in clinet_session_group.sessions:
            print(f"disconnect session: {session}")
            await clinet_session_group.disconnect_from_server(session)
            await asyncio.sleep(2)

logs

connect to server: url='http://x.x.x.x:8080/mcp' headers={} timeout=datetime.timedelta(seconds=30) sse_read_timeout=datetime.timedelta(seconds=300) terminate_on_close=True
----------------------------------------
*****************list tools********************
tool_name: search_ft
----------------------------------------
tool_name: search_fg
----------------------------------------
tool_name: search_law_xl
----------------------------------------
*****************disconnect********************
disconnect session: <mcp.client.session.ClientSession object at 0x000001F6ABF8E180>

进程已结束,退出代码为 0

Activity

  1. changed the title [-]adding multiple servers using the Client Session Group, an error occurs when closing the connection[/-] [+]adding multiple servers using the ClientSessionGroup, an error occurs when closing the connection[/+] on May 23, 2025
  2. mkeid commented on May 25, 2025

    @mkeid
    Contributor

    Merged a fix with #787

    It should be part of the next release. Let me know if it works.

  3. gaojun1212 commented on Jun 2, 2025

    @gaojun1212
    Author

    Merged a fix with #787

    It should be part of the next release. Let me know if it works.

    It worked, when i use it like this below, but another problem occurred, when using the disconnect_from_server method

    async def test_connect_to_server():
        async with ClientSessionGroup() as clinet_session_group:
            for server in ["stream_law", "stream_case"]:
                server_param = server_config_group.get_server_param(server)
                print(f"connect to server: {server_param}")
                print("----------------------------------------")
                session = await clinet_session_group.connect_to_server(server_param)
    
            await asyncio.sleep(3)
            print("*****************list tools********************")
            for name, tool in clinet_session_group.tools.items():
                print(f"tool_name: {name}")
    

    logs

    connect to server: url='http://x.x.x.x:9090/server/hyyd_law/mcp' timeout=datetime.timedelta(seconds=30) sse_read_timeout=datetime.timedelta(seconds=300) terminate_on_close=True
    ----------------------------------------
    connect to server: url='http://x.x.x.x:9090/server/hyyd_case/mcp' timeout=datetime.timedelta(seconds=30) sse_read_timeout=datetime.timedelta(seconds=300) terminate_on_close=True
    ----------------------------------------
    *****************list tools********************
    tool_name: search_ft
    tool_name: search_fg
    tool_name: search_law_xl
    tool_name: search_ptal
    tool_name: search_qwal
    tool_name: search_case_xl
    

    another problem occurred, when using the disconnect_from_server method

    code

    async def test_disconnect_to_server():
        async with ClientSessionGroup() as clinet_session_group:
            sessions: list[ClientSession] = []
            for server in ["stream_law", "stream_case"]:
                server_param = server_config_group.get_server_param(server)
                print(f"connect to server: {server_param}")
                print("----------------------------------------")
                session = await clinet_session_group.connect_to_server(server_param)
                sessions.append(session)
    
            await asyncio.sleep(3)
    
            print(f"disconnect session: {sessions[0]}")
            await clinet_session_group.disconnect_from_server(sessions[0])
    
            await asyncio.sleep(5)
    

    logs

    connect to server: url='http://x.x.x.x:9090/server/hyyd_law/mcp' timeout=datetime.timedelta(seconds=30) sse_read_timeout=datetime.timedelta(seconds=300) terminate_on_close=True
    ----------------------------------------
    connect to server: url='http://x.x.x.x:9090/server/hyyd_case/mcp'  timeout=datetime.timedelta(seconds=30) sse_read_timeout=datetime.timedelta(seconds=300) terminate_on_close=True
    ----------------------------------------
    disconnect session: <mcp.client.session.ClientSession object at 0x000001B4A587E4B0>
    Traceback (most recent call last):
      File "C:\project\python\work\mcp\mcp-client\main.py", line 112, in <module>
        asyncio.run(test_disconnect_to_server())
      File "C:\Users\gaojun\AppData\Local\Programs\Python\Python312\Lib\asyncio\runners.py", line 195, in run
        return runner.run(main)
               ^^^^^^^^^^^^^^^^
      File "C:\Users\gaojun\AppData\Local\Programs\Python\Python312\Lib\asyncio\runners.py", line 118, in run
        return self._loop.run_until_complete(task)
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
      File "C:\Users\gaojun\AppData\Local\Programs\Python\Python312\Lib\asyncio\base_events.py", line 691, in run_until_complete
        return future.result()
               ^^^^^^^^^^^^^^^
      File "C:\project\python\work\mcp\mcp-client\main.py", line 47, in test_disconnect_to_server
        async with ClientSessionGroup() as clinet_session_group:
                   ^^^^^^^^^^^^^^^^^^^^
      File "C:\project\python\work\mcp\mcp-client\.venv\Lib\site-packages\mcp\client\session_group.py", line 150, in __aexit__
        await self._exit_stack.aclose()
      File "C:\Users\gaojun\AppData\Local\Programs\Python\Python312\Lib\contextlib.py", line 696, in aclose
        await self.__aexit__(None, None, None)
      File "C:\Users\gaojun\AppData\Local\Programs\Python\Python312\Lib\contextlib.py", line 754, in __aexit__
        raise exc_details[1]
      File "C:\Users\gaojun\AppData\Local\Programs\Python\Python312\Lib\contextlib.py", line 737, in __aexit__
        cb_suppress = await cb(*exc_details)
                      ^^^^^^^^^^^^^^^^^^^^^^
      File "C:\Users\gaojun\AppData\Local\Programs\Python\Python312\Lib\contextlib.py", line 754, in __aexit__
        raise exc_details[1]
      File "C:\Users\gaojun\AppData\Local\Programs\Python\Python312\Lib\contextlib.py", line 737, in __aexit__
        cb_suppress = await cb(*exc_details)
                      ^^^^^^^^^^^^^^^^^^^^^^
      File "C:\Users\gaojun\AppData\Local\Programs\Python\Python312\Lib\contextlib.py", line 217, in __aexit__
        await anext(self.gen)
      File "C:\project\python\work\mcp\mcp-client\.venv\Lib\site-packages\mcp\client\streamable_http.py", line 464, in streamablehttp_client
        async with anyio.create_task_group() as tg:
                   ^^^^^^^^^^^^^^^^^^^^^^^^^
      File "C:\project\python\work\mcp\mcp-client\.venv\Lib\site-packages\anyio\_backends\_asyncio.py", line 776, in __aexit__
        raise exc_val
      File "C:\project\python\work\mcp\mcp-client\.venv\Lib\site-packages\mcp\client\streamable_http.py", line 499, in streamablehttp_client
        await transport.terminate_session(client)
      File "C:\project\python\work\mcp\mcp-client\.venv\Lib\site-packages\mcp\client\streamable_http.py", line 412, in terminate_session
        response = await client.delete(self.url, headers=headers)
                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
      File "C:\project\python\work\mcp\mcp-client\.venv\Lib\site-packages\httpx\_client.py", line 1966, in delete
        return await self.request(
               ^^^^^^^^^^^^^^^^^^^
      File "C:\project\python\work\mcp\mcp-client\.venv\Lib\site-packages\httpx\_client.py", line 1540, in request
        return await self.send(request, auth=auth, follow_redirects=follow_redirects)
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
      File "C:\project\python\work\mcp\mcp-client\.venv\Lib\site-packages\httpx\_client.py", line 1629, in send
        response = await self._send_handling_auth(
                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
      File "C:\project\python\work\mcp\mcp-client\.venv\Lib\site-packages\httpx\_client.py", line 1657, in _send_handling_auth
        response = await self._send_handling_redirects(
                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
      File "C:\project\python\work\mcp\mcp-client\.venv\Lib\site-packages\httpx\_client.py", line 1694, in _send_handling_redirects
        response = await self._send_single_request(request)
                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
      File "C:\project\python\work\mcp\mcp-client\.venv\Lib\site-packages\httpx\_client.py", line 1730, in _send_single_request
        response = await transport.handle_async_request(request)
                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
      File "C:\project\python\work\mcp\mcp-client\.venv\Lib\site-packages\httpx\_transports\default.py", line 394, in handle_async_request
        resp = await self._pool.handle_async_request(req)
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
      File "C:\project\python\work\mcp\mcp-client\.venv\Lib\site-packages\httpcore\_async\connection_pool.py", line 256, in handle_async_request
        raise exc from None
      File "C:\project\python\work\mcp\mcp-client\.venv\Lib\site-packages\httpcore\_async\connection_pool.py", line 236, in handle_async_request
        response = await connection.handle_async_request(
                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
      File "C:\project\python\work\mcp\mcp-client\.venv\Lib\site-packages\httpcore\_async\http_proxy.py", line 206, in handle_async_request
        return await self._connection.handle_async_request(proxy_request)
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
      File "C:\project\python\work\mcp\mcp-client\.venv\Lib\site-packages\httpcore\_async\connection.py", line 101, in handle_async_request
        raise exc
      File "C:\project\python\work\mcp\mcp-client\.venv\Lib\site-packages\httpcore\_async\connection.py", line 76, in handle_async_request
        async with self._request_lock:
                   ^^^^^^^^^^^^^^^^^^
      File "C:\project\python\work\mcp\mcp-client\.venv\Lib\site-packages\httpcore\_synchronization.py", line 77, in __aenter__
        await self._anyio_lock.acquire()
      File "C:\project\python\work\mcp\mcp-client\.venv\Lib\site-packages\anyio\_backends\_asyncio.py", line 1792, in acquire
        await AsyncIOBackend.checkpoint_if_cancelled()
      File "C:\project\python\work\mcp\mcp-client\.venv\Lib\site-packages\anyio\_backends\_asyncio.py", line 2341, in checkpoint_if_cancelled
        await sleep(0)
      File "C:\Users\gaojun\AppData\Local\Programs\Python\Python312\Lib\asyncio\tasks.py", line 656, in sleep
        await __sleep0()
      File "C:\Users\gaojun\AppData\Local\Programs\Python\Python312\Lib\asyncio\tasks.py", line 650, in __sleep0
        yield
    asyncio.exceptions.CancelledError: Cancelled by cancel scope 1b4a59471d0
    
    
  4. xtang2010 commented on Jun 9, 2025

    @xtang2010

    Have same problem, found somehow the disconnect order is important. Ie:

    async with ClientSessionGroup() as group:
        session1 = await group.connect_to_server(server_params_list["filesystem"])
        session2 = await group.connect_to_server(server_params_list["excel"])
    
        await group.disconnect_from_server(session2)
        await group.disconnect_from_server(session1)
    

    This works fine, but if change the disconnect order, ie:

        ....
        await group.disconnect_from_server(session1)
        await group.disconnect_from_server(session2)
    

    This will cause the problem.

    Hope this info helps.

    -xtang

  5. gaojun1212 commented on Jun 10, 2025

    @gaojun1212
    Author

    Have same problem, found somehow the disconnect order is important. Ie:

    async with ClientSessionGroup() as group:
        session1 = await group.connect_to_server(server_params_list["filesystem"])
        session2 = await group.connect_to_server(server_params_list["excel"])
    
        await group.disconnect_from_server(session2)
        await group.disconnect_from_server(session1)
    

    This works fine, but if change the disconnect order, ie:

        ....
        await group.disconnect_from_server(session1)
        await group.disconnect_from_server(session2)
    

    This will cause the problem.

    Hope this info helps.

    -xtang

    thanks for your suggest
    I just tested your suggestion and it works

  6. xtang2010 commented on Jun 10, 2025

    @xtang2010

    Some further debug showed this related to how BaseSession is using anyio.create_task_group(). I don't think it has anything to do with ClientSessionGroup, thus I created issue#922

  7. mkeid commented on Jun 18, 2025

    @mkeid
    Contributor

    Thanks for the find @xtang2010 - can you close this out, @gaojun1212 ?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions