PrefectHQ/fastmcp · error · RuntimeError
ClientGroup clients are not connected: {names}
Error message
ClientGroup clients are not connected: {names} What it means
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.
Source
Thrown at fastmcp_slim/fastmcp/client/group.py:144
exc_type: type[BaseException] | None,
exc_value: BaseException | None,
traceback: TracebackType | None,
) -> bool | None:
stack = self._exit_stack
self._exit_stack = None
self._tool_routes.clear()
self._catalog_loaded = False
if stack is not None:
return await stack.__aexit__(exc_type, exc_value, traceback)
return None
def _require_connected(self) -> None:
disconnected = [
name for name, client in self._clients.items() if not client.is_connected()
]
if disconnected:
names = ", ".join(repr(name) for name in disconnected)
raise RuntimeError(f"ClientGroup clients are not connected: {names}")
def _require_route_connected(self, route: ToolRoute) -> None:
if not route.client.is_connected():
raise RuntimeError(
f"ClientGroup client for server {route.server_name!r} is not connected"
)
async def list_tools(
self, *, cache_mode: CacheMode = "refresh"
) -> list[mcp_types.Tool]:
"""List tools from every client with namespaced names.
An explicit call is the group's catalog-refresh mechanism, so it
defaults to `cache_mode="refresh"`: a client-side response cache
(SEP-2549) is repopulated rather than served, and the routes reflect
what every server advertises now. Pass `cache_mode="use"` to allow
cache hits when staleness within the server's hint is acceptable.
"""View on GitHub (pinned to 1f02114297)
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.
Example fix
# before
group = ClientGroup.from_config(config)
tools = await group.list_tools() # RuntimeError: clients are not connected
# after
async with ClientGroup.from_config(config) as group:
tools = await group.list_tools() Defensive patterns
Strategy: validation
Validate before calling
def ensure_group_connected(group) -> None:
disconnected = [n for n, c in group.clients.items() if not c.is_connected()]
if disconnected:
raise RuntimeError(f"Connect these servers first: {disconnected}") Type guard
def is_group_ready(group) -> bool:
return all(c.is_connected() for c in group.clients.values()) Try / catch
try:
tools = await group.list_tools()
except RuntimeError as e:
if "not connected" in str(e):
async with group_clients_recreated(config) as fresh_group:
tools = await fresh_group.list_tools()
else:
raise Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- ClientGroup client for server {route.server_name!r} is not c
- Tool name collision: {public_name!r}
- Unknown tool: {name!r}. If servers changed their tools, call
- INVALID_PARAMS
- The client negotiated a modern protocol era (server/discover
AI-assisted analysis of PrefectHQ/fastmcp@1f02114297 (2026-08-29).
Data as JSON: /api/errors/7666edc59c6773f4.
Report an issue: GitHub.