microsoft/autogen · critical · RuntimeError

MCP actor task crashed

Error message

MCP actor task crashed

What it means

call() checks whether the actor's background task (_actor_task) has already completed; if it has, the actor loop crashed (or exited) and every subsequent command would hang on the queue. It raises RuntimeError('MCP actor task crashed', exception) chaining the original exception that killed the loop — inspect the second argument for the root cause (stdio process death, protocol error, unhandled exception in _run_actor).

Source

Thrown at python/packages/autogen-ext/src/autogen_ext/tools/mcp/_actor.py:102

        self._shutdown_future: asyncio.Future[Any] | None = None
        self._active = False
        self._initialize_result: mcp_types.InitializeResult | None = None
        atexit.register(self._sync_shutdown)

    @property
    def initialize_result(self) -> mcp_types.InitializeResult | None:
        return self._initialize_result

    async def initialize(self) -> None:
        if not self._active:
            self._active = True
            self._actor_task = asyncio.create_task(self._run_actor())

    async def call(self, type: str, args: McpActorArgs | None = None) -> McpFuture:
        if not self._active:
            raise RuntimeError("MCP Actor not running, call initialize() first")
        if self._actor_task and self._actor_task.done():
            raise RuntimeError("MCP actor task crashed", self._actor_task.exception())
        fut: asyncio.Future[McpFuture] = asyncio.Future()
        if type in {"list_tools", "list_prompts", "list_resources", "list_resource_templates", "shutdown"}:
            await self._command_queue.put({"type": type, "future": fut})
            res = await fut
        elif type in {"call_tool", "read_resource", "get_prompt"}:
            if args is None:
                raise ValueError(f"args is required for {type}")
            name = args.get("name", None)
            kwargs = args.get("kargs", {})
            if type == "call_tool" and name is None:
                raise ValueError("name is required for call_tool")
            elif type == "read_resource":
                uri = kwargs.get("uri", None)
                if uri is None:
                    raise ValueError("uri is required for read_resource")
                await self._command_queue.put({"type": type, "uri": uri, "future": fut})
            elif type == "get_prompt":
                if name is None:

View on GitHub (pinned to 027ecf0a37)

Solutions

  1. Catch the RuntimeError and inspect .__cause__ / the second constructor argument to find the real failure.
  2. Fix the underlying cause: verify the MCP server command/args, that the binary is on PATH, and that credentials are valid.
  3. Recreate the actor (initialize() a fresh instance) rather than calling the dead one again — the crashed task never recovers.
  4. If the crash is inside your sampling/elicitation callback, fix the callback; its exception propagates and kills the actor loop.

Example fix

# before
actor = McpActor(server_params)
await actor.initialize()
await asyncio.sleep(60)  # server dies meanwhile
tools = await actor.call("list_tools")  # RuntimeError: MCP actor task crashed

# after
try:
    tools = await actor.call("list_tools")
except RuntimeError as e:
    cause = e.args[1] if len(e.args) > 1 else None
    logger.error("actor crashed: %r", cause)
    actor = McpActor(server_params)
    await actor.initialize()
    tools = await actor.call("list_tools")
Defensive patterns

Strategy: retry

Validate before calling

task = actor._actor_task
crashed = task is not None and task.done() and not task.cancelled() and task.exception() is not None
if crashed:
    logger.error("actor already crashed: %r", task.exception())

Try / catch

try:
    res = await actor.call(cmd, args)
except RuntimeError as e:
    if "crashed" in str(e):
        cause = e.args[1] if len(e.args) > 1 else e
        logger.error("MCP actor crashed: %r", cause)
        actor = McpActor(session_factory)
        await actor.initialize()
        res = await actor.call(cmd, args)  # one bounded retry
    else:
        raise

Prevention

When it happens

Trigger: The underlying MCP server process dies (stdio transport: the spawned binary exits) or the session raises inside _run_actor; the next actor.call(...) detects _actor_task.done() and raises RuntimeError with the stored exception.

Common situations: MCP stdio server executable not found or crashing on startup; server closing the stream after auth failure; unhandled exception inside a sampling or elicitation callback; the actor task was garbage collected or cancelled externally; network disconnect for SSE/HTTP transports killing the loop.

Related errors


AI-assisted analysis of microsoft/autogen@027ecf0a37 (2026-08-15). Data as JSON: /api/errors/74c479ee161ac1b1. Report an issue: GitHub.