Skip to content

Commit 82856a6

Browse files
committed
support suffixed and preffixed well-knonw paths
Signed-off-by: Huabing Zhao <[email protected]>
1 parent 717ec56 commit 82856a6

File tree

1 file changed

+124
-43
lines changed

1 file changed

+124
-43
lines changed

crates/rmcp/src/transport/auth.rs

Lines changed: 124 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -179,6 +179,33 @@ struct AuthorizationState {
179179
}
180180

181181
impl AuthorizationManager {
182+
fn well_known_paths(base_path: &str, resource: &str) -> Vec<String> {
183+
let trimmed = base_path.trim_start_matches('/').trim_end_matches('/');
184+
let mut candidates = Vec::new();
185+
186+
let mut push_candidate = |candidate: String| {
187+
if !candidates.contains(&candidate) {
188+
candidates.push(candidate);
189+
}
190+
};
191+
192+
let canonical = format!("/.well-known/{resource}");
193+
194+
if trimmed.is_empty() {
195+
push_candidate(canonical);
196+
return candidates;
197+
}
198+
199+
// This follows the RFC 8414 recommendation for well-known URI discovery
200+
push_candidate(format!("{canonical}/{trimmed}"));
201+
// This is a common pattern used by some identity providers
202+
push_candidate(format!("/{trimmed}/.well-known/{resource}"));
203+
// The canonical path should always be the last fallback
204+
push_candidate(canonical);
205+
206+
candidates
207+
}
208+
182209
/// create new auth manager with base url
183210
pub async fn new<U: IntoUrl>(base_url: U) -> Result<Self, AuthError> {
184211
let base_url = base_url.into_url()?;
@@ -207,53 +234,65 @@ impl AuthorizationManager {
207234

208235
/// discover oauth2 metadata
209236
pub async fn discover_metadata(&self) -> Result<AuthorizationMetadata, AuthError> {
210-
// according to the specification, the metadata should be located at "/.well-known/oauth-authorization-server"
211-
let mut discovery_url = self.base_url.clone();
212-
let path = discovery_url.path();
213-
let path_suffix = if path == "/" { "" } else { path };
214-
discovery_url.set_path(&format!(
215-
"/.well-known/oauth-authorization-server{path_suffix}"
216-
));
217-
debug!("discovery url: {:?}", discovery_url);
218-
let response = self
219-
.http_client
220-
.get(discovery_url)
221-
.header("MCP-Protocol-Version", "2024-11-05")
222-
.send()
223-
.await?;
224-
225-
if response.status() == StatusCode::OK {
226-
let metadata = response
227-
.json::<AuthorizationMetadata>()
237+
for candidate_path in
238+
Self::well_known_paths(self.base_url.path(), "oauth-authorization-server")
239+
{
240+
let mut discovery_url = self.base_url.clone();
241+
discovery_url.set_path(&candidate_path);
242+
debug!("discovery url: {:?}", discovery_url);
243+
244+
let response = match self
245+
.http_client
246+
.get(discovery_url)
247+
.header("MCP-Protocol-Version", "2024-11-05")
248+
.send()
228249
.await
229-
.map_err(|e| {
230-
AuthError::MetadataError(format!("Failed to parse metadata: {}", e))
231-
})?;
232-
debug!("metadata: {:?}", metadata);
233-
Ok(metadata)
234-
} else {
235-
// fallback to default endpoints
236-
let mut auth_base = self.base_url.clone();
237-
// discard the path part, only keep scheme, host, port
238-
auth_base.set_path("");
239-
240-
// Helper function to create endpoint URL
241-
let create_endpoint = |path: &str| -> String {
242-
let mut url = auth_base.clone();
243-
url.set_path(path);
244-
url.to_string()
250+
{
251+
Ok(r) => r,
252+
Err(e) => {
253+
debug!("discovery request failed: {}", e);
254+
continue; // try next candidate
255+
}
245256
};
246257

247-
Ok(AuthorizationMetadata {
248-
authorization_endpoint: create_endpoint("authorize"),
249-
token_endpoint: create_endpoint("token"),
250-
registration_endpoint: create_endpoint("register"),
251-
issuer: None,
252-
jwks_uri: None,
253-
scopes_supported: None,
254-
additional_fields: HashMap::new(),
255-
})
258+
if response.status() == StatusCode::OK {
259+
let metadata = response
260+
.json::<AuthorizationMetadata>()
261+
.await
262+
.map_err(|e| {
263+
AuthError::MetadataError(format!("Failed to parse metadata: {}", e))
264+
})?;
265+
debug!("metadata: {:?}", metadata);
266+
return Ok(metadata);
267+
} else {
268+
debug!("discovery returned non-200: {}", response.status());
269+
continue; // try next candidate
270+
}
256271
}
272+
273+
debug!("No valid metadata found, falling back to default endpoints");
274+
275+
// fallback to default endpoints
276+
let mut auth_base = self.base_url.clone();
277+
// discard the path part, only keep scheme, host, port
278+
auth_base.set_path("");
279+
280+
// Helper function to create endpoint URL
281+
let create_endpoint = |path: &str| -> String {
282+
let mut url = auth_base.clone();
283+
url.set_path(path);
284+
url.to_string()
285+
};
286+
287+
Ok(AuthorizationMetadata {
288+
authorization_endpoint: create_endpoint("authorize"),
289+
token_endpoint: create_endpoint("token"),
290+
registration_endpoint: create_endpoint("register"),
291+
issuer: None,
292+
jwks_uri: None,
293+
scopes_supported: None,
294+
additional_fields: HashMap::new(),
295+
})
257296
}
258297

259298
/// get client id and credentials
@@ -876,3 +915,45 @@ impl OAuthState {
876915
}
877916
}
878917
}
918+
919+
#[cfg(test)]
920+
mod tests {
921+
use super::AuthorizationManager;
922+
923+
#[test]
924+
fn well_known_paths_root() {
925+
let paths = AuthorizationManager::well_known_paths("/", "oauth-authorization-server");
926+
assert_eq!(
927+
paths,
928+
vec!["/.well-known/oauth-authorization-server".to_string()]
929+
);
930+
}
931+
932+
#[test]
933+
fn well_known_paths_with_suffix() {
934+
let paths =
935+
AuthorizationManager::well_known_paths("/mcp", "oauth-authorization-server");
936+
assert_eq!(
937+
paths,
938+
vec![
939+
"/.well-known/oauth-authorization-server/mcp".to_string(),
940+
"/mcp/.well-known/oauth-authorization-server".to_string(),
941+
"/.well-known/oauth-authorization-server".to_string(),
942+
]
943+
);
944+
}
945+
946+
#[test]
947+
fn well_known_paths_trailing_slash() {
948+
let paths =
949+
AuthorizationManager::well_known_paths("/v1/mcp/", "oauth-authorization-server");
950+
assert_eq!(
951+
paths,
952+
vec![
953+
"/.well-known/oauth-authorization-server/v1/mcp".to_string(),
954+
"/v1/mcp/.well-known/oauth-authorization-server".to_string(),
955+
"/.well-known/oauth-authorization-server".to_string(),
956+
]
957+
);
958+
}
959+
}

0 commit comments

Comments
 (0)