Fall Back to Legacy 2025-03-26 OAuth Discovery for Servers Without PRM#414
Merged
Merged
Conversation
atesgoral
reviewed
Jun 16, 2026
Comment on lines
+222
to
+224
| # and the MCP server's own origin acts | ||
| # | ||
| # as the authorization base URL, matching the TypeScript and Python SDKs. Any PRM discovery failure |
Member
Author
There was a problem hiding this comment.
Oops, It looks like I accidentally introduced an odd line break while working on this. Thanks for catching it. I've fixed it.
## Motivation and Context The MCP 2025-03-26 authorization spec predates Protected Resource Metadata: the MCP server itself acted as the authorization base URL, its RFC 8414 metadata was fetched from the server origin, and the spec's "Fallbacks for Servers without Metadata Discovery" section required clients to use the default endpoints `/authorize`, `/token`, and `/register` relative to the authorization base URL when no metadata was published. Both the TypeScript SDK (`discoverOAuthServerInfo` falls back to the server base URL on PRM failure; `startAuthorization` / `executeTokenRequest` / `registerClient` default the endpoint paths) and the Python SDK (`build_oauth_authorization_server_metadata_discovery_urls(None, ...)` plus the same endpoint defaults) keep this as client-side backwards compatibility, and the `auth/2025-03-26-oauth-metadata-backcompat` and `auth/2025-03-26-oauth-endpoint-fallback` conformance scenarios exercise it. The Ruby flow previously raised as soon as PRM discovery failed, so both scenarios were expected conformance failures. This change adds the same fallback: - `Flow#locate_authorization_server` first attempts PRM discovery; on any discovery failure (404s, network errors, malformed documents, matching the TypeScript and Python SDKs' broad fallback) the MCP server's origin becomes the legacy authorization base URL. The Communication Security check (HTTPS or loopback) still applies to that origin, and PRM documents that parse correctly keep their strict shape validation. - `Flow#authorization_server_metadata` fetches RFC 8414 metadata from the base URL. On the legacy path the `issuer` byte-match is skipped: the 2025-03-26 spec predates that requirement, and a pre-PRM server may host its OAuth endpoints under a path prefix whose `issuer` legitimately differs from the discovery origin (neither reference SDK validates the issuer on this path). On the modern path the byte-match is unchanged. - When even the metadata document is absent, synthetic metadata carrying the legacy spec's default endpoints is used, with PKCE S256 assumed (the legacy spec mandates PKCE; the TypeScript and Python SDKs hardcode S256 on this path too). Endpoint HTTPS checks still apply. - `run!` and `refresh!` share the new discovery helpers, and both 2025-03-26 scenarios are removed from `conformance/expected_failures.yml`. ## How Has This Been Tested? New tests in `test/mcp/client/oauth/flow_test.rb`: - the metadata-backcompat shape: no PRM, AS metadata at the server origin with `/oauth`-prefixed endpoints and a mismatched `issuer` completes the flow against those endpoints (no issuer error) - the endpoint-fallback shape: no metadata at all registers, authorizes, and exchanges the code at `/register`, `/authorize`, and `/token` on the origin, still sending `code_challenge` (S256) and `code_verifier` - a remote plain-http origin is rejected as the legacy authorization base - strict-mode regression: with PRM present, a mismatched `issuer` still aborts the flow - a malformed PRM (top-level JSON array) now selects the legacy path instead of leaking a `TypeError`, and surfaces a domain error when that path also dead-ends Two existing tests that asserted PRM failure was terminal were updated to dead-end the legacy path explicitly, preserving their original intent (single flow attempt, domain error type). ## Breaking Changes None for spec-compliant 2025-06-18+ servers, whose PRM-based discovery and issuer validation are unchanged. Servers that previously failed hard during PRM discovery now get one legacy discovery attempt before the flow errors, which can only turn previously failing flows into working ones.
0779322 to
2c223dd
Compare
atesgoral
approved these changes
Jun 16, 2026
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.
Motivation and Context
The MCP 2025-03-26 authorization spec predates Protected Resource Metadata: the MCP server itself acted as the authorization base URL, its RFC 8414 metadata was fetched from the server origin, and the spec's "Fallbacks for Servers without Metadata Discovery" section required clients to use the default endpoints
/authorize,/token, and/registerrelative to the authorization base URL when no metadata was published. Both the TypeScript SDK (discoverOAuthServerInfofalls back to the server base URL on PRM failure;startAuthorization/executeTokenRequest/registerClientdefault the endpoint paths) and the Python SDK (build_oauth_authorization_server_metadata_discovery_urls(None, ...)plus the same endpoint defaults) keep this as client-side backwards compatibility, and theauth/2025-03-26-oauth-metadata-backcompatandauth/2025-03-26-oauth-endpoint-fallbackconformance scenarios exercise it.The Ruby flow previously raised as soon as PRM discovery failed, so both scenarios were expected conformance failures. This change adds the same fallback:
Flow#locate_authorization_serverfirst attempts PRM discovery; on any discovery failure (404s, network errors, malformed documents, matching the TypeScript and Python SDKs' broad fallback) the MCP server's origin becomes the legacy authorization base URL. The Communication Security check (HTTPS or loopback) still applies to that origin, and PRM documents that parse correctly keep their strict shape validation.Flow#authorization_server_metadatafetches RFC 8414 metadata from the base URL. On the legacy path theissuerbyte-match is skipped: the 2025-03-26 spec predates that requirement, and a pre-PRM server may host its OAuth endpoints under a path prefix whoseissuerlegitimately differs from the discovery origin (neither reference SDK validates the issuer on this path). On the modern path the byte-match is unchanged.run!andrefresh!share the new discovery helpers, and both 2025-03-26 scenarios are removed fromconformance/expected_failures.yml.How Has This Been Tested?
New tests in
test/mcp/client/oauth/flow_test.rb:/oauth-prefixed endpoints and a mismatchedissuercompletes the flow against those endpoints (no issuer error)/register,/authorize, and/tokenon the origin, still sendingcode_challenge(S256) andcode_verifierissuerstill aborts the flowTypeError, and surfaces a domain error when that path also dead-endsTwo existing tests that asserted PRM failure was terminal were updated to dead-end the legacy path explicitly, preserving their original intent (single flow attempt, domain error type).
Breaking Changes
None for spec-compliant 2025-06-18+ servers, whose PRM-based discovery and issuer validation are unchanged. Servers that previously failed hard during PRM discovery now get one legacy discovery attempt before the flow errors, which can only turn previously failing flows into working ones.
Types of changes
Checklist