Skip to content

fix(auth): structure metadata discovery errors - #1280

Open
ASTion24 wants to merge 1 commit into
modelcontextprotocol:mainfrom
ASTion24:fix/structured-auth-discovery-errors
Open

ASTion24 wants to merge 1 commit into
modelcontextprotocol:mainfrom
ASTion24:fix/structured-auth-discovery-errors

Conversation

@ASTion24

Copy link
Copy Markdown

Summary

  • replace protected resource metadata discovery failures encoded as MetadataError(String) with structured AuthError variants
  • preserve HTTP discovery errors as source
  • expose invalid metadata reasons and exhausted authorization server candidates as fields
  • box the detailed metadata reason to keep AuthError below Clippy's result_large_err threshold

Testing

  • cargo test --all-features
  • cargo clippy --all-targets --all-features -- -D warnings

Fixes #1265

Comment on lines +5407 to 5421
assert!(
matches!(
error,
AuthError::ProtectedResourceMetadataInvalid {
ref url,
ref reason,
} if url.as_str()
== "https://mcp.example.com/.well-known/oauth-protected-resource"
&& matches!(
reason.as_ref(),
ProtectedResourceMetadataError::NotAMetadataDocument
)
),
"unexpected protected resource metadata error: {error:?}"
);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since this PR intentionally changes the text users see in the logs, would it be worth keeping a to_string() assertion alongside each matches!?

Comment on lines 2717 to +2725
fn validate_resource_metadata_resource(
&self,
resource_metadata_url: &Url,
metadata: &ResourceServerMetadata,
) -> Result<(), AuthError> {
let invalid = |reason| AuthError::ProtectedResourceMetadataInvalid {
url: resource_metadata_url.clone(),
reason: Box::new(reason),
};

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did you consider having the validator return only the reason, then letting the caller add the URL in the Advertised branch, where it's actually needed?

/// Why a protected resource metadata document cannot be used for this resource.
#[derive(Debug, Clone, PartialEq, Eq, Error)]
#[non_exhaustive]
pub enum ProtectedResourceMetadataError {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AuthError and the other auth types are re-exported from rmcp::transport, but the new reason enum is only available as rmcp::transport::auth::ProtectedResourceMetadataError. So callers matching on reason have to import from two different paths.

ResourceHasFragment { resource: Url },

/// The advertised resource identifier does not identify the requested resource.
#[error("resource mismatch: reference '{expected}', permitted '{actual}'")]

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The fields were renamed to expected and actual, but the message still says reference and permitted. Using the same terms in both places makes the log line consistent with the fields.

Suggested change
#[error("resource mismatch: reference '{expected}', permitted '{actual}'")]
#[error("resource mismatch: expected '{expected}', got '{actual}'")]

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

T-core Core library changes T-transport Transport layer changes

Projects

None yet

Development

Successfully merging this pull request may close these issues.

auth: report protected resource metadata discovery failures as structured AuthError variants

2 participants