@@ -634,7 +634,7 @@ export async function discoverOAuthMetadata(
634
634
if ( typeof authorizationServerUrl === 'string' ) {
635
635
authorizationServerUrl = new URL ( authorizationServerUrl ) ;
636
636
}
637
- protocolVersion ??= LATEST_PROTOCOL_VERSION ;
637
+ protocolVersion ??= LATEST_PROTOCOL_VERSION ;
638
638
639
639
const response = await discoverMetadataWithFallback (
640
640
authorizationServerUrl ,
@@ -675,162 +675,104 @@ export async function discoverOAuthMetadata(
675
675
* @param options.protocolVersion - MCP protocol version to use, defaults to LATEST_PROTOCOL_VERSION
676
676
* @returns Promise resolving to authorization server metadata, or undefined if discovery fails
677
677
*/
678
- export async function discoverAuthorizationServerMetadata (
679
- authorizationServerUrl : string | URL ,
680
- {
681
- fetchFn = fetch ,
682
- protocolVersion = LATEST_PROTOCOL_VERSION ,
683
- } : {
684
- fetchFn ?: FetchLike ;
685
- protocolVersion ?: string ;
686
- } = { }
687
- ) : Promise < AuthorizationServerMetadata | undefined > {
678
+ /**
679
+ * Builds a list of discovery URLs to try for authorization server metadata.
680
+ * URLs are returned in priority order:
681
+ * 1. OAuth metadata at the given URL
682
+ * 2. OAuth metadata at root (if URL has path)
683
+ * 3. OIDC metadata endpoints
684
+ */
685
+ function buildDiscoveryUrls ( authorizationServerUrl : string | URL ) : { url : URL ; type : 'oauth' | 'oidc' } [ ] {
688
686
const url = typeof authorizationServerUrl === 'string' ? new URL ( authorizationServerUrl ) : authorizationServerUrl ;
689
687
const hasPath = url . pathname !== '/' ;
690
-
691
- const oauthMetadata = await fetchOAuthMetadata ( authorizationServerUrl , {
692
- fetchFn,
693
- protocolVersion,
688
+ const urlsToTry : { url : URL ; type : 'oauth' | 'oidc' } [ ] = [ ] ;
689
+
690
+ // 1. OAuth metadata at the given URL
691
+ urlsToTry . push ( {
692
+ url : new URL (
693
+ buildWellKnownPath ( 'oauth-authorization-server' , hasPath ? url . pathname : '' ) ,
694
+ url . origin
695
+ ) ,
696
+ type : 'oauth'
694
697
} ) ;
695
-
696
- if ( oauthMetadata ) {
697
- return oauthMetadata ;
698
- }
699
-
698
+
699
+ // 2. OAuth metadata at root (if URL has path)
700
700
if ( hasPath ) {
701
- const rootUrl = new URL ( url . origin ) ;
702
- const rootOauthMetadata = await fetchOAuthMetadata ( rootUrl , {
703
- fetchFn,
704
- protocolVersion,
701
+ urlsToTry . push ( {
702
+ url : new URL ( buildWellKnownPath ( 'oauth-authorization-server' ) , url . origin ) ,
703
+ type : 'oauth'
705
704
} ) ;
706
-
707
- if ( rootOauthMetadata ) {
708
- return rootOauthMetadata ;
709
- }
710
- }
711
-
712
- const oidcMetadata = await retrieveOpenIdProviderMetadataFromAuthorizationServer ( authorizationServerUrl , {
713
- fetchFn,
714
- protocolVersion,
715
- } ) ;
716
-
717
- return oidcMetadata ;
718
- }
719
-
720
- /**
721
- * Retrieves RFC 8414 OAuth 2.0 Authorization Server Metadata from the authorization server.
722
- *
723
- * Per RFC 8414 Section 3.1, when the issuer identifier contains path components,
724
- * the well-known URI is constructed by inserting "/.well-known/oauth-authorization-server"
725
- * before the path component.
726
- *
727
- * @param authorizationServerUrl - The authorization server URL (issuer identifier)
728
- * @param options - Configuration options
729
- * @param options.fetchFn - Optional fetch function for making HTTP requests, defaults to global fetch
730
- * @param options.protocolVersion - MCP protocol version to use (required)
731
- * @returns Promise resolving to OAuth metadata, or undefined if discovery fails
732
- */
733
- async function fetchOAuthMetadata (
734
- authorizationServerUrl : string | URL ,
735
- {
736
- fetchFn = fetch ,
737
- protocolVersion,
738
- } : {
739
- fetchFn ?: FetchLike ;
740
- protocolVersion : string ;
741
- }
742
- ) : Promise < OAuthMetadata | undefined > {
743
- const url = typeof authorizationServerUrl === 'string' ? new URL ( authorizationServerUrl ) : authorizationServerUrl ;
744
- const hasPath = url . pathname !== '/' ;
745
-
746
- const metadataEndpoint = new URL (
747
- buildWellKnownPath ( 'oauth-authorization-server' , hasPath ? url . pathname : '' ) ,
748
- url . origin
749
- ) ;
750
-
751
- const response = await fetchWithCorsRetry ( metadataEndpoint , getProtocolVersionHeader ( protocolVersion ) , fetchFn ) ;
752
-
753
- if ( ! response ) {
754
- throw new Error ( `CORS error trying to load OAuth metadata from ${ metadataEndpoint } ` ) ;
755
705
}
756
-
757
- if ( ! response . ok ) {
758
- if ( response . status === 404 ) {
759
- return undefined ;
760
- }
761
-
762
- throw new Error ( `HTTP ${ response . status } trying to load OAuth metadata from ${ metadataEndpoint } ` ) ;
706
+
707
+ // 3. OIDC metadata endpoints
708
+ if ( hasPath ) {
709
+ // RFC 8414 style: Insert /.well-known/openid-configuration before the path
710
+ urlsToTry . push ( {
711
+ url : new URL ( buildWellKnownPath ( 'openid-configuration' , url . pathname ) , url . origin ) ,
712
+ type : 'oidc'
713
+ } ) ;
714
+ // OIDC Discovery 1.0 style: Append /.well-known/openid-configuration after the path
715
+ urlsToTry . push ( {
716
+ url : new URL ( buildWellKnownPath ( 'openid-configuration' , url . pathname , { prependPathname : true } ) , url . origin ) ,
717
+ type : 'oidc'
718
+ } ) ;
719
+ } else {
720
+ urlsToTry . push ( {
721
+ url : new URL ( buildWellKnownPath ( 'openid-configuration' ) , url . origin ) ,
722
+ type : 'oidc'
723
+ } ) ;
763
724
}
764
-
765
- return OAuthMetadataSchema . parse ( await response . json ( ) ) ;
725
+
726
+ return urlsToTry ;
766
727
}
767
728
768
- /**
769
- * Retrieves OpenID Connect Discovery 1.0 metadata from the authorization server.
770
- *
771
- * Per RFC 8414 Section 5 compatibility notes and OpenID Connect Discovery 1.0 Section 4.1,
772
- * when the issuer identifier contains path components, discovery endpoints are tried in order:
773
- * 1. RFC 8414 style: Insert /.well-known/openid-configuration before the path
774
- * 2. OIDC Discovery 1.0 style: Append /.well-known/openid-configuration after the path
775
- *
776
- * @param authorizationServerUrl - The authorization server URL (issuer identifier)
777
- * @param options - Configuration options
778
- * @param options.fetchFn - Optional fetch function for making HTTP requests, defaults to global fetch
779
- * @param options.protocolVersion - MCP protocol version to use (required)
780
- * @returns Promise resolving to OpenID provider metadata, or undefined if discovery fails
781
- */
782
- async function retrieveOpenIdProviderMetadataFromAuthorizationServer (
729
+ export async function discoverAuthorizationServerMetadata (
783
730
authorizationServerUrl : string | URL ,
784
731
{
785
732
fetchFn = fetch ,
786
- protocolVersion,
733
+ protocolVersion = LATEST_PROTOCOL_VERSION ,
787
734
} : {
788
735
fetchFn ?: FetchLike ;
789
- protocolVersion : string ;
790
- }
791
- ) : Promise < OpenIdProviderDiscoveryMetadata | undefined > {
792
- const url = typeof authorizationServerUrl === 'string' ? new URL ( authorizationServerUrl ) : authorizationServerUrl ;
793
- const hasPath = url . pathname !== '/' ;
794
-
795
- const potentialMetadataEndpoints = hasPath
796
- ? [
797
- // https://example.com/.well-known/openid-configuration/tenant1
798
- new URL ( buildWellKnownPath ( 'openid-configuration' , url . pathname ) , url . origin ) ,
799
- // https://example.com/tenant1/.well-known/openid-configuration
800
- new URL ( buildWellKnownPath ( 'openid-configuration' , url . pathname , { prependPathname : true } ) , `${ url . origin } ` ) ,
801
- ]
802
- : [
803
- // https://example.com/.well-known/openid-configuration
804
- new URL ( buildWellKnownPath ( 'openid-configuration' ) , url . origin ) ,
805
- ] ;
806
-
807
- for ( const endpoint of potentialMetadataEndpoints ) {
808
- const response = await fetchWithCorsRetry ( endpoint , getProtocolVersionHeader ( protocolVersion ) , fetchFn ) ;
809
-
736
+ protocolVersion ?: string ;
737
+ } = { }
738
+ ) : Promise < AuthorizationServerMetadata | undefined > {
739
+ const headers = { 'MCP-Protocol-Version' : protocolVersion } ;
740
+
741
+ // Get the list of URLs to try
742
+ const urlsToTry = buildDiscoveryUrls ( authorizationServerUrl ) ;
743
+
744
+ // Try each URL in order
745
+ for ( const { url : endpointUrl , type } of urlsToTry ) {
746
+ const response = await fetchWithCorsRetry ( endpointUrl , headers , fetchFn ) ;
747
+
810
748
if ( ! response ) {
811
- throw new Error ( `CORS error trying to load OpenID provider metadata from ${ endpoint } ` ) ;
749
+ throw new Error ( `CORS error trying to load ${ type === 'oauth' ? 'OAuth' : ' OpenID provider' } metadata from ${ endpointUrl } ` ) ;
812
750
}
813
-
751
+
814
752
if ( ! response . ok ) {
815
753
if ( response . status === 404 ) {
816
- continue ;
754
+ continue ; // Try next URL
817
755
}
818
-
819
- throw new Error ( `HTTP ${ response . status } trying to load OpenID provider metadata from ${ endpoint } ` ) ;
756
+ throw new Error ( `HTTP ${ response . status } trying to load ${ type === 'oauth' ? 'OAuth' : 'OpenID provider' } metadata from ${ endpointUrl } ` ) ;
820
757
}
821
-
822
- const metadata = OpenIdProviderDiscoveryMetadataSchema . parse ( await response . json ( ) ) ;
823
-
824
- // MCP spec requires OIDC providers to support S256 PKCE
825
- if ( ! metadata . code_challenge_methods_supported ?. includes ( 'S256' ) ) {
826
- throw new Error (
827
- `Incompatible OIDC provider at ${ endpoint } : does not support S256 code challenge method required by MCP specification`
828
- ) ;
758
+
759
+ // Parse and validate based on type
760
+ if ( type === 'oauth' ) {
761
+ return OAuthMetadataSchema . parse ( await response . json ( ) ) ;
762
+ } else {
763
+ const metadata = OpenIdProviderDiscoveryMetadataSchema . parse ( await response . json ( ) ) ;
764
+
765
+ // MCP spec requires OIDC providers to support S256 PKCE
766
+ if ( ! metadata . code_challenge_methods_supported ?. includes ( 'S256' ) ) {
767
+ throw new Error (
768
+ `Incompatible OIDC provider at ${ endpointUrl } : does not support S256 code challenge method required by MCP specification`
769
+ ) ;
770
+ }
771
+
772
+ return metadata ;
829
773
}
830
-
831
- return metadata ;
832
774
}
833
-
775
+
834
776
return undefined ;
835
777
}
836
778
0 commit comments