@@ -235,12 +235,13 @@ export async function auth(
235235 serverUrl : string | URL ;
236236 authorizationCode ?: string ;
237237 scope ?: string ;
238- resourceMetadataUrl ?: URL } ) : Promise < AuthResult > {
238+ resourceMetadataUrl ?: URL
239+ } ) : Promise < AuthResult > {
239240
240241 let resourceMetadata : OAuthProtectedResourceMetadata | undefined ;
241242 let authorizationServerUrl = serverUrl ;
242243 try {
243- resourceMetadata = await discoverOAuthProtectedResourceMetadata ( serverUrl , { resourceMetadataUrl} ) ;
244+ resourceMetadata = await discoverOAuthProtectedResourceMetadata ( serverUrl , { resourceMetadataUrl } ) ;
244245 if ( resourceMetadata . authorization_servers && resourceMetadata . authorization_servers . length > 0 ) {
245246 authorizationServerUrl = resourceMetadata . authorization_servers [ 0 ] ;
246247 }
@@ -327,7 +328,7 @@ export async function auth(
327328 return "REDIRECT" ;
328329}
329330
330- export async function selectResourceURL ( serverUrl : string | URL , provider : OAuthClientProvider , resourceMetadata ?: OAuthProtectedResourceMetadata ) : Promise < URL | undefined > {
331+ export async function selectResourceURL ( serverUrl : string | URL , provider : OAuthClientProvider , resourceMetadata ?: OAuthProtectedResourceMetadata ) : Promise < URL | undefined > {
331332 const defaultResource = resourceUrlFromServerUrl ( serverUrl ) ;
332333
333334 // If provider has custom validation, delegate to it
@@ -386,31 +387,16 @@ export async function discoverOAuthProtectedResourceMetadata(
386387 serverUrl : string | URL ,
387388 opts ?: { protocolVersion ?: string , resourceMetadataUrl ?: string | URL } ,
388389) : Promise < OAuthProtectedResourceMetadata > {
390+ const response = await discoverMetadataWithFallback (
391+ serverUrl ,
392+ 'oauth-protected-resource' ,
393+ {
394+ protocolVersion : opts ?. protocolVersion ,
395+ metadataUrl : opts ?. resourceMetadataUrl ,
396+ } ,
397+ ) ;
389398
390- let url : URL
391- if ( opts ?. resourceMetadataUrl ) {
392- url = new URL ( opts ?. resourceMetadataUrl ) ;
393- } else {
394- url = new URL ( "/.well-known/oauth-protected-resource" , serverUrl ) ;
395- }
396-
397- let response : Response ;
398- try {
399- response = await fetch ( url , {
400- headers : {
401- "MCP-Protocol-Version" : opts ?. protocolVersion ?? LATEST_PROTOCOL_VERSION
402- }
403- } ) ;
404- } catch ( error ) {
405- // CORS errors come back as TypeError
406- if ( error instanceof TypeError ) {
407- response = await fetch ( url ) ;
408- } else {
409- throw error ;
410- }
411- }
412-
413- if ( response . status === 404 ) {
399+ if ( ! response || response . status === 404 ) {
414400 throw new Error ( `Resource server does not implement OAuth 2.0 Protected Resource Metadata.` ) ;
415401 }
416402
@@ -448,8 +434,8 @@ async function fetchWithCorsRetry(
448434/**
449435 * Constructs the well-known path for OAuth metadata discovery
450436 */
451- function buildWellKnownPath ( pathname : string ) : string {
452- let wellKnownPath = `/.well-known/oauth-authorization-server ${ pathname } ` ;
437+ function buildWellKnownPath ( wellKnownPrefix : string , pathname : string ) : string {
438+ let wellKnownPath = `/.well-known/${ wellKnownPrefix } ${ pathname } ` ;
453439 if ( pathname . endsWith ( '/' ) ) {
454440 // Strip trailing slash from pathname to avoid double slashes
455441 wellKnownPath = wellKnownPath . slice ( 0 , - 1 ) ;
@@ -477,6 +463,38 @@ function shouldAttemptFallback(response: Response | undefined, pathname: string)
477463 return ! response || response . status === 404 && pathname !== '/' ;
478464}
479465
466+ /**
467+ * Generic function for discovering OAuth metadata with fallback support
468+ */
469+ async function discoverMetadataWithFallback (
470+ serverUrl : string | URL ,
471+ wellKnownType : 'oauth-authorization-server' | 'oauth-protected-resource' ,
472+ opts ?: { protocolVersion ?: string ; metadataUrl ?: string | URL } ,
473+ ) : Promise < Response | undefined > {
474+ const issuer = new URL ( serverUrl ) ;
475+ const protocolVersion = opts ?. protocolVersion ?? LATEST_PROTOCOL_VERSION ;
476+
477+ let url : URL ;
478+ if ( opts ?. metadataUrl ) {
479+ url = new URL ( opts . metadataUrl ) ;
480+ } else {
481+ // Try path-aware discovery first
482+ const wellKnownPath = buildWellKnownPath ( wellKnownType , issuer . pathname ) ;
483+ url = new URL ( wellKnownPath , issuer ) ;
484+ url . search = issuer . search ;
485+ }
486+
487+ let response = await tryMetadataDiscovery ( url , protocolVersion ) ;
488+
489+ // If path-aware discovery fails with 404 and we're not already at root, try fallback to root discovery
490+ if ( ! opts ?. metadataUrl && shouldAttemptFallback ( response , issuer . pathname ) ) {
491+ const rootUrl = new URL ( `/.well-known/${ wellKnownType } ` , issuer ) ;
492+ response = await tryMetadataDiscovery ( rootUrl , protocolVersion ) ;
493+ }
494+
495+ return response ;
496+ }
497+
480498/**
481499 * Looks up RFC 8414 OAuth 2.0 Authorization Server Metadata.
482500 *
@@ -487,19 +505,12 @@ export async function discoverOAuthMetadata(
487505 authorizationServerUrl : string | URL ,
488506 opts ?: { protocolVersion ?: string } ,
489507) : Promise < OAuthMetadata | undefined > {
490- const issuer = new URL ( authorizationServerUrl ) ;
491- const protocolVersion = opts ?. protocolVersion ?? LATEST_PROTOCOL_VERSION ;
492-
493- // Try path-aware discovery first (RFC 8414 compliant)
494- const wellKnownPath = buildWellKnownPath ( issuer . pathname ) ;
495- const pathAwareUrl = new URL ( wellKnownPath , issuer ) ;
496- let response = await tryMetadataDiscovery ( pathAwareUrl , protocolVersion ) ;
508+ const response = await discoverMetadataWithFallback (
509+ authorizationServerUrl ,
510+ 'oauth-authorization-server' ,
511+ opts ,
512+ ) ;
497513
498- // If path-aware discovery fails with 404, try fallback to root discovery
499- if ( shouldAttemptFallback ( response , issuer . pathname ) ) {
500- const rootUrl = new URL ( "/.well-known/oauth-authorization-server" , issuer ) ;
501- response = await tryMetadataDiscovery ( rootUrl , protocolVersion ) ;
502- }
503514 if ( ! response || response . status === 404 ) {
504515 return undefined ;
505516 }
0 commit comments