@mcp-use/client 2.1.0 → 2.1.1-canary.0
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 +17 -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 +269 -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 +635 -408
- 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 +421 -143
- 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 +11 -1
- package/dist/transport/base.d.ts.map +1 -1
- package/dist/transport/http.d.ts +11 -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,21 @@ 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
|
+
/** Start optional OAuth for a connected mixed-auth server. */
|
|
922
|
+
async authenticate() {
|
|
923
|
+
throw new Error("This connector does not support interactive OAuth");
|
|
924
|
+
}
|
|
783
925
|
/**
|
|
784
926
|
* Disconnects the SDK client and releases transport resources.
|
|
785
927
|
*
|
|
@@ -827,13 +969,13 @@ var BaseConnector = class {
|
|
|
827
969
|
icons: serverInfo.icons
|
|
828
970
|
} : null;
|
|
829
971
|
try {
|
|
830
|
-
const listToolsRes = await this.
|
|
831
|
-
void 0,
|
|
832
|
-
defaultRequestOptions
|
|
972
|
+
const listToolsRes = await this.executeRequest(
|
|
973
|
+
() => this.client.listTools(void 0, defaultRequestOptions)
|
|
833
974
|
);
|
|
834
975
|
this.toolsCache = listToolsRes.tools ?? [];
|
|
835
976
|
logger.debug(`Fetched ${this.toolsCache.length} tools from server`);
|
|
836
977
|
} catch (err) {
|
|
978
|
+
if (isOAuthInteractionRequired(err)) throw err;
|
|
837
979
|
const error = err;
|
|
838
980
|
if (error.code === -32601) {
|
|
839
981
|
logger.debug("Server does not implement tools/list, assuming no tools");
|
|
@@ -907,9 +1049,8 @@ var BaseConnector = class {
|
|
|
907
1049
|
const progressHandler = enhancedOptions?.onprogress;
|
|
908
1050
|
if (progressHandler) this.activeProgressHandlers.add(progressHandler);
|
|
909
1051
|
try {
|
|
910
|
-
const res = await this.
|
|
911
|
-
{ name, arguments: args },
|
|
912
|
-
enhancedOptions
|
|
1052
|
+
const res = await this.executeRequest(
|
|
1053
|
+
() => this.client.callTool({ name, arguments: args }, enhancedOptions)
|
|
913
1054
|
);
|
|
914
1055
|
logger.debug(`Tool '${name}' returned`, res);
|
|
915
1056
|
return res;
|
|
@@ -929,7 +1070,9 @@ var BaseConnector = class {
|
|
|
929
1070
|
throw new Error("MCP client is not connected");
|
|
930
1071
|
}
|
|
931
1072
|
logger.debug("[listTools] Fetching fresh tools from server...");
|
|
932
|
-
const result = await this.
|
|
1073
|
+
const result = await this.executeRequest(
|
|
1074
|
+
() => this.client.listTools(void 0, options)
|
|
1075
|
+
);
|
|
933
1076
|
const tools = result.tools ? [...result.tools] : [];
|
|
934
1077
|
logger.debug(
|
|
935
1078
|
`[listTools] Returned ${tools.length} tools:`,
|
|
@@ -949,7 +1092,9 @@ var BaseConnector = class {
|
|
|
949
1092
|
throw new Error("MCP client is not connected");
|
|
950
1093
|
}
|
|
951
1094
|
logger.debug("Listing resources", cursor ? `with cursor: ${cursor}` : "");
|
|
952
|
-
return await this.
|
|
1095
|
+
return await this.executeRequest(
|
|
1096
|
+
() => this.client.listResources({ cursor }, options)
|
|
1097
|
+
);
|
|
953
1098
|
}
|
|
954
1099
|
/**
|
|
955
1100
|
* List all resources from the server, automatically handling pagination
|
|
@@ -967,14 +1112,16 @@ var BaseConnector = class {
|
|
|
967
1112
|
}
|
|
968
1113
|
try {
|
|
969
1114
|
logger.debug("Listing all resources (with auto-pagination)");
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
1115
|
+
return await this.executeRequest(async () => {
|
|
1116
|
+
const allResources = [];
|
|
1117
|
+
let cursor = void 0;
|
|
1118
|
+
do {
|
|
1119
|
+
const result = await this.client.listResources({ cursor }, options);
|
|
1120
|
+
allResources.push(...result.resources || []);
|
|
1121
|
+
cursor = result.nextCursor;
|
|
1122
|
+
} while (cursor);
|
|
1123
|
+
return { resources: allResources };
|
|
1124
|
+
});
|
|
978
1125
|
} catch (err) {
|
|
979
1126
|
const error = err;
|
|
980
1127
|
if (error.code === -32601) {
|
|
@@ -995,7 +1142,9 @@ var BaseConnector = class {
|
|
|
995
1142
|
throw new Error("MCP client is not connected");
|
|
996
1143
|
}
|
|
997
1144
|
logger.debug("Listing resource templates");
|
|
998
|
-
return await this.
|
|
1145
|
+
return await this.executeRequest(
|
|
1146
|
+
() => this.client.listResourceTemplates(void 0, options)
|
|
1147
|
+
);
|
|
999
1148
|
}
|
|
1000
1149
|
/**
|
|
1001
1150
|
* Request completion suggestions for a prompt or resource template argument
|
|
@@ -1009,7 +1158,9 @@ var BaseConnector = class {
|
|
|
1009
1158
|
throw new Error("MCP client is not connected");
|
|
1010
1159
|
}
|
|
1011
1160
|
logger.debug("[complete] Requesting completions for:", params.ref);
|
|
1012
|
-
const result = await this.
|
|
1161
|
+
const result = await this.executeRequest(
|
|
1162
|
+
() => this.client.complete(params, options)
|
|
1163
|
+
);
|
|
1013
1164
|
logger.debug(
|
|
1014
1165
|
`[complete] Received ${result.completion.values.length} suggestions`
|
|
1015
1166
|
);
|
|
@@ -1027,7 +1178,9 @@ var BaseConnector = class {
|
|
|
1027
1178
|
throw new Error("MCP client is not connected");
|
|
1028
1179
|
}
|
|
1029
1180
|
logger.debug(`Reading resource ${uri}`);
|
|
1030
|
-
const res = await this.
|
|
1181
|
+
const res = await this.executeRequest(
|
|
1182
|
+
() => this.client.readResource({ uri }, options)
|
|
1183
|
+
);
|
|
1031
1184
|
return res;
|
|
1032
1185
|
}
|
|
1033
1186
|
/**
|
|
@@ -1041,7 +1194,9 @@ var BaseConnector = class {
|
|
|
1041
1194
|
throw new Error("MCP client is not connected");
|
|
1042
1195
|
}
|
|
1043
1196
|
logger.debug(`Subscribing to resource: ${uri}`);
|
|
1044
|
-
return await this.
|
|
1197
|
+
return await this.executeRequest(
|
|
1198
|
+
() => this.client.subscribeResource({ uri }, options)
|
|
1199
|
+
);
|
|
1045
1200
|
}
|
|
1046
1201
|
/**
|
|
1047
1202
|
* Unsubscribe from resource updates
|
|
@@ -1054,7 +1209,9 @@ var BaseConnector = class {
|
|
|
1054
1209
|
throw new Error("MCP client is not connected");
|
|
1055
1210
|
}
|
|
1056
1211
|
logger.debug(`Unsubscribing from resource: ${uri}`);
|
|
1057
|
-
return await this.
|
|
1212
|
+
return await this.executeRequest(
|
|
1213
|
+
() => this.client.unsubscribeResource({ uri }, options)
|
|
1214
|
+
);
|
|
1058
1215
|
}
|
|
1059
1216
|
/**
|
|
1060
1217
|
* Lists prompts exposed by the server.
|
|
@@ -1071,7 +1228,7 @@ var BaseConnector = class {
|
|
|
1071
1228
|
}
|
|
1072
1229
|
try {
|
|
1073
1230
|
logger.debug("Listing prompts");
|
|
1074
|
-
return await this.client.listPrompts();
|
|
1231
|
+
return await this.executeRequest(() => this.client.listPrompts());
|
|
1075
1232
|
} catch (err) {
|
|
1076
1233
|
const error = err;
|
|
1077
1234
|
if (error.code === -32601) {
|
|
@@ -1093,7 +1250,9 @@ var BaseConnector = class {
|
|
|
1093
1250
|
throw new Error("MCP client is not connected");
|
|
1094
1251
|
}
|
|
1095
1252
|
logger.debug(`Getting prompt ${name}`);
|
|
1096
|
-
return await this.
|
|
1253
|
+
return await this.executeRequest(
|
|
1254
|
+
() => this.client.getPrompt({ name, arguments: args })
|
|
1255
|
+
);
|
|
1097
1256
|
}
|
|
1098
1257
|
/**
|
|
1099
1258
|
* Sends a raw, potentially non-standard request through the SDK client.
|
|
@@ -1108,10 +1267,12 @@ var BaseConnector = class {
|
|
|
1108
1267
|
throw new Error("MCP client is not connected");
|
|
1109
1268
|
}
|
|
1110
1269
|
logger.debug(`Sending raw request '${method}' with params`, params);
|
|
1111
|
-
return await this.
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1270
|
+
return await this.executeRequest(
|
|
1271
|
+
() => this.client.request(
|
|
1272
|
+
{ method, params: params ?? {} },
|
|
1273
|
+
passthroughResultSchema,
|
|
1274
|
+
options
|
|
1275
|
+
)
|
|
1115
1276
|
);
|
|
1116
1277
|
}
|
|
1117
1278
|
/**
|
|
@@ -1144,6 +1305,7 @@ var BaseConnector = class {
|
|
|
1144
1305
|
}
|
|
1145
1306
|
}
|
|
1146
1307
|
this.toolsCache = null;
|
|
1308
|
+
this.authorizationCache = void 0;
|
|
1147
1309
|
if (issues.length) {
|
|
1148
1310
|
logger.warn(`Resource cleanup finished with ${issues.length} issue(s)`);
|
|
1149
1311
|
}
|
|
@@ -1151,9 +1313,10 @@ var BaseConnector = class {
|
|
|
1151
1313
|
};
|
|
1152
1314
|
|
|
1153
1315
|
// src/transport/http.ts
|
|
1316
|
+
var MIXED_AUTH_DISCOVERY_TIMEOUT_MS = 2e3;
|
|
1154
1317
|
function detectUnauthorized(err, depth = 0) {
|
|
1155
1318
|
if (!err || depth > 5) return false;
|
|
1156
|
-
if (err instanceof
|
|
1319
|
+
if (err instanceof UnauthorizedError2) return true;
|
|
1157
1320
|
if (err instanceof SdkHttpError && err.status === 401) return true;
|
|
1158
1321
|
if (err instanceof Error) {
|
|
1159
1322
|
if (err.cause) {
|
|
@@ -1164,6 +1327,11 @@ function detectUnauthorized(err, depth = 0) {
|
|
|
1164
1327
|
}
|
|
1165
1328
|
return false;
|
|
1166
1329
|
}
|
|
1330
|
+
function isOAuthClientProvider(provider) {
|
|
1331
|
+
return Boolean(
|
|
1332
|
+
provider && "redirectToAuthorization" in provider && typeof provider.redirectToAuthorization === "function" && "tokens" in provider && typeof provider.tokens === "function"
|
|
1333
|
+
);
|
|
1334
|
+
}
|
|
1167
1335
|
function createMcpProxyFetch(logicalServerUrl, proxyUrl, baseFetch, serverId) {
|
|
1168
1336
|
const logical = new URL(logicalServerUrl);
|
|
1169
1337
|
const proxy = proxyUrl.replace(/\/$/, "");
|
|
@@ -1189,6 +1357,31 @@ function createMcpProxyFetch(logicalServerUrl, proxyUrl, baseFetch, serverId) {
|
|
|
1189
1357
|
);
|
|
1190
1358
|
};
|
|
1191
1359
|
}
|
|
1360
|
+
function createDeadlineFetch(baseFetch, deadlineSignal) {
|
|
1361
|
+
return async (input, init) => {
|
|
1362
|
+
const requestSignal = init?.signal;
|
|
1363
|
+
if (!requestSignal) {
|
|
1364
|
+
return baseFetch(input, { ...init, signal: deadlineSignal });
|
|
1365
|
+
}
|
|
1366
|
+
const controller = new AbortController();
|
|
1367
|
+
const abortFromRequest = () => controller.abort(requestSignal.reason);
|
|
1368
|
+
const abortFromDeadline = () => controller.abort(deadlineSignal.reason);
|
|
1369
|
+
if (requestSignal.aborted) abortFromRequest();
|
|
1370
|
+
else
|
|
1371
|
+
requestSignal.addEventListener("abort", abortFromRequest, { once: true });
|
|
1372
|
+
if (deadlineSignal.aborted) abortFromDeadline();
|
|
1373
|
+
else
|
|
1374
|
+
deadlineSignal.addEventListener("abort", abortFromDeadline, {
|
|
1375
|
+
once: true
|
|
1376
|
+
});
|
|
1377
|
+
try {
|
|
1378
|
+
return await baseFetch(input, { ...init, signal: controller.signal });
|
|
1379
|
+
} finally {
|
|
1380
|
+
requestSignal.removeEventListener("abort", abortFromRequest);
|
|
1381
|
+
deadlineSignal.removeEventListener("abort", abortFromDeadline);
|
|
1382
|
+
}
|
|
1383
|
+
};
|
|
1384
|
+
}
|
|
1192
1385
|
var HttpConnector = class extends BaseConnector {
|
|
1193
1386
|
baseUrl;
|
|
1194
1387
|
headers;
|
|
@@ -1199,8 +1392,11 @@ var HttpConnector = class extends BaseConnector {
|
|
|
1199
1392
|
gatewayUrl;
|
|
1200
1393
|
serverId;
|
|
1201
1394
|
reconnectionOptions;
|
|
1395
|
+
detectMixedAuth;
|
|
1202
1396
|
transportType = null;
|
|
1203
1397
|
streamableTransport = null;
|
|
1398
|
+
hadAccessTokenAtConnect = false;
|
|
1399
|
+
pendingOAuthCompletion = null;
|
|
1204
1400
|
/**
|
|
1205
1401
|
* Creates an HTTP connector.
|
|
1206
1402
|
*
|
|
@@ -1231,6 +1427,99 @@ var HttpConnector = class extends BaseConnector {
|
|
|
1231
1427
|
};
|
|
1232
1428
|
this.protocolNegotiation = opts.protocolNegotiation ?? "auto";
|
|
1233
1429
|
this.reconnectionOptions = opts.reconnectionOptions;
|
|
1430
|
+
this.detectMixedAuth = opts.detectMixedAuth ?? true;
|
|
1431
|
+
}
|
|
1432
|
+
get oauthProvider() {
|
|
1433
|
+
return isOAuthClientProvider(this.opts.authProvider) ? this.opts.authProvider : void 0;
|
|
1434
|
+
}
|
|
1435
|
+
async completeInteractiveAuthorization() {
|
|
1436
|
+
const provider = this.oauthProvider;
|
|
1437
|
+
if (!provider) {
|
|
1438
|
+
throw new Error("No OAuth client provider is configured");
|
|
1439
|
+
}
|
|
1440
|
+
if (!this.pendingOAuthCompletion) {
|
|
1441
|
+
this.pendingOAuthCompletion = completeOAuthFlow(provider, this.baseUrl, {
|
|
1442
|
+
fetchFn: this.customFetch,
|
|
1443
|
+
finishAuthorization: async (code, iss) => {
|
|
1444
|
+
const transport = this.streamableTransport;
|
|
1445
|
+
if (!transport) {
|
|
1446
|
+
throw new Error("OAuth transport is no longer connected");
|
|
1447
|
+
}
|
|
1448
|
+
await transport.finishAuth(code, iss);
|
|
1449
|
+
}
|
|
1450
|
+
}).then(() => {
|
|
1451
|
+
if (this.authorizationCache) {
|
|
1452
|
+
this.authorizationCache = {
|
|
1453
|
+
...this.authorizationCache,
|
|
1454
|
+
authenticated: true
|
|
1455
|
+
};
|
|
1456
|
+
}
|
|
1457
|
+
}).finally(() => {
|
|
1458
|
+
this.pendingOAuthCompletion = null;
|
|
1459
|
+
});
|
|
1460
|
+
}
|
|
1461
|
+
await this.pendingOAuthCompletion;
|
|
1462
|
+
}
|
|
1463
|
+
async executeRequest(operation) {
|
|
1464
|
+
try {
|
|
1465
|
+
return await operation();
|
|
1466
|
+
} catch (error) {
|
|
1467
|
+
const provider = this.oauthProvider;
|
|
1468
|
+
if (!provider || provider.preventAutoAuth === true || !isOAuthInteractionRequired(error)) {
|
|
1469
|
+
throw error;
|
|
1470
|
+
}
|
|
1471
|
+
await this.completeInteractiveAuthorization();
|
|
1472
|
+
return operation();
|
|
1473
|
+
}
|
|
1474
|
+
}
|
|
1475
|
+
/** Authenticate an already-connected server without requiring a 401 first. */
|
|
1476
|
+
async authenticate() {
|
|
1477
|
+
if (!this.connected || !this.streamableTransport) {
|
|
1478
|
+
throw new Error("MCP client is not connected");
|
|
1479
|
+
}
|
|
1480
|
+
await this.completeInteractiveAuthorization();
|
|
1481
|
+
}
|
|
1482
|
+
async initialize(defaultRequestOptions = this.opts.defaultRequestOptions ?? {}) {
|
|
1483
|
+
const capabilities = await super.initialize(defaultRequestOptions);
|
|
1484
|
+
if (!this.detectMixedAuth || !this.oauthProvider || this.hadAccessTokenAtConnect) {
|
|
1485
|
+
return capabilities;
|
|
1486
|
+
}
|
|
1487
|
+
const controller = new AbortController();
|
|
1488
|
+
let timeout;
|
|
1489
|
+
const discoveryTimeout = new Promise((_, reject) => {
|
|
1490
|
+
timeout = setTimeout(() => {
|
|
1491
|
+
const error = new Error(
|
|
1492
|
+
`Mixed-auth metadata discovery timed out after ${MIXED_AUTH_DISCOVERY_TIMEOUT_MS}ms`
|
|
1493
|
+
);
|
|
1494
|
+
controller.abort(error);
|
|
1495
|
+
reject(error);
|
|
1496
|
+
}, MIXED_AUTH_DISCOVERY_TIMEOUT_MS);
|
|
1497
|
+
});
|
|
1498
|
+
const baseFetch = this.customFetch ?? globalThis.fetch.bind(globalThis);
|
|
1499
|
+
try {
|
|
1500
|
+
const metadata = await Promise.race([
|
|
1501
|
+
discoverOAuthProtectedResourceMetadata(
|
|
1502
|
+
this.baseUrl,
|
|
1503
|
+
{ protocolVersion: this.negotiatedProtocolVersion },
|
|
1504
|
+
createDeadlineFetch(baseFetch, controller.signal)
|
|
1505
|
+
),
|
|
1506
|
+
discoveryTimeout
|
|
1507
|
+
]);
|
|
1508
|
+
this.authorizationCache = {
|
|
1509
|
+
mode: "mixed",
|
|
1510
|
+
authenticated: false,
|
|
1511
|
+
...metadata.resource ? { resource: metadata.resource } : {},
|
|
1512
|
+
...metadata.scopes_supported ? { scopesSupported: [...metadata.scopes_supported] } : {}
|
|
1513
|
+
};
|
|
1514
|
+
logger.info(
|
|
1515
|
+
"OAuth protected-resource metadata found after anonymous connection; server uses mixed auth"
|
|
1516
|
+
);
|
|
1517
|
+
} catch (error) {
|
|
1518
|
+
logger.debug("Mixed-auth metadata was not discovered:", error);
|
|
1519
|
+
} finally {
|
|
1520
|
+
if (timeout) clearTimeout(timeout);
|
|
1521
|
+
}
|
|
1522
|
+
return capabilities;
|
|
1234
1523
|
}
|
|
1235
1524
|
buildClientOptions() {
|
|
1236
1525
|
return {
|
|
@@ -1337,6 +1626,16 @@ var HttpConnector = class extends BaseConnector {
|
|
|
1337
1626
|
}
|
|
1338
1627
|
const baseUrl = this.baseUrl;
|
|
1339
1628
|
logger.debug(`Connecting to MCP implementation via HTTP: ${baseUrl}`);
|
|
1629
|
+
const oauthProvider = this.oauthProvider;
|
|
1630
|
+
if (oauthProvider) {
|
|
1631
|
+
try {
|
|
1632
|
+
this.hadAccessTokenAtConnect = Boolean(
|
|
1633
|
+
(await oauthProvider.tokens())?.access_token
|
|
1634
|
+
);
|
|
1635
|
+
} catch {
|
|
1636
|
+
this.hadAccessTokenAtConnect = false;
|
|
1637
|
+
}
|
|
1638
|
+
}
|
|
1340
1639
|
try {
|
|
1341
1640
|
await this.connectWithStreamableHttp(baseUrl);
|
|
1342
1641
|
logger.debug("\u2705 Successfully connected via streamable HTTP");
|
|
@@ -1611,7 +1910,7 @@ var HttpConnector = class extends BaseConnector {
|
|
|
1611
1910
|
};
|
|
1612
1911
|
|
|
1613
1912
|
// src/utils/version.ts
|
|
1614
|
-
var VERSION = "2.1.0";
|
|
1913
|
+
var VERSION = "2.1.1-canary.0";
|
|
1615
1914
|
function getPackageVersion() {
|
|
1616
1915
|
return VERSION;
|
|
1617
1916
|
}
|
|
@@ -2577,29 +2876,43 @@ var BrowserOAuthClientProvider = class {
|
|
|
2577
2876
|
* @param authorizationUrl - The fully constructed authorization URL from the SDK.
|
|
2578
2877
|
*/
|
|
2579
2878
|
async redirectToAuthorization(authorizationUrl) {
|
|
2580
|
-
|
|
2879
|
+
await this.prepareAuthorizationUrl(authorizationUrl);
|
|
2581
2880
|
if (this.preventAutoAuth) {
|
|
2582
2881
|
console.info(
|
|
2583
2882
|
`[${this.storageKeyPrefix}] Auto-auth prevented. Authorization URL stored for manual trigger.`
|
|
2584
2883
|
);
|
|
2585
2884
|
return;
|
|
2586
2885
|
}
|
|
2886
|
+
this.startAuthorization();
|
|
2887
|
+
}
|
|
2888
|
+
/**
|
|
2889
|
+
* Open the authorization URL prepared by the official SDK.
|
|
2890
|
+
*
|
|
2891
|
+
* This is the explicit-user-action counterpart to `preventAutoAuth`: the
|
|
2892
|
+
* provider still lets the SDK own discovery and PKCE state, while a host can
|
|
2893
|
+
* launch the stored authorization request later from an Authenticate button.
|
|
2894
|
+
*/
|
|
2895
|
+
startAuthorization() {
|
|
2896
|
+
const authorizationUrl = this.lastAttemptedAuthUrl;
|
|
2897
|
+
if (!authorizationUrl) {
|
|
2898
|
+
throw new Error("No prepared OAuth authorization is available");
|
|
2899
|
+
}
|
|
2587
2900
|
if (this.useRedirectFlow) {
|
|
2588
2901
|
console.info(
|
|
2589
2902
|
`[${this.storageKeyPrefix}] Redirecting to authorization URL (full-page redirect).`
|
|
2590
2903
|
);
|
|
2591
|
-
window.location.href =
|
|
2904
|
+
window.location.href = authorizationUrl;
|
|
2592
2905
|
return;
|
|
2593
2906
|
}
|
|
2594
2907
|
const popupFeatures = "width=600,height=700,resizable=yes,scrollbars=yes,status=yes";
|
|
2595
2908
|
try {
|
|
2596
2909
|
const popup = window.open(
|
|
2597
|
-
|
|
2910
|
+
authorizationUrl,
|
|
2598
2911
|
`mcp_auth_${this.serverUrlHash}`,
|
|
2599
2912
|
popupFeatures
|
|
2600
2913
|
);
|
|
2601
2914
|
if (this.onPopupWindow) {
|
|
2602
|
-
this.onPopupWindow(
|
|
2915
|
+
this.onPopupWindow(authorizationUrl, popupFeatures, popup);
|
|
2603
2916
|
}
|
|
2604
2917
|
if (!popup || popup.closed || typeof popup.closed === "undefined") {
|
|
2605
2918
|
console.warn(
|
|
@@ -3148,103 +3461,6 @@ function setTelemetrySource(source) {
|
|
|
3148
3461
|
Tel.getInstance().setSource(source);
|
|
3149
3462
|
}
|
|
3150
3463
|
|
|
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
3464
|
// src/core/base.ts
|
|
3249
3465
|
init_logging();
|
|
3250
3466
|
|
|
@@ -3475,6 +3691,14 @@ var MCPConnection = class {
|
|
|
3475
3691
|
get serverInfo() {
|
|
3476
3692
|
return this.connector.serverInfo;
|
|
3477
3693
|
}
|
|
3694
|
+
/** OAuth state discovered for this connection, when available. */
|
|
3695
|
+
get authorization() {
|
|
3696
|
+
return this.connector.authorization;
|
|
3697
|
+
}
|
|
3698
|
+
/** Authenticate an already-connected mixed-auth server. */
|
|
3699
|
+
async authenticate() {
|
|
3700
|
+
await this.connector.authenticate();
|
|
3701
|
+
}
|
|
3478
3702
|
/**
|
|
3479
3703
|
* The negotiated protocol era for this session's connection:
|
|
3480
3704
|
* `"legacy"` (2025-era) or `"modern"` (2026-07-28-era).
|
|
@@ -3507,7 +3731,8 @@ var MCPConnection = class {
|
|
|
3507
3731
|
...server ? { server } : {},
|
|
3508
3732
|
capabilities,
|
|
3509
3733
|
instructions: this.connector.instructions,
|
|
3510
|
-
extensions
|
|
3734
|
+
extensions,
|
|
3735
|
+
...this.authorization ? { authorization: this.authorization } : {}
|
|
3511
3736
|
};
|
|
3512
3737
|
}
|
|
3513
3738
|
/**
|
|
@@ -3747,7 +3972,7 @@ function trackClientRemoveServer(name) {
|
|
|
3747
3972
|
}
|
|
3748
3973
|
|
|
3749
3974
|
// src/core/base.ts
|
|
3750
|
-
function
|
|
3975
|
+
function isOAuthClientProvider2(provider) {
|
|
3751
3976
|
return !!provider && typeof provider === "object" && "redirectUrl" in provider && "clientMetadata" in provider;
|
|
3752
3977
|
}
|
|
3753
3978
|
var BaseMCPClient = class {
|
|
@@ -3980,7 +4205,7 @@ var BaseMCPClient = class {
|
|
|
3980
4205
|
...serverConfig,
|
|
3981
4206
|
authProvider: oauthProvider
|
|
3982
4207
|
};
|
|
3983
|
-
} else if ("authProvider" in serverConfig && serverConfig.authProvider &&
|
|
4208
|
+
} else if ("authProvider" in serverConfig && serverConfig.authProvider && isOAuthClientProvider2(serverConfig.authProvider)) {
|
|
3984
4209
|
oauthProvider = serverConfig.authProvider;
|
|
3985
4210
|
}
|
|
3986
4211
|
const openSession = async () => {
|
|
@@ -4320,6 +4545,7 @@ var BrowserMCPClient = class _BrowserMCPClient extends BaseMCPClient {
|
|
|
4320
4545
|
fetch: configuredFetch,
|
|
4321
4546
|
authToken,
|
|
4322
4547
|
authProvider,
|
|
4548
|
+
detectMixedAuth,
|
|
4323
4549
|
wrapTransport,
|
|
4324
4550
|
clientOptions,
|
|
4325
4551
|
protocolNegotiation,
|
|
@@ -4344,6 +4570,7 @@ var BrowserMCPClient = class _BrowserMCPClient extends BaseMCPClient {
|
|
|
4344
4570
|
fetch: configuredFetch ?? globalThis.fetch.bind(globalThis),
|
|
4345
4571
|
authToken,
|
|
4346
4572
|
authProvider,
|
|
4573
|
+
detectMixedAuth,
|
|
4347
4574
|
wrapTransport,
|
|
4348
4575
|
clientOptions,
|
|
4349
4576
|
onSampling: resolved.onSampling,
|
|
@@ -4655,6 +4882,9 @@ function useMcpOperations(params) {
|
|
|
4655
4882
|
});
|
|
4656
4883
|
return result;
|
|
4657
4884
|
} catch (error) {
|
|
4885
|
+
if (isOAuthInteractionRequired(error)) {
|
|
4886
|
+
params.onAuthorizationRequired(error);
|
|
4887
|
+
}
|
|
4658
4888
|
params.addLog("error", `Tool "${name}" call failed:`, error);
|
|
4659
4889
|
Tel.getInstance().trackUseMcpToolCall({
|
|
4660
4890
|
toolName: name,
|
|
@@ -4666,7 +4896,7 @@ function useMcpOperations(params) {
|
|
|
4666
4896
|
throw error;
|
|
4667
4897
|
}
|
|
4668
4898
|
},
|
|
4669
|
-
[params.addLog]
|
|
4899
|
+
[params.addLog, params.onAuthorizationRequired]
|
|
4670
4900
|
);
|
|
4671
4901
|
const listResources = useCallback(async () => {
|
|
4672
4902
|
const connection = requireConnection(params, "list resources");
|
|
@@ -4842,6 +5072,7 @@ function useMcp(options) {
|
|
|
4842
5072
|
reconnectionOptions,
|
|
4843
5073
|
preventAutoAuth = true,
|
|
4844
5074
|
// Default to true - require explicit user action for OAuth
|
|
5075
|
+
detectMixedAuth = true,
|
|
4845
5076
|
useRedirectFlow = false,
|
|
4846
5077
|
// Default to false for backward compatibility (use popup)
|
|
4847
5078
|
onPopupWindow,
|
|
@@ -5019,6 +5250,7 @@ function useMcp(options) {
|
|
|
5019
5250
|
const [log, setLog] = useState([]);
|
|
5020
5251
|
const [authUrl, setAuthUrl] = useState(void 0);
|
|
5021
5252
|
const [authTokens, setAuthTokens] = useState(void 0);
|
|
5253
|
+
const [authorization, setAuthorization] = useState(void 0);
|
|
5022
5254
|
const clientRef = useRef(null);
|
|
5023
5255
|
const connectionRef = useRef(null);
|
|
5024
5256
|
const authProviderRef = useRef(
|
|
@@ -5033,6 +5265,9 @@ function useMcp(options) {
|
|
|
5033
5265
|
const retryScheduledRef = useRef(false);
|
|
5034
5266
|
const popupFlowActiveRef = useRef(false);
|
|
5035
5267
|
const stateRef = useRef(state);
|
|
5268
|
+
const authorizationRef = useRef(authorization);
|
|
5269
|
+
const authorizationServerUrlRef = useRef(url);
|
|
5270
|
+
authorizationRef.current = authorization;
|
|
5036
5271
|
const autoReconnectRef = useRef(autoReconnect);
|
|
5037
5272
|
const successfulTransportRef = useRef(null);
|
|
5038
5273
|
const connectRef = useRef(null);
|
|
@@ -5099,6 +5334,24 @@ function useMcp(options) {
|
|
|
5099
5334
|
},
|
|
5100
5335
|
[instanceLogger]
|
|
5101
5336
|
);
|
|
5337
|
+
const onAuthorizationRequired = useCallback2(
|
|
5338
|
+
(authError) => {
|
|
5339
|
+
const preparedAuthUrl = authProviderRef.current?.getLastAttemptedAuthUrl?.() ?? void 0;
|
|
5340
|
+
addLog(
|
|
5341
|
+
"info",
|
|
5342
|
+
"This server requires OAuth for the requested operation; waiting for authentication.",
|
|
5343
|
+
authError
|
|
5344
|
+
);
|
|
5345
|
+
const authorizationRequired = {
|
|
5346
|
+
...authorizationRef.current ?? { mode: "mixed" },
|
|
5347
|
+
authenticated: false
|
|
5348
|
+
};
|
|
5349
|
+
authorizationRef.current = authorizationRequired;
|
|
5350
|
+
setAuthorization(authorizationRequired);
|
|
5351
|
+
if (preparedAuthUrl) setAuthUrl(preparedAuthUrl);
|
|
5352
|
+
},
|
|
5353
|
+
[addLog]
|
|
5354
|
+
);
|
|
5102
5355
|
const connectionOperations = useMcpOperations({
|
|
5103
5356
|
stateRef,
|
|
5104
5357
|
connectionRef,
|
|
@@ -5109,7 +5362,8 @@ function useMcp(options) {
|
|
|
5109
5362
|
setResourceTemplates,
|
|
5110
5363
|
setPrompts,
|
|
5111
5364
|
setSkills,
|
|
5112
|
-
addLog
|
|
5365
|
+
addLog,
|
|
5366
|
+
onAuthorizationRequired
|
|
5113
5367
|
});
|
|
5114
5368
|
const disconnect = useCallback2(
|
|
5115
5369
|
async (quiet = false) => {
|
|
@@ -5250,6 +5504,11 @@ function useMcp(options) {
|
|
|
5250
5504
|
connectingRef.current = true;
|
|
5251
5505
|
connectEpochRef.current += 1;
|
|
5252
5506
|
connectAttemptRef.current += 1;
|
|
5507
|
+
if (authorizationServerUrlRef.current !== url) {
|
|
5508
|
+
authorizationServerUrlRef.current = url;
|
|
5509
|
+
authorizationRef.current = void 0;
|
|
5510
|
+
setAuthorization(void 0);
|
|
5511
|
+
}
|
|
5253
5512
|
setError(void 0);
|
|
5254
5513
|
setAuthUrl(void 0);
|
|
5255
5514
|
successfulTransportRef.current = null;
|
|
@@ -5336,6 +5595,7 @@ function useMcp(options) {
|
|
|
5336
5595
|
// Protocol era negotiation mode ("legacy" | "auto" | { pin }); the
|
|
5337
5596
|
// connector defaults to automatic v1/v2 negotiation.
|
|
5338
5597
|
...protocolNegotiation !== void 0 && { protocolNegotiation },
|
|
5598
|
+
detectMixedAuth,
|
|
5339
5599
|
// Pass user-configurable reconnection options, or when autoReconnect
|
|
5340
5600
|
// is disabled, disable SDK transport reconnection to prevent
|
|
5341
5601
|
// unwanted GET polling requests
|
|
@@ -5501,8 +5761,13 @@ function useMcp(options) {
|
|
|
5501
5761
|
protocolEra: protocolEra2,
|
|
5502
5762
|
protocolVersion: protocolVersion2,
|
|
5503
5763
|
instructions: instructions2,
|
|
5504
|
-
extensions: extensions2
|
|
5764
|
+
extensions: extensions2,
|
|
5765
|
+
authorization: connectionAuthorization
|
|
5505
5766
|
} = connection.info;
|
|
5767
|
+
if (connectionAuthorization) {
|
|
5768
|
+
setAuthorization(connectionAuthorization);
|
|
5769
|
+
authorizationRef.current = connectionAuthorization;
|
|
5770
|
+
}
|
|
5506
5771
|
if (isMountedRef.current) {
|
|
5507
5772
|
setProtocolEra(protocolEra2);
|
|
5508
5773
|
setProtocolVersion(protocolVersion2);
|
|
@@ -5553,6 +5818,14 @@ function useMcp(options) {
|
|
|
5553
5818
|
return "failed";
|
|
5554
5819
|
}
|
|
5555
5820
|
if (tokens?.access_token) {
|
|
5821
|
+
if (authorizationRef.current?.mode === "mixed") {
|
|
5822
|
+
const authenticatedAuthorization = {
|
|
5823
|
+
...authorizationRef.current,
|
|
5824
|
+
authenticated: true
|
|
5825
|
+
};
|
|
5826
|
+
setAuthorization(authenticatedAuthorization);
|
|
5827
|
+
authorizationRef.current = authenticatedAuthorization;
|
|
5828
|
+
}
|
|
5556
5829
|
const expiresAt = getOAuthTokenExpiry(tokens);
|
|
5557
5830
|
let tokenEndpoint = null;
|
|
5558
5831
|
let resource = null;
|
|
@@ -5732,6 +6005,7 @@ function useMcp(options) {
|
|
|
5732
6005
|
headers,
|
|
5733
6006
|
transportType,
|
|
5734
6007
|
preventAutoAuth,
|
|
6008
|
+
detectMixedAuth,
|
|
5735
6009
|
useRedirectFlow,
|
|
5736
6010
|
onPopupWindow,
|
|
5737
6011
|
enabled,
|
|
@@ -5768,11 +6042,12 @@ function useMcp(options) {
|
|
|
5768
6042
|
const authenticate = useCallback2(async () => {
|
|
5769
6043
|
addLog("info", "Manual authentication requested...");
|
|
5770
6044
|
const currentState = stateRef.current;
|
|
6045
|
+
const isOptionalMixedAuthentication = currentState === "ready" && authorizationRef.current?.mode === "mixed";
|
|
5771
6046
|
if (currentState === "failed") {
|
|
5772
6047
|
addLog("info", "Attempting to reconnect and authenticate via retry...");
|
|
5773
6048
|
retry();
|
|
5774
|
-
} else if (currentState === "pending_auth") {
|
|
5775
|
-
addLog("info", "Proceeding with authentication
|
|
6049
|
+
} else if (currentState === "pending_auth" || currentState === "ready" && authorizationRef.current?.mode === "mixed" && !authorizationRef.current.authenticated) {
|
|
6050
|
+
addLog("info", "Proceeding with authentication...");
|
|
5776
6051
|
try {
|
|
5777
6052
|
assert(
|
|
5778
6053
|
authProviderRef.current,
|
|
@@ -5891,16 +6166,16 @@ function useMcp(options) {
|
|
|
5891
6166
|
case "cancelled":
|
|
5892
6167
|
addLog(
|
|
5893
6168
|
"warn",
|
|
5894
|
-
"Authentication popup was closed before completing. Returning to pending_auth."
|
|
6169
|
+
isOptionalMixedAuthentication ? "Authentication popup was closed before completing. Public tools remain available." : "Authentication popup was closed before completing. Returning to pending_auth."
|
|
5895
6170
|
);
|
|
5896
|
-
setState("pending_auth");
|
|
6171
|
+
setState(isOptionalMixedAuthentication ? "ready" : "pending_auth");
|
|
5897
6172
|
break;
|
|
5898
6173
|
case "timeout":
|
|
5899
6174
|
addLog(
|
|
5900
6175
|
"warn",
|
|
5901
|
-
"Authentication timed out waiting for the popup. Returning to pending_auth."
|
|
6176
|
+
isOptionalMixedAuthentication ? "Authentication timed out waiting for the popup. Public tools remain available." : "Authentication timed out waiting for the popup. Returning to pending_auth."
|
|
5902
6177
|
);
|
|
5903
|
-
setState("pending_auth");
|
|
6178
|
+
setState(isOptionalMixedAuthentication ? "ready" : "pending_auth");
|
|
5904
6179
|
break;
|
|
5905
6180
|
case "error":
|
|
5906
6181
|
failConnection(`Authentication failed: ${result.error}`);
|
|
@@ -6189,6 +6464,7 @@ function useMcp(options) {
|
|
|
6189
6464
|
log,
|
|
6190
6465
|
authUrl,
|
|
6191
6466
|
authTokens,
|
|
6467
|
+
authorization,
|
|
6192
6468
|
client: clientRef.current,
|
|
6193
6469
|
...connectionOperations,
|
|
6194
6470
|
retry,
|
|
@@ -6598,7 +6874,7 @@ function isSameMcpServer(left, right) {
|
|
|
6598
6874
|
return left.id === right.id && sameSerializedValue(
|
|
6599
6875
|
pickLiveServerConfig(left),
|
|
6600
6876
|
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(
|
|
6877
|
+
) && 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
6878
|
left.pendingSamplingRequests,
|
|
6603
6879
|
right.pendingSamplingRequests
|
|
6604
6880
|
) && sameSerializedValue(
|
|
@@ -6780,6 +7056,7 @@ function McpServerWrapper({
|
|
|
6780
7056
|
mcp.instructions,
|
|
6781
7057
|
mcp.extensions,
|
|
6782
7058
|
mcp.authTokens,
|
|
7059
|
+
mcp.authorization,
|
|
6783
7060
|
// Functions excluded - they're stable via useCallback in useMcp
|
|
6784
7061
|
// mcp.log excluded - log changes shouldn't trigger provider updates
|
|
6785
7062
|
// mcp.client excluded - client reference stability handled by manual check
|
|
@@ -8991,6 +9268,7 @@ export {
|
|
|
8991
9268
|
getAllRpcLogs,
|
|
8992
9269
|
getRpcLogs,
|
|
8993
9270
|
getViewResourceUri,
|
|
9271
|
+
isOAuthInteractionRequired,
|
|
8994
9272
|
isToolVisibleToModel,
|
|
8995
9273
|
isViewResource,
|
|
8996
9274
|
isViewTool,
|