OAuth discovery does not fall back to well-known URI when 401 carries a non-Bearer WWW-Authenticate (e.g. Negotiate)

Open Beginner friendly
#1,946 3 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Assessment

Difficulty
2/5
Estimated time
1-3 hours
Newbie friendliness
76/100
Issue type
Bug
Clarity
Clearly specified
Activity status
Quiet
Tech stack
typescript

Research direction

Start in src/client/streamableHttp.ts at the 401 handler in StreamableHTTPClientTransport.send, comparing the current behavior with the fallback described in the issue and related PR #1045. Reproduce with a non-Bearer challenge and a path-suffixed well-known URI; the work is done when discovery falls back to those URIs and the OAuth flow can proceed.

Written by the indexing model from the issue text.

Description

auth bug needs repro

Describe the bug

StreamableHTTPClientTransport (TypeScript SDK 1.29.0) does not fall back to well-known protected-resource metadata discovery when a 401 response carries a non-Bearer WWW-Authenticate challenge such as Negotiate. The 401 is bubbled to the caller and the OAuth flow never starts, even though valid metadata is hosted at the spec-defined well-known URI.

The MCP authorization spec (Protected Resource Metadata Discovery Requirements) states:

MCP clients MUST support both discovery mechanisms and use the resource metadata URL from the parsed WWW-Authenticate headers when present; otherwise, they MUST fall back to constructing and requesting the well-known URIs in the order listed above.

"Otherwise" should include the case where WWW-Authenticate is present but does not advertise Bearer ... resource_metadata=.... Today the fallback only fires when the header is missing entirely (per PR #1045 / SEP-985).

To Reproduce

Steps to reproduce the behavior:

  1. Stand up an MCP server that returns 401 with a non-Bearer challenge for unauthenticated requests, and hosts valid protected-resource metadata at the path-suffixed well-known URI:

    $ curl -i -X POST https://server/api/v1/help_python_skill/mcp \
        -H 'Content-Type: application/json' \
        -H 'Accept: application/json, text/event-stream' \
        -d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"curl","version":"0"}}}'
    HTTP/1.1 401 Unauthorized
    www-authenticate: Negotiate
    content-type: text/plain
    
    Authentication required.
    
    $ curl -i 'https://server/.well-known/oauth-protected-resource/api/v1/help_python_skill/mcp'
    HTTP/1.1 200 OK
    content-type: application/json
    
    {"resource":"https://server/api/v1/help_python_skill/mcp",
     "authorization_servers":["https://server"],
     "scopes_supported":[".default"],
     "bearer_methods_supported":["header"]}
    
  2. Connect to it from MCP Inspector (uses TS SDK 1.29.0):

    npx @modelcontextprotocol/inspector
    
  3. Enter the server URL and pick streamable-http transport. Click Connect.

  4. Observe the connection fails with a bare 401; no request is ever made to either well-known URL.

Expected behavior
On any 401 from the MCP endpoint, after parsing WWW-Authenticate:

  1. If a Bearer challenge is present → use its resource_metadata URL.
  2. Otherwise (header missing OR present with a non-Bearer scheme) → fall back to:
    • <origin>/.well-known/oauth-protected-resource<resource_path>
    • then <origin>/.well-known/oauth-protected-resource

Step 2 currently only fires when the header is missing entirely; it should also fire when the header advertises a non-Bearer scheme.

Logs
Inspector logs from a failing connection attempt:

[MCP] New StreamableHttp connection request
[MCP] Query parameters: {"url":"https://server/api/v1/help_python_skill/mcp","transportType":"streamable-http"}
[MCP] Created StreamableHttp client transport
[MCP] Client <-> Proxy  sessionId: 86af1624-e651-42ae-90a3-1fb0dade5bc5
[MCP] Error from MCP server: StreamableHTTPError: Streamable HTTP error: Error POSTing to endpoint: Authentication required.
[MCP]     at StreamableHTTPClientTransport.send (.../@modelcontextprotocol/sdk/dist/esm/client/streamableHttp.js:364:23)
[MCP]     at process.processTicksAndRejections (node:internal/process/task_queues:95:5) {
[MCP]   code: 401
[MCP] }

Additional context

  • Affected file: src/client/streamableHttp.ts — the 401 handler in StreamableHTTPClientTransport.send (compiled at dist/esm/client/streamableHttp.js:364).
  • Downgrading to @modelcontextprotocol/sdk@1.17.5 makes the connection work — discovery proceeds and OAuth completes — confirming this is a regression introduced in a later version's stricter handling of the 401 response.
  • Workaround: pin @modelcontextprotocol/sdk to 1.17.5 via npm overrides in the consuming package.
  • Real-world impact: any MCP server that (a) supports OAuth via the well-known URI mechanism and (b) sits behind middleware that emits a non-Bearer challenge (Kerberos/SPNEGO is common in enterprise environments) cannot be connected to from any client built on this SDK — including MCP Inspector.
  • Environment:
    • @modelcontextprotocol/sdk: 1.29.0 (regression), 1.17.5 (works)
    • @modelcontextprotocol/inspector: 0.16.6 and 0.21.1 (both reproduce — same underlying SDK)
    • Node: v20.14.0
  • Related: PR #1045 (SEP-985 fallback — handles only missing-header case), Issue #822, Issue #758.
Dominant language
TypeScript
Stars
13.4k
Forks
2.2k
Avg merge
3d 12h
Merged PRs (30d)
3

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.

More from modelcontextprotocol/typescript-sdk

All issues in modelcontextprotocol/typescript-sdk

Similar issues

More TypeScript issues

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.