{"record":{"id":"74c479ee161ac1b1","repo":"microsoft/autogen","slug":"mcp-actor-task-crashed","errorCode":null,"errorMessage":"MCP actor task crashed","messagePattern":"MCP actor task crashed","errorType":"exception","errorClass":"RuntimeError","httpStatus":null,"severity":"critical","filePath":"python/packages/autogen-ext/src/autogen_ext/tools/mcp/_actor.py","lineNumber":102,"sourceCode":"        self._shutdown_future: asyncio.Future[Any] | None = None\n        self._active = False\n        self._initialize_result: mcp_types.InitializeResult | None = None\n        atexit.register(self._sync_shutdown)\n\n    @property\n    def initialize_result(self) -> mcp_types.InitializeResult | None:\n        return self._initialize_result\n\n    async def initialize(self) -> None:\n        if not self._active:\n            self._active = True\n            self._actor_task = asyncio.create_task(self._run_actor())\n\n    async def call(self, type: str, args: McpActorArgs | None = None) -> McpFuture:\n        if not self._active:\n            raise RuntimeError(\"MCP Actor not running, call initialize() first\")\n        if self._actor_task and self._actor_task.done():\n            raise RuntimeError(\"MCP actor task crashed\", self._actor_task.exception())\n        fut: asyncio.Future[McpFuture] = asyncio.Future()\n        if type in {\"list_tools\", \"list_prompts\", \"list_resources\", \"list_resource_templates\", \"shutdown\"}:\n            await self._command_queue.put({\"type\": type, \"future\": fut})\n            res = await fut\n        elif type in {\"call_tool\", \"read_resource\", \"get_prompt\"}:\n            if args is None:\n                raise ValueError(f\"args is required for {type}\")\n            name = args.get(\"name\", None)\n            kwargs = args.get(\"kargs\", {})\n            if type == \"call_tool\" and name is None:\n                raise ValueError(\"name is required for call_tool\")\n            elif type == \"read_resource\":\n                uri = kwargs.get(\"uri\", None)\n                if uri is None:\n                    raise ValueError(\"uri is required for read_resource\")\n                await self._command_queue.put({\"type\": type, \"uri\": uri, \"future\": fut})\n            elif type == \"get_prompt\":\n                if name is None:","sourceCodeStart":84,"sourceCodeEnd":120,"githubUrl":"https://github.com/microsoft/autogen/blob/027ecf0a379bcc1d09956d46d12d44a3ad9cee14/python/packages/autogen-ext/src/autogen_ext/tools/mcp/_actor.py#L84-L120","documentation":"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).","triggerScenarios":"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.","commonSituations":"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.","solutions":["Catch the RuntimeError and inspect .__cause__ / the second constructor argument to find the real failure.","Fix the underlying cause: verify the MCP server command/args, that the binary is on PATH, and that credentials are valid.","Recreate the actor (initialize() a fresh instance) rather than calling the dead one again — the crashed task never recovers.","If the crash is inside your sampling/elicitation callback, fix the callback; its exception propagates and kills the actor loop."],"exampleFix":"# before\nactor = McpActor(server_params)\nawait actor.initialize()\nawait asyncio.sleep(60)  # server dies meanwhile\ntools = await actor.call(\"list_tools\")  # RuntimeError: MCP actor task crashed\n\n# after\ntry:\n    tools = await actor.call(\"list_tools\")\nexcept RuntimeError as e:\n    cause = e.args[1] if len(e.args) > 1 else None\n    logger.error(\"actor crashed: %r\", cause)\n    actor = McpActor(server_params)\n    await actor.initialize()\n    tools = await actor.call(\"list_tools\")","handlingStrategy":"retry","validationCode":"task = actor._actor_task\ncrashed = task is not None and task.done() and not task.cancelled() and task.exception() is not None\nif crashed:\n    logger.error(\"actor already crashed: %r\", task.exception())","typeGuard":null,"tryCatchPattern":"try:\n    res = await actor.call(cmd, args)\nexcept RuntimeError as e:\n    if \"crashed\" in str(e):\n        cause = e.args[1] if len(e.args) > 1 else e\n        logger.error(\"MCP actor crashed: %r\", cause)\n        actor = McpActor(session_factory)\n        await actor.initialize()\n        res = await actor.call(cmd, args)  # one bounded retry\n    else:\n        raise","preventionTips":["Smoke-test the MCP server command manually (run the binary) before wiring it into the actor.","Log the actor task exception when close/call fails so root causes are not lost.","Keep sampling/elicitation callbacks exception-safe — an exception inside them kills the actor loop."],"tags":["mcp","asyncio","crash","lifecycle","task"],"backgroundTag":null,"analyzedSha":"027ecf0a379bcc1d09956d46d12d44a3ad9cee14","analyzedAt":"2026-08-15T03:38:00.719Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}