{"record":{"id":"9413a142c7ce47f1","repo":"PrefectHQ/fastmcp","slug":"unknown-tool-name-r","errorCode":null,"errorMessage":"Unknown tool: {name!r}","messagePattern":"Unknown tool: (.+?)","errorType":"exception","errorClass":"NotFoundError","httpStatus":null,"severity":"error","filePath":"fastmcp_slim/fastmcp/server/server.py","lineNumber":1482,"sourceCode":"                tool: Tool | None = await self.get_tool(name, version=version)\n\n                # If that fails, try hashed-name dispatch. This walks\n                # the provider tree recursively (same pattern as the old\n                # get_app_tool) looking for a tool whose stored hash\n                # matches the parsed prefix.\n                if tool is None:\n                    hashed = parse_hashed_backend_name(name)\n                    if hashed is not None:\n                        digest, local_name = hashed\n                        tool = await self.get_tool_by_hash(digest, local_name)\n                        if tool is not None:\n                            # Auth still applies on the bypass path.\n                            skip_auth, token = _get_auth_context()\n                            if not skip_auth and tool.auth is not None:\n                                try:\n                                    auth_ctx = AuthContext(token=token, component=tool)\n                                    if not await run_auth_checks(tool.auth, auth_ctx):\n                                        raise NotFoundError(f\"Unknown tool: {name!r}\")\n                                except AuthorizationError:\n                                    raise NotFoundError(\n                                        f\"Unknown tool: {name!r}\"\n                                    ) from None\n\n                if tool is None:\n                    raise NotFoundError(f\"Unknown tool: {name!r}\")\n                span.set_attributes(tool.get_span_attributes())\n                try:\n                    return await tool._run(arguments or {})\n                except ValidationError as e:\n                    # Argument-validation failure (a bad call). FunctionTool\n                    # converts pydantic's call-validation error into fastmcp's\n                    # ValidationError (see #4128) so it can be filtered as a\n                    # client error. Log the underlying detail without a URL or\n                    # traceback, matching the previous pydantic-error logging.\n                    cause = e.__cause__\n                    detail = (","sourceCodeStart":1464,"sourceCodeEnd":1500,"githubUrl":"https://github.com/PrefectHQ/fastmcp/blob/1f021142978e0861cd910c8df4e8074bc7cf3978/fastmcp_slim/fastmcp/server/server.py#L1464-L1500","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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)"],"exampleFix":"// before: calling with stale/missing credentials\nawait client.call_tool('a1b2c3_my_tool', {...})\n// after: re-authenticate the client first\nasync with Client(server, auth=BearerAuth(fresh_token)) as client:\n    await client.call_tool('a1b2c3_my_tool', {...})","handlingStrategy":"try-catch","validationCode":"token_is_fresh = auth.expires_at > time.time() and 'tool:invoke' in auth.scopes\nassert token_is_fresh, 'refresh credentials before calling protected tools'","typeGuard":"def is_auth_error(e: BaseException) -> bool:\n    return isinstance(e, NotFoundError) and 'Unknown tool' in str(e)","tryCatchPattern":"try:\n    result = await client.call_tool(hashed_name, args)\nexcept NotFoundError as e:\n    if 'Unknown tool' in str(e):\n        await reauthenticate(client)  # refresh token, then retry once\n    else:\n        raise","preventionTips":["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"],"tags":["auth","authorization","tools","hashed-names"],"backgroundTag":"tool-authorization-denied","analyzedSha":"1f021142978e0861cd910c8df4e8074bc7cf3978","analyzedAt":"2026-08-29T14:31:16.082Z","schemaVersion":2},"datasetVersion":"2026-08-29T17:17:51.833Z"}