@@ -1045,5 +1045,156 @@ describe("OAuth Authorization", () => {
10451045 "https://different-resource.example.com/mcp-server"
10461046 ) ;
10471047 } ) ;
1048+
1049+ describe ( "delegateAuthorization" , ( ) => {
1050+ const validMetadata = {
1051+ issuer : "https://auth.example.com" ,
1052+ authorization_endpoint : "https://auth.example.com/authorize" ,
1053+ token_endpoint : "https://auth.example.com/token" ,
1054+ registration_endpoint : "https://auth.example.com/register" ,
1055+ response_types_supported : [ "code" ] ,
1056+ code_challenge_methods_supported : [ "S256" ] ,
1057+ } ;
1058+
1059+ const validClientInfo = {
1060+ client_id : "client123" ,
1061+ client_secret : "secret123" ,
1062+ redirect_uris : [ "http://localhost:3000/callback" ] ,
1063+ client_name : "Test Client" ,
1064+ } ;
1065+
1066+ const validTokens = {
1067+ access_token : "access123" ,
1068+ token_type : "Bearer" ,
1069+ expires_in : 3600 ,
1070+ refresh_token : "refresh123" ,
1071+ } ;
1072+
1073+ // Setup shared mock function for all tests
1074+ beforeEach ( ( ) => {
1075+ // Reset mockFetch implementation
1076+ mockFetch . mockReset ( ) ;
1077+
1078+ // Set up the mockFetch to respond to all necessary API calls
1079+ mockFetch . mockImplementation ( ( url ) => {
1080+ const urlString = url . toString ( ) ;
1081+
1082+ if ( urlString . includes ( "/.well-known/oauth-protected-resource" ) ) {
1083+ return Promise . resolve ( {
1084+ ok : false ,
1085+ status : 404
1086+ } ) ;
1087+ } else if ( urlString . includes ( "/.well-known/oauth-authorization-server" ) ) {
1088+ return Promise . resolve ( {
1089+ ok : true ,
1090+ status : 200 ,
1091+ json : async ( ) => validMetadata
1092+ } ) ;
1093+ } else if ( urlString . includes ( "/token" ) ) {
1094+ return Promise . resolve ( {
1095+ ok : true ,
1096+ status : 200 ,
1097+ json : async ( ) => validTokens
1098+ } ) ;
1099+ }
1100+
1101+ return Promise . reject ( new Error ( `Unexpected fetch call: ${ urlString } ` ) ) ;
1102+ } ) ;
1103+ } ) ;
1104+
1105+ it ( "should use delegateAuthorization when implemented and return AUTHORIZED" , async ( ) => {
1106+ const mockProvider : OAuthClientProvider = {
1107+ redirectUrl : "http://localhost:3000/callback" ,
1108+ clientMetadata : {
1109+ redirect_uris : [ "http://localhost:3000/callback" ] ,
1110+ client_name : "Test Client"
1111+ } ,
1112+ clientInformation : ( ) => validClientInfo ,
1113+ tokens : ( ) => validTokens ,
1114+ saveTokens : jest . fn ( ) ,
1115+ redirectToAuthorization : jest . fn ( ) ,
1116+ saveCodeVerifier : jest . fn ( ) ,
1117+ codeVerifier : ( ) => "test_verifier" ,
1118+ delegateAuthorization : jest . fn ( ) . mockResolvedValue ( "AUTHORIZED" )
1119+ } ;
1120+
1121+ const result = await auth ( mockProvider , { serverUrl : "https://auth.example.com" } ) ;
1122+
1123+ expect ( result ) . toBe ( "AUTHORIZED" ) ;
1124+ expect ( mockProvider . delegateAuthorization ) . toHaveBeenCalledWith (
1125+ "https://auth.example.com" ,
1126+ expect . objectContaining ( validMetadata )
1127+ ) ;
1128+ expect ( mockProvider . redirectToAuthorization ) . not . toHaveBeenCalled ( ) ;
1129+ } ) ;
1130+
1131+ it ( "should fall back to standard flow when delegateAuthorization returns undefined" , async ( ) => {
1132+ const mockProvider : OAuthClientProvider = {
1133+ redirectUrl : "http://localhost:3000/callback" ,
1134+ clientMetadata : {
1135+ redirect_uris : [ "http://localhost:3000/callback" ] ,
1136+ client_name : "Test Client"
1137+ } ,
1138+ clientInformation : ( ) => validClientInfo ,
1139+ tokens : ( ) => validTokens ,
1140+ saveTokens : jest . fn ( ) ,
1141+ redirectToAuthorization : jest . fn ( ) ,
1142+ saveCodeVerifier : jest . fn ( ) ,
1143+ codeVerifier : ( ) => "test_verifier" ,
1144+ delegateAuthorization : jest . fn ( ) . mockResolvedValue ( undefined )
1145+ } ;
1146+
1147+ const result = await auth ( mockProvider , { serverUrl : "https://auth.example.com" } ) ;
1148+
1149+ expect ( result ) . toBe ( "AUTHORIZED" ) ;
1150+ expect ( mockProvider . delegateAuthorization ) . toHaveBeenCalled ( ) ;
1151+ expect ( mockProvider . saveTokens ) . toHaveBeenCalled ( ) ;
1152+ } ) ;
1153+
1154+ it ( "should not call delegateAuthorization when processing authorizationCode" , async ( ) => {
1155+ const mockProvider : OAuthClientProvider = {
1156+ redirectUrl : "http://localhost:3000/callback" ,
1157+ clientMetadata : {
1158+ redirect_uris : [ "http://localhost:3000/callback" ] ,
1159+ client_name : "Test Client"
1160+ } ,
1161+ clientInformation : ( ) => validClientInfo ,
1162+ tokens : jest . fn ( ) ,
1163+ saveTokens : jest . fn ( ) ,
1164+ redirectToAuthorization : jest . fn ( ) ,
1165+ saveCodeVerifier : jest . fn ( ) ,
1166+ codeVerifier : ( ) => "test_verifier" ,
1167+ delegateAuthorization : jest . fn ( )
1168+ } ;
1169+
1170+ await auth ( mockProvider , {
1171+ serverUrl : "https://auth.example.com" ,
1172+ authorizationCode : "code123"
1173+ } ) ;
1174+
1175+ expect ( mockProvider . delegateAuthorization ) . not . toHaveBeenCalled ( ) ;
1176+ expect ( mockProvider . saveTokens ) . toHaveBeenCalled ( ) ;
1177+ } ) ;
1178+
1179+ it ( "should propagate errors from delegateAuthorization" , async ( ) => {
1180+ const mockProvider : OAuthClientProvider = {
1181+ redirectUrl : "http://localhost:3000/callback" ,
1182+ clientMetadata : {
1183+ redirect_uris : [ "http://localhost:3000/callback" ] ,
1184+ client_name : "Test Client"
1185+ } ,
1186+ clientInformation : ( ) => validClientInfo ,
1187+ tokens : jest . fn ( ) ,
1188+ saveTokens : jest . fn ( ) ,
1189+ redirectToAuthorization : jest . fn ( ) ,
1190+ saveCodeVerifier : jest . fn ( ) ,
1191+ codeVerifier : ( ) => "test_verifier" ,
1192+ delegateAuthorization : jest . fn ( ) . mockRejectedValue ( new Error ( "Delegation failed" ) )
1193+ } ;
1194+
1195+ await expect ( auth ( mockProvider , { serverUrl : "https://auth.example.com" } ) )
1196+ . rejects . toThrow ( "Delegation failed" ) ;
1197+ } ) ;
1198+ } ) ;
10481199 } ) ;
10491200} ) ;
0 commit comments