fix(client): preserve resource_metadata URL across non-Bearer WWW-Authenticate challenges#1951
Open
Zelys-DFKH wants to merge 1 commit intomodelcontextprotocol:mainfrom
Open
Conversation
…henticate challenges When WWW-Authenticate carries a non-Bearer scheme (e.g. Negotiate), extractWWWAuthenticateParams returns undefined for resourceMetadataUrl. Both transports were unconditionally writing that undefined back to _resourceMetadataUrl, wiping out any URL stored from an earlier Bearer challenge and breaking PRM discovery for multi-challenge sequences. - Add optional resourceMetadataUrl to UnauthorizedContext so transports can pass the stored Bearer URL into the onUnauthorized callback - handleOAuthUnauthorized now uses extractedUrl ?? ctx.resourceMetadataUrl, falling back to the stored URL when the current challenge has none - Both StreamableHTTPClientTransport and SSEClientTransport guard _resourceMetadataUrl and _scope updates to only overwrite when the extracted value is non-undefined, and include this._resourceMetadataUrl in every onUnauthorized context Fixes modelcontextprotocol#1946 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
🦋 Changeset detectedLatest commit: 3295106 The changes in this PR will be included in the next version bump. This PR includes changesets to release 1 package
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
@modelcontextprotocol/client
@modelcontextprotocol/server
@modelcontextprotocol/express
@modelcontextprotocol/fastify
@modelcontextprotocol/hono
@modelcontextprotocol/node
commit: |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
`extractWWWAuthenticateParams` returns `{}` — and therefore `undefined` for
`resourceMetadataUrl` — when `WWW-Authenticate` is a non-Bearer scheme like
`Negotiate`. That's correct. But both transports then unconditionally wrote that
`undefined` back to `_resourceMetadataUrl`, wiping out any URL stored from an
earlier Bearer challenge.
`discoverMetadataWithFallback` uses that field as its starting point. `undefined`
triggers the right fallback: construct the well-known URI from `serverUrl`. But if
a real Bearer URL was stored first and a later `Negotiate` 401 cleared it, discovery
restarts from the wrong origin. The MCP auth spec requires clients to fall back to
well-known when `WWW-Authenticate` is absent or does not carry a Bearer
`resource_metadata` parameter; clearing a previously-stored URL silently breaks that
for multi-challenge sequences.
`UnauthorizedContext` gets an optional `resourceMetadataUrl` field so transports can
pass the stored Bearer URL into the callback. `handleOAuthUnauthorized` now uses
`extractedUrl ?? ctx.resourceMetadataUrl`, falling back to the stored URL when the
current challenge has none. Both `StreamableHTTPClientTransport` and `SSEClientTransport`
now guard `_resourceMetadataUrl` and `_scope` updates to only overwrite when the
extracted value is non-`undefined`, and include `this._resourceMetadataUrl` in every
`onUnauthorized` context.
Test plan
`streamableHttp.test.ts`, up from 53)
fires on a `Negotiate` 401 with no `resource_metadata`; a second verifies a `Negotiate`
401 following a Bearer 401 does not clear `_resourceMetadataUrl` and the stored URL
reaches `onUnauthorized` via context.
Fixes #1946