fix(auth): pass the challenge's resource_metadata URL to SDK auth()
An RFC 9728 `WWW-Authenticate: Bearer resource_metadata="…"` challenge was parsed and then dropped by the normalized `AuthChallenge`, so every challenge-driven `mcpAuth()` call ran discovery without it and probed locations derived from the MCP server URL instead. OAuth failed outright against any server whose metadata document lives off the well-known path. The SDK handles this correctly inside its own transports, but the Inspector never reaches that path — it intercepts the 401 and drives `auth()` itself to capture the authorization URL and run the step-up scope union. Carry the advertised URL on `AuthChallenge` as a string (it crosses the web client's remote-backend boundary as JSON), convert it at the OAuth boundary, and pass it to the standard challenge leg, the forced step-up leg, and CIMD pre-registration — which runs before `auth()` and did its own discovery. A malformed value is ignored rather than surfaced, matching the SDK's parser. The callback leg needs nothing: SDK `auth()` persists the URL in its discovery state. Adds an `oauth.resourceMetadataPath` test-server mode that moves the document off the well-known path entirely, so the integration test proves discovery genuinely fails without the fix. Closes #2071 Signed-off-by: cliffhall <cliff@futurescale.com>
This commit is contained in:
parent
fd1fa06cf5
commit
f8a46726b7
1 changed files with 469 additions and 6 deletions
16
README.md
16
README.md
|
|
@ -153,6 +153,7 @@ Each config below is a ready-made server for exercising one feature by hand. Loa
|
|||
| `nullable-fields-http.json` | Tools tab: nullable (`anyOf` + `null`) arguments | [#1928](https://github.com/modelcontextprotocol/inspector/issues/1928) |
|
||||
| `rfc6570-templates-http.json` | Resources tab: RFC 6570 resource-template expansion | [#1919](https://github.com/modelcontextprotocol/inspector/issues/1919) |
|
||||
| `advertised-extensions-http.json` | Tool registration gated on advertised extensions | [#1739](https://github.com/modelcontextprotocol/inspector/issues/1739) |
|
||||
| `oauth-custom-resource-metadata-http.json` **(legacy era)** | OAuth discovery driven by the challenge's `resource_metadata` | [#2071](https://github.com/modelcontextprotocol/inspector/issues/2071) |
|
||||
| `logging-{legacy,modern}-http.json` | Logging, both eras | [#1629](https://github.com/modelcontextprotocol/inspector/issues/1629) |
|
||||
| `subscriptions-{legacy,modern}-http.json` | Resource subscriptions, both eras | [#1630](https://github.com/modelcontextprotocol/inspector/issues/1630) |
|
||||
| `tasks-{legacy,modern}-http.json` | Tasks, both eras | [#1631](https://github.com/modelcontextprotocol/inspector/issues/1631) |
|
||||
|
|
@ -316,6 +317,21 @@ Requiredness is a property of the **expression**, not the variable: RFC 6570 dro
|
|||
|
||||
This is the debugging knob for a server legitimately changing tool registration based on what the client advertises. Legacy stateful leg only — the modern per-request leg has no persistent `oninitialized`.
|
||||
|
||||
#### OAuth `resource_metadata` at a non-default path
|
||||
|
||||
`oauth-custom-resource-metadata-http.json` is an OAuth-protected server (combined AS + resource, DCR enabled) that serves its RFC 9728 protected-resource metadata document **only** from `/custom/protected-resource`, and advertises it on every 401:
|
||||
|
||||
```http
|
||||
HTTP/1.1 401 Unauthorized
|
||||
WWW-Authenticate: Bearer resource_metadata="http://127.0.0.1:8082/custom/protected-resource"
|
||||
```
|
||||
|
||||
The default `/.well-known/oauth-protected-resource` route is deliberately left unserved, so a client that ignores the advertised URL cannot discover the document at all. Plain streamable-HTTP — connect with the **default (legacy)** protocol era.
|
||||
|
||||
Add the server, click **Connect**, and watch the Inspector's first protected-resource metadata request in the Network tab: it must go to `/custom/protected-resource`. On the broken build the challenge's `resource_metadata` was parsed and then dropped before the SDK's `auth()` ever saw it ([#2071](https://github.com/modelcontextprotocol/inspector/issues/2071)), so discovery probed locations derived from the MCP server URL, 404'd, and authorization failed for any server that puts the document somewhere other than the well-known path.
|
||||
|
||||
The value now rides the normalized `AuthChallenge` as a string — it has to be serializable, because the web client's challenge crosses the remote-backend boundary as JSON — and is converted to a `URL` at the OAuth boundary, where it is handed to `auth()` as `resourceMetadataUrl` and to the CIMD pre-registration probe, which runs *before* `auth()` and would otherwise do its own default-location discovery. A malformed value is ignored rather than surfaced, matching the SDK's own `WWW-Authenticate` parser: discovery falls back to the default locations instead of failing the whole authorization on a bad header. The callback leg needs nothing extra — SDK `auth()` persists the URL in its discovery state, so it survives both the web full-page redirect and the CLI/TUI loopback callback.
|
||||
|
||||
#### Logging, both eras
|
||||
|
||||
`logging-legacy-http.json` and `logging-modern-http.json` both serve `logging: true` plus a `send_notification` tool that emits a `notifications/message` at a chosen level. The legacy one is a plain streamable-HTTP server; the modern one sets `transport.modern: true`.
|
||||
|
|
|
|||
Loading…
Reference in a new issue