agentscope-ai/agentscope · warning · RuntimeError

gateway failed to remove MCP {self.name!r}: {_safe_detail(st

Error message

gateway failed to remove MCP {self.name!r}: {_safe_detail(status, resp_body)}

What it means

Raised by GatewayMCPClient.close() when the gateway HTTP DELETE /mcps/{name} request returns a 4xx/5xx status and ignore_errors=False. The gateway process inside the sandbox failed to deregister the MCP server (e.g. already removed, unknown agent/session id, or gateway internal error). _safe_detail embeds the HTTP status and a truncated response body so the upstream reason is visible.

Source

Thrown at src/agentscope/workspace/_gateway_client.py:296

        """
        if not self._is_connected:
            if ignore_errors:
                return
            raise RuntimeError(
                f"MCP {self.name!r} is not connected. Call connect() first.",
            )
        assert self._gateway is not None
        try:
            status, resp_body = await self._gateway.exec_request(
                "DELETE",
                f"/mcps/{self.name}",
                params={
                    "agent_id": self._agent_id,
                    "session_id": self._session_id,
                },
            )
            if status >= 400 and not ignore_errors:
                raise RuntimeError(
                    f"gateway failed to remove MCP {self.name!r}: "
                    f"{_safe_detail(status, resp_body)}",
                )
        except Exception:
            if not ignore_errors:
                raise
        self._is_connected = False

    # ── tool discovery ────────────────────────────────────────────

    async def list_raw_tools(self) -> list[mcp.types.Tool]:
        """Fetch upstream tools via ``GET /mcps/{name}/tools``.

        Returns raw :class:`mcp.types.Tool` descriptors (upstream names,
        no ``mcp__`` prefix) so the inherited :meth:`get_tool` can
        re-wrap them like a local :class:`MCPClient`. The unfiltered
        list is cached; the returned list has ``enable_tools`` /
        ``disable_tools`` applied.

View on GitHub (pinned to e90f1c7592)

Solutions

  1. Use the default `await client.close()` (ignore_errors=True) in shutdown/cleanup paths — it suppresses 4xx/5xx by design
  2. Check gateway health (`await gateway.health()`) before closing if you must pass ignore_errors=False
  3. Re-list MCPs (`await gateway.list_mcps(agent_id, session_id)`) and only close clients that still appear
  4. Inspect the embedded status/body detail in the message to see the gateway's actual complaint (404 vs 500) and fix the agent_id/session_id accordingly

Example fix

// before
await client.close(ignore_errors=False)
// after
await client.close()  # ignore_errors=True by default; safe for cleanup
Defensive patterns

Strategy: try-catch

Validate before calling

alive = await gateway.list_mcps(agent_id, session_id)
registered = any(c.name == client.name for c in alive)

Try / catch

try:
    await client.close(ignore_errors=False)
except RuntimeError as e:
    logger.warning("MCP deregistration failed (continuing): %s", e)

Prevention

When it happens

Trigger: Calling `await client.close(ignore_errors=False)` on a connected GatewayMCPClient after the gateway has already dropped the MCP (restart, expiry), or with an agent_id/session_id that no longer matches a registered MCP. Any DELETE /mcps/{name} returning status >= 400.

Common situations: Gateway restarted or crashed between connect() and close(); session ids recycled across runs; calling close() twice with different id scopes; passing ignore_errors=False in cleanup paths where the gateway state is already stale.

Related errors


AI-assisted analysis of agentscope-ai/agentscope@e90f1c7592 (2026-08-28). Data as JSON: /api/errors/fff0f2fb97850c42. Report an issue: GitHub.