@@ -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