@@ -270,10 +270,10 @@ private async Task<string> GetAccessTokenAsync(HttpResponseMessage response, boo
270270 LogSelectedAuthorizationServer ( selectedAuthServer , availableAuthorizationServers . Count ) ;
271271
272272 // Get auth server metadata
273- var authServerMetadata = await GetAuthServerMetadataAsync ( selectedAuthServer , cancellationToken ) . ConfigureAwait ( false ) ;
273+ var authServerMetadata = await GetAuthServerMetadataAsync ( selectedAuthServer , protectedResourceMetadata . Resource , cancellationToken ) . ConfigureAwait ( false ) ;
274274
275275 // The existing access token must be invalid to have resulted in a 401 response, but refresh might still work.
276- var resourceUri = GetRequiredResourceUri ( protectedResourceMetadata ) ;
276+ var resourceUri = GetResourceUri ( protectedResourceMetadata ) ;
277277
278278 // Only attempt a token refresh if we haven't attempted to already for this request.
279279 // Also only attempt a token refresh for a 401 Unauthorized responses. Other response status codes
@@ -332,7 +332,7 @@ static bool IsValidClientMetadataDocumentUri(Uri uri)
332332 && uri . AbsolutePath . Length > 1 ; // AbsolutePath always starts with "/"
333333 }
334334
335- private async Task < AuthorizationServerMetadata > GetAuthServerMetadataAsync ( Uri authServerUri , CancellationToken cancellationToken )
335+ private async Task < AuthorizationServerMetadata > GetAuthServerMetadataAsync ( Uri authServerUri , string ? resourceUri , CancellationToken cancellationToken )
336336 {
337337 foreach ( var wellKnownEndpoint in GetWellKnownAuthorizationServerMetadataUris ( authServerUri ) )
338338 {
@@ -376,9 +376,35 @@ private async Task<AuthorizationServerMetadata> GetAuthServerMetadataAsync(Uri a
376376 }
377377 }
378378
379+ if ( resourceUri is null )
380+ {
381+ // 2025-03-26 backcompat: when PRM is unavailable and auth server metadata discovery
382+ // also fails, fall back to default endpoint paths per the 2025-03-26 spec.
383+ return BuildDefaultAuthServerMetadata ( authServerUri ) ;
384+ }
385+
379386 throw new McpException ( $ "Failed to find .well-known/openid-configuration or .well-known/oauth-authorization-server metadata for authorization server: '{ authServerUri } '") ;
380387 }
381388
389+ /// <summary>
390+ /// Constructs default authorization server metadata using conventional endpoint paths
391+ /// as specified by the MCP 2025-03-26 specification for servers without metadata discovery.
392+ /// </summary>
393+ private static AuthorizationServerMetadata BuildDefaultAuthServerMetadata ( Uri authServerUri )
394+ {
395+ var baseUrl = authServerUri . GetLeftPart ( UriPartial . Authority ) ;
396+ return new AuthorizationServerMetadata
397+ {
398+ AuthorizationEndpoint = new Uri ( $ "{ baseUrl } /authorize") ,
399+ TokenEndpoint = new Uri ( $ "{ baseUrl } /token") ,
400+ RegistrationEndpoint = new Uri ( $ "{ baseUrl } /register") ,
401+ ResponseTypesSupported = [ "code" ] ,
402+ GrantTypesSupported = [ "authorization_code" , "refresh_token" ] ,
403+ TokenEndpointAuthMethodsSupported = [ "client_secret_post" ] ,
404+ CodeChallengeMethodsSupported = [ "S256" ] ,
405+ } ;
406+ }
407+
382408 private static IEnumerable < Uri > GetWellKnownAuthorizationServerMetadataUris ( Uri issuer )
383409 {
384410 var builder = new UriBuilder ( issuer ) ;
@@ -398,15 +424,19 @@ private static IEnumerable<Uri> GetWellKnownAuthorizationServerMetadataUris(Uri
398424 }
399425 }
400426
401- private async Task < string ? > RefreshTokensAsync ( string refreshToken , string resourceUri , AuthorizationServerMetadata authServerMetadata , CancellationToken cancellationToken )
427+ private async Task < string ? > RefreshTokensAsync ( string refreshToken , string ? resourceUri , AuthorizationServerMetadata authServerMetadata , CancellationToken cancellationToken )
402428 {
403429 Dictionary < string , string > formFields = new ( )
404430 {
405431 [ "grant_type" ] = "refresh_token" ,
406432 [ "refresh_token" ] = refreshToken ,
407- [ "resource" ] = resourceUri ,
408433 } ;
409434
435+ if ( resourceUri is not null )
436+ {
437+ formFields [ "resource" ] = resourceUri ;
438+ }
439+
410440 using var request = CreateTokenRequest ( authServerMetadata . TokenEndpoint , formFields ) ;
411441
412442 using var httpResponse = await _httpClient . SendAsync ( request , cancellationToken ) . ConfigureAwait ( false ) ;
@@ -445,7 +475,7 @@ private Uri BuildAuthorizationUrl(
445475 AuthorizationServerMetadata authServerMetadata ,
446476 string codeChallenge )
447477 {
448- var resourceUri = GetRequiredResourceUri ( protectedResourceMetadata ) ;
478+ var resourceUri = GetResourceUri ( protectedResourceMetadata ) ;
449479
450480 var queryParamsDictionary = new Dictionary < string , string >
451481 {
@@ -454,9 +484,13 @@ private Uri BuildAuthorizationUrl(
454484 [ "response_type" ] = "code" ,
455485 [ "code_challenge" ] = codeChallenge ,
456486 [ "code_challenge_method" ] = "S256" ,
457- [ "resource" ] = resourceUri ,
458487 } ;
459488
489+ if ( resourceUri is not null )
490+ {
491+ queryParamsDictionary [ "resource" ] = resourceUri ;
492+ }
493+
460494 var scope = GetScopeParameter ( protectedResourceMetadata ) ;
461495 if ( ! string . IsNullOrEmpty ( scope ) )
462496 {
@@ -490,17 +524,21 @@ private async Task<string> ExchangeCodeForTokenAsync(
490524 string codeVerifier ,
491525 CancellationToken cancellationToken )
492526 {
493- var resourceUri = GetRequiredResourceUri ( protectedResourceMetadata ) ;
527+ var resourceUri = GetResourceUri ( protectedResourceMetadata ) ;
494528
495529 Dictionary < string , string > formFields = new ( )
496530 {
497531 [ "grant_type" ] = "authorization_code" ,
498532 [ "code" ] = authorizationCode ,
499533 [ "redirect_uri" ] = _redirectUri . ToString ( ) ,
500534 [ "code_verifier" ] = codeVerifier ,
501- [ "resource" ] = resourceUri ,
502535 } ;
503536
537+ if ( resourceUri is not null )
538+ {
539+ formFields [ "resource" ] = resourceUri ;
540+ }
541+
504542 using var request = CreateTokenRequest ( authServerMetadata . TokenEndpoint , formFields ) ;
505543
506544 using var httpResponse = await _httpClient . SendAsync ( request , cancellationToken ) . ConfigureAwait ( false ) ;
@@ -671,15 +709,8 @@ private async Task PerformDynamicClientRegistrationAsync(
671709 }
672710 }
673711
674- private static string GetRequiredResourceUri ( ProtectedResourceMetadata protectedResourceMetadata )
675- {
676- if ( protectedResourceMetadata . Resource is null )
677- {
678- ThrowFailedToHandleUnauthorizedResponse ( "Protected resource metadata did not include a 'resource' value." ) ;
679- }
680-
681- return protectedResourceMetadata . Resource ;
682- }
712+ private static string ? GetResourceUri ( ProtectedResourceMetadata protectedResourceMetadata )
713+ => protectedResourceMetadata . Resource ;
683714
684715 private string ? GetScopeParameter ( ProtectedResourceMetadata protectedResourceMetadata )
685716 {
@@ -801,6 +832,7 @@ private async Task<ProtectedResourceMetadata> ExtractProtectedResourceMetadata(H
801832 }
802833
803834 ProtectedResourceMetadata ? metadata = null ;
835+ bool isLegacyFallback = false ;
804836
805837 if ( resourceMetadataUrl is not null )
806838 {
@@ -822,7 +854,14 @@ private async Task<ProtectedResourceMetadata> ExtractProtectedResourceMetadata(H
822854
823855 if ( metadata is null )
824856 {
825- throw new McpException ( $ "Failed to find protected resource metadata at a well-known location for { _serverUrl } ") ;
857+ // 2025-03-26 backcompat: server doesn't support PRM (RFC 9728).
858+ // Fall back to treating the MCP server's origin as the authorization server.
859+ var serverOrigin = _serverUrl . GetLeftPart ( UriPartial . Authority ) ;
860+ metadata = new ProtectedResourceMetadata
861+ {
862+ AuthorizationServers = [ serverOrigin ] ,
863+ } ;
864+ isLegacyFallback = true ;
826865 }
827866 }
828867
@@ -833,7 +872,7 @@ private async Task<ProtectedResourceMetadata> ExtractProtectedResourceMetadata(H
833872 // Per RFC: The resource value must be identical to the URL that the client used to make the request to the resource server
834873 LogValidatingResourceMetadata ( resourceUri ) ;
835874
836- if ( ! VerifyResourceMatch ( metadata , resourceUri ) )
875+ if ( ! isLegacyFallback && ! VerifyResourceMatch ( metadata , resourceUri ) )
837876 {
838877 throw new McpException ( $ "Resource URI in metadata ({ metadata . Resource } ) does not match the expected URI ({ resourceUri } )") ;
839878 }
0 commit comments