PrefectHQ/fastmcp · error · NotFoundError
Unknown tool: {name!r}
Error message
Unknown tool: {name!r} What it means
When a tool is resolved via hashed backend-name dispatch (<hash>_<local_name>), FastMCP still runs the tool's auth checks on the bypass path. If run_auth_checks denies the request, or the tool's auth raises AuthorizationError, the server raises NotFoundError('Unknown tool: ...') — deliberately indistinguishable from a missing tool so existence is not leaked to unauthorized callers.
Source
Thrown at fastmcp_slim/fastmcp/server/server.py:1482
tool: Tool | None = await self.get_tool(name, version=version)
# If that fails, try hashed-name dispatch. This walks
# the provider tree recursively (same pattern as the old
# get_app_tool) looking for a tool whose stored hash
# matches the parsed prefix.
if tool is None:
hashed = parse_hashed_backend_name(name)
if hashed is not None:
digest, local_name = hashed
tool = await self.get_tool_by_hash(digest, local_name)
if tool is not None:
# Auth still applies on the bypass path.
skip_auth, token = _get_auth_context()
if not skip_auth and tool.auth is not None:
try:
auth_ctx = AuthContext(token=token, component=tool)
if not await run_auth_checks(tool.auth, auth_ctx):
raise NotFoundError(f"Unknown tool: {name!r}")
except AuthorizationError:
raise NotFoundError(
f"Unknown tool: {name!r}"
) from None
if tool is None:
raise NotFoundError(f"Unknown tool: {name!r}")
span.set_attributes(tool.get_span_attributes())
try:
return await tool._run(arguments or {})
except ValidationError as e:
# Argument-validation failure (a bad call). FunctionTool
# converts pydantic's call-validation error into fastmcp's
# ValidationError (see #4128) so it can be filtered as a
# client error. Log the underlying detail without a URL or
# traceback, matching the previous pydantic-error logging.
cause = e.__cause__
detail = (View on GitHub (pinned to 1f02114297)
Solutions
- Refresh the client's credentials/token so run_auth_checks passes
- Check the tool's auth configuration (tool.auth) and confirm the caller meets its requirements
- If you don't need hashed-name dispatch, call the tool by its display name through the normal get_tool path
- Verify _get_auth_context is supplying the intended token (skip_auth/token wiring)
Example fix
// before: calling with stale/missing credentials
await client.call_tool('a1b2c3_my_tool', {...})
// after: re-authenticate the client first
async with Client(server, auth=BearerAuth(fresh_token)) as client:
await client.call_tool('a1b2c3_my_tool', {...}) Defensive patterns
Strategy: try-catch
Validate before calling
token_is_fresh = auth.expires_at > time.time() and 'tool:invoke' in auth.scopes assert token_is_fresh, 'refresh credentials before calling protected tools'
Type guard
def is_auth_error(e: BaseException) -> bool:
return isinstance(e, NotFoundError) and 'Unknown tool' in str(e) Try / catch
try:
result = await client.call_tool(hashed_name, args)
except NotFoundError as e:
if 'Unknown tool' in str(e):
await reauthenticate(client) # refresh token, then retry once
else:
raise Prevention
- Refresh tokens proactively before expiry
- Audit tool.auth rules against your client's scopes
- Prefer display-name calls through normal routing when auth is simpler there
- Log auth denials server-side to correlate with 'Unknown tool' responses
When it happens
Trigger: Calling a hashed-name tool (parsed by parse_hashed_backend_name) whose auth checks fail: an invalid/expired bearer token, a token lacking the required scope, or tool.auth rules that reject the caller. Also raised when AuthorizationError escapes the auth check.
Common situations: Expired or rotated API tokens; per-tool auth configured after deployment and clients still using old credentials; calling an app-callable backend tool directly by hashed name with insufficient permissions.
Related errors
- Authorization failed for tool '{tool_name}': not found or no
- require_roles() needs at least one role; a check with no rol
- OAuth authorization failed: {error} - {error_desc}
- OAuth client not found - cached credentials may be stale
- OAuth server rejected the static client credentials. Verify
AI-assisted analysis of PrefectHQ/fastmcp@1f02114297 (2026-08-29).
Data as JSON: /api/errors/9413a142c7ce47f1.
Report an issue: GitHub.