Skip to content

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

Open

Description

@asoorm

Describe the bug

The StreamableHTTPClientTransport 403 insufficient_scope handler overwrites this._scope with the scope from the WWW-Authenticate header instead of accumulating scopes across responses:

https://github.com/modelcontextprotocol/typescript-sdk/blob/main/packages/client/src/client/streamableHttp.ts#L540

if (scope) {
  this._scope = scope; // overwrites - previous scopes are lost
}

This causes an infinite re-authorization loop when an MCP server requires different scopes for different operations (progressive/step-up authorization).

The same overwrite exists on https://github.com/modelcontextprotocol/typescript-sdk/blob/main/packages/client/src/client/streamableHttp.ts#L507 in the 401 handler.

The Python SDK has the same behavior in https://github.com/modelcontextprotocol/python-sdk/blob/main/src/mcp/client/auth/oauth2.py.

To Reproduce

  1. Configure an MCP server with per-operation scopes:
  • initialize requires init scope
  • tools/list requires mcp:tools:read scope
  • tools/call (specific tool) requires mcp:tools:write
  1. When a request lacks the required scope, the server returns 403 with only the scopes that specific operation needs per https://datatracker.ietf.org/doc/html/rfc6750#section-3.1 (e.g. WWW-Authenticate: Bearer error="insufficient_scope", scope="mcp:tools:read")

  2. Connect with the TypeScript SDK using StreamableHTTPClientTransport with an OAuth provider

  3. After initialize succeeds (granted init), call tools/list

  4. The 403 handler sets _scope = "mcp:tools:read", re-authorizes, and the auth server grants mcp:tools:read - but init was not requested, so it is not included in the new token

  5. The next operation requiring init fails with 403, overwriting _scope = "init", losing mcp:tools:read

  6. Infinite loop between steps 5 and 6

Expected behavior

The transport should accumulate (union) scopes across 401/403 responses. After steps 1–5 above, _scope should be "init mcp:tools:read", not just "mcp:tools:read".

It is incorrect to expect the MCP server to solve this by returning all accumulated scopes in the WWW-Authenticate header because:

  • Per RFC 6750 §3.1, the scope attribute describes "the scope necessary to access the protected resource" - the specific resource being accessed, not every resource the client might access in the future.
  • The server has no knowledge of client-side token state. It validates the presented token and reports what's missing for this specific operation.
  • Different operations have different scope requirements. Including scopes beyond what the operation needs (e.g. returning init in a tools/list 403) would misrepresent the requirements of that operation.

The responsibility for accumulating scopes across operations belongs to the client, not the server.

Related: #1039, #1115, #1151, #941, #1317

Activity

  1. added
    authIssues and PRs related to Authentication / OAuth
    on Mar 4, 2026
  2. added a commit that references this issue on Mar 10, 2026
    080d9dd
  3. added a commit that references this issue on Mar 27, 2026
    a4b48e1
  4. ghost added
    ready for workEnough information for someone to start working on
    on Apr 16, 2026
  5. ghost added
    P2Moderate issues affecting some users, edge cases, potentially valuable feature
    on Apr 16, 2026
  6. ghost added
    fix proposedBot has a verified fix diff in the comment
    on Apr 16, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    P2Moderate issues affecting some users, edge cases, potentially valuable featureauthIssues and PRs related to Authentication / OAuthbugSomething isn't workingfix proposedBot has a verified fix diff in the commentready for workEnough information for someone to start working on

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions