{"record":{"id":"0e32419cb97a530f","repo":"PrefectHQ/fastmcp","slug":"clientgroup-requires-at-least-one-client","errorCode":null,"errorMessage":"ClientGroup requires at least one client","messagePattern":"ClientGroup requires at least one client","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"fastmcp_slim/fastmcp/client/group.py","lineNumber":44,"sourceCode":"    server_name: str\n    client: Client[Any]\n    upstream_name: str\n\n\nclass ClientGroup:\n    \"\"\"Coordinate independent clients without introducing a proxy server.\n\n    Each client retains its own transport, session, capabilities, and protocol\n    version. The group only combines tool discovery and routes tool calls.\n\n    Callers may manage the clients' connections themselves or use the group as\n    a convenience context manager. Entering an already-connected FastMCP client\n    is safe because client contexts are reference counted.\n    \"\"\"\n\n    def __init__(self, clients: Mapping[str, Client[Any]]) -> None:\n        if not clients:\n            raise ValueError(\"ClientGroup requires at least one client\")\n\n        self._clients = dict(clients)\n        self._exit_stack: contextlib.AsyncExitStack | None = None\n        self._tool_routes: dict[str, ToolRoute] = {}\n        self._catalog_loaded = False\n        self._route_lock = anyio.Lock()\n\n    @property\n    def clients(self) -> Mapping[str, Client[Any]]:\n        \"\"\"The group's clients, keyed by server name.\n\n        Read-only: membership is fixed at construction, since discovered routes\n        hold the client that advertised each tool and would silently go stale\n        if the mapping were swapped underneath them.\n        \"\"\"\n        return MappingProxyType(self._clients)\n\n    @classmethod","sourceCodeStart":26,"sourceCodeEnd":62,"githubUrl":"https://github.com/PrefectHQ/fastmcp/blob/1f021142978e0861cd910c8df4e8074bc7cf3978/fastmcp_slim/fastmcp/client/group.py#L26-L62","documentation":"ClientGroup fans out MCP operations across multiple named clients; constructing it with an empty mapping leaves nothing to operate on, so `__init__` raises this ValueError immediately. It is eager, fail-fast argument validation.","triggerScenarios":"`ClientGroup({})` or `ClientGroup(dict())` — e.g. a config file with no mcpServers entries, or a filtered dict that ended up empty.","commonSituations":"Empty or missing `mcpServers` section in the MCP config; environment-specific config with no servers; programmatically built client maps where all entries were filtered out.","solutions":["Pass at least one client: ClientGroup({\"server1\": client1})","Check the config file actually defines servers before constructing the group","Build the mapping, check its length, or skip the group entirely when empty"],"exampleFix":"// before\nclients = load_config_servers(path)  # may be {}\ngroup = ClientGroup(clients)  # ValueError\n\n// after\nclients = load_config_servers(path)\nif not clients:\n    raise RuntimeError(f\"No MCP servers configured in {path}\")\ngroup = ClientGroup(clients)","handlingStrategy":"validation","validationCode":"if not clients:\n    raise ValueError(\"Cannot build ClientGroup: no servers configured\")\ngroup = ClientGroup(clients)","typeGuard":null,"tryCatchPattern":"try:\n    group = ClientGroup(clients)\nexcept ValueError:\n    logger.warning(\"no MCP servers configured; skipping group setup\")\n    group = None","preventionTips":["Validate config files define mcpServers before loading","Check dict emptiness right before constructing the group","Fail early with a clear message at config-load time"],"tags":["python","validation","configuration","client-group"],"backgroundTag":"empty-collection-argument","analyzedSha":"1f021142978e0861cd910c8df4e8074bc7cf3978","analyzedAt":"2026-08-29T14:31:16.082Z","schemaVersion":2},"datasetVersion":"2026-08-29T17:17:51.833Z"}