Skip to content

Python: MCP stdio shutdown raises anyio cancel-scope error in agent_framework MCPStdioTool #2846

@jspv

Description

@jspv

Issue: Launching an MCP stdio server via agent_framework.MCPStdioTool works (server responds to ListTools/CallTool), but teardown crashes the stdio client. The error appears after the tool has responded:

an error occurred during closing of asynchronous generator stdio_client…
RuntimeError: Attempted to exit cancel scope in a different task than it was entered in

Expected: stdio MCP starts, responds, and shuts down without exceptions.
Actual: stdio shutdown raises the anyio cancel-scope error, causing the agent run to fail after the MCP server has already responded.

Steps to reproduce (minimal code provided) - the sample uses a provided basic standalone mcp, but you can use any stdio mcp and the issue occurs.

agent-framework-core = 1.0.0b251211

"""
Agent Framework repro: stdio MCP shutdown using local_mcp_server.py.

Starts the bundled stdio MCP server (scripts/local_mcp_server.py) via MCPStdioTool
and runs a single prompt to trigger startup/shutdown.
"""

from __future__ import annotations

import asyncio
import os
import sys
from pathlib import Path

from agent_framework import ChatAgent, MCPStdioTool, ai_function
from agent_framework.openai import OpenAIChatClient


@ai_function
def ping() -> str:
    return "pong"


async def main() -> None:
    server_path = Path(__file__).resolve().parent / "local_mcp_server.py"

    sample_tool = MCPStdioTool(
        name="sample_local_mcp",
        command=sys.executable,
        args=[str(server_path)],
        env={"LOG_LEVEL": "warning"},
    )

    client = OpenAIChatClient(
        model_id="gpt-4o-mini",
        api_key=os.environ["OPENAI_API_KEY"],
    )

    agent = ChatAgent(
        chat_client=client,
        tools=[sample_tool, ping],
        instructions="Trigger MCP startup/shutdown via stdio.",
    )

    try:
        await agent.run(messages="hi", store=False)
    except Exception as exc:  # noqa: BLE001
        raise RuntimeError(f"Agent run failed: {type(exc).__name__}: {exc}") from exc


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

local_mcp_server.py (or use your own)

"""
Minimal MCP stdio server exposing a single `ping` tool.
"""

from __future__ import annotations

import asyncio

from mcp import types
from mcp.server.lowlevel import Server
from mcp.server.models import InitializationOptions
from mcp.server.stdio import stdio_server
from mcp.shared.exceptions import McpError


async def main() -> None:
    server = Server("sample_local_mcp")

    @server.list_tools()
    async def list_tools() -> list[types.Tool]:
        return [
            types.Tool(
                name="ping",
                description="Return pong.",
                inputSchema={
                    "type": "object",
                    "properties": {},
                    "additionalProperties": False,
                },
            )
        ]

    @server.call_tool()
    async def call_tool(request: types.CallToolRequest) -> list[types.TextContent]:
        if request.params.name != "ping":
            raise McpError(f"Unknown tool: {request.params.name}")
        return [types.TextContent(type="text", text="pong")]

    async with stdio_server() as (read_stream, write_stream):
        await server.run(
            read_stream,
            write_stream,
            initialization_options=InitializationOptions(
                server_name="sample_local_mcp",
                server_version="0.0.1",
                capabilities=types.ServerCapabilities(
                    tools=types.ToolsCapability(),
                ),
            ),
            raise_exceptions=True,
        )


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

Metadata

Metadata

Labels

model context protocolIssue related to Model Context Protocolpythonv1.0Features being tracked for the version 1.0 GA

Type

Projects

Status

Done

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions