{"record":{"id":"60fa42c42bdecabb","repo":"PrefectHQ/fastmcp","slug":"authorization-failed-for-tool-tool-name-insuf-60fa42","errorCode":null,"errorMessage":"Authorization failed for tool '{tool_name}': insufficient permissions","messagePattern":"Authorization failed for tool '(.+?)': insufficient permissions","errorType":"exception","errorClass":"AuthorizationError","httpStatus":null,"severity":"error","filePath":"fastmcp_slim/fastmcp/server/middleware/authorization.py","lineNumber":250,"sourceCode":"                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                )\n            raise AuthorizationError(\n                f\"Authorization failed for tool '{tool_name}': insufficient permissions\"\n            )\n\n        return await call_next(context)\n\n    async def on_list_resources(\n        self,\n        context: MiddlewareContext[mt.ListResourcesRequest],\n        call_next: CallNext[mt.ListResourcesRequest, Sequence[Resource]],\n    ) -> Sequence[Resource]:\n        \"\"\"Filter resources/list response based on auth checks.\"\"\"\n        resources = await call_next(context)\n\n        # STDIO has no auth concept, skip filtering\n        from fastmcp.server.context import _current_transport\n\n        if _current_transport.get() == \"stdio\":\n            return resources","sourceCodeStart":232,"sourceCodeEnd":268,"githubUrl":"https://github.com/PrefectHQ/fastmcp/blob/1f021142978e0861cd910c8df4e8074bc7cf3978/fastmcp_slim/fastmcp/server/middleware/authorization.py#L232-L268","documentation":"Fallback branch of the global auth check in on_call_tool: the auth checks rejected the request but produced no specific scope shortfall, so a generic AuthorizationError is raised instead of InsufficientScopeError. Denial came from non-scope logic in the auth provider (role checks, custom predicates, audience validation, etc.).","triggerScenarios":"run_auth_checks_with_shortfall returns authorized=False with an empty `missing` list for the tool's AuthContext — a custom auth check returning False without reporting a shortfall, role/permission checks failing, JWT audience/issuer mismatch, or an unintended default-deny policy.","commonSituations":"Custom auth providers whose check() returns False silently; tokens lacking required roles/claims (as opposed to scopes); audience/issuer mismatch on the JWT; deny-all policy accidentally applying to tools.","solutions":["Inspect your auth provider's checks — any check that returns False must either raise with a reason or report the missing scopes so the middleware can produce a shortfall.","Decode the access token and compare its claims (roles, scopes, aud, iss) against what the tool's auth policy expects.","Fix the token: obtain one with the required roles/claims, or correct the provider's claim mapping.","Review global vs component auth policy so the request isn't denied by an unintended default-deny rule."],"exampleFix":"# before\nclass RoleCheck(AuthCheck):\n    async def check(self, ctx): return False  # denial with no shortfall -> generic error\n# after\nclass RoleCheck(AuthCheck):\n    async def check(self, ctx):\n        if 'admin' not in ctx.token.roles:\n            raise AuthorizationError('missing admin role')\n        return True","handlingStrategy":"try-catch","validationCode":"claims = decode_jwt(token)\nassert claims.get('aud') == expected_audience, 'audience mismatch will cause generic denial'\nassert required_roles.issubset(set(claims.get('roles', []))), 'token missing roles'","typeGuard":null,"tryCatchPattern":"from fastmcp.exceptions import AuthorizationError\ntry:\n    result = await client.call_tool('my_tool', args)\nexcept AuthorizationError as e:\n    if 'insufficient permissions' in str(e):\n        logger.error('Denied without scope shortfall; check custom auth checks and token claims')\n    else:\n        raise","preventionTips":["Make custom AuthChecks raise specific errors or report shortfalls instead of returning bare False.","Validate JWT audience/issuer/claims mapping in your provider config.","Test auth policies with an explicitly scoped test token before production.","Log denials server-side to distinguish policy vs token problems."],"tags":["auth","middleware","access-control","custom-auth"],"backgroundTag":"authorization-failed","analyzedSha":"1f021142978e0861cd910c8df4e8074bc7cf3978","analyzedAt":"2026-08-29T14:31:16.082Z","schemaVersion":2},"datasetVersion":"2026-08-29T17:17:51.833Z"}