@@ -107,12 +107,13 @@ export async function auth(
107107 serverUrl : string | URL ;
108108 authorizationCode ?: string ;
109109 scope ?: string ;
110- resourceMetadataUrl ?: URL } ) : Promise < AuthResult > {
110+ resourceMetadataUrl ?: URL
111+ } ) : Promise < AuthResult > {
111112
112113 let resourceMetadata : OAuthProtectedResourceMetadata | undefined ;
113114 let authorizationServerUrl = serverUrl ;
114115 try {
115- resourceMetadata = await discoverOAuthProtectedResourceMetadata ( serverUrl , { resourceMetadataUrl} ) ;
116+ resourceMetadata = await discoverOAuthProtectedResourceMetadata ( serverUrl , { resourceMetadataUrl } ) ;
116117 if ( resourceMetadata . authorization_servers && resourceMetadata . authorization_servers . length > 0 ) {
117118 authorizationServerUrl = resourceMetadata . authorization_servers [ 0 ] ;
118119 }
@@ -197,7 +198,7 @@ export async function auth(
197198 return "REDIRECT" ;
198199}
199200
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 > {
201202 const defaultResource = resourceUrlFromServerUrl ( serverUrl ) ;
202203
203204 // If provider has custom validation, delegate to it
@@ -256,34 +257,16 @@ export async function discoverOAuthProtectedResourceMetadata(
256257 serverUrl : string | URL ,
257258 opts ?: { protocolVersion ?: string , resourceMetadataUrl ?: string | URL } ,
258259) : Promise < OAuthProtectedResourceMetadata > {
260+ const response = await discoverMetadataWithFallback (
261+ serverUrl ,
262+ 'oauth-protected-resource' ,
263+ {
264+ protocolVersion : opts ?. protocolVersion ,
265+ metadataUrl : opts ?. resourceMetadataUrl ,
266+ } ,
267+ ) ;
259268
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 ) {
287270 throw new Error ( `Resource server does not implement OAuth 2.0 Protected Resource Metadata.` ) ;
288271 }
289272
@@ -350,6 +333,38 @@ function shouldAttemptFallback(response: Response | undefined, pathname: string)
350333 return ! response || response . status === 404 && pathname !== '/' ;
351334}
352335
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+
353368/**
354369 * Looks up RFC 8414 OAuth 2.0 Authorization Server Metadata.
355370 *
@@ -360,20 +375,12 @@ export async function discoverOAuthMetadata(
360375 authorizationServerUrl : string | URL ,
361376 opts ?: { protocolVersion ?: string } ,
362377) : 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+ ) ;
371383
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- }
377384 if ( ! response || response . status === 404 ) {
378385 return undefined ;
379386 }
0 commit comments