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:
-
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"]}
-
Connect to it from MCP Inspector (uses TS SDK 1.29.0):
npx @modelcontextprotocol/inspector
-
Enter the server URL and pick streamable-http transport. Click Connect.
-
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:
- If a
Bearer challenge is present → use its resource_metadata URL.
- 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.
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-BearerWWW-Authenticatechallenge such asNegotiate. 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:
"Otherwise" should include the case where
WWW-Authenticateis present but does not advertiseBearer ... 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:
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:
Connect to it from MCP Inspector (uses TS SDK 1.29.0):
Enter the server URL and pick
streamable-httptransport. Click Connect.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:Bearerchallenge is present → use itsresource_metadataURL.<origin>/.well-known/oauth-protected-resource<resource_path><origin>/.well-known/oauth-protected-resourceStep 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:
Additional context
src/client/streamableHttp.ts— the 401 handler inStreamableHTTPClientTransport.send(compiled atdist/esm/client/streamableHttp.js:364).@modelcontextprotocol/sdk@1.17.5makes 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.@modelcontextprotocol/sdkto1.17.5via npmoverridesin the consuming package.@modelcontextprotocol/sdk: 1.29.0 (regression), 1.17.5 (works)@modelcontextprotocol/inspector: 0.16.6 and 0.21.1 (both reproduce — same underlying SDK)