@mcp-use/client 2.1.0 → 2.1.1-canary.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/.tsbuildinfo +1 -1
- package/dist/auth/browser.d.ts +8 -0
- package/dist/auth/browser.d.ts.map +1 -1
- package/dist/auth/flow.d.ts +9 -0
- package/dist/auth/flow.d.ts.map +1 -1
- package/dist/core/browser.d.ts.map +1 -1
- package/dist/core/config.d.ts +6 -0
- package/dist/core/config.d.ts.map +1 -1
- package/dist/core/session.d.ts +19 -0
- package/dist/core/session.d.ts.map +1 -1
- package/dist/index-browser.d.ts +1 -1
- package/dist/index-browser.d.ts.map +1 -1
- package/dist/index-browser.js +286 -41
- package/dist/index-browser.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +651 -407
- package/dist/index.js.map +1 -1
- package/dist/react/McpClientProvider.d.ts.map +1 -1
- package/dist/react/index.d.ts +1 -0
- package/dist/react/index.d.ts.map +1 -1
- package/dist/react/index.js +488 -179
- package/dist/react/index.js.map +1 -1
- package/dist/react/types.d.ts +10 -1
- package/dist/react/types.d.ts.map +1 -1
- package/dist/react/useMcp-operations.d.ts +1 -0
- package/dist/react/useMcp-operations.d.ts.map +1 -1
- package/dist/react/useMcp.d.ts.map +1 -1
- package/dist/transport/base.d.ts +16 -1
- package/dist/transport/base.d.ts.map +1 -1
- package/dist/transport/http.d.ts +14 -0
- package/dist/transport/http.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/react/index.js
CHANGED
|
@@ -270,6 +270,7 @@ var PERSISTED_SERVER_CONFIG_KEYS = [
|
|
|
270
270
|
"reconnectionOptions",
|
|
271
271
|
"popupFeatures",
|
|
272
272
|
"preventAutoAuth",
|
|
273
|
+
"detectMixedAuth",
|
|
273
274
|
"useRedirectFlow",
|
|
274
275
|
"protocolNegotiation",
|
|
275
276
|
"timeout",
|
|
@@ -449,11 +450,136 @@ import { useCallback as useCallback2, useEffect, useMemo, useRef, useState } fro
|
|
|
449
450
|
// src/transport/http.ts
|
|
450
451
|
import {
|
|
451
452
|
Client,
|
|
453
|
+
discoverOAuthProtectedResourceMetadata,
|
|
452
454
|
SdkError,
|
|
453
455
|
SdkHttpError,
|
|
454
456
|
StreamableHTTPClientTransport,
|
|
457
|
+
UnauthorizedError as UnauthorizedError2
|
|
458
|
+
} from "@modelcontextprotocol/client";
|
|
459
|
+
|
|
460
|
+
// src/auth/flow.ts
|
|
461
|
+
import {
|
|
462
|
+
auth,
|
|
463
|
+
InsufficientScopeError,
|
|
455
464
|
UnauthorizedError
|
|
456
465
|
} from "@modelcontextprotocol/client";
|
|
466
|
+
var DEFAULT_AUTH_TIMEOUT_MS = 5 * 6e4;
|
|
467
|
+
function isUnauthorized(err, depth = 0) {
|
|
468
|
+
if (!err || depth > 5) return false;
|
|
469
|
+
if (err instanceof UnauthorizedError) return true;
|
|
470
|
+
if (err instanceof Error) {
|
|
471
|
+
const code = err.code;
|
|
472
|
+
if (code === 401) return true;
|
|
473
|
+
if (err.name === "UnauthorizedError") return true;
|
|
474
|
+
const message = err.message ?? "";
|
|
475
|
+
if (message.includes("401") || message.includes("Unauthorized")) {
|
|
476
|
+
return true;
|
|
477
|
+
}
|
|
478
|
+
if (err.cause && isUnauthorized(err.cause, depth + 1)) return true;
|
|
479
|
+
const data = err.data;
|
|
480
|
+
if (data?.cause && isUnauthorized(data.cause, depth + 1)) return true;
|
|
481
|
+
}
|
|
482
|
+
return false;
|
|
483
|
+
}
|
|
484
|
+
function isOAuthInteractionRequired(err, depth = 0) {
|
|
485
|
+
if (!err || depth > 5) return false;
|
|
486
|
+
if (err instanceof InsufficientScopeError || err instanceof UnauthorizedError) {
|
|
487
|
+
return true;
|
|
488
|
+
}
|
|
489
|
+
if (err instanceof Error) {
|
|
490
|
+
if (err.name === "InsufficientScopeError" || err.name === "UnauthorizedError") {
|
|
491
|
+
return true;
|
|
492
|
+
}
|
|
493
|
+
if (err.cause && isOAuthInteractionRequired(err.cause, depth + 1)) {
|
|
494
|
+
return true;
|
|
495
|
+
}
|
|
496
|
+
const data = err.data;
|
|
497
|
+
if (data?.cause && isOAuthInteractionRequired(data.cause, depth + 1)) {
|
|
498
|
+
return true;
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
return false;
|
|
502
|
+
}
|
|
503
|
+
async function completeOAuthFlow(provider, serverUrl, options = {}) {
|
|
504
|
+
const flowProvider = provider;
|
|
505
|
+
const timeoutMs = options.timeoutMs ?? DEFAULT_AUTH_TIMEOUT_MS;
|
|
506
|
+
const fetchFn = options.fetchFn ?? flowProvider.getProxyFetch?.() ?? void 0;
|
|
507
|
+
if (!flowProvider.hasPendingFlow) {
|
|
508
|
+
const result = await auth(provider, { serverUrl, fetchFn });
|
|
509
|
+
if (result === "AUTHORIZED") return;
|
|
510
|
+
if (result !== "REDIRECT") {
|
|
511
|
+
throw new Error(`Unexpected OAuth auth() result: ${result}`);
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
if (flowProvider.preventAutoAuth === true && typeof flowProvider.startAuthorization === "function") {
|
|
515
|
+
flowProvider.startAuthorization();
|
|
516
|
+
}
|
|
517
|
+
if (typeof flowProvider.getAuthorizationResponse === "function" || typeof flowProvider.getAuthorizationCode === "function") {
|
|
518
|
+
const response = typeof flowProvider.getAuthorizationResponse === "function" ? await flowProvider.getAuthorizationResponse() : { code: await flowProvider.getAuthorizationCode() };
|
|
519
|
+
if (options.finishAuthorization) {
|
|
520
|
+
await options.finishAuthorization(response.code, response.iss);
|
|
521
|
+
} else {
|
|
522
|
+
await auth(provider, {
|
|
523
|
+
serverUrl,
|
|
524
|
+
authorizationCode: response.code,
|
|
525
|
+
...response.iss !== void 0 ? { iss: response.iss } : {},
|
|
526
|
+
fetchFn
|
|
527
|
+
});
|
|
528
|
+
}
|
|
529
|
+
return;
|
|
530
|
+
}
|
|
531
|
+
await waitForBrowserAuthComplete(flowProvider, timeoutMs);
|
|
532
|
+
}
|
|
533
|
+
async function waitForBrowserAuthComplete(provider, timeoutMs) {
|
|
534
|
+
if (typeof window === "undefined") {
|
|
535
|
+
throw new Error(
|
|
536
|
+
"OAuth redirect requires a browser environment or a provider with getAuthorizationCode()"
|
|
537
|
+
);
|
|
538
|
+
}
|
|
539
|
+
if (provider.useRedirectFlow) {
|
|
540
|
+
await new Promise(() => {
|
|
541
|
+
});
|
|
542
|
+
return;
|
|
543
|
+
}
|
|
544
|
+
const tokensKey = provider.getKey?.("tokens");
|
|
545
|
+
if (!tokensKey) {
|
|
546
|
+
throw new Error(
|
|
547
|
+
"Browser OAuth provider must expose getKey() for token storage"
|
|
548
|
+
);
|
|
549
|
+
}
|
|
550
|
+
let state = null;
|
|
551
|
+
const authUrl = provider.getLastAttemptedAuthUrl?.();
|
|
552
|
+
if (authUrl) {
|
|
553
|
+
try {
|
|
554
|
+
state = new URL(authUrl).searchParams.get("state");
|
|
555
|
+
} catch {
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
try {
|
|
559
|
+
const result = await runAuthPopup({
|
|
560
|
+
popup: null,
|
|
561
|
+
state,
|
|
562
|
+
tokensKey,
|
|
563
|
+
timeoutMs
|
|
564
|
+
});
|
|
565
|
+
switch (result.kind) {
|
|
566
|
+
case "success":
|
|
567
|
+
return;
|
|
568
|
+
case "cancelled":
|
|
569
|
+
throw new Error("OAuth authentication was cancelled.");
|
|
570
|
+
case "timeout":
|
|
571
|
+
throw new Error(
|
|
572
|
+
`OAuth callback not received within ${timeoutMs}ms. Ensure /oauth/callback calls onMcpAuthorization().`
|
|
573
|
+
);
|
|
574
|
+
case "error":
|
|
575
|
+
throw new Error(result.error);
|
|
576
|
+
default:
|
|
577
|
+
throw new Error("Unexpected OAuth popup result");
|
|
578
|
+
}
|
|
579
|
+
} finally {
|
|
580
|
+
provider.markFlowComplete?.();
|
|
581
|
+
}
|
|
582
|
+
}
|
|
457
583
|
|
|
458
584
|
// src/utils/json-schema-validator.ts
|
|
459
585
|
import {
|
|
@@ -517,6 +643,7 @@ var BaseConnector = class {
|
|
|
517
643
|
toolsCache = null;
|
|
518
644
|
capabilitiesCache = null;
|
|
519
645
|
serverInfoCache = null;
|
|
646
|
+
authorizationCache;
|
|
520
647
|
connected = false;
|
|
521
648
|
opts;
|
|
522
649
|
notificationHandlers = [];
|
|
@@ -780,6 +907,28 @@ var BaseConnector = class {
|
|
|
780
907
|
"setupElicitationHandler: Elicitation handler registered successfully"
|
|
781
908
|
);
|
|
782
909
|
}
|
|
910
|
+
/**
|
|
911
|
+
* Run one logical MCP operation. HTTP connectors override this host seam to
|
|
912
|
+
* finish an SDK-started interactive OAuth flow and retry exactly once.
|
|
913
|
+
*/
|
|
914
|
+
async executeRequest(operation) {
|
|
915
|
+
return operation();
|
|
916
|
+
}
|
|
917
|
+
/** OAuth state discovered for the active connection, when available. */
|
|
918
|
+
get authorization() {
|
|
919
|
+
return this.authorizationCache;
|
|
920
|
+
}
|
|
921
|
+
/**
|
|
922
|
+
* Discover optional authorization metadata without delaying connection
|
|
923
|
+
* readiness. HTTP connectors override this with RFC 9728 discovery.
|
|
924
|
+
*/
|
|
925
|
+
async discoverAuthorization() {
|
|
926
|
+
return this.authorization;
|
|
927
|
+
}
|
|
928
|
+
/** Start optional OAuth for a connected mixed-auth server. */
|
|
929
|
+
async authenticate() {
|
|
930
|
+
throw new Error("This connector does not support interactive OAuth");
|
|
931
|
+
}
|
|
783
932
|
/**
|
|
784
933
|
* Disconnects the SDK client and releases transport resources.
|
|
785
934
|
*
|
|
@@ -827,13 +976,13 @@ var BaseConnector = class {
|
|
|
827
976
|
icons: serverInfo.icons
|
|
828
977
|
} : null;
|
|
829
978
|
try {
|
|
830
|
-
const listToolsRes = await this.
|
|
831
|
-
void 0,
|
|
832
|
-
defaultRequestOptions
|
|
979
|
+
const listToolsRes = await this.executeRequest(
|
|
980
|
+
() => this.client.listTools(void 0, defaultRequestOptions)
|
|
833
981
|
);
|
|
834
982
|
this.toolsCache = listToolsRes.tools ?? [];
|
|
835
983
|
logger.debug(`Fetched ${this.toolsCache.length} tools from server`);
|
|
836
984
|
} catch (err) {
|
|
985
|
+
if (isOAuthInteractionRequired(err)) throw err;
|
|
837
986
|
const error = err;
|
|
838
987
|
if (error.code === -32601) {
|
|
839
988
|
logger.debug("Server does not implement tools/list, assuming no tools");
|
|
@@ -907,9 +1056,8 @@ var BaseConnector = class {
|
|
|
907
1056
|
const progressHandler = enhancedOptions?.onprogress;
|
|
908
1057
|
if (progressHandler) this.activeProgressHandlers.add(progressHandler);
|
|
909
1058
|
try {
|
|
910
|
-
const res = await this.
|
|
911
|
-
{ name, arguments: args },
|
|
912
|
-
enhancedOptions
|
|
1059
|
+
const res = await this.executeRequest(
|
|
1060
|
+
() => this.client.callTool({ name, arguments: args }, enhancedOptions)
|
|
913
1061
|
);
|
|
914
1062
|
logger.debug(`Tool '${name}' returned`, res);
|
|
915
1063
|
return res;
|
|
@@ -929,7 +1077,9 @@ var BaseConnector = class {
|
|
|
929
1077
|
throw new Error("MCP client is not connected");
|
|
930
1078
|
}
|
|
931
1079
|
logger.debug("[listTools] Fetching fresh tools from server...");
|
|
932
|
-
const result = await this.
|
|
1080
|
+
const result = await this.executeRequest(
|
|
1081
|
+
() => this.client.listTools(void 0, options)
|
|
1082
|
+
);
|
|
933
1083
|
const tools = result.tools ? [...result.tools] : [];
|
|
934
1084
|
logger.debug(
|
|
935
1085
|
`[listTools] Returned ${tools.length} tools:`,
|
|
@@ -949,7 +1099,9 @@ var BaseConnector = class {
|
|
|
949
1099
|
throw new Error("MCP client is not connected");
|
|
950
1100
|
}
|
|
951
1101
|
logger.debug("Listing resources", cursor ? `with cursor: ${cursor}` : "");
|
|
952
|
-
return await this.
|
|
1102
|
+
return await this.executeRequest(
|
|
1103
|
+
() => this.client.listResources({ cursor }, options)
|
|
1104
|
+
);
|
|
953
1105
|
}
|
|
954
1106
|
/**
|
|
955
1107
|
* List all resources from the server, automatically handling pagination
|
|
@@ -967,14 +1119,16 @@ var BaseConnector = class {
|
|
|
967
1119
|
}
|
|
968
1120
|
try {
|
|
969
1121
|
logger.debug("Listing all resources (with auto-pagination)");
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
1122
|
+
return await this.executeRequest(async () => {
|
|
1123
|
+
const allResources = [];
|
|
1124
|
+
let cursor = void 0;
|
|
1125
|
+
do {
|
|
1126
|
+
const result = await this.client.listResources({ cursor }, options);
|
|
1127
|
+
allResources.push(...result.resources || []);
|
|
1128
|
+
cursor = result.nextCursor;
|
|
1129
|
+
} while (cursor);
|
|
1130
|
+
return { resources: allResources };
|
|
1131
|
+
});
|
|
978
1132
|
} catch (err) {
|
|
979
1133
|
const error = err;
|
|
980
1134
|
if (error.code === -32601) {
|
|
@@ -995,7 +1149,9 @@ var BaseConnector = class {
|
|
|
995
1149
|
throw new Error("MCP client is not connected");
|
|
996
1150
|
}
|
|
997
1151
|
logger.debug("Listing resource templates");
|
|
998
|
-
return await this.
|
|
1152
|
+
return await this.executeRequest(
|
|
1153
|
+
() => this.client.listResourceTemplates(void 0, options)
|
|
1154
|
+
);
|
|
999
1155
|
}
|
|
1000
1156
|
/**
|
|
1001
1157
|
* Request completion suggestions for a prompt or resource template argument
|
|
@@ -1009,7 +1165,9 @@ var BaseConnector = class {
|
|
|
1009
1165
|
throw new Error("MCP client is not connected");
|
|
1010
1166
|
}
|
|
1011
1167
|
logger.debug("[complete] Requesting completions for:", params.ref);
|
|
1012
|
-
const result = await this.
|
|
1168
|
+
const result = await this.executeRequest(
|
|
1169
|
+
() => this.client.complete(params, options)
|
|
1170
|
+
);
|
|
1013
1171
|
logger.debug(
|
|
1014
1172
|
`[complete] Received ${result.completion.values.length} suggestions`
|
|
1015
1173
|
);
|
|
@@ -1027,7 +1185,9 @@ var BaseConnector = class {
|
|
|
1027
1185
|
throw new Error("MCP client is not connected");
|
|
1028
1186
|
}
|
|
1029
1187
|
logger.debug(`Reading resource ${uri}`);
|
|
1030
|
-
const res = await this.
|
|
1188
|
+
const res = await this.executeRequest(
|
|
1189
|
+
() => this.client.readResource({ uri }, options)
|
|
1190
|
+
);
|
|
1031
1191
|
return res;
|
|
1032
1192
|
}
|
|
1033
1193
|
/**
|
|
@@ -1041,7 +1201,9 @@ var BaseConnector = class {
|
|
|
1041
1201
|
throw new Error("MCP client is not connected");
|
|
1042
1202
|
}
|
|
1043
1203
|
logger.debug(`Subscribing to resource: ${uri}`);
|
|
1044
|
-
return await this.
|
|
1204
|
+
return await this.executeRequest(
|
|
1205
|
+
() => this.client.subscribeResource({ uri }, options)
|
|
1206
|
+
);
|
|
1045
1207
|
}
|
|
1046
1208
|
/**
|
|
1047
1209
|
* Unsubscribe from resource updates
|
|
@@ -1054,7 +1216,9 @@ var BaseConnector = class {
|
|
|
1054
1216
|
throw new Error("MCP client is not connected");
|
|
1055
1217
|
}
|
|
1056
1218
|
logger.debug(`Unsubscribing from resource: ${uri}`);
|
|
1057
|
-
return await this.
|
|
1219
|
+
return await this.executeRequest(
|
|
1220
|
+
() => this.client.unsubscribeResource({ uri }, options)
|
|
1221
|
+
);
|
|
1058
1222
|
}
|
|
1059
1223
|
/**
|
|
1060
1224
|
* Lists prompts exposed by the server.
|
|
@@ -1071,7 +1235,7 @@ var BaseConnector = class {
|
|
|
1071
1235
|
}
|
|
1072
1236
|
try {
|
|
1073
1237
|
logger.debug("Listing prompts");
|
|
1074
|
-
return await this.client.listPrompts();
|
|
1238
|
+
return await this.executeRequest(() => this.client.listPrompts());
|
|
1075
1239
|
} catch (err) {
|
|
1076
1240
|
const error = err;
|
|
1077
1241
|
if (error.code === -32601) {
|
|
@@ -1093,7 +1257,9 @@ var BaseConnector = class {
|
|
|
1093
1257
|
throw new Error("MCP client is not connected");
|
|
1094
1258
|
}
|
|
1095
1259
|
logger.debug(`Getting prompt ${name}`);
|
|
1096
|
-
return await this.
|
|
1260
|
+
return await this.executeRequest(
|
|
1261
|
+
() => this.client.getPrompt({ name, arguments: args })
|
|
1262
|
+
);
|
|
1097
1263
|
}
|
|
1098
1264
|
/**
|
|
1099
1265
|
* Sends a raw, potentially non-standard request through the SDK client.
|
|
@@ -1108,10 +1274,12 @@ var BaseConnector = class {
|
|
|
1108
1274
|
throw new Error("MCP client is not connected");
|
|
1109
1275
|
}
|
|
1110
1276
|
logger.debug(`Sending raw request '${method}' with params`, params);
|
|
1111
|
-
return await this.
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1277
|
+
return await this.executeRequest(
|
|
1278
|
+
() => this.client.request(
|
|
1279
|
+
{ method, params: params ?? {} },
|
|
1280
|
+
passthroughResultSchema,
|
|
1281
|
+
options
|
|
1282
|
+
)
|
|
1115
1283
|
);
|
|
1116
1284
|
}
|
|
1117
1285
|
/**
|
|
@@ -1144,6 +1312,7 @@ var BaseConnector = class {
|
|
|
1144
1312
|
}
|
|
1145
1313
|
}
|
|
1146
1314
|
this.toolsCache = null;
|
|
1315
|
+
this.authorizationCache = void 0;
|
|
1147
1316
|
if (issues.length) {
|
|
1148
1317
|
logger.warn(`Resource cleanup finished with ${issues.length} issue(s)`);
|
|
1149
1318
|
}
|
|
@@ -1151,9 +1320,10 @@ var BaseConnector = class {
|
|
|
1151
1320
|
};
|
|
1152
1321
|
|
|
1153
1322
|
// src/transport/http.ts
|
|
1323
|
+
var MIXED_AUTH_DISCOVERY_TIMEOUT_MS = 2e3;
|
|
1154
1324
|
function detectUnauthorized(err, depth = 0) {
|
|
1155
1325
|
if (!err || depth > 5) return false;
|
|
1156
|
-
if (err instanceof
|
|
1326
|
+
if (err instanceof UnauthorizedError2) return true;
|
|
1157
1327
|
if (err instanceof SdkHttpError && err.status === 401) return true;
|
|
1158
1328
|
if (err instanceof Error) {
|
|
1159
1329
|
if (err.cause) {
|
|
@@ -1164,6 +1334,11 @@ function detectUnauthorized(err, depth = 0) {
|
|
|
1164
1334
|
}
|
|
1165
1335
|
return false;
|
|
1166
1336
|
}
|
|
1337
|
+
function isOAuthClientProvider(provider) {
|
|
1338
|
+
return Boolean(
|
|
1339
|
+
provider && "redirectToAuthorization" in provider && typeof provider.redirectToAuthorization === "function" && "tokens" in provider && typeof provider.tokens === "function"
|
|
1340
|
+
);
|
|
1341
|
+
}
|
|
1167
1342
|
function createMcpProxyFetch(logicalServerUrl, proxyUrl, baseFetch, serverId) {
|
|
1168
1343
|
const logical = new URL(logicalServerUrl);
|
|
1169
1344
|
const proxy = proxyUrl.replace(/\/$/, "");
|
|
@@ -1189,6 +1364,31 @@ function createMcpProxyFetch(logicalServerUrl, proxyUrl, baseFetch, serverId) {
|
|
|
1189
1364
|
);
|
|
1190
1365
|
};
|
|
1191
1366
|
}
|
|
1367
|
+
function createDeadlineFetch(baseFetch, deadlineSignal) {
|
|
1368
|
+
return async (input, init) => {
|
|
1369
|
+
const requestSignal = init?.signal;
|
|
1370
|
+
if (!requestSignal) {
|
|
1371
|
+
return baseFetch(input, { ...init, signal: deadlineSignal });
|
|
1372
|
+
}
|
|
1373
|
+
const controller = new AbortController();
|
|
1374
|
+
const abortFromRequest = () => controller.abort(requestSignal.reason);
|
|
1375
|
+
const abortFromDeadline = () => controller.abort(deadlineSignal.reason);
|
|
1376
|
+
if (requestSignal.aborted) abortFromRequest();
|
|
1377
|
+
else
|
|
1378
|
+
requestSignal.addEventListener("abort", abortFromRequest, { once: true });
|
|
1379
|
+
if (deadlineSignal.aborted) abortFromDeadline();
|
|
1380
|
+
else
|
|
1381
|
+
deadlineSignal.addEventListener("abort", abortFromDeadline, {
|
|
1382
|
+
once: true
|
|
1383
|
+
});
|
|
1384
|
+
try {
|
|
1385
|
+
return await baseFetch(input, { ...init, signal: controller.signal });
|
|
1386
|
+
} finally {
|
|
1387
|
+
requestSignal.removeEventListener("abort", abortFromRequest);
|
|
1388
|
+
deadlineSignal.removeEventListener("abort", abortFromDeadline);
|
|
1389
|
+
}
|
|
1390
|
+
};
|
|
1391
|
+
}
|
|
1192
1392
|
var HttpConnector = class extends BaseConnector {
|
|
1193
1393
|
baseUrl;
|
|
1194
1394
|
headers;
|
|
@@ -1199,8 +1399,12 @@ var HttpConnector = class extends BaseConnector {
|
|
|
1199
1399
|
gatewayUrl;
|
|
1200
1400
|
serverId;
|
|
1201
1401
|
reconnectionOptions;
|
|
1402
|
+
detectMixedAuth;
|
|
1202
1403
|
transportType = null;
|
|
1203
1404
|
streamableTransport = null;
|
|
1405
|
+
hadAccessTokenAtConnect = false;
|
|
1406
|
+
pendingOAuthCompletion = null;
|
|
1407
|
+
authorizationDiscovery = null;
|
|
1204
1408
|
/**
|
|
1205
1409
|
* Creates an HTTP connector.
|
|
1206
1410
|
*
|
|
@@ -1231,6 +1435,103 @@ var HttpConnector = class extends BaseConnector {
|
|
|
1231
1435
|
};
|
|
1232
1436
|
this.protocolNegotiation = opts.protocolNegotiation ?? "auto";
|
|
1233
1437
|
this.reconnectionOptions = opts.reconnectionOptions;
|
|
1438
|
+
this.detectMixedAuth = opts.detectMixedAuth ?? true;
|
|
1439
|
+
}
|
|
1440
|
+
get oauthProvider() {
|
|
1441
|
+
return isOAuthClientProvider(this.opts.authProvider) ? this.opts.authProvider : void 0;
|
|
1442
|
+
}
|
|
1443
|
+
async completeInteractiveAuthorization() {
|
|
1444
|
+
const provider = this.oauthProvider;
|
|
1445
|
+
if (!provider) {
|
|
1446
|
+
throw new Error("No OAuth client provider is configured");
|
|
1447
|
+
}
|
|
1448
|
+
if (!this.pendingOAuthCompletion) {
|
|
1449
|
+
this.pendingOAuthCompletion = completeOAuthFlow(provider, this.baseUrl, {
|
|
1450
|
+
fetchFn: this.customFetch,
|
|
1451
|
+
finishAuthorization: async (code, iss) => {
|
|
1452
|
+
const transport = this.streamableTransport;
|
|
1453
|
+
if (!transport) {
|
|
1454
|
+
throw new Error("OAuth transport is no longer connected");
|
|
1455
|
+
}
|
|
1456
|
+
await transport.finishAuth(code, iss);
|
|
1457
|
+
}
|
|
1458
|
+
}).then(() => {
|
|
1459
|
+
if (this.authorizationCache) {
|
|
1460
|
+
this.authorizationCache = {
|
|
1461
|
+
...this.authorizationCache,
|
|
1462
|
+
authenticated: true
|
|
1463
|
+
};
|
|
1464
|
+
}
|
|
1465
|
+
}).finally(() => {
|
|
1466
|
+
this.pendingOAuthCompletion = null;
|
|
1467
|
+
});
|
|
1468
|
+
}
|
|
1469
|
+
await this.pendingOAuthCompletion;
|
|
1470
|
+
}
|
|
1471
|
+
async executeRequest(operation) {
|
|
1472
|
+
try {
|
|
1473
|
+
return await operation();
|
|
1474
|
+
} catch (error) {
|
|
1475
|
+
const provider = this.oauthProvider;
|
|
1476
|
+
if (!provider || provider.preventAutoAuth === true || !isOAuthInteractionRequired(error)) {
|
|
1477
|
+
throw error;
|
|
1478
|
+
}
|
|
1479
|
+
await this.completeInteractiveAuthorization();
|
|
1480
|
+
return operation();
|
|
1481
|
+
}
|
|
1482
|
+
}
|
|
1483
|
+
/** Authenticate an already-connected server without requiring a 401 first. */
|
|
1484
|
+
async authenticate() {
|
|
1485
|
+
if (!this.connected || !this.streamableTransport) {
|
|
1486
|
+
throw new Error("MCP client is not connected");
|
|
1487
|
+
}
|
|
1488
|
+
await this.completeInteractiveAuthorization();
|
|
1489
|
+
}
|
|
1490
|
+
async discoverAuthorization() {
|
|
1491
|
+
if (!this.detectMixedAuth || !this.oauthProvider || this.hadAccessTokenAtConnect) {
|
|
1492
|
+
return this.authorizationCache;
|
|
1493
|
+
}
|
|
1494
|
+
if (this.authorizationDiscovery) return this.authorizationDiscovery;
|
|
1495
|
+
this.authorizationDiscovery = this.discoverMixedAuthorization();
|
|
1496
|
+
return this.authorizationDiscovery;
|
|
1497
|
+
}
|
|
1498
|
+
async discoverMixedAuthorization() {
|
|
1499
|
+
const controller = new AbortController();
|
|
1500
|
+
let timeout;
|
|
1501
|
+
const discoveryTimeout = new Promise((_, reject) => {
|
|
1502
|
+
timeout = setTimeout(() => {
|
|
1503
|
+
const error = new Error(
|
|
1504
|
+
`Mixed-auth metadata discovery timed out after ${MIXED_AUTH_DISCOVERY_TIMEOUT_MS}ms`
|
|
1505
|
+
);
|
|
1506
|
+
controller.abort(error);
|
|
1507
|
+
reject(error);
|
|
1508
|
+
}, MIXED_AUTH_DISCOVERY_TIMEOUT_MS);
|
|
1509
|
+
});
|
|
1510
|
+
const baseFetch = this.customFetch ?? globalThis.fetch.bind(globalThis);
|
|
1511
|
+
try {
|
|
1512
|
+
const metadata = await Promise.race([
|
|
1513
|
+
discoverOAuthProtectedResourceMetadata(
|
|
1514
|
+
this.baseUrl,
|
|
1515
|
+
{ protocolVersion: this.negotiatedProtocolVersion },
|
|
1516
|
+
createDeadlineFetch(baseFetch, controller.signal)
|
|
1517
|
+
),
|
|
1518
|
+
discoveryTimeout
|
|
1519
|
+
]);
|
|
1520
|
+
this.authorizationCache = {
|
|
1521
|
+
mode: "mixed",
|
|
1522
|
+
authenticated: false,
|
|
1523
|
+
...metadata.resource ? { resource: metadata.resource } : {},
|
|
1524
|
+
...metadata.scopes_supported ? { scopesSupported: [...metadata.scopes_supported] } : {}
|
|
1525
|
+
};
|
|
1526
|
+
logger.info(
|
|
1527
|
+
"OAuth protected-resource metadata found after anonymous connection; server uses mixed auth"
|
|
1528
|
+
);
|
|
1529
|
+
} catch (error) {
|
|
1530
|
+
logger.debug("Mixed-auth metadata was not discovered:", error);
|
|
1531
|
+
} finally {
|
|
1532
|
+
if (timeout) clearTimeout(timeout);
|
|
1533
|
+
}
|
|
1534
|
+
return this.authorizationCache;
|
|
1234
1535
|
}
|
|
1235
1536
|
buildClientOptions() {
|
|
1236
1537
|
return {
|
|
@@ -1337,6 +1638,16 @@ var HttpConnector = class extends BaseConnector {
|
|
|
1337
1638
|
}
|
|
1338
1639
|
const baseUrl = this.baseUrl;
|
|
1339
1640
|
logger.debug(`Connecting to MCP implementation via HTTP: ${baseUrl}`);
|
|
1641
|
+
const oauthProvider = this.oauthProvider;
|
|
1642
|
+
if (oauthProvider) {
|
|
1643
|
+
try {
|
|
1644
|
+
this.hadAccessTokenAtConnect = Boolean(
|
|
1645
|
+
(await oauthProvider.tokens())?.access_token
|
|
1646
|
+
);
|
|
1647
|
+
} catch {
|
|
1648
|
+
this.hadAccessTokenAtConnect = false;
|
|
1649
|
+
}
|
|
1650
|
+
}
|
|
1340
1651
|
try {
|
|
1341
1652
|
await this.connectWithStreamableHttp(baseUrl);
|
|
1342
1653
|
logger.debug("\u2705 Successfully connected via streamable HTTP");
|
|
@@ -1607,11 +1918,12 @@ var HttpConnector = class extends BaseConnector {
|
|
|
1607
1918
|
}
|
|
1608
1919
|
}
|
|
1609
1920
|
await super.cleanupResources();
|
|
1921
|
+
this.authorizationDiscovery = null;
|
|
1610
1922
|
}
|
|
1611
1923
|
};
|
|
1612
1924
|
|
|
1613
1925
|
// src/utils/version.ts
|
|
1614
|
-
var VERSION = "2.1.
|
|
1926
|
+
var VERSION = "2.1.1-canary.1";
|
|
1615
1927
|
function getPackageVersion() {
|
|
1616
1928
|
return VERSION;
|
|
1617
1929
|
}
|
|
@@ -2577,29 +2889,43 @@ var BrowserOAuthClientProvider = class {
|
|
|
2577
2889
|
* @param authorizationUrl - The fully constructed authorization URL from the SDK.
|
|
2578
2890
|
*/
|
|
2579
2891
|
async redirectToAuthorization(authorizationUrl) {
|
|
2580
|
-
|
|
2892
|
+
await this.prepareAuthorizationUrl(authorizationUrl);
|
|
2581
2893
|
if (this.preventAutoAuth) {
|
|
2582
2894
|
console.info(
|
|
2583
2895
|
`[${this.storageKeyPrefix}] Auto-auth prevented. Authorization URL stored for manual trigger.`
|
|
2584
2896
|
);
|
|
2585
2897
|
return;
|
|
2586
2898
|
}
|
|
2899
|
+
this.startAuthorization();
|
|
2900
|
+
}
|
|
2901
|
+
/**
|
|
2902
|
+
* Open the authorization URL prepared by the official SDK.
|
|
2903
|
+
*
|
|
2904
|
+
* This is the explicit-user-action counterpart to `preventAutoAuth`: the
|
|
2905
|
+
* provider still lets the SDK own discovery and PKCE state, while a host can
|
|
2906
|
+
* launch the stored authorization request later from an Authenticate button.
|
|
2907
|
+
*/
|
|
2908
|
+
startAuthorization() {
|
|
2909
|
+
const authorizationUrl = this.lastAttemptedAuthUrl;
|
|
2910
|
+
if (!authorizationUrl) {
|
|
2911
|
+
throw new Error("No prepared OAuth authorization is available");
|
|
2912
|
+
}
|
|
2587
2913
|
if (this.useRedirectFlow) {
|
|
2588
2914
|
console.info(
|
|
2589
2915
|
`[${this.storageKeyPrefix}] Redirecting to authorization URL (full-page redirect).`
|
|
2590
2916
|
);
|
|
2591
|
-
window.location.href =
|
|
2917
|
+
window.location.href = authorizationUrl;
|
|
2592
2918
|
return;
|
|
2593
2919
|
}
|
|
2594
2920
|
const popupFeatures = "width=600,height=700,resizable=yes,scrollbars=yes,status=yes";
|
|
2595
2921
|
try {
|
|
2596
2922
|
const popup = window.open(
|
|
2597
|
-
|
|
2923
|
+
authorizationUrl,
|
|
2598
2924
|
`mcp_auth_${this.serverUrlHash}`,
|
|
2599
2925
|
popupFeatures
|
|
2600
2926
|
);
|
|
2601
2927
|
if (this.onPopupWindow) {
|
|
2602
|
-
this.onPopupWindow(
|
|
2928
|
+
this.onPopupWindow(authorizationUrl, popupFeatures, popup);
|
|
2603
2929
|
}
|
|
2604
2930
|
if (!popup || popup.closed || typeof popup.closed === "undefined") {
|
|
2605
2931
|
console.warn(
|
|
@@ -3148,103 +3474,6 @@ function setTelemetrySource(source) {
|
|
|
3148
3474
|
Tel.getInstance().setSource(source);
|
|
3149
3475
|
}
|
|
3150
3476
|
|
|
3151
|
-
// src/auth/flow.ts
|
|
3152
|
-
import {
|
|
3153
|
-
auth,
|
|
3154
|
-
UnauthorizedError as UnauthorizedError2
|
|
3155
|
-
} from "@modelcontextprotocol/client";
|
|
3156
|
-
var DEFAULT_AUTH_TIMEOUT_MS = 5 * 6e4;
|
|
3157
|
-
function isUnauthorized(err, depth = 0) {
|
|
3158
|
-
if (!err || depth > 5) return false;
|
|
3159
|
-
if (err instanceof UnauthorizedError2) return true;
|
|
3160
|
-
if (err instanceof Error) {
|
|
3161
|
-
const code = err.code;
|
|
3162
|
-
if (code === 401) return true;
|
|
3163
|
-
if (err.name === "UnauthorizedError") return true;
|
|
3164
|
-
const message = err.message ?? "";
|
|
3165
|
-
if (message.includes("401") || message.includes("Unauthorized")) {
|
|
3166
|
-
return true;
|
|
3167
|
-
}
|
|
3168
|
-
if (err.cause && isUnauthorized(err.cause, depth + 1)) return true;
|
|
3169
|
-
const data = err.data;
|
|
3170
|
-
if (data?.cause && isUnauthorized(data.cause, depth + 1)) return true;
|
|
3171
|
-
}
|
|
3172
|
-
return false;
|
|
3173
|
-
}
|
|
3174
|
-
async function completeOAuthFlow(provider, serverUrl, options = {}) {
|
|
3175
|
-
const flowProvider = provider;
|
|
3176
|
-
const timeoutMs = options.timeoutMs ?? DEFAULT_AUTH_TIMEOUT_MS;
|
|
3177
|
-
const fetchFn = options.fetchFn ?? flowProvider.getProxyFetch?.() ?? void 0;
|
|
3178
|
-
if (!flowProvider.hasPendingFlow) {
|
|
3179
|
-
const result = await auth(provider, { serverUrl, fetchFn });
|
|
3180
|
-
if (result === "AUTHORIZED") return;
|
|
3181
|
-
if (result !== "REDIRECT") {
|
|
3182
|
-
throw new Error(`Unexpected OAuth auth() result: ${result}`);
|
|
3183
|
-
}
|
|
3184
|
-
}
|
|
3185
|
-
if (typeof flowProvider.getAuthorizationResponse === "function" || typeof flowProvider.getAuthorizationCode === "function") {
|
|
3186
|
-
const response = typeof flowProvider.getAuthorizationResponse === "function" ? await flowProvider.getAuthorizationResponse() : { code: await flowProvider.getAuthorizationCode() };
|
|
3187
|
-
await auth(provider, {
|
|
3188
|
-
serverUrl,
|
|
3189
|
-
authorizationCode: response.code,
|
|
3190
|
-
...response.iss !== void 0 ? { iss: response.iss } : {},
|
|
3191
|
-
fetchFn
|
|
3192
|
-
});
|
|
3193
|
-
return;
|
|
3194
|
-
}
|
|
3195
|
-
await waitForBrowserAuthComplete(flowProvider, timeoutMs);
|
|
3196
|
-
}
|
|
3197
|
-
async function waitForBrowserAuthComplete(provider, timeoutMs) {
|
|
3198
|
-
if (typeof window === "undefined") {
|
|
3199
|
-
throw new Error(
|
|
3200
|
-
"OAuth redirect requires a browser environment or a provider with getAuthorizationCode()"
|
|
3201
|
-
);
|
|
3202
|
-
}
|
|
3203
|
-
if (provider.useRedirectFlow) {
|
|
3204
|
-
await new Promise(() => {
|
|
3205
|
-
});
|
|
3206
|
-
return;
|
|
3207
|
-
}
|
|
3208
|
-
const tokensKey = provider.getKey?.("tokens");
|
|
3209
|
-
if (!tokensKey) {
|
|
3210
|
-
throw new Error(
|
|
3211
|
-
"Browser OAuth provider must expose getKey() for token storage"
|
|
3212
|
-
);
|
|
3213
|
-
}
|
|
3214
|
-
let state = null;
|
|
3215
|
-
const authUrl = provider.getLastAttemptedAuthUrl?.();
|
|
3216
|
-
if (authUrl) {
|
|
3217
|
-
try {
|
|
3218
|
-
state = new URL(authUrl).searchParams.get("state");
|
|
3219
|
-
} catch {
|
|
3220
|
-
}
|
|
3221
|
-
}
|
|
3222
|
-
try {
|
|
3223
|
-
const result = await runAuthPopup({
|
|
3224
|
-
popup: null,
|
|
3225
|
-
state,
|
|
3226
|
-
tokensKey,
|
|
3227
|
-
timeoutMs
|
|
3228
|
-
});
|
|
3229
|
-
switch (result.kind) {
|
|
3230
|
-
case "success":
|
|
3231
|
-
return;
|
|
3232
|
-
case "cancelled":
|
|
3233
|
-
throw new Error("OAuth authentication was cancelled.");
|
|
3234
|
-
case "timeout":
|
|
3235
|
-
throw new Error(
|
|
3236
|
-
`OAuth callback not received within ${timeoutMs}ms. Ensure /oauth/callback calls onMcpAuthorization().`
|
|
3237
|
-
);
|
|
3238
|
-
case "error":
|
|
3239
|
-
throw new Error(result.error);
|
|
3240
|
-
default:
|
|
3241
|
-
throw new Error("Unexpected OAuth popup result");
|
|
3242
|
-
}
|
|
3243
|
-
} finally {
|
|
3244
|
-
provider.markFlowComplete?.();
|
|
3245
|
-
}
|
|
3246
|
-
}
|
|
3247
|
-
|
|
3248
3477
|
// src/core/base.ts
|
|
3249
3478
|
init_logging();
|
|
3250
3479
|
|
|
@@ -3475,6 +3704,18 @@ var MCPConnection = class {
|
|
|
3475
3704
|
get serverInfo() {
|
|
3476
3705
|
return this.connector.serverInfo;
|
|
3477
3706
|
}
|
|
3707
|
+
/** OAuth state discovered for this connection, when available. */
|
|
3708
|
+
get authorization() {
|
|
3709
|
+
return this.connector.authorization;
|
|
3710
|
+
}
|
|
3711
|
+
/** Discover optional OAuth metadata without delaying MCP readiness. */
|
|
3712
|
+
async discoverAuthorization() {
|
|
3713
|
+
return this.connector.discoverAuthorization();
|
|
3714
|
+
}
|
|
3715
|
+
/** Authenticate an already-connected mixed-auth server. */
|
|
3716
|
+
async authenticate() {
|
|
3717
|
+
await this.connector.authenticate();
|
|
3718
|
+
}
|
|
3478
3719
|
/**
|
|
3479
3720
|
* The negotiated protocol era for this session's connection:
|
|
3480
3721
|
* `"legacy"` (2025-era) or `"modern"` (2026-07-28-era).
|
|
@@ -3507,7 +3748,8 @@ var MCPConnection = class {
|
|
|
3507
3748
|
...server ? { server } : {},
|
|
3508
3749
|
capabilities,
|
|
3509
3750
|
instructions: this.connector.instructions,
|
|
3510
|
-
extensions
|
|
3751
|
+
extensions,
|
|
3752
|
+
...this.authorization ? { authorization: this.authorization } : {}
|
|
3511
3753
|
};
|
|
3512
3754
|
}
|
|
3513
3755
|
/**
|
|
@@ -3747,7 +3989,7 @@ function trackClientRemoveServer(name) {
|
|
|
3747
3989
|
}
|
|
3748
3990
|
|
|
3749
3991
|
// src/core/base.ts
|
|
3750
|
-
function
|
|
3992
|
+
function isOAuthClientProvider2(provider) {
|
|
3751
3993
|
return !!provider && typeof provider === "object" && "redirectUrl" in provider && "clientMetadata" in provider;
|
|
3752
3994
|
}
|
|
3753
3995
|
var BaseMCPClient = class {
|
|
@@ -3980,7 +4222,7 @@ var BaseMCPClient = class {
|
|
|
3980
4222
|
...serverConfig,
|
|
3981
4223
|
authProvider: oauthProvider
|
|
3982
4224
|
};
|
|
3983
|
-
} else if ("authProvider" in serverConfig && serverConfig.authProvider &&
|
|
4225
|
+
} else if ("authProvider" in serverConfig && serverConfig.authProvider && isOAuthClientProvider2(serverConfig.authProvider)) {
|
|
3984
4226
|
oauthProvider = serverConfig.authProvider;
|
|
3985
4227
|
}
|
|
3986
4228
|
const openSession = async () => {
|
|
@@ -4320,6 +4562,7 @@ var BrowserMCPClient = class _BrowserMCPClient extends BaseMCPClient {
|
|
|
4320
4562
|
fetch: configuredFetch,
|
|
4321
4563
|
authToken,
|
|
4322
4564
|
authProvider,
|
|
4565
|
+
detectMixedAuth,
|
|
4323
4566
|
wrapTransport,
|
|
4324
4567
|
clientOptions,
|
|
4325
4568
|
protocolNegotiation,
|
|
@@ -4344,6 +4587,7 @@ var BrowserMCPClient = class _BrowserMCPClient extends BaseMCPClient {
|
|
|
4344
4587
|
fetch: configuredFetch ?? globalThis.fetch.bind(globalThis),
|
|
4345
4588
|
authToken,
|
|
4346
4589
|
authProvider,
|
|
4590
|
+
detectMixedAuth,
|
|
4347
4591
|
wrapTransport,
|
|
4348
4592
|
clientOptions,
|
|
4349
4593
|
onSampling: resolved.onSampling,
|
|
@@ -4655,6 +4899,9 @@ function useMcpOperations(params) {
|
|
|
4655
4899
|
});
|
|
4656
4900
|
return result;
|
|
4657
4901
|
} catch (error) {
|
|
4902
|
+
if (isOAuthInteractionRequired(error)) {
|
|
4903
|
+
params.onAuthorizationRequired(error);
|
|
4904
|
+
}
|
|
4658
4905
|
params.addLog("error", `Tool "${name}" call failed:`, error);
|
|
4659
4906
|
Tel.getInstance().trackUseMcpToolCall({
|
|
4660
4907
|
toolName: name,
|
|
@@ -4666,7 +4913,7 @@ function useMcpOperations(params) {
|
|
|
4666
4913
|
throw error;
|
|
4667
4914
|
}
|
|
4668
4915
|
},
|
|
4669
|
-
[params.addLog]
|
|
4916
|
+
[params.addLog, params.onAuthorizationRequired]
|
|
4670
4917
|
);
|
|
4671
4918
|
const listResources = useCallback(async () => {
|
|
4672
4919
|
const connection = requireConnection(params, "list resources");
|
|
@@ -4842,6 +5089,7 @@ function useMcp(options) {
|
|
|
4842
5089
|
reconnectionOptions,
|
|
4843
5090
|
preventAutoAuth = true,
|
|
4844
5091
|
// Default to true - require explicit user action for OAuth
|
|
5092
|
+
detectMixedAuth = true,
|
|
4845
5093
|
useRedirectFlow = false,
|
|
4846
5094
|
// Default to false for backward compatibility (use popup)
|
|
4847
5095
|
onPopupWindow,
|
|
@@ -5019,6 +5267,7 @@ function useMcp(options) {
|
|
|
5019
5267
|
const [log, setLog] = useState([]);
|
|
5020
5268
|
const [authUrl, setAuthUrl] = useState(void 0);
|
|
5021
5269
|
const [authTokens, setAuthTokens] = useState(void 0);
|
|
5270
|
+
const [authorization, setAuthorization] = useState(void 0);
|
|
5022
5271
|
const clientRef = useRef(null);
|
|
5023
5272
|
const connectionRef = useRef(null);
|
|
5024
5273
|
const authProviderRef = useRef(
|
|
@@ -5033,6 +5282,9 @@ function useMcp(options) {
|
|
|
5033
5282
|
const retryScheduledRef = useRef(false);
|
|
5034
5283
|
const popupFlowActiveRef = useRef(false);
|
|
5035
5284
|
const stateRef = useRef(state);
|
|
5285
|
+
const authorizationRef = useRef(authorization);
|
|
5286
|
+
const authorizationServerUrlRef = useRef(url);
|
|
5287
|
+
authorizationRef.current = authorization;
|
|
5036
5288
|
const autoReconnectRef = useRef(autoReconnect);
|
|
5037
5289
|
const successfulTransportRef = useRef(null);
|
|
5038
5290
|
const connectRef = useRef(null);
|
|
@@ -5099,6 +5351,24 @@ function useMcp(options) {
|
|
|
5099
5351
|
},
|
|
5100
5352
|
[instanceLogger]
|
|
5101
5353
|
);
|
|
5354
|
+
const onAuthorizationRequired = useCallback2(
|
|
5355
|
+
(authError) => {
|
|
5356
|
+
const preparedAuthUrl = authProviderRef.current?.getLastAttemptedAuthUrl?.() ?? void 0;
|
|
5357
|
+
addLog(
|
|
5358
|
+
"info",
|
|
5359
|
+
"This server requires OAuth for the requested operation; waiting for authentication.",
|
|
5360
|
+
authError
|
|
5361
|
+
);
|
|
5362
|
+
const authorizationRequired = {
|
|
5363
|
+
...authorizationRef.current ?? { mode: "mixed" },
|
|
5364
|
+
authenticated: false
|
|
5365
|
+
};
|
|
5366
|
+
authorizationRef.current = authorizationRequired;
|
|
5367
|
+
setAuthorization(authorizationRequired);
|
|
5368
|
+
if (preparedAuthUrl) setAuthUrl(preparedAuthUrl);
|
|
5369
|
+
},
|
|
5370
|
+
[addLog]
|
|
5371
|
+
);
|
|
5102
5372
|
const connectionOperations = useMcpOperations({
|
|
5103
5373
|
stateRef,
|
|
5104
5374
|
connectionRef,
|
|
@@ -5109,7 +5379,8 @@ function useMcp(options) {
|
|
|
5109
5379
|
setResourceTemplates,
|
|
5110
5380
|
setPrompts,
|
|
5111
5381
|
setSkills,
|
|
5112
|
-
addLog
|
|
5382
|
+
addLog,
|
|
5383
|
+
onAuthorizationRequired
|
|
5113
5384
|
});
|
|
5114
5385
|
const disconnect = useCallback2(
|
|
5115
5386
|
async (quiet = false) => {
|
|
@@ -5250,6 +5521,11 @@ function useMcp(options) {
|
|
|
5250
5521
|
connectingRef.current = true;
|
|
5251
5522
|
connectEpochRef.current += 1;
|
|
5252
5523
|
connectAttemptRef.current += 1;
|
|
5524
|
+
if (authorizationServerUrlRef.current !== url) {
|
|
5525
|
+
authorizationServerUrlRef.current = url;
|
|
5526
|
+
authorizationRef.current = void 0;
|
|
5527
|
+
setAuthorization(void 0);
|
|
5528
|
+
}
|
|
5253
5529
|
setError(void 0);
|
|
5254
5530
|
setAuthUrl(void 0);
|
|
5255
5531
|
successfulTransportRef.current = null;
|
|
@@ -5336,6 +5612,7 @@ function useMcp(options) {
|
|
|
5336
5612
|
// Protocol era negotiation mode ("legacy" | "auto" | { pin }); the
|
|
5337
5613
|
// connector defaults to automatic v1/v2 negotiation.
|
|
5338
5614
|
...protocolNegotiation !== void 0 && { protocolNegotiation },
|
|
5615
|
+
detectMixedAuth,
|
|
5339
5616
|
// Pass user-configurable reconnection options, or when autoReconnect
|
|
5340
5617
|
// is disabled, disable SDK transport reconnection to prevent
|
|
5341
5618
|
// unwanted GET polling requests
|
|
@@ -5467,6 +5744,62 @@ function useMcp(options) {
|
|
|
5467
5744
|
}).catch(() => {
|
|
5468
5745
|
});
|
|
5469
5746
|
setTools(connection.tools || []);
|
|
5747
|
+
const {
|
|
5748
|
+
server: serverInfo2,
|
|
5749
|
+
capabilities: capabilities2,
|
|
5750
|
+
protocolEra: protocolEra2,
|
|
5751
|
+
protocolVersion: protocolVersion2,
|
|
5752
|
+
instructions: instructions2,
|
|
5753
|
+
extensions: extensions2,
|
|
5754
|
+
authorization: connectionAuthorization
|
|
5755
|
+
} = connection.info;
|
|
5756
|
+
if (connectionAuthorization) {
|
|
5757
|
+
setAuthorization(connectionAuthorization);
|
|
5758
|
+
authorizationRef.current = connectionAuthorization;
|
|
5759
|
+
}
|
|
5760
|
+
setProtocolEra(protocolEra2);
|
|
5761
|
+
setProtocolVersion(protocolVersion2);
|
|
5762
|
+
setInstructions(instructions2);
|
|
5763
|
+
setExtensions(extensions2);
|
|
5764
|
+
if (serverInfo2) {
|
|
5765
|
+
addLog("debug", "Server info:", serverInfo2);
|
|
5766
|
+
setServerInfo(serverInfo2);
|
|
5767
|
+
iconLoadingPromiseRef.current = loadServerIcon({
|
|
5768
|
+
serverInfo: serverInfo2,
|
|
5769
|
+
url,
|
|
5770
|
+
isMounted: () => isMountedRef.current,
|
|
5771
|
+
setServerInfo,
|
|
5772
|
+
addLog
|
|
5773
|
+
});
|
|
5774
|
+
}
|
|
5775
|
+
if (capabilities2) {
|
|
5776
|
+
addLog("debug", "Server capabilities:", capabilities2);
|
|
5777
|
+
setCapabilities(capabilities2);
|
|
5778
|
+
}
|
|
5779
|
+
successfulTransportRef.current = transportTypeParam;
|
|
5780
|
+
setState("ready");
|
|
5781
|
+
const discoverAuthorizationAfterReady = () => {
|
|
5782
|
+
if (!isMountedRef.current || connectionRef.current !== connection) {
|
|
5783
|
+
return;
|
|
5784
|
+
}
|
|
5785
|
+
const authorizationDiscovery = connection.discoverAuthorization?.();
|
|
5786
|
+
if (authorizationDiscovery) {
|
|
5787
|
+
void authorizationDiscovery.then((discovered) => {
|
|
5788
|
+
if (!discovered || !isMountedRef.current || connectionRef.current !== connection) {
|
|
5789
|
+
return;
|
|
5790
|
+
}
|
|
5791
|
+
authorizationRef.current = discovered;
|
|
5792
|
+
setAuthorization(discovered);
|
|
5793
|
+
});
|
|
5794
|
+
}
|
|
5795
|
+
};
|
|
5796
|
+
if (typeof globalThis.requestAnimationFrame === "function") {
|
|
5797
|
+
globalThis.requestAnimationFrame(() => {
|
|
5798
|
+
setTimeout(discoverAuthorizationAfterReady, 0);
|
|
5799
|
+
});
|
|
5800
|
+
} else {
|
|
5801
|
+
setTimeout(discoverAuthorizationAfterReady, 0);
|
|
5802
|
+
}
|
|
5470
5803
|
const [resourcesResult, promptsResult, templatesResult] = await Promise.all([
|
|
5471
5804
|
connection.listAllResources().catch((error2) => {
|
|
5472
5805
|
addLog("warn", "Failed to load initial resources:", error2);
|
|
@@ -5495,19 +5828,7 @@ function useMcp(options) {
|
|
|
5495
5828
|
setResources(resourcesResult.resources || []);
|
|
5496
5829
|
setPrompts(promptsResult.prompts || []);
|
|
5497
5830
|
setResourceTemplates(templatesResult.resourceTemplates || []);
|
|
5498
|
-
const {
|
|
5499
|
-
server: serverInfo2,
|
|
5500
|
-
capabilities: capabilities2,
|
|
5501
|
-
protocolEra: protocolEra2,
|
|
5502
|
-
protocolVersion: protocolVersion2,
|
|
5503
|
-
instructions: instructions2,
|
|
5504
|
-
extensions: extensions2
|
|
5505
|
-
} = connection.info;
|
|
5506
5831
|
if (isMountedRef.current) {
|
|
5507
|
-
setProtocolEra(protocolEra2);
|
|
5508
|
-
setProtocolVersion(protocolVersion2);
|
|
5509
|
-
setInstructions(instructions2);
|
|
5510
|
-
setExtensions(extensions2);
|
|
5511
5832
|
if (extensions2["io.modelcontextprotocol/skills"] !== void 0) {
|
|
5512
5833
|
try {
|
|
5513
5834
|
const result = await connection.listAllSkills();
|
|
@@ -5520,29 +5841,6 @@ function useMcp(options) {
|
|
|
5520
5841
|
setSkills([]);
|
|
5521
5842
|
}
|
|
5522
5843
|
}
|
|
5523
|
-
if (serverInfo2) {
|
|
5524
|
-
addLog("debug", "Server info:", serverInfo2);
|
|
5525
|
-
if (!isMountedRef.current) {
|
|
5526
|
-
addLog("debug", "Skipping state update - component unmounted");
|
|
5527
|
-
return "failed";
|
|
5528
|
-
}
|
|
5529
|
-
setServerInfo(serverInfo2);
|
|
5530
|
-
iconLoadingPromiseRef.current = loadServerIcon({
|
|
5531
|
-
serverInfo: serverInfo2,
|
|
5532
|
-
url,
|
|
5533
|
-
isMounted: () => isMountedRef.current,
|
|
5534
|
-
setServerInfo,
|
|
5535
|
-
addLog
|
|
5536
|
-
});
|
|
5537
|
-
}
|
|
5538
|
-
if (capabilities2) {
|
|
5539
|
-
addLog("debug", "Server capabilities:", capabilities2);
|
|
5540
|
-
if (!isMountedRef.current) {
|
|
5541
|
-
addLog("debug", "Skipping state update - component unmounted");
|
|
5542
|
-
return "failed";
|
|
5543
|
-
}
|
|
5544
|
-
setCapabilities(capabilities2);
|
|
5545
|
-
}
|
|
5546
5844
|
if (authProviderRef.current) {
|
|
5547
5845
|
const tokens = await authProviderRef.current.tokens?.();
|
|
5548
5846
|
if (!isMountedRef.current) {
|
|
@@ -5553,6 +5851,14 @@ function useMcp(options) {
|
|
|
5553
5851
|
return "failed";
|
|
5554
5852
|
}
|
|
5555
5853
|
if (tokens?.access_token) {
|
|
5854
|
+
if (authorizationRef.current?.mode === "mixed") {
|
|
5855
|
+
const authenticatedAuthorization = {
|
|
5856
|
+
...authorizationRef.current,
|
|
5857
|
+
authenticated: true
|
|
5858
|
+
};
|
|
5859
|
+
setAuthorization(authenticatedAuthorization);
|
|
5860
|
+
authorizationRef.current = authenticatedAuthorization;
|
|
5861
|
+
}
|
|
5556
5862
|
const expiresAt = getOAuthTokenExpiry(tokens);
|
|
5557
5863
|
let tokenEndpoint = null;
|
|
5558
5864
|
let resource = null;
|
|
@@ -5589,8 +5895,6 @@ function useMcp(options) {
|
|
|
5589
5895
|
});
|
|
5590
5896
|
}
|
|
5591
5897
|
}
|
|
5592
|
-
successfulTransportRef.current = transportTypeParam;
|
|
5593
|
-
setState("ready");
|
|
5594
5898
|
return "success";
|
|
5595
5899
|
} catch (err) {
|
|
5596
5900
|
const error2 = err;
|
|
@@ -5732,6 +6036,7 @@ function useMcp(options) {
|
|
|
5732
6036
|
headers,
|
|
5733
6037
|
transportType,
|
|
5734
6038
|
preventAutoAuth,
|
|
6039
|
+
detectMixedAuth,
|
|
5735
6040
|
useRedirectFlow,
|
|
5736
6041
|
onPopupWindow,
|
|
5737
6042
|
enabled,
|
|
@@ -5768,11 +6073,12 @@ function useMcp(options) {
|
|
|
5768
6073
|
const authenticate = useCallback2(async () => {
|
|
5769
6074
|
addLog("info", "Manual authentication requested...");
|
|
5770
6075
|
const currentState = stateRef.current;
|
|
6076
|
+
const isOptionalMixedAuthentication = currentState === "ready" && authorizationRef.current?.mode === "mixed";
|
|
5771
6077
|
if (currentState === "failed") {
|
|
5772
6078
|
addLog("info", "Attempting to reconnect and authenticate via retry...");
|
|
5773
6079
|
retry();
|
|
5774
|
-
} else if (currentState === "pending_auth") {
|
|
5775
|
-
addLog("info", "Proceeding with authentication
|
|
6080
|
+
} else if (currentState === "pending_auth" || currentState === "ready" && authorizationRef.current?.mode === "mixed" && !authorizationRef.current.authenticated) {
|
|
6081
|
+
addLog("info", "Proceeding with authentication...");
|
|
5776
6082
|
try {
|
|
5777
6083
|
assert(
|
|
5778
6084
|
authProviderRef.current,
|
|
@@ -5891,16 +6197,16 @@ function useMcp(options) {
|
|
|
5891
6197
|
case "cancelled":
|
|
5892
6198
|
addLog(
|
|
5893
6199
|
"warn",
|
|
5894
|
-
"Authentication popup was closed before completing. Returning to pending_auth."
|
|
6200
|
+
isOptionalMixedAuthentication ? "Authentication popup was closed before completing. Public tools remain available." : "Authentication popup was closed before completing. Returning to pending_auth."
|
|
5895
6201
|
);
|
|
5896
|
-
setState("pending_auth");
|
|
6202
|
+
setState(isOptionalMixedAuthentication ? "ready" : "pending_auth");
|
|
5897
6203
|
break;
|
|
5898
6204
|
case "timeout":
|
|
5899
6205
|
addLog(
|
|
5900
6206
|
"warn",
|
|
5901
|
-
"Authentication timed out waiting for the popup. Returning to pending_auth."
|
|
6207
|
+
isOptionalMixedAuthentication ? "Authentication timed out waiting for the popup. Public tools remain available." : "Authentication timed out waiting for the popup. Returning to pending_auth."
|
|
5902
6208
|
);
|
|
5903
|
-
setState("pending_auth");
|
|
6209
|
+
setState(isOptionalMixedAuthentication ? "ready" : "pending_auth");
|
|
5904
6210
|
break;
|
|
5905
6211
|
case "error":
|
|
5906
6212
|
failConnection(`Authentication failed: ${result.error}`);
|
|
@@ -6189,6 +6495,7 @@ function useMcp(options) {
|
|
|
6189
6495
|
log,
|
|
6190
6496
|
authUrl,
|
|
6191
6497
|
authTokens,
|
|
6498
|
+
authorization,
|
|
6192
6499
|
client: clientRef.current,
|
|
6193
6500
|
...connectionOperations,
|
|
6194
6501
|
retry,
|
|
@@ -6598,7 +6905,7 @@ function isSameMcpServer(left, right) {
|
|
|
6598
6905
|
return left.id === right.id && sameSerializedValue(
|
|
6599
6906
|
pickLiveServerConfig(left),
|
|
6600
6907
|
pickLiveServerConfig(right)
|
|
6601
|
-
) && left.name === right.name && left.state === right.state && left.error === right.error && left.authUrl === right.authUrl && sameSerializedValue(left.authTokens, right.authTokens) && left.protocolEra === right.protocolEra && left.protocolVersion === right.protocolVersion && sameSerializedValue(left.serverInfo, right.serverInfo) && sameSerializedValue(left.capabilities, right.capabilities) && left.instructions === right.instructions && sameSerializedValue(left.extensions, right.extensions) && sameSerializedValue(left.tools, right.tools) && sameSerializedValue(left.resources, right.resources) && sameSerializedValue(left.resourceTemplates, right.resourceTemplates) && sameSerializedValue(left.prompts, right.prompts) && sameSerializedValue(left.notifications, right.notifications) && left.unreadNotificationCount === right.unreadNotificationCount && sameSerializedValue(
|
|
6908
|
+
) && left.name === right.name && left.state === right.state && left.error === right.error && left.authUrl === right.authUrl && sameSerializedValue(left.authTokens, right.authTokens) && sameSerializedValue(left.authorization, right.authorization) && left.protocolEra === right.protocolEra && left.protocolVersion === right.protocolVersion && sameSerializedValue(left.serverInfo, right.serverInfo) && sameSerializedValue(left.capabilities, right.capabilities) && left.instructions === right.instructions && sameSerializedValue(left.extensions, right.extensions) && sameSerializedValue(left.tools, right.tools) && sameSerializedValue(left.resources, right.resources) && sameSerializedValue(left.resourceTemplates, right.resourceTemplates) && sameSerializedValue(left.prompts, right.prompts) && sameSerializedValue(left.notifications, right.notifications) && left.unreadNotificationCount === right.unreadNotificationCount && sameSerializedValue(
|
|
6602
6909
|
left.pendingSamplingRequests,
|
|
6603
6910
|
right.pendingSamplingRequests
|
|
6604
6911
|
) && sameSerializedValue(
|
|
@@ -6780,6 +7087,7 @@ function McpServerWrapper({
|
|
|
6780
7087
|
mcp.instructions,
|
|
6781
7088
|
mcp.extensions,
|
|
6782
7089
|
mcp.authTokens,
|
|
7090
|
+
mcp.authorization,
|
|
6783
7091
|
// Functions excluded - they're stable via useCallback in useMcp
|
|
6784
7092
|
// mcp.log excluded - log changes shouldn't trigger provider updates
|
|
6785
7093
|
// mcp.client excluded - client reference stability handled by manual check
|
|
@@ -8991,6 +9299,7 @@ export {
|
|
|
8991
9299
|
getAllRpcLogs,
|
|
8992
9300
|
getRpcLogs,
|
|
8993
9301
|
getViewResourceUri,
|
|
9302
|
+
isOAuthInteractionRequired,
|
|
8994
9303
|
isToolVisibleToModel,
|
|
8995
9304
|
isViewResource,
|
|
8996
9305
|
isViewTool,
|