{"record":{"id":"5e0a48cd2de288e4","repo":"agentscope-ai/agentscope","slug":"mcp-self-name-is-already-connected-call-close","errorCode":null,"errorMessage":"MCP '{self.name}' is already connected. Call close() before reconnecting.","messagePattern":"MCP '(.+?)' is already connected\\. Call close\\(\\) before reconnecting\\.","errorType":"exception","errorClass":"RuntimeError","httpStatus":null,"severity":"error","filePath":"src/agentscope/mcp/_mcp_client.py","lineNumber":230,"sourceCode":"        )\n\n    async def connect(self) -> None:\n        \"\"\"Connect to the MCP server (for stateful connections only).\n\n        For stateless connections, this method does nothing.\n\n        Raises:\n            RuntimeError: If already connected.\n        \"\"\"\n        if not self.is_stateful:\n            logger.debug(\n                \"Stateless MCP '%s' does not require explicit connect.\",\n                self.name,\n            )\n            return\n\n        if self._is_connected:\n            raise RuntimeError(\n                f\"MCP '{self.name}' is already connected. \"\n                \"Call close() before reconnecting.\",\n            )\n\n        # Transports are one-shot context managers. Recreate them before every\n        # connection so connect() -> close() -> connect() starts a fresh one.\n        if self._client is None:\n            if self.mcp_config.type == \"http_mcp\":\n                self._client = self._create_http_client()\n            else:\n                self._initialize_client()\n\n        assert self._client is not None\n        self._stack = AsyncExitStack()\n\n        try:\n            context = await self._stack.enter_async_context(self._client)\n            read_stream, write_stream = context[0], context[1]","sourceCodeStart":212,"sourceCodeEnd":248,"githubUrl":"https://github.com/agentscope-ai/agentscope/blob/e90f1c7592896cc95f6e5ee506194f533378247d/src/agentscope/mcp/_mcp_client.py#L212-L248","documentation":"For stateful MCP connections, connect() refuses to run twice without an intervening close(), because the underlying transport/session resources (an AsyncExitStack of one-shot transports) are already allocated. This prevents leaking sessions and duplicated subprocesses/connections.","triggerScenarios":"Calling await client.connect() a second time on a stateful MCPClient (e.g. in a retry loop or after an exception that left the client connected), typically via agent.add_mcp(...) reconnect logic.","commonSituations":"Retrying initialization on transient failures without closing first; framework restart/reload handlers that re-run setup code; multiple components each trying to register/connect the same shared MCPClient.","solutions":["Call await client.close() before reconnecting, ideally in a finally block after the first connect","Guard with if client._is_connected / a wrapper that checks connection state before connect()","Reuse a single connected client instead of reconnecting per operation"],"exampleFix":"// before\nawait client.connect()\ntry:\n    ...\nexcept Exception:\n    await client.connect()  # RuntimeError 226\n\n// after\ntry:\n    await client.connect()\n    ...\nfinally:\n    await client.close()\nawait client.connect()  # fresh one-shot transport, OK","handlingStrategy":"try-catch","validationCode":"if getattr(client, \"_is_connected\", False):\n    await client.close()\nawait client.connect()","typeGuard":null,"tryCatchPattern":"try:\n    await client.connect()\nexcept RuntimeError as e:\n    if \"already connected\" in str(e):\n        await client.close()\n        await client.connect()\n    else:\n        raise","preventionTips":["Wrap connect/close in a lifecycle manager (async context manager) so pairing is guaranteed","Use try/finally around the connected region","Retry loops must close before reconnecting"],"tags":["mcp","connection-lifecycle","asyncio"],"backgroundTag":"double-connect-error","analyzedSha":"e90f1c7592896cc95f6e5ee506194f533378247d","analyzedAt":"2026-08-28T18:24:12.087Z","schemaVersion":2},"datasetVersion":"2026-08-28T21:17:43.275Z"}