Repository navigation
feat(auth): ask for a missing OAuth scope with a 403 insufficient_scope challenge - #261
Closed
mattzcarey wants to merge 4 commits into
Closed
mattzcarey wants to merge 4 commits into
mattzcarey wants to merge 4 commits into
Conversation
This was referenced Oct 7, 2026
mattzcarey
force-pushed
the
feat/insufficient-scope-challenge
branch
from
October 7, 2026 05:14
a8213d3 to
0c12156
Compare
This was referenced Oct 7, 2026
Member
Author
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
When Cloudflare refuses a call because the OAuth connection lacks a scope, answer with the spec's step-up challenge (MCP scope challenge handling) instead of a tool error. Claude, Claude Code, the MCP Inspector and the SDK client then re-authorize for the scope and retry the call. Replaces #256.
When the server challenges. All of these must hold. Otherwise the call gets the explanation from #260 as a tool error.
403. There's no up-front check from the spec: mapping permission names to scopes is good enough to explain a refusal, but not to block a call that might have worked. That's also why this doesn't use the SDK'sscopeChallenge, which runs before the tool does.scopeToRequestpicks a read scope for GET/HEAD and a write scope otherwise. Next it takes the account or zone variant the path implies (access.readorzone-access.read). Then it takes the alternative that matches the path best (workers-scripts.readrather thanworkers-tail.read). A tie names no scope.executeneeds the refusal to be what made the code fail, and every request the code sent to be a GET/HEAD or refused. The sandbox wrapsfetchbefore the code runs. If an earlier write went through, the error says the code wasn't offered for an automatic retry and asks for only the remaining steps to be run.What it names. The baseline
user:read account:readplus the missing scope. Earlier step-ups aren't repeated: the spec makes accumulating scopes the client's job, and repeating them would bring back the long authorize URLs from #253.Trust. The
executesandbox reports the missing scope, so it's untrusted. The host accepts only a catalog scope the connection lacks. Code that lies gains nothing, since it could trigger the same challenge by calling an endpoint that needs the scope.HTTP plumbing.
enableJsonResponse). The SDK fallback opens an SSE stream before the tool runs, which would commit a200too early. 2026-07-28 requests are already JSON.WWW-Authenticateis added toAccess-Control-Expose-Headers.ChatGPT. ChatGPT reads challenges from the tool result's
_meta["mcp/www_authenticate"], not from an HTTP403. Tools always add the challenge there. With/mcp?scopeChallenge=tool, that result is sent as-is instead of being swapped for the403.