Skip to content

fix: accumulate OAuth scopes across 401/403 for progressive authorization#1604

Open
giulio-leone wants to merge 1 commit intomodelcontextprotocol:mainfrom
giulio-leone:fix/scope-accumulation-1582
Open

fix: accumulate OAuth scopes across 401/403 for progressive authorization#1604
giulio-leone wants to merge 1 commit intomodelcontextprotocol:mainfrom
giulio-leone:fix/scope-accumulation-1582

Conversation

@giulio-leone
Copy link

Summary

Fixes #1582

The StreamableHTTPClientTransport overwrites _scope on each 401/403 response instead of accumulating scopes. This causes an infinite re-authorization loop when an MCP server uses per-operation scopes (progressive/step-up authorization):

  1. initialize succeeds with scope init
  2. tools/list returns 403 → _scope = "mcp:tools:read" (init lost)
  3. New token grants mcp:tools:read but not init
  4. Next operation needing init fails → _scope = "init" (read lost)
  5. Loop between steps 2–4

Changes

  • mergeScopes() helper: Computes the set-union of space-delimited OAuth scope strings per RFC 6749 §3.3
  • 401 handler (line 522): this._scope = mergeScopes(this._scope, scope)
  • 403 handler (line 555): this._scope = mergeScopes(this._scope, scope)

Test

Added accumulates scopes across multiple 403 responses for progressive authorization test that verifies:

  1. First 403 sets scope to mcp:tools:read
  2. Second 403 accumulates to mcp:tools:read mcp:tools:write
  3. Auth is called with the merged scope string

All 253 client tests pass (2 consecutive clean runs).

Note: The Python SDK has the same bug — will submit a companion PR there.

@giulio-leone giulio-leone requested a review from a team as a code owner February 28, 2026 06:16
@changeset-bot
Copy link

changeset-bot bot commented Feb 28, 2026

⚠️ No Changeset found

Latest commit: aaa9865

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@pkg-pr-new
Copy link

pkg-pr-new bot commented Feb 28, 2026

Open in StackBlitz

@modelcontextprotocol/client

npm i https://pkg.pr.new/modelcontextprotocol/typescript-sdk/@modelcontextprotocol/client@1604

@modelcontextprotocol/server

npm i https://pkg.pr.new/modelcontextprotocol/typescript-sdk/@modelcontextprotocol/server@1604

@modelcontextprotocol/express

npm i https://pkg.pr.new/modelcontextprotocol/typescript-sdk/@modelcontextprotocol/express@1604

@modelcontextprotocol/hono

npm i https://pkg.pr.new/modelcontextprotocol/typescript-sdk/@modelcontextprotocol/hono@1604

@modelcontextprotocol/node

npm i https://pkg.pr.new/modelcontextprotocol/typescript-sdk/@modelcontextprotocol/node@1604

commit: 257e73b

giulio-leone added a commit to giulio-leone/python-sdk that referenced this pull request Feb 28, 2026
… authorization

Replace scope overwrite with union-based accumulation in OAuthClientProvider.
Both the 401 and 403 (insufficient_scope) handlers now merge new scopes with
previously-granted scopes via merge_scopes(), preventing infinite
re-authorization loops when a server uses per-operation scopes.

Companion fix to modelcontextprotocol/typescript-sdk#1604.

Closes modelcontextprotocol/typescript-sdk#1582

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
giulio-leone added a commit to giulio-leone/python-sdk that referenced this pull request Feb 28, 2026
… authorization

Replace scope overwrite with union-based accumulation in OAuthClientProvider.
Both the 401 and 403 (insufficient_scope) handlers now merge new scopes with
previously-granted scopes via merge_scopes(), preventing infinite
re-authorization loops when a server uses per-operation scopes.

Companion fix to modelcontextprotocol/typescript-sdk#1604.

Closes modelcontextprotocol/typescript-sdk#1582
… authorization

Replace scope overwrite with union-based accumulation in StreamableHTTPClientTransport.
Both the 401 and 403 (insufficient_scope) handlers now merge new scopes with
previously-granted scopes, preventing the infinite re-authorization loop
described in modelcontextprotocol#1582.

Adds mergeScopes() helper that computes the set union of space-delimited
OAuth scope strings per RFC 6749 §3.3.

Closes modelcontextprotocol#1582
@giulio-leone giulio-leone force-pushed the fix/scope-accumulation-1582 branch from c315630 to aaa9865 Compare February 28, 2026 14:41
@giulio-leone
Copy link
Author

Friendly ping — CI is green and this is ready for review. Happy to address any feedback. Thanks!

@giulio-leone
Copy link
Author

All CI checks pass. Ready for review.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Scope overwrite in 403 upscoping prevents progressive authorization for servers with per-operation scopes

1 participant