{"record":{"id":"7666edc59c6773f4","repo":"PrefectHQ/fastmcp","slug":"clientgroup-clients-are-not-connected-names","errorCode":null,"errorMessage":"ClientGroup clients are not connected: {names}","messagePattern":"ClientGroup clients are not connected: (.+?)","errorType":"exception","errorClass":"RuntimeError","httpStatus":null,"severity":"error","filePath":"fastmcp_slim/fastmcp/client/group.py","lineNumber":144,"sourceCode":"        exc_type: type[BaseException] | None,\n        exc_value: BaseException | None,\n        traceback: TracebackType | None,\n    ) -> bool | None:\n        stack = self._exit_stack\n        self._exit_stack = None\n        self._tool_routes.clear()\n        self._catalog_loaded = False\n        if stack is not None:\n            return await stack.__aexit__(exc_type, exc_value, traceback)\n        return None\n\n    def _require_connected(self) -> None:\n        disconnected = [\n            name for name, client in self._clients.items() if not client.is_connected()\n        ]\n        if disconnected:\n            names = \", \".join(repr(name) for name in disconnected)\n            raise RuntimeError(f\"ClientGroup clients are not connected: {names}\")\n\n    def _require_route_connected(self, route: ToolRoute) -> None:\n        if not route.client.is_connected():\n            raise RuntimeError(\n                f\"ClientGroup client for server {route.server_name!r} is not connected\"\n            )\n\n    async def list_tools(\n        self, *, cache_mode: CacheMode = \"refresh\"\n    ) -> list[mcp_types.Tool]:\n        \"\"\"List tools from every client with namespaced names.\n\n        An explicit call is the group's catalog-refresh mechanism, so it\n        defaults to `cache_mode=\"refresh\"`: a client-side response cache\n        (SEP-2549) is repopulated rather than served, and the routes reflect\n        what every server advertises now. Pass `cache_mode=\"use\"` to allow\n        cache hits when staleness within the server's hint is acceptable.\n        \"\"\"","sourceCodeStart":126,"sourceCodeEnd":162,"githubUrl":"https://github.com/PrefectHQ/fastmcp/blob/1f021142978e0861cd910c8df4e8074bc7cf3978/fastmcp_slim/fastmcp/client/group.py#L126-L162","documentation":"ClientGroup raises this RuntimeError before any group operation (e.g. list_tools) when one or more of its managed clients have no active MCP session. The group never connects clients implicitly during operations; connections are established by entering the group's async context (`async with group:`) or by entering each client yourself. The error names every disconnected server so you can tell partial from total disconnect.","triggerScenarios":"Calling group.list_tools(), resolve_tool(), call_tool(), or call_tool_mcp() (which triggers lazy discovery) while at least one client in the group is not connected — e.g. you constructed ClientGroup(...) or ClientGroup.from_config(...) but never entered its async context, you used the clients outside the `async with` block, or a client's context was exited while the group object is still referenced.","commonSituations":"Forgetting `async with ClientGroup.from_config(cfg) as group:` and instead doing `group = ClientGroup.from_config(cfg); group.list_tools()`; managing client connections manually and one transport (stdio server that crashed, remote server that dropped) has died; reusing a group after its context manager exited; running list_tools inside a background task spawned after the `async with` block closed.","solutions":["Wrap usage in the group's async context manager: `async with ClientGroup.from_config(cfg) as group:` so every client connects on entry.","If managing connections manually, enter each client's context (`async with client:`) — or the group context — before calling group methods; FastMCP client contexts are reference-counted so nesting is safe.","Check connectivity up front with `[n for n, c in group.clients.items() if not c.is_connected()]` and reconnect the offenders (re-enter their contexts).","If a client's session died mid-run (transport crash), recreate the ClientGroup or reconnect that client, then retry; routes/catalog reset on group exit so a fresh `async with` is the clean path."],"exampleFix":"# before\ngroup = ClientGroup.from_config(config)\ntools = await group.list_tools()  # RuntimeError: clients are not connected\n\n# after\nasync with ClientGroup.from_config(config) as group:\n    tools = await group.list_tools()","handlingStrategy":"validation","validationCode":"def ensure_group_connected(group) -> None:\n    disconnected = [n for n, c in group.clients.items() if not c.is_connected()]\n    if disconnected:\n        raise RuntimeError(f\"Connect these servers first: {disconnected}\")","typeGuard":"def is_group_ready(group) -> bool:\n    return all(c.is_connected() for c in group.clients.values())","tryCatchPattern":"try:\n    tools = await group.list_tools()\nexcept RuntimeError as e:\n    if \"not connected\" in str(e):\n        async with group_clients_recreated(config) as fresh_group:\n            tools = await fresh_group.list_tools()\n    else:\n        raise","preventionTips":["Always use `async with ClientGroup.from_config(cfg) as group:` instead of manual client connection","Keep all group calls inside the context-manager scope; never spawn tasks that outlive it","Check `client.is_connected()` before group operations in long-running services","After any transport error, recreate the group rather than reusing a partially-disconnected one"],"tags":["mcp","connection","client-group","async-context"],"backgroundTag":"client-not-connected","analyzedSha":"1f021142978e0861cd910c8df4e8074bc7cf3978","analyzedAt":"2026-08-29T14:31:16.082Z","schemaVersion":2},"datasetVersion":"2026-08-29T17:17:51.833Z"}