Skip to content

RuntimeError: Attempted to exit cancel scope in a different task than it was entered in with AsyncExitStack #79

@rectalogic

Description

@rectalogic

Describe the bug
I am pushing the stdio_client and ClientSession onto an AsyncExitStack to avoid needing to wrap everyting in with statements. My client will have multiple simultaneous instances of different ClientSessions, so I wanted a manager class to handle cleaning up the session when it is GC'd. My plan was to run a task in the manager classes __del__ method to clean up, e.g. asyncio.create_task(self.exit_stack.aclose()).

However this raises RuntimeError: Attempted to exit cancel scope in a different task than it was entered in

To Reproduce
uv run this script:

# /// script
# requires-python = ">=3.10"
# dependencies = [
#     "mcp",
# ]
# ///

import asyncio
from contextlib import AsyncExitStack
from mcp import ClientSession, StdioServerParameters
from mcp.client.stdio import stdio_client


async def main() -> None:
    exit_stack = AsyncExitStack()
    server_params = StdioServerParameters(
        command="npx",
        args=["-y", "@modelcontextprotocol/server-filesystem", "/tmp"],
    )
    read, write = await exit_stack.enter_async_context(stdio_client(server_params))
    session = await exit_stack.enter_async_context(ClientSession(read, write))
    await session.initialize()

    asyncio.create_task(exit_stack.aclose())


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

Expected behavior
Clean shutdown.

Logs

(node:63152) ExperimentalWarning: CommonJS module /opt/homebrew/lib/node_modules/npm/node_modules/debug/src/node.js is loading ES Module /opt/homebrew/lib/node_modules/npm/node_modules/supports-color/index.js using require().
Support for loading ES Module in require() is an experimental feature and might change at any time
(Use `node --trace-warnings ...` to show where the warning was created)
Secure MCP Filesystem Server running on stdio
Allowed directories: [ '/tmp' ]
unhandled exception during asyncio.run() shutdown
task: <Task finished name='Task-6' coro=<AsyncExitStack.aclose() done, defined at /Users/aw/.local/share/uv/python/cpython-3.10.15-macos-aarch64-none/lib/python3.10/contextlib.py:654> exception=RuntimeError('Attempted to exit cancel scope in a different task than it was entered in')>
Traceback (most recent call last):
  File "/Users/aw/Library/Caches/uv/archive-v0/bkm4-HGL-Ma8SIqOuzaOM/lib/python3.10/site-packages/mcp/client/stdio.py", line 128, in stdio_client
    yield read_stream, write_stream
  File "/Users/aw/.local/share/uv/python/cpython-3.10.15-macos-aarch64-none/lib/python3.10/contextlib.py", line 697, in __aexit__
    cb_suppress = await cb(*exc_details)
  File "/Users/aw/Library/Caches/uv/archive-v0/bkm4-HGL-Ma8SIqOuzaOM/lib/python3.10/site-packages/mcp/shared/session.py", line 122, in __aexit__
    return await self._task_group.__aexit__(exc_type, exc_val, exc_tb)
  File "/Users/aw/Library/Caches/uv/archive-v0/bkm4-HGL-Ma8SIqOuzaOM/lib/python3.10/site-packages/anyio/_backends/_asyncio.py", line 769, in __aexit__
    if self.cancel_scope.__exit__(type(exc), exc, exc.__traceback__):
  File "/Users/aw/Library/Caches/uv/archive-v0/bkm4-HGL-Ma8SIqOuzaOM/lib/python3.10/site-packages/anyio/_backends/_asyncio.py", line 433, 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:

  + Exception Group Traceback (most recent call last):
  |   File "/Users/aw/Library/Caches/uv/archive-v0/bkm4-HGL-Ma8SIqOuzaOM/lib/python3.10/site-packages/anyio/_backends/_asyncio.py", line 763, in __aexit__
  |     raise BaseExceptionGroup(
  | exceptiongroup.ExceptionGroup: unhandled errors in a TaskGroup (1 sub-exception)
  +-+---------------- 1 ----------------
    | Traceback (most recent call last):
    |   File "/Users/aw/Library/Caches/uv/archive-v0/bkm4-HGL-Ma8SIqOuzaOM/lib/python3.10/site-packages/anyio/_backends/_asyncio.py", line 767, in __aexit__
    |     raise exc_val
    |   File "/Users/aw/Library/Caches/uv/archive-v0/bkm4-HGL-Ma8SIqOuzaOM/lib/python3.10/site-packages/anyio/_backends/_asyncio.py", line 741, in __aexit__
    |     await _wait(self._tasks)
    |   File "/Users/aw/Library/Caches/uv/archive-v0/bkm4-HGL-Ma8SIqOuzaOM/lib/python3.10/site-packages/anyio/_backends/_asyncio.py", line 706, in _wait
    |     await waiter
    | asyncio.exceptions.CancelledError
    |
    | During handling of the above exception, another exception occurred:
    |
    | Traceback (most recent call last):
    |   File "/Users/aw/Library/Caches/uv/archive-v0/bkm4-HGL-Ma8SIqOuzaOM/lib/python3.10/site-packages/mcp/client/stdio.py", line 128, in stdio_client
    |     yield read_stream, write_stream
    |   File "/Users/aw/.local/share/uv/python/cpython-3.10.15-macos-aarch64-none/lib/python3.10/contextlib.py", line 697, in __aexit__
    |     cb_suppress = await cb(*exc_details)
    |   File "/Users/aw/Library/Caches/uv/archive-v0/bkm4-HGL-Ma8SIqOuzaOM/lib/python3.10/site-packages/mcp/shared/session.py", line 122, in __aexit__
    |     return await self._task_group.__aexit__(exc_type, exc_val, exc_tb)
    |   File "/Users/aw/Library/Caches/uv/archive-v0/bkm4-HGL-Ma8SIqOuzaOM/lib/python3.10/site-packages/anyio/_backends/_asyncio.py", line 769, in __aexit__
    |     if self.cancel_scope.__exit__(type(exc), exc, exc.__traceback__):
    |   File "/Users/aw/Library/Caches/uv/archive-v0/bkm4-HGL-Ma8SIqOuzaOM/lib/python3.10/site-packages/anyio/_backends/_asyncio.py", line 433, 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 "/Users/aw/.local/share/uv/python/cpython-3.10.15-macos-aarch64-none/lib/python3.10/contextlib.py", line 656, in aclose
    await self.__aexit__(None, None, None)
  File "/Users/aw/.local/share/uv/python/cpython-3.10.15-macos-aarch64-none/lib/python3.10/contextlib.py", line 714, in __aexit__
    raise exc_details[1]
  File "/Users/aw/.local/share/uv/python/cpython-3.10.15-macos-aarch64-none/lib/python3.10/contextlib.py", line 697, in __aexit__
    cb_suppress = await cb(*exc_details)
  File "/Users/aw/.local/share/uv/python/cpython-3.10.15-macos-aarch64-none/lib/python3.10/contextlib.py", line 217, in __aexit__
    await self.gen.athrow(typ, value, traceback)
  File "/Users/aw/Library/Caches/uv/archive-v0/bkm4-HGL-Ma8SIqOuzaOM/lib/python3.10/site-packages/mcp/client/stdio.py", line 122, in stdio_client
    async with (
  File "/Users/aw/Library/Caches/uv/archive-v0/bkm4-HGL-Ma8SIqOuzaOM/lib/python3.10/site-packages/anyio/_backends/_asyncio.py", line 769, in __aexit__
    if self.cancel_scope.__exit__(type(exc), exc, exc.__traceback__):
  File "/Users/aw/Library/Caches/uv/archive-v0/bkm4-HGL-Ma8SIqOuzaOM/lib/python3.10/site-packages/anyio/_backends/_asyncio.py", line 433, in __exit__
    raise RuntimeError(
RuntimeError: Attempted to exit cancel scope in a different task than it was entered in

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions