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
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions