Summary
AuthorizationManager::discover_metadata() currently returns a successful
AuthorizationMetadata result even when every discovery request fails and the
server provides no evidence that it supports OAuth.
In that situation, rmcp synthesizes /authorize, /token, and /register
endpoints from the MCP server's base URL. The resulting value is
indistinguishable from metadata actually discovered from an authorization
server.
Legacy endpoint fallback is useful for the
auth/2025-03-26-oauth-endpoint-fallback conformance scenario, but callers also
use discover_metadata() to determine whether a server supports OAuth at all.
Those callers can no longer distinguish an unauthenticated server from a legacy
OAuth server.
Current behavior
After protected-resource metadata and authorization-server metadata discovery
both fail, rmcp does the following:
pub async fn discover_metadata(&self) -> Result<AuthorizationMetadata, AuthError> {
if let Some(metadata) = self.discover_oauth_server_via_resource_metadata().await? {
return Ok(metadata);
}
if let Some(metadata) = self.try_discover_oauth_server(&self.base_url).await? {
return Ok(metadata);
}
Ok(Self::legacy_authorization_metadata(&self.base_url))
}
The synthesized result is equivalent to:
AuthorizationMetadata {
authorization_endpoint: "https://example.com/authorize".into(),
token_endpoint: "https://example.com/token".into(),
registration_endpoint: Some("https://example.com/register".into()),
..Default::default()
}
The upstream test
discover_metadata_falls_back_to_legacy_default_endpoints explicitly asserts
that five HTTP 404 responses still produce Ok(AuthorizationMetadata).
Why this is a problem
An application that checks whether authentication is available or required
might reasonably write:
match authorization_manager.discover_metadata().await {
Ok(metadata) => show_oauth_login(metadata),
Err(AuthError::NoAuthorizationSupport) => connect_without_oauth(),
Err(error) => report_discovery_error(error),
}
Before the legacy fallback was reintroduced, an MCP server with no OAuth
metadata reached NoAuthorizationSupport. It now reaches the OAuth-login path,
even though no authorization server, OAuth metadata, or functioning OAuth
endpoint was discovered.
Observable consequences include:
- Public MCP servers can incorrectly appear to require an interactive login.
- Clients can issue unnecessary authorization, token, or registration requests
to guessed endpoints.
- Applications cannot distinguish verified discovery from an unverified
compatibility fallback.
- Consumers must reverse-engineer rmcp's current synthesized metadata shape to
recover the missing information. Such checks are brittle and can silently
break when rmcp changes its fallback representation.
For example, a downstream workaround currently has to identify the exact
combination of /authorize, /token, /register, absent issuer, absent
optional metadata fields, and empty extension fields. This information should
be provided explicitly by the SDK instead.
Expected behavior
Callers should be able to distinguish at least these outcomes:
- Authorization metadata was actually discovered.
- No authorization metadata was found, but legacy endpoints were synthesized
as a compatibility fallback.
- No authorization support was found.
The legacy fallback should remain available where required for older MCP OAuth
flows and conformance. The request is to expose its provenance or make its use
explicit, not necessarily to remove it.
Possible API designs
Option A: Expose discovery provenance
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum AuthorizationMetadataSource {
ProtectedResourceMetadata,
AuthorizationServerMetadata,
LegacyEndpointFallback,
}
pub struct DiscoveredAuthorizationMetadata {
pub metadata: AuthorizationMetadata,
pub source: AuthorizationMetadataSource,
}
impl AuthorizationManager {
pub async fn discover_metadata_with_source(
&self,
) -> Result<DiscoveredAuthorizationMetadata, AuthError> {
// ...
}
}
Existing discover_metadata() behavior could remain unchanged for API and
conformance compatibility, while callers that need accurate capability
detection use the provenance-aware method.
Option B: Add strict versus legacy-compatible discovery modes
pub enum OAuthDiscoveryPolicy {
Strict,
AllowLegacyEndpointFallback,
}
impl AuthorizationManager {
pub async fn discover_metadata_with_policy(
&self,
policy: OAuthDiscoveryPolicy,
) -> Result<AuthorizationMetadata, AuthError> {
// ...
}
}
With Strict, absent metadata should return
AuthError::NoAuthorizationSupport; with AllowLegacyEndpointFallback, the
current behavior can be retained.
A dedicated discover_metadata_strict() method would also address this use
case.
Option C: Only synthesize endpoints when OAuth is otherwise indicated
Another possibility is to require evidence of authorization support, such as a
relevant bearer challenge, or an explicitly requested OAuth login before
constructing legacy endpoints. This would avoid treating a server that returns
only 404 responses as OAuth-capable.
This option should be evaluated against the existing legacy conformance
scenario; explicit provenance or an opt-in policy may be more broadly useful.
Regression history
The SDK previously removed guessed authorization metadata in
PR #507:
// No valid authorization metadata found - return error instead of guessing
// OAuth endpoints must be discovered from the server, not constructed by the client
Err(AuthError::NoAuthorizationSupport)
The fallback was reintroduced in
PR #960 to satisfy
client conformance, including
auth/2025-03-26-oauth-endpoint-fallback.
Preserving that compatibility makes sense, but silently collapsing real
discovery and guessed fallback into the same successful result creates a
regression for callers that use discovery to determine authentication support.
Suggested regression coverage
- A server that returns only 404 responses is distinguishable from a server
that publishes real authorization metadata.
- Strict/provenance-aware discovery identifies the all-404 case as unsupported
or synthesized, rather than verified OAuth support.
- The existing
auth/2025-03-26-oauth-endpoint-fallback conformance scenario
continues to pass when legacy fallback is explicitly enabled or consumed.
- Existing callers of
discover_metadata() retain compatibility if the new
API is introduced additively.
Summary
AuthorizationManager::discover_metadata()currently returns a successfulAuthorizationMetadataresult even when every discovery request fails and theserver provides no evidence that it supports OAuth.
In that situation, rmcp synthesizes
/authorize,/token, and/registerendpoints from the MCP server's base URL. The resulting value is
indistinguishable from metadata actually discovered from an authorization
server.
Legacy endpoint fallback is useful for the
auth/2025-03-26-oauth-endpoint-fallbackconformance scenario, but callers alsouse
discover_metadata()to determine whether a server supports OAuth at all.Those callers can no longer distinguish an unauthenticated server from a legacy
OAuth server.
Current behavior
After protected-resource metadata and authorization-server metadata discovery
both fail, rmcp does the following:
The synthesized result is equivalent to:
The upstream test
discover_metadata_falls_back_to_legacy_default_endpointsexplicitly assertsthat five HTTP 404 responses still produce
Ok(AuthorizationMetadata).Why this is a problem
An application that checks whether authentication is available or required
might reasonably write:
Before the legacy fallback was reintroduced, an MCP server with no OAuth
metadata reached
NoAuthorizationSupport. It now reaches the OAuth-login path,even though no authorization server, OAuth metadata, or functioning OAuth
endpoint was discovered.
Observable consequences include:
to guessed endpoints.
compatibility fallback.
recover the missing information. Such checks are brittle and can silently
break when rmcp changes its fallback representation.
For example, a downstream workaround currently has to identify the exact
combination of
/authorize,/token,/register, absent issuer, absentoptional metadata fields, and empty extension fields. This information should
be provided explicitly by the SDK instead.
Expected behavior
Callers should be able to distinguish at least these outcomes:
as a compatibility fallback.
The legacy fallback should remain available where required for older MCP OAuth
flows and conformance. The request is to expose its provenance or make its use
explicit, not necessarily to remove it.
Possible API designs
Option A: Expose discovery provenance
Existing
discover_metadata()behavior could remain unchanged for API andconformance compatibility, while callers that need accurate capability
detection use the provenance-aware method.
Option B: Add strict versus legacy-compatible discovery modes
With
Strict, absent metadata should returnAuthError::NoAuthorizationSupport; withAllowLegacyEndpointFallback, thecurrent behavior can be retained.
A dedicated
discover_metadata_strict()method would also address this usecase.
Option C: Only synthesize endpoints when OAuth is otherwise indicated
Another possibility is to require evidence of authorization support, such as a
relevant bearer challenge, or an explicitly requested OAuth login before
constructing legacy endpoints. This would avoid treating a server that returns
only 404 responses as OAuth-capable.
This option should be evaluated against the existing legacy conformance
scenario; explicit provenance or an opt-in policy may be more broadly useful.
Regression history
The SDK previously removed guessed authorization metadata in
PR #507:
The fallback was reintroduced in
PR #960 to satisfy
client conformance, including
auth/2025-03-26-oauth-endpoint-fallback.Preserving that compatibility makes sense, but silently collapsing real
discovery and guessed fallback into the same successful result creates a
regression for callers that use discovery to determine authentication support.
Suggested regression coverage
that publishes real authorization metadata.
or synthesized, rather than verified OAuth support.
auth/2025-03-26-oauth-endpoint-fallbackconformance scenariocontinues to pass when legacy fallback is explicitly enabled or consumed.
discover_metadata()retain compatibility if the newAPI is introduced additively.