From 420de173d9086b8da0bc584e472a532af0148454 Mon Sep 17 00:00:00 2001 From: Guy Lichtman <1395797+glicht@users.noreply.github.com> Date: Mon, 2 Feb 2026 22:18:12 +0200 Subject: [PATCH 1/2] fix(auth): oauth metadata discovery --- crates/rmcp/src/transport/auth.rs | 38 ++++++++++++++++++++++++++++--- 1 file changed, 35 insertions(+), 3 deletions(-) diff --git a/crates/rmcp/src/transport/auth.rs b/crates/rmcp/src/transport/auth.rs index 6a5567f4..d81654f6 100644 --- a/crates/rmcp/src/transport/auth.rs +++ b/crates/rmcp/src/transport/auth.rs @@ -807,6 +807,8 @@ impl AuthorizationManager { push_candidate(format!("/.well-known/openid-configuration/{trimmed}")); // 3. OpenID Connect with path appending push_candidate(format!("/{trimmed}/.well-known/openid-configuration")); + // 4. Canonical OAuth fallback (without path suffix) + push_candidate("/.well-known/oauth-authorization-server".to_string()); } candidates @@ -1605,7 +1607,7 @@ mod tests { // Test URL with single path segment: follow spec priority order let base_url = Url::parse("https://auth.example.com/tenant1").unwrap(); let urls = AuthorizationManager::generate_discovery_urls(&base_url); - assert_eq!(urls.len(), 3); + assert_eq!(urls.len(), 4); assert_eq!( urls[0].as_str(), "https://auth.example.com/.well-known/oauth-authorization-server/tenant1" @@ -1618,11 +1620,15 @@ mod tests { urls[2].as_str(), "https://auth.example.com/tenant1/.well-known/openid-configuration" ); + assert_eq!( + urls[3].as_str(), + "https://auth.example.com/.well-known/oauth-authorization-server" + ); // Test URL with path and trailing slash let base_url = Url::parse("https://auth.example.com/v1/mcp/").unwrap(); let urls = AuthorizationManager::generate_discovery_urls(&base_url); - assert_eq!(urls.len(), 3); + assert_eq!(urls.len(), 4); assert_eq!( urls[0].as_str(), "https://auth.example.com/.well-known/oauth-authorization-server/v1/mcp" @@ -1635,11 +1641,15 @@ mod tests { urls[2].as_str(), "https://auth.example.com/v1/mcp/.well-known/openid-configuration" ); + assert_eq!( + urls[3].as_str(), + "https://auth.example.com/.well-known/oauth-authorization-server" + ); // Test URL with multiple path segments let base_url = Url::parse("https://auth.example.com/tenant1/subtenant").unwrap(); let urls = AuthorizationManager::generate_discovery_urls(&base_url); - assert_eq!(urls.len(), 3); + assert_eq!(urls.len(), 4); assert_eq!( urls[0].as_str(), "https://auth.example.com/.well-known/oauth-authorization-server/tenant1/subtenant" @@ -1652,6 +1662,10 @@ mod tests { urls[2].as_str(), "https://auth.example.com/tenant1/subtenant/.well-known/openid-configuration" ); + assert_eq!( + urls[3].as_str(), + "https://auth.example.com/.well-known/oauth-authorization-server" + ); } // StateStore and StoredAuthorizationState tests @@ -1786,6 +1800,24 @@ mod tests { } } + #[test] + fn test_discovery_urls_with_path_suffix() { + // When the base URL has a path suffix (e.g., /mcp), the discovery should + // eventually fall back to checking /.well-known/oauth-authorization-server + // at the root, not just /.well-known/oauth-authorization-server/{path}. + let base_url = Url::parse("https://mcp.example.com/mcp").unwrap(); + let urls = AuthorizationManager::generate_discovery_urls(&base_url); + + let canonical_oauth_fallback = "https://mcp.example.com/.well-known/oauth-authorization-server"; + + assert!( + urls.iter().any(|u| u.as_str() == canonical_oauth_fallback), + "Expected discovery URLs to include canonical OAuth fallback '{}', but got: {:?}", + canonical_oauth_fallback, + urls.iter().map(|u| u.as_str()).collect::>() + ); + } + #[tokio::test] async fn test_custom_state_store_with_authorization_manager() { use std::sync::atomic::{AtomicUsize, Ordering}; From a03a238921617f2a347cb1bee04c04cd50c2239b Mon Sep 17 00:00:00 2001 From: Guy Lichtman <1395797+glicht@users.noreply.github.com> Date: Wed, 4 Feb 2026 17:15:54 +0200 Subject: [PATCH 2/2] fix: format auth.rs --- crates/rmcp/src/transport/auth.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/crates/rmcp/src/transport/auth.rs b/crates/rmcp/src/transport/auth.rs index d81654f6..de2cf5e9 100644 --- a/crates/rmcp/src/transport/auth.rs +++ b/crates/rmcp/src/transport/auth.rs @@ -1804,11 +1804,12 @@ mod tests { fn test_discovery_urls_with_path_suffix() { // When the base URL has a path suffix (e.g., /mcp), the discovery should // eventually fall back to checking /.well-known/oauth-authorization-server - // at the root, not just /.well-known/oauth-authorization-server/{path}. + // at the root, not just /.well-known/oauth-authorization-server/{path}. let base_url = Url::parse("https://mcp.example.com/mcp").unwrap(); let urls = AuthorizationManager::generate_discovery_urls(&base_url); - let canonical_oauth_fallback = "https://mcp.example.com/.well-known/oauth-authorization-server"; + let canonical_oauth_fallback = + "https://mcp.example.com/.well-known/oauth-authorization-server"; assert!( urls.iter().any(|u| u.as_str() == canonical_oauth_fallback),