Skip to content

RuntimeError: Attempted to exit cancel scope in a different task than it was entered in when using MultiServerMCPClient with async context manager #99

@hieptran1401

Description

@hieptran1401

🐛 Describe the bug

I'm building an AI agent that uses MCP tools via langchain-mcp-adapters and streams responses back to the client using LangGraph, LangServe, and FastAPI.

The agent is created with tools provided by MultiServerMCPClient, and the stream is handled using:

async for event in agent.astream_events(...):

The problem occurs when the request finishes and the context manager for the MultiServerMCPClient attempts to close. I consistently get the following error:

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

💥Full Error Traceback

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

Traceback (most recent call last):
  File "/usr/local/lib/python3.12/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 "/usr/local/lib/python3.12/site-packages/mcp/client/sse.py", line 43, in sse_client
    async with anyio.create_task_group() as tg:
  File "/usr/local/lib/python3.12/site-packages/anyio/_backends/_asyncio.py", line 778, in __aexit__
    if self.cancel_scope.__exit__(type(exc), exc, exc.__traceback__):
  File "/usr/local/lib/python3.12/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

This seems to stem from the sse_client method and the internal AnyIO TaskGroup / CancelScope handling.


To Reproduce

Steps to reproduce the behavior:

  1. Set up MultiServerMCPClient with an SSE-based tool like Zapier.
  2. Use it within an asynccontextmanager.
  3. Call agent.astream_events() from inside a FastAPI route.
  4. Let the request finish, and observe the stack trace.

💻 Minimal Reproducible Code

from typing import Annotated, Sequence, TypedDict, List, Union
from pydantic import BaseModel, Field
from langchain_core.messages import AIMessage, HumanMessage, SystemMessage, BaseMessage
from langgraph.prebuilt import create_react_agent
from langchain_mcp_adapters.client import MultiServerMCPClient
from contextlib import asynccontextmanager
from langgraph.graph.message import add_messages
from app.utils.llm import AgentModel, get_llm_by_model

class TestRequest(BaseModel):
    messages: List[Union[SystemMessage, HumanMessage, AIMessage]]

class State(TypedDict):
    messages: Annotated[Sequence[BaseMessage], add_messages]

@asynccontextmanager
async def make_graph():
    mcp_client = MultiServerMCPClient(
        {
            "zapier": {
                "url": "https://actions.zapier.com/mcp/sk-ak-xxxxxx/sse",
                "transport": "sse",
            }
        }
    )

    async with mcp_client:
        mcp_tools = mcp_client.get_tools()
        model = get_llm_by_model(AgentModel.GPT_4_1_MINI, 0.1)
        agent = create_react_agent(model, mcp_tools)
        yield agent

async def run(request: TestRequest):
    async with make_graph() as agent:
        async for event in agent.astream_events(request, version="v1"):
            kind = event["event"]
            if kind == "on_chat_model_stream":
                chunk = event["data"]["chunk"]
                if chunk.content and len(chunk.tool_calls) == 0:
                    yield chunk

✅ Expected behavior

The MultiServerMCPClient should cleanly enter and exit its internal cancel_scope or task_group without triggering a RuntimeError, as long as it is used inside a single async task context.

In this case, the context manager (async with) is entered and exited within the same function, and no lifecycle is shared across tasks. The expectation is that the client and its associated SSE connections can gracefully shut down after use in an @asynccontextmanager or standard async function.


🧩 Environment

  • OS: Ubuntu 22.04 (Docker)
  • Python: 3.12
  • Poetry: yes (pyproject.toml attached)
  • anyio: 4.3.0
  • langchain: 0.3.x
  • langgraph: 0.2.20+
  • langchain-mcp-adapters: 0.0.8
  • Run context: FastAPI + LangGraph agent streaming via agent.astream_events(...)

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