[BUG] claude.ai connector: reconnecting after a 403 insufficient_scope doesn't request the challenged scope
What happens
A claude.ai connector (remote MCP server with OAuth) is used from the Claude desktop app, Code tab. A tool call needs a scope the connector's token lacks. The server answers 403 with WWW-Authenticate: Bearer error="insufficient_scope", scope="<held scopes> <required scope>", per the MCP authorization spec.
Claude shows:
This connector requires additional permissions. The user needs to reconnect it from connector settings with the appropriate access.
The user reconnects from the claude.ai connector settings. The authorization request does not include the challenged scope: the consent page only offers the scopes the connector already held. The required scope never appears, so it can't be granted.
Reproduced twice in a row. The second time, the failing call was made again right before reconnecting, so the challenge was fresh.
Server side
Metabase MCP v2 (self-hosted OSS v0.64.0.6-beta). A write tool needs agent:content:write. The connector held agent:resource:read agent:content:read agent:query:run agent:sql:run. The server builds the 403 challenge as held + required (step-up-scopes in src/metabase/mcp/v2/api.clj). The consent page shows exactly the scopes in the authorization request. It offered only the four held scopes, never agent:content:write.
agent:sql:run had been granted through a step-up earlier. So step-up works on some path, but not for a 403 raised while the connector is used from the Code tab.
Expected
After a 403 insufficient_scope, the next authorization of that connector requests the challenged scope, whichever surface raised the 403 and wherever the user reconnects from (automatic prompt or connector settings).
Related
#92518 and #73712 cover step-up for MCP servers configured in Claude Code itself. This report is about claude.ai connectors used from the desktop app.
3 Comments
Correction after checking the server's access logs: the reconnections that followed the
agent:content:write403s did not request the held scopes either. The authorization requests carried only the three baseline scopes (agent:content:read agent:query:run agent:resource:read), so the new token even lostagent:sql:run, which an earlier step-up had granted. Workaround on the server side: addingagent:content:writeandagent:sql:runto the 401 challenge and toscopes_supportedmakes the connector request them on reconnect, and the consent page then shows them.Useful server-side correction in the follow-up — especially that reconnect after the Code-tab
403 insufficient_scopenot only skippedagent:content:write, it also dropped back to the three baseline scopes and lost the earlieragent:sql:runstep-up.Two client-side fingerprints that usually split "consent UI never saw the challenge" from "challenge never left the tool-call path":
scope=is consumedAfter a tool-call
403withWWW-Authenticate: Bearer error="insufficient_scope", scope="…", check whether the next/authorize(or AS authorize URL) includes that challenged scope list, or only the connector's stored / baseline set. Your access-log note already shows baseline-only on reconnect from connector settings — that points at the Code-tab / claude.ai-connector path not forwarding the challenge into the next auth request (as opposed to Metabase omitting it fromscopes_supported).You already saw step-up work earlier for
agent:sql:run. Worth one controlled compare: trigger the same write tool so the403is fresh, then (a) use whatever in-product "additional permissions" affordance appears on the tool error (if any) before opening connector settings, vs (b) only Settings → Reconnect. If (a) requests held+required and (b) requests baseline-only, that pins the bug to the Settings reconnect entrypoint not reading the latest challenge.Server workaround (put required scopes on the
401/scopes_supported) is a solid interim; the client still ought to honor the403challenge scope on the next authorize regardless of which surface started the reconnect.Happy to compare authorize query strings if anyone else hits step-up from a claude.ai connector on the Code tab.
Thanks, the access logs already answer part of that compare:
403 insufficient_scopenamingagent:sql:runat 08:49:09 UTC was followed 20 seconds later by an/oauth/authorizerequesting the three baseline scopes plusagent:sql:run, consistent with an in-flow prompt.agent:content:write403s (08:39 and 08:41 UTC) requested the baseline only.So the Settings reconnect entry point seems to ignore the latest challenge, and the Code tab offers no other way in. We can't rerun a clean compare now: the server-side workaround puts both scopes in the baseline request.