@@ -107,12 +107,13 @@ export async function auth(
107
107
serverUrl : string | URL ;
108
108
authorizationCode ?: string ;
109
109
scope ?: string ;
110
- resourceMetadataUrl ?: URL } ) : Promise < AuthResult > {
110
+ resourceMetadataUrl ?: URL
111
+ } ) : Promise < AuthResult > {
111
112
112
113
let resourceMetadata : OAuthProtectedResourceMetadata | undefined ;
113
114
let authorizationServerUrl = serverUrl ;
114
115
try {
115
- resourceMetadata = await discoverOAuthProtectedResourceMetadata ( serverUrl , { resourceMetadataUrl} ) ;
116
+ resourceMetadata = await discoverOAuthProtectedResourceMetadata ( serverUrl , { resourceMetadataUrl } ) ;
116
117
if ( resourceMetadata . authorization_servers && resourceMetadata . authorization_servers . length > 0 ) {
117
118
authorizationServerUrl = resourceMetadata . authorization_servers [ 0 ] ;
118
119
}
@@ -197,7 +198,7 @@ export async function auth(
197
198
return "REDIRECT" ;
198
199
}
199
200
200
- export async function selectResourceURL ( serverUrl : string | URL , provider : OAuthClientProvider , resourceMetadata ?: OAuthProtectedResourceMetadata ) : Promise < URL | undefined > {
201
+ export async function selectResourceURL ( serverUrl : string | URL , provider : OAuthClientProvider , resourceMetadata ?: OAuthProtectedResourceMetadata ) : Promise < URL | undefined > {
201
202
const defaultResource = resourceUrlFromServerUrl ( serverUrl ) ;
202
203
203
204
// If provider has custom validation, delegate to it
@@ -256,34 +257,16 @@ export async function discoverOAuthProtectedResourceMetadata(
256
257
serverUrl : string | URL ,
257
258
opts ?: { protocolVersion ?: string , resourceMetadataUrl ?: string | URL } ,
258
259
) : Promise < OAuthProtectedResourceMetadata > {
260
+ const response = await discoverMetadataWithFallback (
261
+ serverUrl ,
262
+ 'oauth-protected-resource' ,
263
+ {
264
+ protocolVersion : opts ?. protocolVersion ,
265
+ metadataUrl : opts ?. resourceMetadataUrl ,
266
+ } ,
267
+ ) ;
259
268
260
- let url : URL
261
- if ( opts ?. resourceMetadataUrl ) {
262
- url = new URL ( opts ?. resourceMetadataUrl ) ;
263
- } else {
264
- const issuer = new URL ( serverUrl ) ;
265
- const wellKnownPath = buildWellKnownPath ( 'oauth-protected-resource' , issuer . pathname ) ;
266
- url = new URL ( wellKnownPath , issuer ) ;
267
- url . search = issuer . search ;
268
- }
269
-
270
- let response : Response ;
271
- try {
272
- response = await fetch ( url , {
273
- headers : {
274
- "MCP-Protocol-Version" : opts ?. protocolVersion ?? LATEST_PROTOCOL_VERSION
275
- }
276
- } ) ;
277
- } catch ( error ) {
278
- // CORS errors come back as TypeError
279
- if ( error instanceof TypeError ) {
280
- response = await fetch ( url ) ;
281
- } else {
282
- throw error ;
283
- }
284
- }
285
-
286
- if ( response . status === 404 ) {
269
+ if ( ! response || response . status === 404 ) {
287
270
throw new Error ( `Resource server does not implement OAuth 2.0 Protected Resource Metadata.` ) ;
288
271
}
289
272
@@ -350,6 +333,38 @@ function shouldAttemptFallback(response: Response | undefined, pathname: string)
350
333
return ! response || response . status === 404 && pathname !== '/' ;
351
334
}
352
335
336
+ /**
337
+ * Generic function for discovering OAuth metadata with fallback support
338
+ */
339
+ async function discoverMetadataWithFallback (
340
+ serverUrl : string | URL ,
341
+ wellKnownType : 'oauth-authorization-server' | 'oauth-protected-resource' ,
342
+ opts ?: { protocolVersion ?: string ; metadataUrl ?: string | URL } ,
343
+ ) : Promise < Response | undefined > {
344
+ const issuer = new URL ( serverUrl ) ;
345
+ const protocolVersion = opts ?. protocolVersion ?? LATEST_PROTOCOL_VERSION ;
346
+
347
+ let url : URL ;
348
+ if ( opts ?. metadataUrl ) {
349
+ url = new URL ( opts . metadataUrl ) ;
350
+ } else {
351
+ // Try path-aware discovery first
352
+ const wellKnownPath = buildWellKnownPath ( wellKnownType , issuer . pathname ) ;
353
+ url = new URL ( wellKnownPath , issuer ) ;
354
+ url . search = issuer . search ;
355
+ }
356
+
357
+ let response = await tryMetadataDiscovery ( url , protocolVersion ) ;
358
+
359
+ // If path-aware discovery fails with 404 and we're not already at root, try fallback to root discovery
360
+ if ( ! opts ?. metadataUrl && shouldAttemptFallback ( response , issuer . pathname ) ) {
361
+ const rootUrl = new URL ( `/.well-known/${ wellKnownType } ` , issuer ) ;
362
+ response = await tryMetadataDiscovery ( rootUrl , protocolVersion ) ;
363
+ }
364
+
365
+ return response ;
366
+ }
367
+
353
368
/**
354
369
* Looks up RFC 8414 OAuth 2.0 Authorization Server Metadata.
355
370
*
@@ -360,20 +375,12 @@ export async function discoverOAuthMetadata(
360
375
authorizationServerUrl : string | URL ,
361
376
opts ?: { protocolVersion ?: string } ,
362
377
) : Promise < OAuthMetadata | undefined > {
363
- const issuer = new URL ( authorizationServerUrl ) ;
364
- const protocolVersion = opts ?. protocolVersion ?? LATEST_PROTOCOL_VERSION ;
365
-
366
- // Try path-aware discovery first (RFC 8414 compliant)
367
- const wellKnownPath = buildWellKnownPath ( 'oauth-authorization-server' , issuer . pathname ) ;
368
- const pathAwareUrl = new URL ( wellKnownPath , issuer ) ;
369
- pathAwareUrl . search = issuer . search ;
370
- let response = await tryMetadataDiscovery ( pathAwareUrl , protocolVersion ) ;
378
+ const response = await discoverMetadataWithFallback (
379
+ authorizationServerUrl ,
380
+ 'oauth-authorization-server' ,
381
+ opts ,
382
+ ) ;
371
383
372
- // If path-aware discovery fails with 404, try fallback to root discovery
373
- if ( shouldAttemptFallback ( response , issuer . pathname ) ) {
374
- const rootUrl = new URL ( "/.well-known/oauth-authorization-server" , issuer ) ;
375
- response = await tryMetadataDiscovery ( rootUrl , protocolVersion ) ;
376
- }
377
384
if ( ! response || response . status === 404 ) {
378
385
return undefined ;
379
386
}
0 commit comments