{"record":{"id":"677cf02e7d85395d","repo":"PrefectHQ/fastmcp","slug":"authorization-failed-for-tool-tool-name-not-f","errorCode":null,"errorMessage":"Authorization failed for tool '{tool_name}': not found or not authorized","messagePattern":"Authorization failed for tool '(.+?)': not found or not authorized","errorType":"exception","errorClass":"AuthorizationError","httpStatus":null,"severity":"error","filePath":"fastmcp_slim/fastmcp/server/middleware/authorization.py","lineNumber":231,"sourceCode":"        fastmcp = context.fastmcp_context\n        if fastmcp is None:\n            # Fail closed: deny access when context is missing\n            logger.warning(\n                f\"AuthMiddleware: fastmcp_context is None for tool '{tool_name}'. \"\n                \"Denying access for security.\"\n            )\n            raise AuthorizationError(\n                f\"Authorization failed for tool '{tool_name}': missing context\"\n            )\n\n        # get_tool returns None both when the tool does not exist and when\n        # component-level auth denied access, so the two cases are\n        # indistinguishable here. Keep the message ambiguous to avoid\n        # disclosing existence of tools the caller is not authorized to see.\n        version = _requested_version(context.message.meta)\n        tool = await fastmcp.fastmcp.get_tool(tool_name, version=version)\n        if tool is None:\n            raise AuthorizationError(\n                f\"Authorization failed for tool '{tool_name}': \"\n                \"not found or not authorized\"\n            )\n\n        # Global auth check\n        token = get_access_token()\n        ctx = AuthContext(token=token, component=tool)\n        authorized, missing = await run_auth_checks_with_shortfall(self.auth, ctx)\n        if not authorized:\n            if missing:\n                missing = self._chain_shortfall(missing, ctx, fastmcp.fastmcp)\n                raise InsufficientScopeError(\n                    missing,\n                    message=(\n                        f\"Authorization failed for tool '{tool_name}': \"\n                        f\"insufficient scope (required: {', '.join(missing)})\"\n                    ),\n                )","sourceCodeStart":213,"sourceCodeEnd":249,"githubUrl":"https://github.com/PrefectHQ/fastmcp/blob/1f021142978e0861cd910c8df4e8074bc7cf3978/fastmcp_slim/fastmcp/server/middleware/authorization.py#L213-L249","documentation":"AuthMiddleware.on_call_tool looks the tool up with `fastmcp.get_tool(tool_name, version=version)` and denies the request when it returns None. Because component-level auth can also make get_tool return None, the message deliberately does not distinguish a missing tool from one the caller is not allowed to see, to avoid leaking tool existence.","triggerScenarios":"Client calls tools/call with a name that is not registered, a typo'd or renamed tool, a version requested via `_meta` with no matching tool variant, or the tool exists but component-level auth rejected this caller so get_tool hides it.","commonSituations":"Client SDK out of sync with the server after a tool rename; calling a tool behind an auth provider that filters it for this token; requesting a versioned tool variant that was removed; staging vs production servers with different tool sets.","solutions":["Verify the exact tool name by listing tools as the authorized client (`session.list_tools()`) or via `await mcp.get_tool(name)`; fix typos/renames.","Check the requested version in the call's `_meta` matches a registered tool version, or drop the version to get the default.","If component-level auth is intended to hide the tool, grant the caller's token the required scopes/roles configured on the tool.","Confirm the tool is registered on the same FastMCP instance the client connects to (not a different server object)."],"exampleFix":"# before\nresult = await client.call_tool('get_weahter', {'city': 'SF'})  # typo\n# after\ntools = await client.list_tools()\nassert any(t.name == 'get_weather' for t in tools)\nresult = await client.call_tool('get_weather', {'city': 'SF'})","handlingStrategy":"validation","validationCode":"names = {t.name for t in await client.list_tools()}\nassert 'my_tool' in names, 'tool not visible to this caller; check name or auth'","typeGuard":null,"tryCatchPattern":"from fastmcp.exceptions import AuthorizationError\ntry:\n    result = await client.call_tool('my_tool', args)\nexcept AuthorizationError as e:\n    if 'not found or not authorized' in str(e):\n        available = await client.list_tools()\n        logger.warning('tool unavailable; visible: %s', [t.name for t in available])\n    else:\n        raise","preventionTips":["Verify tool names with list_tools() before calling, especially after renames.","Keep client and server deployments in sync on tool names and versions.","Remember the message is intentionally ambiguous — check both existence and your token's permissions.","Omit _meta version fields unless you know the versioned variant exists."],"tags":["auth","tools","middleware","access-control"],"backgroundTag":"authorization-failed","analyzedSha":"1f021142978e0861cd910c8df4e8074bc7cf3978","analyzedAt":"2026-08-29T14:31:16.082Z","schemaVersion":2},"datasetVersion":"2026-08-29T17:17:51.833Z"}