modelcontextprotocol / modelcontextprotocol/typescript-sdk

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

Open
#1,582 1 comment 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

auth bug fix proposed P2 ready for work
Dominant language
TypeScript
Stars
13.4k
Forks
2.2k
Avg merge
3d 15h
Merged PRs (30d)
4

Description

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

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start in packages/client/src/client/streamableHttp.ts at the 401 and 403 handlers around lines 507 and 540, and trace how _scope changes during OAuth reauthorization. Use the listed sequential init, tools/list, and tools/call scenario to validate the behavior; done means scopes learned from earlier challenges remain available and the authorization loop ends.

Written by the indexing model from the issue text.

Assessment

Tech stack
typescript
Domain
api, authentication
Issue type
Bug
Difficulty
3/5
Estimated time
1-2 days
Activity status
Quiet
Clarity
Clearly specified
Newbie friendliness
65/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.