PrefectHQ/fastmcp · error · KeyError
Unknown tool: {name!r}. If servers changed their tools, call
Error message
Unknown tool: {name!r}. If servers changed their tools, call list_tools() to refresh the catalog. What it means
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.
Source
Thrown at fastmcp_slim/fastmcp/client/group.py:202
self._tool_routes = routes
self._catalog_loaded = True
return tools
async def resolve_tool(self, name: str) -> ToolRoute:
"""Resolve a public tool name to its client and upstream identity.
A known route only requires its own client to be connected; one dead
server does not couple failures onto calls routed to healthy servers.
Loading the catalog (the first resolution, or after a refresh) still
requires every client, since discovery queries them all.
"""
route = self._tool_routes.get(name)
if route is not None:
self._require_route_connected(route)
return route
if self._catalog_loaded:
raise KeyError(
f"Unknown tool: {name!r}. If servers changed their tools,"
" call list_tools() to refresh the catalog."
)
async with self._route_lock:
route = self._tool_routes.get(name)
if route is not None:
return route
if not self._catalog_loaded:
# Lazy cold-start discovery may serve a cached listing; only an
# explicit list_tools() call promises a refreshed catalog.
await self.list_tools(cache_mode="use")
route = self._tool_routes.get(name)
if route is None:
raise KeyError(
f"Unknown tool: {name!r}. If servers changed their tools,"
" call list_tools() to refresh the catalog."View on GitHub (pinned to 1f02114297)
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.
Example fix
# before
result = await group.call_tool("get_forecast", {"city": "SF"}) # KeyError: Unknown tool
# after
tools = await group.list_tools() # refresh + get namespaced names
result = await group.call_tool("weather_get_forecast", {"city": "SF"}) Defensive patterns
Strategy: try-catch
Validate before calling
available = {t.name for t in await group.list_tools()}
if name not in available:
raise LookupError(f"{name!r} not offered by group; available: {sorted(available)}") Try / catch
try:
result = await group.call_tool(name, args)
except KeyError as e:
await group.list_tools() # refresh catalog
result = await group.call_tool(name, args) # retry once; may still KeyError if truly absent Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- ClientGroup clients are not connected: {names}
- ClientGroup client for server {route.server_name!r} is not c
- Tool name collision: {public_name!r}
- 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/742798d02461e435.
Report an issue: GitHub.