Pin RFC 8414 Default Well-Known Suffix per SEP-2351

## Motivation and Context

SEP-2351 (modelcontextprotocol/modelcontextprotocol#2351, merged for the 2026-07-28 spec release)
makes explicit that MCP uses the default `oauth-authorization-server` well-known URI suffix from
RFC 8414 Section 3.1 and defines no application-specific suffix.

`MCP::Client::OAuth::Discovery.authorization_server_metadata_urls` already conforms:
it probes the RFC 8414 default suffix first (path-inserted form for path issuers) and
otherwise uses only the OpenID Connect Discovery `openid-configuration` suffix.
Neither the Python SDK nor the TypeScript SDK needed a behavior change for this SEP either;
their tracking issues (python-sdk#2799, typescript-sdk#2256) remain documentation-level.

This change documents the SEP-2351 guarantee in the discovery helper and locks the behavior
in with regression tests so a future refactor cannot silently introduce a non-default suffix.

Resolves #384.

## How Has This Been Tested?

New regression tests in `test/mcp/client/oauth/discovery_test.rb`:

- the first probed candidate for a root issuer is exactly `/.well-known/oauth-authorization-server`
- every generated candidate uses only the `oauth-authorization-server` or `openid-configuration` suffix
- the RFC 8414 path-inserted `oauth-authorization-server` candidate comes first for issuers with a path component
- an issuer URL with a trailing slash is treated as a root issuer

## Breaking Changes

None. Documentation and regression tests only; no behavior change.
This commit is contained in:
Koichi ITO 2026-06-10 15:48:32 +09:00
commit 2a26ad2030
2 changed files with 48 additions and 1 deletions

View file

@ -90,7 +90,17 @@ module MCP
end
# Returns the candidate Authorization Server metadata URLs to probe, in priority order.
# https://modelcontextprotocol.io/specification/2025-11-25/basic/authorization#authorization-server-metadata-discovery
#
# Per SEP-2351, MCP uses the default `oauth-authorization-server` well-known URI suffix
# registered by RFC 8414 Section 7.3 and defines no application-specific suffix of its own.
# The OAuth candidates below therefore use only that default suffix
# (plus the `openid-configuration` suffix from OpenID Connect Discovery),
# both in the RFC 8414 Section 3.1 path-inserted form for issuers with a path component
# and in the root form for issuers without one.
#
# - https://modelcontextprotocol.io/specification/2025-11-25/basic/authorization#authorization-server-metadata-discovery
# - https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2351
# - https://www.rfc-editor.org/rfc/rfc8414#section-3.1
def authorization_server_metadata_urls(issuer_url)
uri = URI.parse(issuer_url)
path = uri.path == "/" ? "" : uri.path.to_s

View file

@ -121,6 +121,43 @@ module MCP
assert_includes(urls, "https://auth.example.com/tenant1/.well-known/openid-configuration")
end
# Per SEP-2351, MCP explicitly uses the RFC 8414 default `oauth-authorization-server` well-known URI suffix
# and defines no application-specific suffix. The first probed candidate for a root issuer must be exactly
# that default suffix.
def test_authorization_server_metadata_urls_probe_rfc8414_default_suffix_first
urls = Discovery.authorization_server_metadata_urls("https://auth.example.com")
assert_equal("https://auth.example.com/.well-known/oauth-authorization-server", urls.first)
end
def test_authorization_server_metadata_urls_use_only_registered_well_known_suffixes
root_urls = Discovery.authorization_server_metadata_urls("https://auth.example.com")
path_urls = Discovery.authorization_server_metadata_urls("https://auth.example.com/tenant1")
(root_urls + path_urls).each do |url|
suffix = url[%r{/\.well-known/([^/]+)}, 1]
assert_includes(
["oauth-authorization-server", "openid-configuration"], suffix, "unexpected well-known suffix in #{url}"
)
end
end
def test_authorization_server_metadata_urls_put_path_inserted_oauth_candidate_first_for_path_issuer
urls = Discovery.authorization_server_metadata_urls("https://auth.example.com/tenant1")
assert_equal("https://auth.example.com/.well-known/oauth-authorization-server/tenant1", urls.first)
end
def test_authorization_server_metadata_urls_treat_trailing_slash_issuer_as_root
urls = Discovery.authorization_server_metadata_urls("https://auth.example.com/")
assert_equal(
["https://auth.example.com/.well-known/oauth-authorization-server", "https://auth.example.com/.well-known/openid-configuration"],
urls,
)
end
def test_canonicalize_url_normalizes_scheme_host_port_and_path
assert_equal(
"https://srv.example.com/mcp",