@@ -235,12 +235,13 @@ export async function auth(
235
235
serverUrl : string | URL ;
236
236
authorizationCode ?: string ;
237
237
scope ?: string ;
238
- resourceMetadataUrl ?: URL } ) : Promise < AuthResult > {
238
+ resourceMetadataUrl ?: URL
239
+ } ) : Promise < AuthResult > {
239
240
240
241
let resourceMetadata : OAuthProtectedResourceMetadata | undefined ;
241
242
let authorizationServerUrl = serverUrl ;
242
243
try {
243
- resourceMetadata = await discoverOAuthProtectedResourceMetadata ( serverUrl , { resourceMetadataUrl} ) ;
244
+ resourceMetadata = await discoverOAuthProtectedResourceMetadata ( serverUrl , { resourceMetadataUrl } ) ;
244
245
if ( resourceMetadata . authorization_servers && resourceMetadata . authorization_servers . length > 0 ) {
245
246
authorizationServerUrl = resourceMetadata . authorization_servers [ 0 ] ;
246
247
}
@@ -327,7 +328,7 @@ export async function auth(
327
328
return "REDIRECT" ;
328
329
}
329
330
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 > {
331
332
const defaultResource = resourceUrlFromServerUrl ( serverUrl ) ;
332
333
333
334
// If provider has custom validation, delegate to it
@@ -386,31 +387,16 @@ export async function discoverOAuthProtectedResourceMetadata(
386
387
serverUrl : string | URL ,
387
388
opts ?: { protocolVersion ?: string , resourceMetadataUrl ?: string | URL } ,
388
389
) : Promise < OAuthProtectedResourceMetadata > {
390
+ const response = await discoverMetadataWithFallback (
391
+ serverUrl ,
392
+ 'oauth-protected-resource' ,
393
+ {
394
+ protocolVersion : opts ?. protocolVersion ,
395
+ metadataUrl : opts ?. resourceMetadataUrl ,
396
+ } ,
397
+ ) ;
389
398
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 ) {
414
400
throw new Error ( `Resource server does not implement OAuth 2.0 Protected Resource Metadata.` ) ;
415
401
}
416
402
@@ -448,8 +434,8 @@ async function fetchWithCorsRetry(
448
434
/**
449
435
* Constructs the well-known path for OAuth metadata discovery
450
436
*/
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 } ` ;
453
439
if ( pathname . endsWith ( '/' ) ) {
454
440
// Strip trailing slash from pathname to avoid double slashes
455
441
wellKnownPath = wellKnownPath . slice ( 0 , - 1 ) ;
@@ -477,6 +463,38 @@ function shouldAttemptFallback(response: Response | undefined, pathname: string)
477
463
return ! response || response . status === 404 && pathname !== '/' ;
478
464
}
479
465
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
+
480
498
/**
481
499
* Looks up RFC 8414 OAuth 2.0 Authorization Server Metadata.
482
500
*
@@ -487,19 +505,12 @@ export async function discoverOAuthMetadata(
487
505
authorizationServerUrl : string | URL ,
488
506
opts ?: { protocolVersion ?: string } ,
489
507
) : 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
+ ) ;
497
513
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
- }
503
514
if ( ! response || response . status === 404 ) {
504
515
return undefined ;
505
516
}
0 commit comments