Repository navigation
feat(auth): ask for a missing OAuth scope with a 403 insufficient_scope challenge - #268
Open
mattzcarey wants to merge 1 commit into
Open
mattzcarey wants to merge 1 commit into
mattzcarey wants to merge 1 commit into
Conversation
…pe challenge Ported onto the Forge artifacts: endpoint tools pick the scope from their mcp-tools.json entry's template and permissions (explainRefusalFor); execute keeps the spec.json lookup.
This was referenced Oct 7, 2026
mattzcarey
added a commit
that referenced
this pull request
Oct 9, 2026
Combines #267 and #268, ported from the Forge stack onto main. - 401/403 tool errors name the method, path and status, and say whether the connection lacks a scope, already holds one, or needs an API token. - The scheduled handler keeps x-api-token-group in spec.json and copies it into each mcp-tools.json entry as `permissions`. - execute looks a refused path up in the cached mcp-tools.json, not the ~25 MB spec.json. - An OAuth connection missing the scope gets a 403 insufficient_scope challenge when retrying the call is safe; ?scopeChallenge=tool keeps the challenge in the tool result's _meta instead.
mattzcarey
added a commit
that referenced
this pull request
Oct 12, 2026
Combines #267 and #268, ported from the Forge stack onto main. - 401/403 tool errors name the method, path and status, and say whether the connection lacks a scope, already holds one, or needs an API token. - The scheduled handler keeps x-api-token-group in spec.json and copies it into each mcp-tools.json entry as `permissions`. - execute looks a refused path up in the cached mcp-tools.json, not the ~25 MB spec.json. - An OAuth connection missing the scope gets a 403 insufficient_scope challenge when retrying the call is safe; ?scopeChallenge=tool keeps the challenge in the tool result's _meta instead.
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 #267 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.