{"record":{"id":"742798d02461e435","repo":"PrefectHQ/fastmcp","slug":"unknown-tool-name-r-if-servers-changed-their-t","errorCode":null,"errorMessage":"Unknown tool: {name!r}. If servers changed their tools, call list_tools() to refresh the catalog.","messagePattern":"Unknown tool: (.+?)\\. If servers changed their tools, call list_tools\\(\\) to refresh the catalog\\.","errorType":"exception","errorClass":"KeyError","httpStatus":null,"severity":"error","filePath":"fastmcp_slim/fastmcp/client/group.py","lineNumber":202,"sourceCode":"\n        self._tool_routes = routes\n        self._catalog_loaded = True\n        return tools\n\n    async def resolve_tool(self, name: str) -> ToolRoute:\n        \"\"\"Resolve a public tool name to its client and upstream identity.\n\n        A known route only requires its own client to be connected; one dead\n        server does not couple failures onto calls routed to healthy servers.\n        Loading the catalog (the first resolution, or after a refresh) still\n        requires every client, since discovery queries them all.\n        \"\"\"\n        route = self._tool_routes.get(name)\n        if route is not None:\n            self._require_route_connected(route)\n            return route\n        if self._catalog_loaded:\n            raise KeyError(\n                f\"Unknown tool: {name!r}. If servers changed their tools,\"\n                \" call list_tools() to refresh the catalog.\"\n            )\n\n        async with self._route_lock:\n            route = self._tool_routes.get(name)\n            if route is not None:\n                return route\n            if not self._catalog_loaded:\n                # Lazy cold-start discovery may serve a cached listing; only an\n                # explicit list_tools() call promises a refreshed catalog.\n                await self.list_tools(cache_mode=\"use\")\n                route = self._tool_routes.get(name)\n\n        if route is None:\n            raise KeyError(\n                f\"Unknown tool: {name!r}. If servers changed their tools,\"\n                \" call list_tools() to refresh the catalog.\"","sourceCodeStart":184,"sourceCodeEnd":220,"githubUrl":"https://github.com/PrefectHQ/fastmcp/blob/1f021142978e0861cd910c8df4e8074bc7cf3978/fastmcp_slim/fastmcp/client/group.py#L184-L220","documentation":"ClientGroup.resolve_tool raises this KeyError when the tool catalog has already been loaded (self._catalog_loaded is True) but the requested public name is not in the cached routes. The group intentionally does not re-query servers on every call: only an explicit list_tools() refreshes the catalog, so a name that was not present at last discovery is rejected immediately. The message tells you the remedy — call list_tools() to refresh.","triggerScenarios":"Calling group.call_tool(name) / call_tool_mcp(name) with a name that (a) was never listed — e.g. a typo, or forgetting the required `{server_name}_` namespace prefix — or (b) the upstream server started advertising after your last list_tools() call, so the stale catalog does not contain it.","commonSituations":"Calling the upstream tool name (e.g. `get_forecast`) instead of the namespaced one (`weather_get_forecast`); a server deployed new tools after the group connected; agent code caching tool lists across group restarts; tests calling tools before any list_tools() refresh following a server change.","solutions":["Call `await group.list_tools()` to refresh the catalog from all servers, then retry the call.","Use the namespaced public name: prefix the tool with its server name as shown by list_tools() (e.g. `weather_get_forecast`).","Catch KeyError, call list_tools() once, and retry the resolution to tolerate servers that added tools mid-session.","Verify the exact names with `tools = await group.list_tools(); print([t.name for t in tools])` before hardcoding calls."],"exampleFix":"# before\nresult = await group.call_tool(\"get_forecast\", {\"city\": \"SF\"})  # KeyError: Unknown tool\n\n# after\ntools = await group.list_tools()  # refresh + get namespaced names\nresult = await group.call_tool(\"weather_get_forecast\", {\"city\": \"SF\"})","handlingStrategy":"try-catch","validationCode":"available = {t.name for t in await group.list_tools()}\nif name not in available:\n    raise LookupError(f\"{name!r} not offered by group; available: {sorted(available)}\")","typeGuard":null,"tryCatchPattern":"try:\n    result = await group.call_tool(name, args)\nexcept KeyError as e:\n    await group.list_tools()  # refresh catalog\n    result = await group.call_tool(name, args)  # retry once; may still KeyError if truly absent","preventionTips":["Always use the namespaced `{server_name}_{tool.name}` form, never the bare upstream name","Discover names via list_tools() instead of hardcoding tool names","Call list_tools() after server deployments or restarts","Catch KeyError with a single refresh-and-retry for tools added mid-session"],"tags":["mcp","client-group","unknown-tool","stale-cache"],"backgroundTag":"unknown-tool","analyzedSha":"1f021142978e0861cd910c8df4e8074bc7cf3978","analyzedAt":"2026-08-29T14:31:16.082Z","schemaVersion":2},"datasetVersion":"2026-08-29T17:17:51.833Z"}