@@ -270,10 +270,20 @@ 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+ AuthorizationServerMetadata authServerMetadata ;
274+ try
275+ {
276+ authServerMetadata = await GetAuthServerMetadataAsync ( selectedAuthServer , cancellationToken ) . ConfigureAwait ( false ) ;
277+ }
278+ catch ( McpException ) when ( protectedResourceMetadata . Resource is null )
279+ {
280+ // 2025-03-26 backcompat: when PRM is unavailable and auth server metadata discovery
281+ // also fails, fall back to default endpoint paths per the 2025-03-26 spec.
282+ authServerMetadata = BuildDefaultAuthServerMetadata ( selectedAuthServer ) ;
283+ }
274284
275285 // The existing access token must be invalid to have resulted in a 401 response, but refresh might still work.
276- var resourceUri = GetRequiredResourceUri ( protectedResourceMetadata ) ;
286+ var resourceUri = GetResourceUri ( protectedResourceMetadata ) ;
277287
278288 // Only attempt a token refresh if we haven't attempted to already for this request.
279289 // Also only attempt a token refresh for a 401 Unauthorized responses. Other response status codes
@@ -379,6 +389,25 @@ private async Task<AuthorizationServerMetadata> GetAuthServerMetadataAsync(Uri a
379389 throw new McpException ( $ "Failed to find .well-known/openid-configuration or .well-known/oauth-authorization-server metadata for authorization server: '{ authServerUri } '") ;
380390 }
381391
392+ /// <summary>
393+ /// Constructs default authorization server metadata using conventional endpoint paths
394+ /// as specified by the MCP 2025-03-26 specification for servers without metadata discovery.
395+ /// </summary>
396+ private static AuthorizationServerMetadata BuildDefaultAuthServerMetadata ( Uri authServerUri )
397+ {
398+ var baseUrl = authServerUri . GetLeftPart ( UriPartial . Authority ) ;
399+ return new AuthorizationServerMetadata
400+ {
401+ AuthorizationEndpoint = new Uri ( $ "{ baseUrl } /authorize") ,
402+ TokenEndpoint = new Uri ( $ "{ baseUrl } /token") ,
403+ RegistrationEndpoint = new Uri ( $ "{ baseUrl } /register") ,
404+ ResponseTypesSupported = [ "code" ] ,
405+ GrantTypesSupported = [ "authorization_code" , "refresh_token" ] ,
406+ TokenEndpointAuthMethodsSupported = [ "client_secret_post" ] ,
407+ CodeChallengeMethodsSupported = [ "S256" ] ,
408+ } ;
409+ }
410+
382411 private static IEnumerable < Uri > GetWellKnownAuthorizationServerMetadataUris ( Uri issuer )
383412 {
384413 var builder = new UriBuilder ( issuer ) ;
@@ -398,15 +427,19 @@ private static IEnumerable<Uri> GetWellKnownAuthorizationServerMetadataUris(Uri
398427 }
399428 }
400429
401- private async Task < string ? > RefreshTokensAsync ( string refreshToken , string resourceUri , AuthorizationServerMetadata authServerMetadata , CancellationToken cancellationToken )
430+ private async Task < string ? > RefreshTokensAsync ( string refreshToken , string ? resourceUri , AuthorizationServerMetadata authServerMetadata , CancellationToken cancellationToken )
402431 {
403432 Dictionary < string , string > formFields = new ( )
404433 {
405434 [ "grant_type" ] = "refresh_token" ,
406435 [ "refresh_token" ] = refreshToken ,
407- [ "resource" ] = resourceUri ,
408436 } ;
409437
438+ if ( resourceUri is not null )
439+ {
440+ formFields [ "resource" ] = resourceUri ;
441+ }
442+
410443 using var request = CreateTokenRequest ( authServerMetadata . TokenEndpoint , formFields ) ;
411444
412445 using var httpResponse = await _httpClient . SendAsync ( request , cancellationToken ) . ConfigureAwait ( false ) ;
@@ -445,7 +478,7 @@ private Uri BuildAuthorizationUrl(
445478 AuthorizationServerMetadata authServerMetadata ,
446479 string codeChallenge )
447480 {
448- var resourceUri = GetRequiredResourceUri ( protectedResourceMetadata ) ;
481+ var resourceUri = GetResourceUri ( protectedResourceMetadata ) ;
449482
450483 var queryParamsDictionary = new Dictionary < string , string >
451484 {
@@ -454,9 +487,13 @@ private Uri BuildAuthorizationUrl(
454487 [ "response_type" ] = "code" ,
455488 [ "code_challenge" ] = codeChallenge ,
456489 [ "code_challenge_method" ] = "S256" ,
457- [ "resource" ] = resourceUri ,
458490 } ;
459491
492+ if ( resourceUri is not null )
493+ {
494+ queryParamsDictionary [ "resource" ] = resourceUri ;
495+ }
496+
460497 var scope = GetScopeParameter ( protectedResourceMetadata ) ;
461498 if ( ! string . IsNullOrEmpty ( scope ) )
462499 {
@@ -490,17 +527,21 @@ private async Task<string> ExchangeCodeForTokenAsync(
490527 string codeVerifier ,
491528 CancellationToken cancellationToken )
492529 {
493- var resourceUri = GetRequiredResourceUri ( protectedResourceMetadata ) ;
530+ var resourceUri = GetResourceUri ( protectedResourceMetadata ) ;
494531
495532 Dictionary < string , string > formFields = new ( )
496533 {
497534 [ "grant_type" ] = "authorization_code" ,
498535 [ "code" ] = authorizationCode ,
499536 [ "redirect_uri" ] = _redirectUri . ToString ( ) ,
500537 [ "code_verifier" ] = codeVerifier ,
501- [ "resource" ] = resourceUri ,
502538 } ;
503539
540+ if ( resourceUri is not null )
541+ {
542+ formFields [ "resource" ] = resourceUri ;
543+ }
544+
504545 using var request = CreateTokenRequest ( authServerMetadata . TokenEndpoint , formFields ) ;
505546
506547 using var httpResponse = await _httpClient . SendAsync ( request , cancellationToken ) . ConfigureAwait ( false ) ;
@@ -671,15 +712,8 @@ private async Task PerformDynamicClientRegistrationAsync(
671712 }
672713 }
673714
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- }
715+ private static string ? GetResourceUri ( ProtectedResourceMetadata protectedResourceMetadata )
716+ => protectedResourceMetadata . Resource ;
683717
684718 private string ? GetScopeParameter ( ProtectedResourceMetadata protectedResourceMetadata )
685719 {
@@ -801,6 +835,7 @@ private async Task<ProtectedResourceMetadata> ExtractProtectedResourceMetadata(H
801835 }
802836
803837 ProtectedResourceMetadata ? metadata = null ;
838+ bool isLegacyFallback = false ;
804839
805840 if ( resourceMetadataUrl is not null )
806841 {
@@ -822,7 +857,14 @@ private async Task<ProtectedResourceMetadata> ExtractProtectedResourceMetadata(H
822857
823858 if ( metadata is null )
824859 {
825- throw new McpException ( $ "Failed to find protected resource metadata at a well-known location for { _serverUrl } ") ;
860+ // 2025-03-26 backcompat: server doesn't support PRM (RFC 9728).
861+ // Fall back to treating the MCP server's origin as the authorization server.
862+ var serverOrigin = _serverUrl . GetLeftPart ( UriPartial . Authority ) ;
863+ metadata = new ProtectedResourceMetadata
864+ {
865+ AuthorizationServers = [ serverOrigin ] ,
866+ } ;
867+ isLegacyFallback = true ;
826868 }
827869 }
828870
@@ -833,7 +875,7 @@ private async Task<ProtectedResourceMetadata> ExtractProtectedResourceMetadata(H
833875 // Per RFC: The resource value must be identical to the URL that the client used to make the request to the resource server
834876 LogValidatingResourceMetadata ( resourceUri ) ;
835877
836- if ( ! VerifyResourceMatch ( metadata , resourceUri ) )
878+ if ( ! isLegacyFallback && ! VerifyResourceMatch ( metadata , resourceUri ) )
837879 {
838880 throw new McpException ( $ "Resource URI in metadata ({ metadata . Resource } ) does not match the expected URI ({ resourceUri } )") ;
839881 }
0 commit comments