Repository navigation
fix(oauth): ask for the media scopes, grant them only on a media-eligible project - #541
Merged
Merged
Conversation
The 401 challenge only asked for content:read content:write project:metadata, so every new grant lacked media:*. Media was meant to arrive by step-up (403 insufficient_scope on a media tool call), but tools/list hides media tools from a grant without a media scope, so the model never sees one, never calls it, and the step-up never fires. On deployments with the media stack (the same useMediaProvider() switch advertisedScopes uses), the 401 challenge now also asks for media:read media:write. Per-project eligibility (plan, cdn_enabled, owner) still decides what tools/list shows, and step-up is unchanged.
The challenge asks for media:* on every media deployment, but consent showed the requested scopes read-only, so every first grant carried media:write (URL ingest + delete) even on a project that cannot use it. projectMediaEligible (media stack, plan media.upload, cdn_enabled, workspace owner) is the proxy's media rule moved out unchanged; the proxy, consent GET and consent POST share it. Consent POST narrows the code's scope with withoutMediaScopes on a non-eligible project, and the token endpoint issues that narrowed scope. The consent page lists the media permissions only once a media-eligible project is picked. A project that becomes eligible later needs a reconnect.
ABB65
added a commit
that referenced
this pull request
Nov 4, 2026
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.
New grants never got the media scopes, so the media tools never appeared, even on eligible projects.
Cause: the 401 challenge (
CHALLENGE_SCOPES) asked only forcontent:read content:write project:metadata, and clients request the challenge scope. Media was meant to arrive by step-up: a media tool call answers 403insufficient_scopeand the client re-consents. Since #536,tools/listhides media tools from a grant without a media scope. The model never sees one and never calls one, so the step-up never fires.Change:
challengeScopes({ mediaAvailable })(scopes.ts) is the base three, plusmedia:read media:writewhere the media stack exists. This is the sameuseMediaProvider() !== nullswitchadvertisedScopesuses.mcpUnauthorizedtakes{ mediaAvailable }, and/api/mcp/remotepasses it.projectMediaEligible(server/utils/media-eligibility.ts) is the proxy's media rule, moved out unchanged: media stack, planmedia.upload,cdn_enabled, workspace owner. The proxy, consent GET and consent POST all call it.withoutMediaScopeson a project that isn't eligible. That helper goes throughnormalizeScope, so a grant never ends up with nothing callable.stored.scope, so the grant and the token response carry the narrowed scope.mediaEligibleper project. The page lists the media permissions only once a media-eligible project is picked. A project that becomes eligible later needs a reconnect. No new copy.tools/listfilter stays, so no listed tool answers 403./oauth/authorizealready keeps every known scope (normalizeScope).Tests:
oauth-consent(functional), on a media deployment with the challenge scope requested:mediaEligible: trueand a non-CDN one asfalse;media:read media:write;media.upload, or on a deployment without the media stack mints a code without media.mcp-oauth-proxy(functional):normalizeScopeinto a grant that carries media;tools/liston an eligible project lists the media tools, and the loopback gets the media identity headers;media:.oauth-consent-page: the media permission appears only after a media-eligible project is picked.challengeScopesin both modes,withoutMediaScopes, and the 401 header on a media deployment.Docs: one clause in CLAUDE.md / AGENTS.md (MCP Cloud paragraph) and in
docs/REMOTE_MCP.md(scopes). Existing connections reconnect once after deploy to pick up media.Class: GENERIC