{"record":{"id":"6c4aeca1ee5bc8a5","repo":"PrefectHQ/fastmcp","slug":"authorization-failed-for-tool-tool-name-insuf","errorCode":null,"errorMessage":"Authorization failed for tool '{tool_name}': insufficient scope (required: {', '.join(missing)})","messagePattern":"Authorization failed for tool '(.+?)': insufficient scope \\(required: (.+?)\\)","errorType":"exception","errorClass":"InsufficientScopeError","httpStatus":null,"severity":"error","filePath":"fastmcp_slim/fastmcp/server/middleware/authorization.py","lineNumber":243,"sourceCode":"        # 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                )\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.\"\"\"","sourceCodeStart":225,"sourceCodeEnd":261,"githubUrl":"https://github.com/PrefectHQ/fastmcp/blob/1f021142978e0861cd910c8df4e8074bc7cf3978/fastmcp_slim/fastmcp/server/middleware/authorization.py#L225-L261","documentation":"The caller's token passed all existence checks but the auth provider's checks returned unauthorized with a non-empty shortfall of missing scopes. AuthMiddleware converts that into InsufficientScopeError listing the required scopes (after chaining shortfalls via _chain_shortfall), following RFC 6750 insufficient_scope semantics.","triggerScenarios":"tools/call for a tool whose configured required scopes (tool-level or global auth policy) are not fully satisfied by the access token — e.g. token has 'read' but the tool requires 'read' and 'write'.","commonSituations":"Downscoped or expired OAuth tokens; JWT issued by a provider without the scope attached to the tool's auth config; switching auth providers so scope names changed (e.g. 'admin' vs 'tools:admin'); test-client tokens missing production scopes.","solutions":["Re-acquire an access token that includes the missing scopes listed in the error message (printed after 'required:').","Update the token request's scope parameter or the OAuth provider client configuration to grant the required scopes.","If the tool's required scopes are wrong, adjust the tool's auth configuration (e.g. requires_scopes) to match your actual token scopes.","Verify scope naming/claims mapping (SCOPE claim, audience) between your identity provider and the FastMCP auth server configuration."],"exampleFix":"# before\ntoken = get_token(client_id, scopes=['read'])\nresult = await client.call_tool('deploy', {...})  # requires 'deploy:write'\n# after\ntoken = get_token(client_id, scopes=['read', 'deploy:write'])\nresult = await client.call_tool('deploy', {...})","handlingStrategy":"try-catch","validationCode":"# client-side: ensure requested scopes are granted before calling\nclaims = decode_jwt(token)  # provider-specific\nrequired = {'deploy:write'}\nassert required.issubset(set(claims.get('scope', '').split())), 'token missing scopes'","typeGuard":null,"tryCatchPattern":"from fastmcp.exceptions import InsufficientScopeError\ntry:\n    result = await client.call_tool('deploy', args)\nexcept InsufficientScopeError as e:\n    missing = getattr(e, 'missing_scopes', None) or parse_scopes(str(e))\n    token = await auth_provider.refresh_with_scopes(missing)\n    client.set_auth(BearerAuth(token))\n    result = await client.call_tool('deploy', args)","preventionTips":["Request all scopes the protected components require when obtaining tokens.","Handle insufficient_scope responses with a scope-upgrade (re-auth) flow instead of retrying blindly.","Keep scope names consistent between the auth provider and tool/resource auth config.","Decode and inspect tokens in staging before deploying scope changes."],"tags":["auth","oauth","scopes","jwt","insufficient-scope"],"backgroundTag":"insufficient-scope","analyzedSha":"1f021142978e0861cd910c8df4e8074bc7cf3978","analyzedAt":"2026-08-29T14:31:16.082Z","schemaVersion":2},"datasetVersion":"2026-08-29T17:17:51.833Z"}