@@ -179,6 +179,33 @@ struct AuthorizationState {
179179}
180180
181181impl 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,68 @@ 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 ?;
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 ( )
249+ . await
250+ {
251+ Ok ( r) => r,
252+ Err ( e) => {
253+ debug ! ( "discovery request failed: {}" , e) ;
254+ continue ; // try next candidate if request fails
255+ }
256+ } ;
257+
258+ if response. status ( ) != StatusCode :: OK {
259+ debug ! ( "discovery returned non-200: {}" , response. status( ) ) ;
260+ continue ; // try next candidate if response is not OK
261+ }
224262
225- if response . status ( ) == StatusCode :: OK {
263+ // parse metadata
226264 let metadata = response
227265 . json :: < AuthorizationMetadata > ( )
228266 . await
229267 . map_err ( |e| {
268+ // Fail the discovery if we get a 200 but cannot parse the response
269+ // This indicates a misconfiguration on the server side
230270 AuthError :: MetadataError ( format ! ( "Failed to parse metadata: {}" , e) )
231271 } ) ?;
232272 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 ( )
245- } ;
246-
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- } )
273+ return Ok ( metadata) ;
256274 }
275+
276+ debug ! ( "No valid .well-known endpoint found, falling back to default endpoints" ) ;
277+
278+ // fallback to default endpoints
279+ let mut auth_base = self . base_url . clone ( ) ;
280+ // discard the path part, only keep scheme, host, port
281+ auth_base. set_path ( "" ) ;
282+
283+ // Helper function to create endpoint URL
284+ let create_endpoint = |path : & str | -> String {
285+ let mut url = auth_base. clone ( ) ;
286+ url. set_path ( path) ;
287+ url. to_string ( )
288+ } ;
289+
290+ Ok ( AuthorizationMetadata {
291+ authorization_endpoint : create_endpoint ( "authorize" ) ,
292+ token_endpoint : create_endpoint ( "token" ) ,
293+ registration_endpoint : create_endpoint ( "register" ) ,
294+ issuer : None ,
295+ jwks_uri : None ,
296+ scopes_supported : None ,
297+ additional_fields : HashMap :: new ( ) ,
298+ } )
257299 }
258300
259301 /// get client id and credentials
@@ -876,3 +918,44 @@ impl OAuthState {
876918 }
877919 }
878920}
921+
922+ #[ cfg( test) ]
923+ mod tests {
924+ use super :: AuthorizationManager ;
925+
926+ #[ test]
927+ fn well_known_paths_root ( ) {
928+ let paths = AuthorizationManager :: well_known_paths ( "/" , "oauth-authorization-server" ) ;
929+ assert_eq ! (
930+ paths,
931+ vec![ "/.well-known/oauth-authorization-server" . to_string( ) ]
932+ ) ;
933+ }
934+
935+ #[ test]
936+ fn well_known_paths_with_suffix ( ) {
937+ let paths = AuthorizationManager :: well_known_paths ( "/mcp" , "oauth-authorization-server" ) ;
938+ assert_eq ! (
939+ paths,
940+ vec![
941+ "/.well-known/oauth-authorization-server/mcp" . to_string( ) ,
942+ "/mcp/.well-known/oauth-authorization-server" . to_string( ) ,
943+ "/.well-known/oauth-authorization-server" . to_string( ) ,
944+ ]
945+ ) ;
946+ }
947+
948+ #[ test]
949+ fn well_known_paths_trailing_slash ( ) {
950+ let paths =
951+ AuthorizationManager :: well_known_paths ( "/v1/mcp/" , "oauth-authorization-server" ) ;
952+ assert_eq ! (
953+ paths,
954+ vec![
955+ "/.well-known/oauth-authorization-server/v1/mcp" . to_string( ) ,
956+ "/v1/mcp/.well-known/oauth-authorization-server" . to_string( ) ,
957+ "/.well-known/oauth-authorization-server" . to_string( ) ,
958+ ]
959+ ) ;
960+ }
961+ }
0 commit comments