@mcp-use/client 2.0.0-beta.8 → 2.0.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/README.md +18 -19
- package/dist/.tsbuildinfo +1 -1
- package/dist/auth/browser.d.ts +62 -6
- package/dist/auth/browser.d.ts.map +1 -1
- package/dist/auth/callback.d.ts.map +1 -1
- package/dist/auth/flow.d.ts.map +1 -1
- package/dist/auth/node.d.ts +115 -5
- package/dist/auth/node.d.ts.map +1 -1
- package/dist/auth/session-store.d.ts +7 -1
- package/dist/auth/session-store.d.ts.map +1 -1
- package/dist/auth/storage.d.ts +12 -3
- package/dist/auth/storage.d.ts.map +1 -1
- package/dist/code-mode/executor-e2b.d.ts.map +1 -1
- package/dist/core/base.d.ts +4 -8
- package/dist/core/base.d.ts.map +1 -1
- package/dist/core/browser.d.ts +14 -2
- package/dist/core/browser.d.ts.map +1 -1
- package/dist/core/config.d.ts +82 -20
- package/dist/core/config.d.ts.map +1 -1
- package/dist/core/node.d.ts +7 -10
- package/dist/core/node.d.ts.map +1 -1
- package/dist/core/session.d.ts +102 -25
- package/dist/core/session.d.ts.map +1 -1
- package/dist/index-browser.d.ts +1 -0
- package/dist/index-browser.d.ts.map +1 -1
- package/dist/index-browser.js +568 -163
- package/dist/index-browser.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +407 -92
- package/dist/index.js.map +1 -1
- package/dist/react/McpClientProvider.d.ts +22 -10
- package/dist/react/McpClientProvider.d.ts.map +1 -1
- package/dist/react/index.d.ts +5 -2
- package/dist/react/index.d.ts.map +1 -1
- package/dist/react/index.js +1104 -368
- package/dist/react/index.js.map +1 -1
- package/dist/react/rpc-logger.d.ts +5 -0
- package/dist/react/rpc-logger.d.ts.map +1 -1
- package/dist/react/storage.d.ts +57 -10
- package/dist/react/storage.d.ts.map +1 -1
- package/dist/react/token-expiry.d.ts +9 -0
- package/dist/react/token-expiry.d.ts.map +1 -0
- package/dist/react/types.d.ts +134 -22
- package/dist/react/types.d.ts.map +1 -1
- package/dist/react/useMcp-helpers.d.ts.map +1 -1
- package/dist/react/useMcp-operations.d.ts +24 -6
- package/dist/react/useMcp-operations.d.ts.map +1 -1
- package/dist/react/useMcp.d.ts.map +1 -1
- package/dist/react/useMcpServerQueues.d.ts +36 -9
- package/dist/react/useMcpServerQueues.d.ts.map +1 -1
- package/dist/react/view/ViewRenderer.d.ts +12 -4
- package/dist/react/view/ViewRenderer.d.ts.map +1 -1
- package/dist/react/view/ext-apps-bridge.d.ts +1 -1
- package/dist/react/view/ext-apps-bridge.d.ts.map +1 -1
- package/dist/react/view/inject-openai-file-apis.d.ts.map +1 -1
- package/dist/react/view/parse-custom-props.d.ts +8 -0
- package/dist/react/view/parse-custom-props.d.ts.map +1 -1
- package/dist/react/view/resolve-view-resource.d.ts +10 -0
- package/dist/react/view/resolve-view-resource.d.ts.map +1 -1
- package/dist/react/view/sandbox-blob-url.d.ts +2 -0
- package/dist/react/view/sandbox-blob-url.d.ts.map +1 -1
- package/dist/react/view/types.d.ts +111 -4
- package/dist/react/view/types.d.ts.map +1 -1
- package/dist/react/view/view-detection.d.ts +18 -0
- package/dist/react/view/view-detection.d.ts.map +1 -1
- package/dist/react/view/view-host-policy.d.ts +61 -0
- package/dist/react/view/view-host-policy.d.ts.map +1 -0
- package/dist/sandbox.d.ts +2 -0
- package/dist/sandbox.d.ts.map +1 -0
- package/dist/sandbox.js +283 -0
- package/dist/sandbox.js.map +1 -0
- package/dist/telemetry/events.d.ts.map +1 -1
- package/dist/telemetry/tel-fetch.d.ts.map +1 -1
- package/dist/telemetry/telemetry.d.ts +5 -1
- package/dist/telemetry/telemetry.d.ts.map +1 -1
- package/dist/transport/base.d.ts +169 -34
- package/dist/transport/base.d.ts.map +1 -1
- package/dist/transport/connection-manager.d.ts +5 -1
- package/dist/transport/connection-manager.d.ts.map +1 -1
- package/dist/transport/http.d.ts +51 -2
- package/dist/transport/http.d.ts.map +1 -1
- package/dist/transport/stdio.d.ts +29 -1
- package/dist/transport/stdio.d.ts.map +1 -1
- package/dist/utils/elicitation.d.ts +4 -2
- package/dist/utils/elicitation.d.ts.map +1 -1
- package/dist/utils/logging.d.ts +1 -0
- package/dist/utils/logging.d.ts.map +1 -1
- package/dist/utils/version.d.ts +6 -0
- package/dist/utils/version.d.ts.map +1 -1
- package/package.json +9 -3
package/dist/react/index.js
CHANGED
|
@@ -251,6 +251,67 @@ var init_rpc_logger = __esm({
|
|
|
251
251
|
}
|
|
252
252
|
});
|
|
253
253
|
|
|
254
|
+
// src/react/types.ts
|
|
255
|
+
var PERSISTED_SERVER_CONFIG_KEYS = [
|
|
256
|
+
"url",
|
|
257
|
+
"displayName",
|
|
258
|
+
"enabled",
|
|
259
|
+
"oauthProxyUrl",
|
|
260
|
+
"connectionMode",
|
|
261
|
+
"autoProxyFallback",
|
|
262
|
+
"callbackUrl",
|
|
263
|
+
"storageKeyPrefix",
|
|
264
|
+
"logLevel",
|
|
265
|
+
"autoRetry",
|
|
266
|
+
"autoReconnect",
|
|
267
|
+
"reconnectionOptions",
|
|
268
|
+
"popupFeatures",
|
|
269
|
+
"preventAutoAuth",
|
|
270
|
+
"useRedirectFlow",
|
|
271
|
+
"protocolNegotiation",
|
|
272
|
+
"timeout",
|
|
273
|
+
"clientInfo"
|
|
274
|
+
];
|
|
275
|
+
function pickPersistedServerConfig(source) {
|
|
276
|
+
const out = {};
|
|
277
|
+
for (const key of PERSISTED_SERVER_CONFIG_KEYS) {
|
|
278
|
+
const value = source[key];
|
|
279
|
+
if (value !== void 0) {
|
|
280
|
+
out[key] = value;
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
if (source.proxyConfig?.proxyAddress !== void 0) {
|
|
284
|
+
out.proxyConfig = { proxyAddress: source.proxyConfig.proxyAddress };
|
|
285
|
+
}
|
|
286
|
+
if (source.oauth) {
|
|
287
|
+
const oauth = {};
|
|
288
|
+
if (source.oauth.clientId !== void 0) {
|
|
289
|
+
oauth.clientId = source.oauth.clientId;
|
|
290
|
+
}
|
|
291
|
+
if (source.oauth.clientMetadataUrl !== void 0) {
|
|
292
|
+
oauth.clientMetadataUrl = source.oauth.clientMetadataUrl;
|
|
293
|
+
}
|
|
294
|
+
if (source.oauth.scope !== void 0) {
|
|
295
|
+
oauth.scope = source.oauth.scope;
|
|
296
|
+
}
|
|
297
|
+
if (Object.keys(oauth).length > 0) {
|
|
298
|
+
out.oauth = oauth;
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
return out;
|
|
302
|
+
}
|
|
303
|
+
function pickLiveServerConfig(source) {
|
|
304
|
+
return {
|
|
305
|
+
...pickPersistedServerConfig(source),
|
|
306
|
+
...source.headers !== void 0 ? { headers: source.headers } : {},
|
|
307
|
+
...source.proxyConfig !== void 0 ? { proxyConfig: source.proxyConfig } : {},
|
|
308
|
+
...source.clientOptions !== void 0 ? { clientOptions: source.clientOptions } : {}
|
|
309
|
+
};
|
|
310
|
+
}
|
|
311
|
+
function toPersistedServerConfig(config) {
|
|
312
|
+
return pickPersistedServerConfig(config);
|
|
313
|
+
}
|
|
314
|
+
|
|
254
315
|
// src/react/useMcp.ts
|
|
255
316
|
import { auth as auth2 } from "@modelcontextprotocol/client";
|
|
256
317
|
|
|
@@ -456,6 +517,11 @@ var BaseConnector = class {
|
|
|
456
517
|
notificationHandlers = [];
|
|
457
518
|
rootsCache = [];
|
|
458
519
|
activeProgressHandlers = /* @__PURE__ */ new Set();
|
|
520
|
+
/**
|
|
521
|
+
* Creates a connector with shared SDK and callback options.
|
|
522
|
+
*
|
|
523
|
+
* @param opts - Connector initialization options.
|
|
524
|
+
*/
|
|
459
525
|
constructor(opts = {}) {
|
|
460
526
|
this.opts = opts;
|
|
461
527
|
if (opts.roots) {
|
|
@@ -638,7 +704,9 @@ var BaseConnector = class {
|
|
|
638
704
|
}
|
|
639
705
|
}
|
|
640
706
|
/**
|
|
641
|
-
*
|
|
707
|
+
* Returns the roots currently advertised to the server.
|
|
708
|
+
*
|
|
709
|
+
* @returns A copy of the configured roots.
|
|
642
710
|
*/
|
|
643
711
|
getRoots() {
|
|
644
712
|
return [...this.rootsCache];
|
|
@@ -707,7 +775,11 @@ var BaseConnector = class {
|
|
|
707
775
|
"setupElicitationHandler: Elicitation handler registered successfully"
|
|
708
776
|
);
|
|
709
777
|
}
|
|
710
|
-
/**
|
|
778
|
+
/**
|
|
779
|
+
* Disconnects the SDK client and releases transport resources.
|
|
780
|
+
*
|
|
781
|
+
* @returns A promise that resolves after cleanup completes.
|
|
782
|
+
*/
|
|
711
783
|
async disconnect() {
|
|
712
784
|
if (!this.connected) {
|
|
713
785
|
logger.debug("Not connected to MCP implementation");
|
|
@@ -718,7 +790,7 @@ var BaseConnector = class {
|
|
|
718
790
|
this.connected = false;
|
|
719
791
|
logger.debug("Disconnected from MCP implementation");
|
|
720
792
|
}
|
|
721
|
-
/**
|
|
793
|
+
/** Whether an SDK client currently exists for this connector. */
|
|
722
794
|
get isClientConnected() {
|
|
723
795
|
return this.client != null;
|
|
724
796
|
}
|
|
@@ -728,6 +800,10 @@ var BaseConnector = class {
|
|
|
728
800
|
* In the SDK, `Client.connect(transport)` automatically performs the
|
|
729
801
|
* protocol‑level `initialize` handshake, so we only need to cache the list of
|
|
730
802
|
* tools and expose some server info.
|
|
803
|
+
*
|
|
804
|
+
* @param defaultRequestOptions - Options used while fetching the initial tool list.
|
|
805
|
+
* @returns The capabilities advertised by the server.
|
|
806
|
+
* @throws When {@link BaseConnector.connect} has not completed.
|
|
731
807
|
*/
|
|
732
808
|
async initialize(defaultRequestOptions = this.opts.defaultRequestOptions ?? {}) {
|
|
733
809
|
if (!this.client) {
|
|
@@ -765,18 +841,22 @@ var BaseConnector = class {
|
|
|
765
841
|
logger.debug("Server info:", serverInfo);
|
|
766
842
|
return capabilities;
|
|
767
843
|
}
|
|
768
|
-
/**
|
|
844
|
+
/**
|
|
845
|
+
* Returns the tool list cached during initialization.
|
|
846
|
+
*
|
|
847
|
+
* @throws When {@link BaseConnector.initialize} has not completed.
|
|
848
|
+
*/
|
|
769
849
|
get tools() {
|
|
770
850
|
if (!this.toolsCache) {
|
|
771
851
|
throw new Error("MCP client is not initialized; call initialize() first");
|
|
772
852
|
}
|
|
773
853
|
return this.toolsCache;
|
|
774
854
|
}
|
|
775
|
-
/**
|
|
855
|
+
/** Capabilities cached during initialization, or an empty object. */
|
|
776
856
|
get serverCapabilities() {
|
|
777
857
|
return this.capabilitiesCache || {};
|
|
778
858
|
}
|
|
779
|
-
/**
|
|
859
|
+
/** Server identity cached during initialization, or `null`. */
|
|
780
860
|
get serverInfo() {
|
|
781
861
|
return this.serverInfoCache;
|
|
782
862
|
}
|
|
@@ -797,7 +877,15 @@ var BaseConnector = class {
|
|
|
797
877
|
get negotiatedProtocolVersion() {
|
|
798
878
|
return this.client?.getNegotiatedProtocolVersion?.();
|
|
799
879
|
}
|
|
800
|
-
/**
|
|
880
|
+
/**
|
|
881
|
+
* Calls a tool on the connected server.
|
|
882
|
+
*
|
|
883
|
+
* @param name - Tool name.
|
|
884
|
+
* @param args - Tool arguments.
|
|
885
|
+
* @param options - Per-request timeout, cancellation, and progress options.
|
|
886
|
+
* @returns The tool result returned by the server.
|
|
887
|
+
* @throws When the connector is not connected or the tool call fails.
|
|
888
|
+
*/
|
|
801
889
|
async callTool(name, args, options) {
|
|
802
890
|
if (!this.client) {
|
|
803
891
|
throw new Error("MCP client is not connected");
|
|
@@ -922,7 +1010,13 @@ var BaseConnector = class {
|
|
|
922
1010
|
);
|
|
923
1011
|
return result;
|
|
924
1012
|
}
|
|
925
|
-
/**
|
|
1013
|
+
/**
|
|
1014
|
+
* Reads a resource by URI.
|
|
1015
|
+
*
|
|
1016
|
+
* @param uri - Resource URI to read.
|
|
1017
|
+
* @param options - Per-request options.
|
|
1018
|
+
* @returns The resource contents returned by the server.
|
|
1019
|
+
*/
|
|
926
1020
|
async readResource(uri, options) {
|
|
927
1021
|
if (!this.client) {
|
|
928
1022
|
throw new Error("MCP client is not connected");
|
|
@@ -957,6 +1051,11 @@ var BaseConnector = class {
|
|
|
957
1051
|
logger.debug(`Unsubscribing from resource: ${uri}`);
|
|
958
1052
|
return await this.client.unsubscribeResource({ uri }, options);
|
|
959
1053
|
}
|
|
1054
|
+
/**
|
|
1055
|
+
* Lists prompts exposed by the server.
|
|
1056
|
+
*
|
|
1057
|
+
* @returns The prompt list, or an empty list when prompts are unsupported.
|
|
1058
|
+
*/
|
|
960
1059
|
async listPrompts() {
|
|
961
1060
|
if (!this.client) {
|
|
962
1061
|
throw new Error("MCP client is not connected");
|
|
@@ -977,6 +1076,13 @@ var BaseConnector = class {
|
|
|
977
1076
|
throw err;
|
|
978
1077
|
}
|
|
979
1078
|
}
|
|
1079
|
+
/**
|
|
1080
|
+
* Gets a prompt with the supplied arguments.
|
|
1081
|
+
*
|
|
1082
|
+
* @param name - Prompt name.
|
|
1083
|
+
* @param args - Prompt arguments.
|
|
1084
|
+
* @returns The rendered prompt returned by the server.
|
|
1085
|
+
*/
|
|
980
1086
|
async getPrompt(name, args) {
|
|
981
1087
|
if (!this.client) {
|
|
982
1088
|
throw new Error("MCP client is not connected");
|
|
@@ -984,7 +1090,14 @@ var BaseConnector = class {
|
|
|
984
1090
|
logger.debug(`Getting prompt ${name}`);
|
|
985
1091
|
return await this.client.getPrompt({ name, arguments: args });
|
|
986
1092
|
}
|
|
987
|
-
/**
|
|
1093
|
+
/**
|
|
1094
|
+
* Sends a raw, potentially non-standard request through the SDK client.
|
|
1095
|
+
*
|
|
1096
|
+
* @param method - JSON-RPC method name.
|
|
1097
|
+
* @param params - Request parameters. Defaults to an empty object.
|
|
1098
|
+
* @param options - Per-request options.
|
|
1099
|
+
* @returns The unvalidated result returned by the server.
|
|
1100
|
+
*/
|
|
988
1101
|
async request(method, params = null, options) {
|
|
989
1102
|
if (!this.client) {
|
|
990
1103
|
throw new Error("MCP client is not connected");
|
|
@@ -1083,6 +1196,12 @@ var HttpConnector = class extends BaseConnector {
|
|
|
1083
1196
|
reconnectionOptions;
|
|
1084
1197
|
transportType = null;
|
|
1085
1198
|
streamableTransport = null;
|
|
1199
|
+
/**
|
|
1200
|
+
* Creates an HTTP connector.
|
|
1201
|
+
*
|
|
1202
|
+
* @param baseUrl - MCP endpoint URL.
|
|
1203
|
+
* @param opts - Authentication, transport, SDK, and reconnection options.
|
|
1204
|
+
*/
|
|
1086
1205
|
constructor(baseUrl, opts = {}) {
|
|
1087
1206
|
super(opts);
|
|
1088
1207
|
const originalUrl = baseUrl.replace(/\/$/, "");
|
|
@@ -1200,7 +1319,12 @@ var HttpConnector = class extends BaseConnector {
|
|
|
1200
1319
|
}
|
|
1201
1320
|
return { fallbackReason, is401Error, httpStatusCode };
|
|
1202
1321
|
}
|
|
1203
|
-
/**
|
|
1322
|
+
/**
|
|
1323
|
+
* Establishes a streamable HTTP connection to the MCP server.
|
|
1324
|
+
*
|
|
1325
|
+
* @returns A promise that resolves after protocol negotiation completes.
|
|
1326
|
+
* @throws An error with `code: 401` when authentication is required.
|
|
1327
|
+
*/
|
|
1204
1328
|
async connect() {
|
|
1205
1329
|
if (this.connected) {
|
|
1206
1330
|
logger.debug("Already connected to MCP implementation");
|
|
@@ -1428,6 +1552,11 @@ var HttpConnector = class extends BaseConnector {
|
|
|
1428
1552
|
throw err;
|
|
1429
1553
|
}
|
|
1430
1554
|
}
|
|
1555
|
+
/**
|
|
1556
|
+
* Returns fields that identify the endpoint and negotiated transport.
|
|
1557
|
+
*
|
|
1558
|
+
* @returns HTTP connector identity metadata.
|
|
1559
|
+
*/
|
|
1431
1560
|
get publicIdentifier() {
|
|
1432
1561
|
return {
|
|
1433
1562
|
type: "http",
|
|
@@ -1436,7 +1565,11 @@ var HttpConnector = class extends BaseConnector {
|
|
|
1436
1565
|
protocolEra: this.protocolEra ?? "unknown"
|
|
1437
1566
|
};
|
|
1438
1567
|
}
|
|
1439
|
-
/**
|
|
1568
|
+
/**
|
|
1569
|
+
* Returns the active transport type.
|
|
1570
|
+
*
|
|
1571
|
+
* @returns `"streamable-http"` after connection, otherwise `null`.
|
|
1572
|
+
*/
|
|
1440
1573
|
getTransportType() {
|
|
1441
1574
|
return this.transportType;
|
|
1442
1575
|
}
|
|
@@ -1473,7 +1606,7 @@ var HttpConnector = class extends BaseConnector {
|
|
|
1473
1606
|
};
|
|
1474
1607
|
|
|
1475
1608
|
// src/utils/version.ts
|
|
1476
|
-
var VERSION = "2.0.0
|
|
1609
|
+
var VERSION = "2.0.0";
|
|
1477
1610
|
function getPackageVersion() {
|
|
1478
1611
|
return VERSION;
|
|
1479
1612
|
}
|
|
@@ -1544,6 +1677,186 @@ function resolveClientOptions(clientOptions) {
|
|
|
1544
1677
|
};
|
|
1545
1678
|
}
|
|
1546
1679
|
|
|
1680
|
+
// src/auth/storage.ts
|
|
1681
|
+
var AUTH_CRYPTO_DATABASE = "mcp-use-oauth-crypto";
|
|
1682
|
+
var AUTH_CRYPTO_STORE = "keys";
|
|
1683
|
+
var AUTH_CRYPTO_KEY = "aes-gcm-v1";
|
|
1684
|
+
var textEncoder = new TextEncoder();
|
|
1685
|
+
var textDecoder = new TextDecoder();
|
|
1686
|
+
var LocalStorageKVStore = class {
|
|
1687
|
+
fallback = /* @__PURE__ */ new Map();
|
|
1688
|
+
keyPromise;
|
|
1689
|
+
durable = true;
|
|
1690
|
+
async get(key) {
|
|
1691
|
+
if (!this.durable) return this.fallback.get(key) ?? null;
|
|
1692
|
+
let stored;
|
|
1693
|
+
try {
|
|
1694
|
+
stored = localStorage.getItem(key);
|
|
1695
|
+
} catch {
|
|
1696
|
+
this.durable = false;
|
|
1697
|
+
return this.fallback.get(key) ?? null;
|
|
1698
|
+
}
|
|
1699
|
+
if (stored === null) return null;
|
|
1700
|
+
const envelope = parseEncryptedEnvelope(stored);
|
|
1701
|
+
if (!envelope) {
|
|
1702
|
+
await this.set(key, stored);
|
|
1703
|
+
return stored;
|
|
1704
|
+
}
|
|
1705
|
+
try {
|
|
1706
|
+
const cryptoKey = await this.getCryptoKey();
|
|
1707
|
+
const plaintext = await globalThis.crypto.subtle.decrypt(
|
|
1708
|
+
{
|
|
1709
|
+
name: "AES-GCM",
|
|
1710
|
+
iv: decodeBase64(envelope.iv),
|
|
1711
|
+
additionalData: textEncoder.encode(key)
|
|
1712
|
+
},
|
|
1713
|
+
cryptoKey,
|
|
1714
|
+
decodeBase64(envelope.ciphertext)
|
|
1715
|
+
);
|
|
1716
|
+
return textDecoder.decode(plaintext);
|
|
1717
|
+
} catch {
|
|
1718
|
+
await this.remove(key);
|
|
1719
|
+
return null;
|
|
1720
|
+
}
|
|
1721
|
+
}
|
|
1722
|
+
async set(key, value) {
|
|
1723
|
+
if (!this.durable) {
|
|
1724
|
+
this.fallback.set(key, value);
|
|
1725
|
+
return;
|
|
1726
|
+
}
|
|
1727
|
+
try {
|
|
1728
|
+
const cryptoKey = await this.getCryptoKey();
|
|
1729
|
+
const iv = globalThis.crypto.getRandomValues(new Uint8Array(12));
|
|
1730
|
+
const ciphertext = await globalThis.crypto.subtle.encrypt(
|
|
1731
|
+
{
|
|
1732
|
+
name: "AES-GCM",
|
|
1733
|
+
iv,
|
|
1734
|
+
additionalData: textEncoder.encode(key)
|
|
1735
|
+
},
|
|
1736
|
+
cryptoKey,
|
|
1737
|
+
textEncoder.encode(value)
|
|
1738
|
+
);
|
|
1739
|
+
const envelope = {
|
|
1740
|
+
v: 1,
|
|
1741
|
+
alg: "A256GCM",
|
|
1742
|
+
iv: encodeBase64(iv),
|
|
1743
|
+
ciphertext: encodeBase64(new Uint8Array(ciphertext))
|
|
1744
|
+
};
|
|
1745
|
+
localStorage.setItem(key, JSON.stringify(envelope));
|
|
1746
|
+
this.fallback.delete(key);
|
|
1747
|
+
} catch {
|
|
1748
|
+
this.durable = false;
|
|
1749
|
+
try {
|
|
1750
|
+
localStorage.removeItem(key);
|
|
1751
|
+
} catch {
|
|
1752
|
+
}
|
|
1753
|
+
this.fallback.set(key, value);
|
|
1754
|
+
}
|
|
1755
|
+
}
|
|
1756
|
+
remove(key) {
|
|
1757
|
+
this.fallback.delete(key);
|
|
1758
|
+
try {
|
|
1759
|
+
localStorage.removeItem(key);
|
|
1760
|
+
} catch {
|
|
1761
|
+
this.durable = false;
|
|
1762
|
+
}
|
|
1763
|
+
}
|
|
1764
|
+
keys() {
|
|
1765
|
+
const out = new Set(this.fallback.keys());
|
|
1766
|
+
if (this.durable) {
|
|
1767
|
+
try {
|
|
1768
|
+
for (let i = 0; i < localStorage.length; i++) {
|
|
1769
|
+
const key = localStorage.key(i);
|
|
1770
|
+
if (key) out.add(key);
|
|
1771
|
+
}
|
|
1772
|
+
} catch {
|
|
1773
|
+
this.durable = false;
|
|
1774
|
+
}
|
|
1775
|
+
}
|
|
1776
|
+
return [...out];
|
|
1777
|
+
}
|
|
1778
|
+
getCryptoKey() {
|
|
1779
|
+
this.keyPromise ??= getOrCreateCryptoKey();
|
|
1780
|
+
return this.keyPromise;
|
|
1781
|
+
}
|
|
1782
|
+
};
|
|
1783
|
+
function parseEncryptedEnvelope(value) {
|
|
1784
|
+
try {
|
|
1785
|
+
const parsed = JSON.parse(value);
|
|
1786
|
+
if (!parsed || typeof parsed !== "object" || !("v" in parsed) || parsed.v !== 1 || !("alg" in parsed) || parsed.alg !== "A256GCM" || !("iv" in parsed) || typeof parsed.iv !== "string" || !("ciphertext" in parsed) || typeof parsed.ciphertext !== "string") {
|
|
1787
|
+
return void 0;
|
|
1788
|
+
}
|
|
1789
|
+
return parsed;
|
|
1790
|
+
} catch {
|
|
1791
|
+
return void 0;
|
|
1792
|
+
}
|
|
1793
|
+
}
|
|
1794
|
+
async function getOrCreateCryptoKey() {
|
|
1795
|
+
if (!globalThis.crypto?.subtle || typeof indexedDB === "undefined") {
|
|
1796
|
+
throw new Error("Durable browser cryptography is unavailable");
|
|
1797
|
+
}
|
|
1798
|
+
const candidate = await globalThis.crypto.subtle.generateKey(
|
|
1799
|
+
{ name: "AES-GCM", length: 256 },
|
|
1800
|
+
false,
|
|
1801
|
+
["encrypt", "decrypt"]
|
|
1802
|
+
);
|
|
1803
|
+
const database = await openCryptoDatabase();
|
|
1804
|
+
try {
|
|
1805
|
+
return await new Promise((resolve, reject) => {
|
|
1806
|
+
const transaction = database.transaction(AUTH_CRYPTO_STORE, "readwrite");
|
|
1807
|
+
const store = transaction.objectStore(AUTH_CRYPTO_STORE);
|
|
1808
|
+
const request = store.get(AUTH_CRYPTO_KEY);
|
|
1809
|
+
let selected;
|
|
1810
|
+
request.onsuccess = () => {
|
|
1811
|
+
selected = request.result;
|
|
1812
|
+
if (!selected) {
|
|
1813
|
+
selected = candidate;
|
|
1814
|
+
store.put(candidate, AUTH_CRYPTO_KEY);
|
|
1815
|
+
}
|
|
1816
|
+
};
|
|
1817
|
+
request.onerror = () => reject(request.error);
|
|
1818
|
+
transaction.oncomplete = () => {
|
|
1819
|
+
if (selected) resolve(selected);
|
|
1820
|
+
else reject(new Error("OAuth encryption key was not initialized"));
|
|
1821
|
+
};
|
|
1822
|
+
transaction.onerror = () => reject(transaction.error);
|
|
1823
|
+
transaction.onabort = () => reject(transaction.error);
|
|
1824
|
+
});
|
|
1825
|
+
} finally {
|
|
1826
|
+
database.close();
|
|
1827
|
+
}
|
|
1828
|
+
}
|
|
1829
|
+
function openCryptoDatabase() {
|
|
1830
|
+
return new Promise((resolve, reject) => {
|
|
1831
|
+
const request = indexedDB.open(AUTH_CRYPTO_DATABASE, 1);
|
|
1832
|
+
request.onupgradeneeded = () => {
|
|
1833
|
+
const database = request.result;
|
|
1834
|
+
if (!database.objectStoreNames.contains(AUTH_CRYPTO_STORE)) {
|
|
1835
|
+
database.createObjectStore(AUTH_CRYPTO_STORE);
|
|
1836
|
+
}
|
|
1837
|
+
};
|
|
1838
|
+
request.onsuccess = () => resolve(request.result);
|
|
1839
|
+
request.onerror = () => reject(request.error);
|
|
1840
|
+
request.onblocked = () => reject(new Error("OAuth encryption database is blocked"));
|
|
1841
|
+
});
|
|
1842
|
+
}
|
|
1843
|
+
function encodeBase64(bytes) {
|
|
1844
|
+
let binary = "";
|
|
1845
|
+
for (const byte of bytes) binary += String.fromCharCode(byte);
|
|
1846
|
+
return btoa(binary);
|
|
1847
|
+
}
|
|
1848
|
+
function decodeBase64(value) {
|
|
1849
|
+
const binary = atob(value);
|
|
1850
|
+
const bytes = new Uint8Array(binary.length);
|
|
1851
|
+
for (let index = 0; index < binary.length; index++) {
|
|
1852
|
+
bytes[index] = binary.charCodeAt(index);
|
|
1853
|
+
}
|
|
1854
|
+
return bytes;
|
|
1855
|
+
}
|
|
1856
|
+
|
|
1857
|
+
// src/auth/session-store.ts
|
|
1858
|
+
import { validateClientMetadataUrl } from "@modelcontextprotocol/client";
|
|
1859
|
+
|
|
1547
1860
|
// src/auth/url.ts
|
|
1548
1861
|
function sanitizeUrl(raw) {
|
|
1549
1862
|
const abort = () => {
|
|
@@ -1568,29 +1881,7 @@ function sanitizeParam([k, v]) {
|
|
|
1568
1881
|
return `${encodeURIComponent(k)}${v.length > 0 ? `=${encodeURIComponent(v)}` : ""}`;
|
|
1569
1882
|
}
|
|
1570
1883
|
|
|
1571
|
-
// src/auth/storage.ts
|
|
1572
|
-
var LocalStorageKVStore = class {
|
|
1573
|
-
get(key) {
|
|
1574
|
-
return localStorage.getItem(key);
|
|
1575
|
-
}
|
|
1576
|
-
set(key, value) {
|
|
1577
|
-
localStorage.setItem(key, value);
|
|
1578
|
-
}
|
|
1579
|
-
remove(key) {
|
|
1580
|
-
localStorage.removeItem(key);
|
|
1581
|
-
}
|
|
1582
|
-
keys() {
|
|
1583
|
-
const out = [];
|
|
1584
|
-
for (let i = 0; i < localStorage.length; i++) {
|
|
1585
|
-
const k = localStorage.key(i);
|
|
1586
|
-
if (k) out.push(k);
|
|
1587
|
-
}
|
|
1588
|
-
return out;
|
|
1589
|
-
}
|
|
1590
|
-
};
|
|
1591
|
-
|
|
1592
1884
|
// src/auth/session-store.ts
|
|
1593
|
-
import { validateClientMetadataUrl } from "@modelcontextprotocol/client";
|
|
1594
1885
|
var OAuthSessionStore = class _OAuthSessionStore {
|
|
1595
1886
|
serverUrl;
|
|
1596
1887
|
storageKeyPrefix;
|
|
@@ -1696,29 +1987,39 @@ var OAuthSessionStore = class _OAuthSessionStore {
|
|
|
1696
1987
|
if (ctx) await this.store.set(this.credentialKey("tokens"), serialized);
|
|
1697
1988
|
await this.store.remove(this.getKey("code_verifier"));
|
|
1698
1989
|
await this.store.remove(this.getKey("last_auth_url"));
|
|
1990
|
+
await this.store.remove(this.getKey("last_auth_callback_url"));
|
|
1699
1991
|
}
|
|
1700
1992
|
async clientInformation(ctx) {
|
|
1993
|
+
if (!this.allowClientSecret) {
|
|
1994
|
+
const registeredRedirectUri = await this.store.get(
|
|
1995
|
+
this.getKey("client_info_redirect_uri")
|
|
1996
|
+
);
|
|
1997
|
+
if (registeredRedirectUri !== this.redirectUrl) {
|
|
1998
|
+
await this.invalidateCredentials("registration");
|
|
1999
|
+
console.info(
|
|
2000
|
+
`[${this.storageKeyPrefix}] Re-registering browser OAuth client after its Inspector callback changed or could not be verified.`
|
|
2001
|
+
);
|
|
2002
|
+
return void 0;
|
|
2003
|
+
}
|
|
2004
|
+
}
|
|
1701
2005
|
const stored = await this.readCredential("client_info", ctx);
|
|
1702
2006
|
if (!stored) return void 0;
|
|
1703
2007
|
const { key, value: clientInfo } = stored;
|
|
1704
2008
|
try {
|
|
1705
2009
|
if (!this.allowClientSecret && clientInfo.client_secret) {
|
|
1706
|
-
await this.
|
|
1707
|
-
if (ctx) await this.store.remove(this.credentialKey("client_info"));
|
|
2010
|
+
await this.invalidateCredentials("registration");
|
|
1708
2011
|
console.warn(
|
|
1709
|
-
`[${this.storageKeyPrefix}]
|
|
2012
|
+
`[${this.storageKeyPrefix}] Recovered stale browser OAuth credentials containing a client_secret.`
|
|
1710
2013
|
);
|
|
1711
2014
|
return void 0;
|
|
1712
2015
|
}
|
|
1713
2016
|
const storedRedirectUris = Array.isArray(clientInfo.redirect_uris) ? clientInfo.redirect_uris : [];
|
|
1714
|
-
const hasMatchingRedirect = storedRedirectUris.length === 0 || storedRedirectUris.includes(this.redirectUrl);
|
|
2017
|
+
const hasMatchingRedirect = storedRedirectUris.length === 0 && this.allowClientSecret || storedRedirectUris.includes(this.redirectUrl);
|
|
1715
2018
|
if (!hasMatchingRedirect) {
|
|
1716
2019
|
console.info(
|
|
1717
|
-
`[${this.storageKeyPrefix}]
|
|
2020
|
+
`[${this.storageKeyPrefix}] Recovering cached OAuth credentials after a redirect URI change.`
|
|
1718
2021
|
);
|
|
1719
|
-
await this.
|
|
1720
|
-
await this.store.remove(this.credentialKey("tokens", ctx));
|
|
1721
|
-
await this.store.remove(this.getKey("last_auth_url"));
|
|
2022
|
+
await this.invalidateCredentials("registration");
|
|
1722
2023
|
return void 0;
|
|
1723
2024
|
}
|
|
1724
2025
|
return clientInfo;
|
|
@@ -1736,11 +2037,20 @@ var OAuthSessionStore = class _OAuthSessionStore {
|
|
|
1736
2037
|
"Browser OAuth clients must be public clients; client_secret persistence is not allowed."
|
|
1737
2038
|
);
|
|
1738
2039
|
}
|
|
1739
|
-
const
|
|
2040
|
+
const persistedClientInformation = !this.allowClientSecret && (!("redirect_uris" in clientInformation) || !Array.isArray(
|
|
2041
|
+
clientInformation.redirect_uris
|
|
2042
|
+
) || clientInformation.redirect_uris.length === 0) ? { ...clientInformation, redirect_uris: [this.redirectUrl] } : clientInformation;
|
|
2043
|
+
const serialized = JSON.stringify(persistedClientInformation);
|
|
1740
2044
|
await this.store.set(this.credentialKey("client_info", ctx), serialized);
|
|
1741
2045
|
if (ctx) {
|
|
1742
2046
|
await this.store.set(this.credentialKey("client_info"), serialized);
|
|
1743
2047
|
}
|
|
2048
|
+
if (!this.allowClientSecret) {
|
|
2049
|
+
await this.store.set(
|
|
2050
|
+
this.getKey("client_info_redirect_uri"),
|
|
2051
|
+
this.redirectUrl
|
|
2052
|
+
);
|
|
2053
|
+
}
|
|
1744
2054
|
}
|
|
1745
2055
|
async saveCodeVerifier(codeVerifier) {
|
|
1746
2056
|
await this.store.set(this.getKey("code_verifier"), codeVerifier);
|
|
@@ -1765,11 +2075,22 @@ var OAuthSessionStore = class _OAuthSessionStore {
|
|
|
1765
2075
|
}
|
|
1766
2076
|
};
|
|
1767
2077
|
switch (scope) {
|
|
2078
|
+
case "registration":
|
|
2079
|
+
await removeCredentialKeys("tokens");
|
|
2080
|
+
await removeCredentialKeys("client_info");
|
|
2081
|
+
await this.store.remove(this.getKey("code_verifier"));
|
|
2082
|
+
await this.store.remove(this.getKey("last_auth_url"));
|
|
2083
|
+
await this.store.remove(this.getKey("last_auth_callback_url"));
|
|
2084
|
+
await this.store.remove(this.getKey("client_info_redirect_uri"));
|
|
2085
|
+
await this.store.remove(this.getKey("token_endpoint"));
|
|
2086
|
+
break;
|
|
1768
2087
|
case "all":
|
|
1769
2088
|
await removeCredentialKeys("tokens");
|
|
1770
2089
|
await removeCredentialKeys("client_info");
|
|
1771
2090
|
await this.store.remove(this.getKey("code_verifier"));
|
|
1772
2091
|
await this.store.remove(this.getKey("last_auth_url"));
|
|
2092
|
+
await this.store.remove(this.getKey("last_auth_callback_url"));
|
|
2093
|
+
await this.store.remove(this.getKey("client_info_redirect_uri"));
|
|
1773
2094
|
await this.store.remove(this.getKey("discovery_state"));
|
|
1774
2095
|
await this.store.remove(this.getKey("token_endpoint"));
|
|
1775
2096
|
break;
|
|
@@ -1819,7 +2140,7 @@ var OAuthSessionStore = class _OAuthSessionStore {
|
|
|
1819
2140
|
*/
|
|
1820
2141
|
async storeAuthorizationState(authorizationUrl, opts = {}) {
|
|
1821
2142
|
const state = globalThis.crypto.randomUUID();
|
|
1822
|
-
const stateKey = `${this.storageKeyPrefix}
|
|
2143
|
+
const stateKey = `${this.storageKeyPrefix}_${this.serverUrlHash}_state_${state}`;
|
|
1823
2144
|
const stateData = {
|
|
1824
2145
|
serverUrlHash: this.serverUrlHash,
|
|
1825
2146
|
expiry: Date.now() + 1e3 * 60 * 10,
|
|
@@ -1839,6 +2160,10 @@ var OAuthSessionStore = class _OAuthSessionStore {
|
|
|
1839
2160
|
authorizationUrl.searchParams.set("state", state);
|
|
1840
2161
|
const sanitizedAuthUrl = sanitizeUrl(authorizationUrl.toString());
|
|
1841
2162
|
await this.store.set(stateKey, JSON.stringify(stateData));
|
|
2163
|
+
await this.store.set(
|
|
2164
|
+
this.getKey("last_auth_callback_url"),
|
|
2165
|
+
this.redirectUrl
|
|
2166
|
+
);
|
|
1842
2167
|
await this.store.set(this.getKey("last_auth_url"), sanitizedAuthUrl);
|
|
1843
2168
|
return sanitizedAuthUrl;
|
|
1844
2169
|
}
|
|
@@ -1868,16 +2193,30 @@ async function serializeBody(body) {
|
|
|
1868
2193
|
if (body instanceof Blob) return await body.text();
|
|
1869
2194
|
return body;
|
|
1870
2195
|
}
|
|
2196
|
+
function trimTrailingSlashes(value) {
|
|
2197
|
+
let end = value.length;
|
|
2198
|
+
while (end > 0 && value.charCodeAt(end - 1) === 47) {
|
|
2199
|
+
end--;
|
|
2200
|
+
}
|
|
2201
|
+
return value.slice(0, end);
|
|
2202
|
+
}
|
|
1871
2203
|
var BrowserOAuthClientProvider = class {
|
|
2204
|
+
/** Protected MCP server URL associated with this provider. */
|
|
1872
2205
|
serverUrl;
|
|
2206
|
+
/** Pre-registered public client information, when configured. */
|
|
1873
2207
|
staticClientInfo;
|
|
1874
2208
|
session;
|
|
2209
|
+
storage;
|
|
1875
2210
|
// Browser-only state
|
|
2211
|
+
/** Whether initial connection waits for explicit authentication. */
|
|
1876
2212
|
preventAutoAuth;
|
|
1877
2213
|
useRedirectFlow;
|
|
1878
2214
|
oauthProxyUrl;
|
|
1879
2215
|
connectionUrl;
|
|
1880
2216
|
proxyOAuthRequests;
|
|
2217
|
+
lastAttemptedAuthUrl = null;
|
|
2218
|
+
authorizationPending = false;
|
|
2219
|
+
/** Callback invoked immediately before an authorization popup opens. */
|
|
1881
2220
|
onPopupWindow;
|
|
1882
2221
|
constructor(serverUrl, options = {}) {
|
|
1883
2222
|
if (options.staticClientInfo?.client_secret) {
|
|
@@ -1886,10 +2225,11 @@ var BrowserOAuthClientProvider = class {
|
|
|
1886
2225
|
);
|
|
1887
2226
|
}
|
|
1888
2227
|
this.serverUrl = serverUrl;
|
|
2228
|
+
this.storage = new LocalStorageKVStore();
|
|
1889
2229
|
this.session = new OAuthSessionStore(
|
|
1890
2230
|
serverUrl,
|
|
1891
2231
|
{ ...options, allowClientSecret: false },
|
|
1892
|
-
|
|
2232
|
+
this.storage
|
|
1893
2233
|
);
|
|
1894
2234
|
this.preventAutoAuth = options.preventAutoAuth;
|
|
1895
2235
|
this.useRedirectFlow = options.useRedirectFlow;
|
|
@@ -1900,33 +2240,54 @@ var BrowserOAuthClientProvider = class {
|
|
|
1900
2240
|
this.onPopupWindow = options.onPopupWindow;
|
|
1901
2241
|
}
|
|
1902
2242
|
// --- Identity / key fields exposed for callback handling ---
|
|
2243
|
+
/** Prefix used for persisted OAuth keys. */
|
|
1903
2244
|
get storageKeyPrefix() {
|
|
1904
2245
|
return this.session.storageKeyPrefix;
|
|
1905
2246
|
}
|
|
2247
|
+
/** Stable hash used to namespace storage for this server. */
|
|
1906
2248
|
get serverUrlHash() {
|
|
1907
2249
|
return this.session.serverUrlHash;
|
|
1908
2250
|
}
|
|
2251
|
+
/** Human-readable OAuth client name. */
|
|
1909
2252
|
get clientName() {
|
|
1910
2253
|
return this.session.clientName;
|
|
1911
2254
|
}
|
|
2255
|
+
/** Public website describing the OAuth client. */
|
|
1912
2256
|
get clientUri() {
|
|
1913
2257
|
return this.session.clientUri;
|
|
1914
2258
|
}
|
|
2259
|
+
/** Public OAuth client logo URL. */
|
|
1915
2260
|
get logoUri() {
|
|
1916
2261
|
return this.session.logoUri;
|
|
1917
2262
|
}
|
|
2263
|
+
/** OAuth redirect URI. */
|
|
1918
2264
|
get callbackUrl() {
|
|
1919
2265
|
return this.session.callbackUrl;
|
|
1920
2266
|
}
|
|
2267
|
+
/** Space-delimited OAuth scopes requested by the client. */
|
|
1921
2268
|
get scope() {
|
|
1922
2269
|
return this.session.scope;
|
|
1923
2270
|
}
|
|
1924
2271
|
get clientMetadataUrl() {
|
|
1925
2272
|
return this.session.clientMetadataUrl;
|
|
1926
2273
|
}
|
|
2274
|
+
/**
|
|
2275
|
+
* Returns a provider-scoped storage key.
|
|
2276
|
+
*
|
|
2277
|
+
* @param keySuffix - Suffix identifying the stored value.
|
|
2278
|
+
* @returns Namespaced storage key.
|
|
2279
|
+
*/
|
|
1927
2280
|
getKey(keySuffix) {
|
|
1928
2281
|
return this.session.getKey(keySuffix);
|
|
1929
2282
|
}
|
|
2283
|
+
/** Whether an authorization flow is awaiting completion. */
|
|
2284
|
+
get hasPendingFlow() {
|
|
2285
|
+
return this.authorizationPending;
|
|
2286
|
+
}
|
|
2287
|
+
/** Marks the current authorization flow as complete. */
|
|
2288
|
+
markFlowComplete() {
|
|
2289
|
+
this.authorizationPending = false;
|
|
2290
|
+
}
|
|
1930
2291
|
/**
|
|
1931
2292
|
* Re-anchor an SDK-derived OAuth discovery URL from the MCP connection
|
|
1932
2293
|
* (proxy) origin onto the actual MCP server.
|
|
@@ -1954,8 +2315,8 @@ var BrowserOAuthClientProvider = class {
|
|
|
1954
2315
|
const [doc, ...suffixParts] = rest.split("/");
|
|
1955
2316
|
if (!doc) return url;
|
|
1956
2317
|
const suffix = suffixParts.length ? `/${suffixParts.join("/")}` : "";
|
|
1957
|
-
const connectionPath = connection.pathname
|
|
1958
|
-
const targetPath = target.pathname
|
|
2318
|
+
const connectionPath = trimTrailingSlashes(connection.pathname);
|
|
2319
|
+
const targetPath = trimTrailingSlashes(target.pathname);
|
|
1959
2320
|
const newSuffix = suffix && suffix === connectionPath ? targetPath : suffix;
|
|
1960
2321
|
return `${target.origin}/.well-known/${doc}${newSuffix}${requested.search}`;
|
|
1961
2322
|
} catch {
|
|
@@ -1974,20 +2335,19 @@ var BrowserOAuthClientProvider = class {
|
|
|
1974
2335
|
* therefore never alters fetch behavior for other servers, other
|
|
1975
2336
|
* connections, or the rest of the page.
|
|
1976
2337
|
*
|
|
1977
|
-
*
|
|
1978
|
-
*
|
|
1979
|
-
*
|
|
1980
|
-
*
|
|
2338
|
+
* OAuth metadata is always fetched with `cache: "no-store"`, including in
|
|
2339
|
+
* direct mode. Authorization servers commonly vary CORS headers by Origin;
|
|
2340
|
+
* bypassing the browser HTTP cache prevents a revalidated response cached
|
|
2341
|
+
* for another localhost origin from poisoning discovery. When OAuth proxying
|
|
2342
|
+
* is disabled or no `oauthProxyUrl` is configured, all requests still go
|
|
2343
|
+
* directly to their original URLs.
|
|
1981
2344
|
*
|
|
1982
2345
|
* @param baseFetch - The fetch used for non-OAuth requests and for the
|
|
1983
2346
|
* underlying proxy calls. Defaults to the global `fetch`.
|
|
1984
2347
|
*/
|
|
1985
2348
|
getProxyFetch(baseFetch) {
|
|
1986
|
-
if (!this.proxyOAuthRequests || !this.oauthProxyUrl) {
|
|
1987
|
-
return baseFetch;
|
|
1988
|
-
}
|
|
1989
2349
|
const base = baseFetch ?? globalThis.fetch.bind(globalThis);
|
|
1990
|
-
const oauthProxyUrl = this.oauthProxyUrl;
|
|
2350
|
+
const oauthProxyUrl = this.proxyOAuthRequests && this.oauthProxyUrl ? this.oauthProxyUrl : void 0;
|
|
1991
2351
|
const discoveredEndpoints = /* @__PURE__ */ new Set();
|
|
1992
2352
|
let restoredDiscovery = false;
|
|
1993
2353
|
return async (input, init) => {
|
|
@@ -2000,6 +2360,12 @@ var BrowserOAuthClientProvider = class {
|
|
|
2000
2360
|
return await base(input, init);
|
|
2001
2361
|
}
|
|
2002
2362
|
const isMetadata = pathname.includes("/.well-known/");
|
|
2363
|
+
if (!oauthProxyUrl) {
|
|
2364
|
+
return await base(
|
|
2365
|
+
isMetadata ? url : input,
|
|
2366
|
+
isMetadata ? { ...init, cache: "no-store" } : init
|
|
2367
|
+
);
|
|
2368
|
+
}
|
|
2003
2369
|
if (!restoredDiscovery) {
|
|
2004
2370
|
restoredDiscovery = true;
|
|
2005
2371
|
const metadata = (await this.discoveryState())?.authorizationServerMetadata;
|
|
@@ -2034,7 +2400,8 @@ var BrowserOAuthClientProvider = class {
|
|
|
2034
2400
|
if (isMetadata) {
|
|
2035
2401
|
const response2 = await base(proxyEndpoint, {
|
|
2036
2402
|
...init,
|
|
2037
|
-
method: "GET"
|
|
2403
|
+
method: "GET",
|
|
2404
|
+
cache: "no-store"
|
|
2038
2405
|
});
|
|
2039
2406
|
try {
|
|
2040
2407
|
const metadata = await response2.clone().json();
|
|
@@ -2052,23 +2419,40 @@ var BrowserOAuthClientProvider = class {
|
|
|
2052
2419
|
}
|
|
2053
2420
|
return response2;
|
|
2054
2421
|
}
|
|
2055
|
-
const
|
|
2422
|
+
const inputRequest = input instanceof Request ? input : void 0;
|
|
2423
|
+
const method = init?.method ?? inputRequest?.method ?? "POST";
|
|
2424
|
+
const requestHeaders = init?.headers ?? inputRequest?.headers;
|
|
2425
|
+
let body;
|
|
2426
|
+
if (init?.body !== void 0 && init.body !== null) {
|
|
2427
|
+
body = await serializeBody(init.body);
|
|
2428
|
+
} else if (inputRequest?.body && method !== "GET" && method !== "HEAD") {
|
|
2429
|
+
body = await inputRequest.clone().text();
|
|
2430
|
+
}
|
|
2056
2431
|
const response = await base(proxyEndpoint, {
|
|
2057
2432
|
method: "POST",
|
|
2058
2433
|
headers: { "Content-Type": "application/json" },
|
|
2059
2434
|
body: JSON.stringify({
|
|
2060
2435
|
serverUrl: this.serverUrl,
|
|
2061
2436
|
url,
|
|
2062
|
-
method
|
|
2063
|
-
headers:
|
|
2437
|
+
method,
|
|
2438
|
+
headers: requestHeaders ? Object.fromEntries(new Headers(requestHeaders)) : {},
|
|
2064
2439
|
body
|
|
2065
2440
|
})
|
|
2066
2441
|
});
|
|
2067
2442
|
const data = await response.json();
|
|
2443
|
+
if (!response.ok || typeof data.status !== "number") {
|
|
2444
|
+
return new Response(JSON.stringify(data), {
|
|
2445
|
+
status: response.status,
|
|
2446
|
+
statusText: response.statusText,
|
|
2447
|
+
headers: response.headers
|
|
2448
|
+
});
|
|
2449
|
+
}
|
|
2068
2450
|
return new Response(JSON.stringify(data.body), {
|
|
2069
2451
|
status: data.status,
|
|
2070
|
-
statusText: data.statusText,
|
|
2071
|
-
headers: new Headers(
|
|
2452
|
+
statusText: typeof data.statusText === "string" ? data.statusText : void 0,
|
|
2453
|
+
headers: new Headers(
|
|
2454
|
+
data.headers && typeof data.headers === "object" ? data.headers : void 0
|
|
2455
|
+
)
|
|
2072
2456
|
});
|
|
2073
2457
|
};
|
|
2074
2458
|
}
|
|
@@ -2083,15 +2467,41 @@ var BrowserOAuthClientProvider = class {
|
|
|
2083
2467
|
return this.session.tokens(ctx);
|
|
2084
2468
|
}
|
|
2085
2469
|
saveTokens(tokens, ctx) {
|
|
2470
|
+
this.lastAttemptedAuthUrl = null;
|
|
2471
|
+
this.authorizationPending = false;
|
|
2086
2472
|
return this.session.saveTokens(tokens, ctx);
|
|
2087
2473
|
}
|
|
2474
|
+
/**
|
|
2475
|
+
* Returns the configured or dynamically registered OAuth client information.
|
|
2476
|
+
*
|
|
2477
|
+
* @param ctx - Optional registration context.
|
|
2478
|
+
* @returns OAuth client information, or `undefined` when not registered.
|
|
2479
|
+
*/
|
|
2088
2480
|
async clientInformation(ctx) {
|
|
2089
2481
|
if (this.staticClientInfo) return this.staticClientInfo;
|
|
2090
2482
|
return this.session.clientInformation(ctx);
|
|
2091
2483
|
}
|
|
2484
|
+
/**
|
|
2485
|
+
* Persists public OAuth client registration information.
|
|
2486
|
+
*
|
|
2487
|
+
* Static client configuration takes precedence, and browser providers discard
|
|
2488
|
+
* any client secret returned for a public client.
|
|
2489
|
+
*
|
|
2490
|
+
* @param clientInformation - Registration information to save.
|
|
2491
|
+
* @param ctx - Optional registration context.
|
|
2492
|
+
*/
|
|
2092
2493
|
async saveClientInformation(clientInformation, ctx) {
|
|
2093
2494
|
if (this.staticClientInfo) return;
|
|
2094
|
-
|
|
2495
|
+
const { client_secret: discardedClientSecret, ...publicClientInformation } = clientInformation;
|
|
2496
|
+
if (discardedClientSecret) {
|
|
2497
|
+
console.info(
|
|
2498
|
+
`[${this.storageKeyPrefix}] Discarded client_secret returned for a public browser OAuth client.`
|
|
2499
|
+
);
|
|
2500
|
+
}
|
|
2501
|
+
return this.session.saveClientInformation(
|
|
2502
|
+
publicClientInformation,
|
|
2503
|
+
ctx
|
|
2504
|
+
);
|
|
2095
2505
|
}
|
|
2096
2506
|
codeVerifier() {
|
|
2097
2507
|
return this.session.codeVerifier();
|
|
@@ -2140,20 +2550,26 @@ var BrowserOAuthClientProvider = class {
|
|
|
2140
2550
|
* use `redirectToAuthorization` for that.
|
|
2141
2551
|
*/
|
|
2142
2552
|
async prepareAuthorizationUrl(authorizationUrl) {
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
|
|
2146
|
-
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
|
|
2151
|
-
|
|
2152
|
-
|
|
2553
|
+
const prepared = await this.session.storeAuthorizationState(
|
|
2554
|
+
authorizationUrl,
|
|
2555
|
+
{
|
|
2556
|
+
extraProviderOptions: {
|
|
2557
|
+
oauthProxyUrl: this.oauthProxyUrl,
|
|
2558
|
+
...this.clientMetadataUrl ? { clientMetadataUrl: this.clientMetadataUrl } : {},
|
|
2559
|
+
...this.staticClientInfo ? { staticClientInfo: this.staticClientInfo } : {},
|
|
2560
|
+
...this.scope ? { scope: this.scope } : {}
|
|
2561
|
+
},
|
|
2562
|
+
flowType: this.useRedirectFlow ? "redirect" : "popup",
|
|
2563
|
+
returnUrl: typeof window !== "undefined" ? window.location.href : void 0
|
|
2564
|
+
}
|
|
2565
|
+
);
|
|
2566
|
+
this.lastAttemptedAuthUrl = prepared;
|
|
2567
|
+
this.authorizationPending = true;
|
|
2568
|
+
return prepared;
|
|
2153
2569
|
}
|
|
2154
2570
|
/**
|
|
2155
2571
|
* Redirects the user agent to the authorization URL, storing necessary state.
|
|
2156
|
-
* @param authorizationUrl The fully constructed authorization URL from the SDK.
|
|
2572
|
+
* @param authorizationUrl - The fully constructed authorization URL from the SDK.
|
|
2157
2573
|
*/
|
|
2158
2574
|
async redirectToAuthorization(authorizationUrl) {
|
|
2159
2575
|
const sanitizedAuthUrl = await this.prepareAuthorizationUrl(authorizationUrl);
|
|
@@ -2201,40 +2617,27 @@ var BrowserOAuthClientProvider = class {
|
|
|
2201
2617
|
* Retrieves the last URL passed to `redirectToAuthorization`. Useful for manual fallback.
|
|
2202
2618
|
*/
|
|
2203
2619
|
getLastAttemptedAuthUrl() {
|
|
2204
|
-
|
|
2205
|
-
if (!storedUrl) return null;
|
|
2206
|
-
return sanitizeUrl(storedUrl);
|
|
2620
|
+
return this.lastAttemptedAuthUrl;
|
|
2207
2621
|
}
|
|
2622
|
+
/**
|
|
2623
|
+
* Removes OAuth state stored for this server.
|
|
2624
|
+
*
|
|
2625
|
+
* @returns The number of storage entries removed.
|
|
2626
|
+
*/
|
|
2208
2627
|
clearStorage() {
|
|
2628
|
+
this.lastAttemptedAuthUrl = null;
|
|
2629
|
+
this.authorizationPending = false;
|
|
2209
2630
|
const prefixPattern = `${this.storageKeyPrefix}_${this.serverUrlHash}_`;
|
|
2210
|
-
const statePattern = `${this.storageKeyPrefix}:state_`;
|
|
2211
2631
|
const keysToRemove = [];
|
|
2212
2632
|
let count = 0;
|
|
2213
|
-
for (
|
|
2214
|
-
const key = localStorage.key(i);
|
|
2215
|
-
if (!key) continue;
|
|
2633
|
+
for (const key of this.storage.keys()) {
|
|
2216
2634
|
if (key.startsWith(prefixPattern)) {
|
|
2217
2635
|
keysToRemove.push(key);
|
|
2218
|
-
} else if (key.startsWith(statePattern)) {
|
|
2219
|
-
try {
|
|
2220
|
-
const item = localStorage.getItem(key);
|
|
2221
|
-
if (item) {
|
|
2222
|
-
const state = JSON.parse(item);
|
|
2223
|
-
if (state.serverUrlHash === this.serverUrlHash) {
|
|
2224
|
-
keysToRemove.push(key);
|
|
2225
|
-
}
|
|
2226
|
-
}
|
|
2227
|
-
} catch (e) {
|
|
2228
|
-
console.warn(
|
|
2229
|
-
`[${this.storageKeyPrefix}] Error parsing state key ${key} during clearStorage:`,
|
|
2230
|
-
e
|
|
2231
|
-
);
|
|
2232
|
-
}
|
|
2233
2636
|
}
|
|
2234
2637
|
}
|
|
2235
2638
|
const uniqueKeysToRemove = [...new Set(keysToRemove)];
|
|
2236
2639
|
uniqueKeysToRemove.forEach((key) => {
|
|
2237
|
-
|
|
2640
|
+
this.storage.remove(key);
|
|
2238
2641
|
count++;
|
|
2239
2642
|
});
|
|
2240
2643
|
return count;
|
|
@@ -2265,16 +2668,13 @@ var MCPAgentExecutionEvent = class extends BaseTelemetryEvent {
|
|
|
2265
2668
|
return {
|
|
2266
2669
|
// Core execution info
|
|
2267
2670
|
execution_method: this.data.executionMethod,
|
|
2268
|
-
query: this.data.query,
|
|
2269
2671
|
query_length: this.data.query.length,
|
|
2270
2672
|
success: this.data.success,
|
|
2271
2673
|
// Agent configuration
|
|
2272
2674
|
model_provider: this.data.modelProvider,
|
|
2273
2675
|
model_name: this.data.modelName,
|
|
2274
2676
|
server_count: this.data.serverCount,
|
|
2275
|
-
server_identifiers: this.data.serverIdentifiers,
|
|
2276
2677
|
total_tools_available: this.data.totalToolsAvailable,
|
|
2277
|
-
tools_available_names: this.data.toolsAvailableNames,
|
|
2278
2678
|
max_steps_configured: this.data.maxStepsConfigured,
|
|
2279
2679
|
memory_enabled: this.data.memoryEnabled,
|
|
2280
2680
|
use_server_manager: this.data.useServerManager,
|
|
@@ -2285,8 +2685,6 @@ var MCPAgentExecutionEvent = class extends BaseTelemetryEvent {
|
|
|
2285
2685
|
// Execution results (always include, even if null)
|
|
2286
2686
|
steps_taken: this.data.stepsTaken ?? null,
|
|
2287
2687
|
tools_used_count: this.data.toolsUsedCount ?? null,
|
|
2288
|
-
tools_used_names: this.data.toolsUsedNames ?? null,
|
|
2289
|
-
response: this.data.response ?? null,
|
|
2290
2688
|
response_length: this.data.response ? this.data.response.length : null,
|
|
2291
2689
|
execution_time_ms: this.data.executionTimeMs ?? null,
|
|
2292
2690
|
error_type: this.data.errorType ?? null,
|
|
@@ -2382,21 +2780,72 @@ async function telFetch(url, init) {
|
|
|
2382
2780
|
}
|
|
2383
2781
|
var POSTHOG_HOST = "https://eu.i.posthog.com";
|
|
2384
2782
|
var POSTHOG_API_KEY = "phc_lyTtbYwvkdSbrcMQNPiKiiRWrrM1seyKIMjycSvItEI";
|
|
2385
|
-
|
|
2386
|
-
|
|
2387
|
-
|
|
2388
|
-
|
|
2389
|
-
|
|
2390
|
-
|
|
2391
|
-
|
|
2392
|
-
|
|
2783
|
+
var CONTENT_PROPERTY = /(^|_)(arguments?|args|body|command|headers?|location|message|query|response|secret|subject|token|uri|url|user_agent)(_|$)/i;
|
|
2784
|
+
var IDENTIFYING_PROPERTY = /(^|_)(server_identifiers?|server_names?|servers|tool_names?|tools_(available|used)_names)(_|$)/i;
|
|
2785
|
+
var AGGREGATE_PROPERTY = /(_count|_length|_duration(?:_ms)?|_time_ms|(^|_)num_[a-z0-9_]+)$/i;
|
|
2786
|
+
function normalizePropertyKey(key) {
|
|
2787
|
+
return key.replace(/([a-z0-9])([A-Z])/g, "$1_$2").replace(/[^a-z0-9_$]+/gi, "_").toLowerCase();
|
|
2788
|
+
}
|
|
2789
|
+
function sanitizeValue(value, seen) {
|
|
2790
|
+
if (Array.isArray(value)) {
|
|
2791
|
+
if (seen.has(value)) {
|
|
2792
|
+
throw new TypeError("Cyclic telemetry properties are not supported");
|
|
2793
|
+
}
|
|
2794
|
+
seen.add(value);
|
|
2795
|
+
const sanitized = value.map((item) => sanitizeValue(item, seen));
|
|
2796
|
+
seen.delete(value);
|
|
2797
|
+
return sanitized;
|
|
2798
|
+
}
|
|
2799
|
+
if (value !== null && typeof value === "object" && (Object.getPrototypeOf(value) === Object.prototype || Object.getPrototypeOf(value) === null)) {
|
|
2800
|
+
if (seen.has(value)) {
|
|
2801
|
+
throw new TypeError("Cyclic telemetry properties are not supported");
|
|
2802
|
+
}
|
|
2803
|
+
seen.add(value);
|
|
2804
|
+
const sanitized = sanitizeProperties(
|
|
2805
|
+
value,
|
|
2806
|
+
seen
|
|
2807
|
+
);
|
|
2808
|
+
seen.delete(value);
|
|
2809
|
+
return sanitized;
|
|
2810
|
+
}
|
|
2811
|
+
return value;
|
|
2812
|
+
}
|
|
2813
|
+
function sanitizeProperties(properties, seen = /* @__PURE__ */ new WeakSet()) {
|
|
2814
|
+
const sanitized = {};
|
|
2815
|
+
for (const [key, value] of Object.entries(properties)) {
|
|
2816
|
+
const normalizedKey = normalizePropertyKey(key);
|
|
2817
|
+
if (AGGREGATE_PROPERTY.test(normalizedKey)) {
|
|
2818
|
+
if (value === null || typeof value === "number") {
|
|
2819
|
+
sanitized[key] = value;
|
|
2820
|
+
}
|
|
2821
|
+
continue;
|
|
2822
|
+
}
|
|
2823
|
+
if (IDENTIFYING_PROPERTY.test(normalizedKey) || CONTENT_PROPERTY.test(normalizedKey)) {
|
|
2824
|
+
continue;
|
|
2825
|
+
}
|
|
2826
|
+
sanitized[key] = sanitizeValue(value, seen);
|
|
2827
|
+
}
|
|
2828
|
+
return sanitized;
|
|
2829
|
+
}
|
|
2830
|
+
async function capturePostHog(params) {
|
|
2831
|
+
try {
|
|
2832
|
+
const host = params.host ?? POSTHOG_HOST;
|
|
2833
|
+
const apiKey = params.apiKey ?? POSTHOG_API_KEY;
|
|
2834
|
+
const body = JSON.stringify({
|
|
2393
2835
|
api_key: apiKey,
|
|
2394
2836
|
event: params.event,
|
|
2395
2837
|
distinct_id: params.distinctId,
|
|
2396
|
-
properties: params.properties,
|
|
2838
|
+
properties: sanitizeProperties(params.properties),
|
|
2397
2839
|
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
2398
|
-
})
|
|
2399
|
-
|
|
2840
|
+
});
|
|
2841
|
+
await telFetch(`${host}/i/v0/e/`, {
|
|
2842
|
+
method: "POST",
|
|
2843
|
+
headers: { "Content-Type": "application/json" },
|
|
2844
|
+
keepalive: true,
|
|
2845
|
+
body
|
|
2846
|
+
});
|
|
2847
|
+
} catch {
|
|
2848
|
+
}
|
|
2400
2849
|
}
|
|
2401
2850
|
|
|
2402
2851
|
// src/telemetry/telemetry.ts
|
|
@@ -2728,11 +3177,12 @@ async function completeOAuthFlow(provider, serverUrl, options = {}) {
|
|
|
2728
3177
|
throw new Error(`Unexpected OAuth auth() result: ${result}`);
|
|
2729
3178
|
}
|
|
2730
3179
|
}
|
|
2731
|
-
if (typeof flowProvider.getAuthorizationCode === "function") {
|
|
2732
|
-
const
|
|
3180
|
+
if (typeof flowProvider.getAuthorizationResponse === "function" || typeof flowProvider.getAuthorizationCode === "function") {
|
|
3181
|
+
const response = typeof flowProvider.getAuthorizationResponse === "function" ? await flowProvider.getAuthorizationResponse() : { code: await flowProvider.getAuthorizationCode() };
|
|
2733
3182
|
await auth(provider, {
|
|
2734
3183
|
serverUrl,
|
|
2735
|
-
authorizationCode: code,
|
|
3184
|
+
authorizationCode: response.code,
|
|
3185
|
+
...response.iss !== void 0 ? { iss: response.iss } : {},
|
|
2736
3186
|
fetchFn
|
|
2737
3187
|
});
|
|
2738
3188
|
return;
|
|
@@ -2746,6 +3196,8 @@ async function waitForBrowserAuthComplete(provider, timeoutMs) {
|
|
|
2746
3196
|
);
|
|
2747
3197
|
}
|
|
2748
3198
|
if (provider.useRedirectFlow) {
|
|
3199
|
+
await new Promise(() => {
|
|
3200
|
+
});
|
|
2749
3201
|
return;
|
|
2750
3202
|
}
|
|
2751
3203
|
const tokensKey = provider.getKey?.("tokens");
|
|
@@ -2762,25 +3214,29 @@ async function waitForBrowserAuthComplete(provider, timeoutMs) {
|
|
|
2762
3214
|
} catch {
|
|
2763
3215
|
}
|
|
2764
3216
|
}
|
|
2765
|
-
|
|
2766
|
-
|
|
2767
|
-
|
|
2768
|
-
|
|
2769
|
-
|
|
2770
|
-
|
|
2771
|
-
|
|
2772
|
-
|
|
2773
|
-
|
|
2774
|
-
|
|
2775
|
-
|
|
2776
|
-
|
|
2777
|
-
|
|
2778
|
-
|
|
2779
|
-
|
|
2780
|
-
|
|
2781
|
-
|
|
2782
|
-
|
|
2783
|
-
|
|
3217
|
+
try {
|
|
3218
|
+
const result = await runAuthPopup({
|
|
3219
|
+
popup: null,
|
|
3220
|
+
state,
|
|
3221
|
+
tokensKey,
|
|
3222
|
+
timeoutMs
|
|
3223
|
+
});
|
|
3224
|
+
switch (result.kind) {
|
|
3225
|
+
case "success":
|
|
3226
|
+
return;
|
|
3227
|
+
case "cancelled":
|
|
3228
|
+
throw new Error("OAuth authentication was cancelled.");
|
|
3229
|
+
case "timeout":
|
|
3230
|
+
throw new Error(
|
|
3231
|
+
`OAuth callback not received within ${timeoutMs}ms. Ensure /oauth/callback calls onMcpAuthorization().`
|
|
3232
|
+
);
|
|
3233
|
+
case "error":
|
|
3234
|
+
throw new Error(result.error);
|
|
3235
|
+
default:
|
|
3236
|
+
throw new Error("Unexpected OAuth popup result");
|
|
3237
|
+
}
|
|
3238
|
+
} finally {
|
|
3239
|
+
provider.markFlowComplete?.();
|
|
2784
3240
|
}
|
|
2785
3241
|
}
|
|
2786
3242
|
|
|
@@ -3029,7 +3485,7 @@ var MCPConnection = class {
|
|
|
3029
3485
|
/**
|
|
3030
3486
|
* Normalized server metadata for this ready connection.
|
|
3031
3487
|
*
|
|
3032
|
-
* @throws
|
|
3488
|
+
* @throws When called before protocol negotiation completes.
|
|
3033
3489
|
*/
|
|
3034
3490
|
get info() {
|
|
3035
3491
|
const protocolEra = this.protocolEra;
|
|
@@ -3250,12 +3706,10 @@ function isOAuthClientProvider(provider) {
|
|
|
3250
3706
|
var BaseMCPClient = class {
|
|
3251
3707
|
/**
|
|
3252
3708
|
* Internal configuration object containing MCP server definitions.
|
|
3253
|
-
* @protected
|
|
3254
3709
|
*/
|
|
3255
3710
|
config = {};
|
|
3256
3711
|
/**
|
|
3257
3712
|
* Map of server names to their active sessions.
|
|
3258
|
-
* @protected
|
|
3259
3713
|
*/
|
|
3260
3714
|
sessions = {};
|
|
3261
3715
|
/**
|
|
@@ -3299,7 +3753,7 @@ var BaseMCPClient = class {
|
|
|
3299
3753
|
*
|
|
3300
3754
|
* @param _cfg - Configuration dictionary
|
|
3301
3755
|
* @returns Client instance
|
|
3302
|
-
* @throws
|
|
3756
|
+
* @throws If called on the base class instead of a concrete implementation
|
|
3303
3757
|
*
|
|
3304
3758
|
* @example
|
|
3305
3759
|
* ```typescript
|
|
@@ -3441,7 +3895,7 @@ var BaseMCPClient = class {
|
|
|
3441
3895
|
* @param serverName - The name of the server as defined in the client configuration
|
|
3442
3896
|
* @param autoInitialize - Whether to automatically initialize the session (default: true)
|
|
3443
3897
|
* @returns A promise that resolves to the created MCPSession instance
|
|
3444
|
-
* @throws
|
|
3898
|
+
* @throws If the server is not found in the configuration
|
|
3445
3899
|
*
|
|
3446
3900
|
* @example
|
|
3447
3901
|
* ```typescript
|
|
@@ -3611,7 +4065,7 @@ var BaseMCPClient = class {
|
|
|
3611
4065
|
*
|
|
3612
4066
|
* @param serverName - Name of the server
|
|
3613
4067
|
* @returns The session instance
|
|
3614
|
-
* @throws
|
|
4068
|
+
* @throws If the session is not found
|
|
3615
4069
|
*
|
|
3616
4070
|
* @example
|
|
3617
4071
|
* ```typescript
|
|
@@ -3780,16 +4234,28 @@ function trackBrowserClientInit(config) {
|
|
|
3780
4234
|
}
|
|
3781
4235
|
var BrowserMCPClient = class _BrowserMCPClient extends BaseMCPClient {
|
|
3782
4236
|
/**
|
|
3783
|
-
*
|
|
3784
|
-
*
|
|
4237
|
+
* Returns the installed `@mcp-use/client` package version.
|
|
4238
|
+
*
|
|
4239
|
+
* @returns The package version string.
|
|
3785
4240
|
*/
|
|
3786
4241
|
static getPackageVersion() {
|
|
3787
4242
|
return getPackageVersion();
|
|
3788
4243
|
}
|
|
4244
|
+
/**
|
|
4245
|
+
* Creates a browser MCP client.
|
|
4246
|
+
*
|
|
4247
|
+
* @param config - Client configuration containing an optional `mcpServers` map.
|
|
4248
|
+
*/
|
|
3789
4249
|
constructor(config) {
|
|
3790
4250
|
super(config);
|
|
3791
4251
|
trackBrowserClientInit(this.config);
|
|
3792
4252
|
}
|
|
4253
|
+
/**
|
|
4254
|
+
* Creates a browser client from an inline configuration object.
|
|
4255
|
+
*
|
|
4256
|
+
* @param cfg - Client configuration containing an optional `mcpServers` map.
|
|
4257
|
+
* @returns A browser client initialized with `cfg`.
|
|
4258
|
+
*/
|
|
3793
4259
|
static fromDict(cfg) {
|
|
3794
4260
|
return new _BrowserMCPClient(cfg);
|
|
3795
4261
|
}
|
|
@@ -4047,9 +4513,14 @@ function startConnectionHealthMonitoring(params) {
|
|
|
4047
4513
|
return;
|
|
4048
4514
|
}
|
|
4049
4515
|
const authHeaders = params.getAuthHeaders ? await params.getAuthHeaders() : {};
|
|
4516
|
+
const healthCheckHeaders = {
|
|
4517
|
+
...params.allHeaders,
|
|
4518
|
+
...authHeaders,
|
|
4519
|
+
...params.gatewayUrl && params.url ? { "X-Target-URL": params.url } : {}
|
|
4520
|
+
};
|
|
4050
4521
|
const response = await fetch(healthCheckUrl, {
|
|
4051
4522
|
method: "HEAD",
|
|
4052
|
-
headers:
|
|
4523
|
+
headers: healthCheckHeaders,
|
|
4053
4524
|
signal: AbortSignal.timeout(5e3)
|
|
4054
4525
|
});
|
|
4055
4526
|
if (response.status === 405 || response.status === 404) {
|
|
@@ -4254,6 +4725,16 @@ function useMcpOperations(params) {
|
|
|
4254
4725
|
};
|
|
4255
4726
|
}
|
|
4256
4727
|
|
|
4728
|
+
// src/react/token-expiry.ts
|
|
4729
|
+
function getOAuthTokenExpiry(tokens) {
|
|
4730
|
+
try {
|
|
4731
|
+
const payload = JSON.parse(atob(tokens.access_token?.split(".")[1] ?? ""));
|
|
4732
|
+
if (typeof payload.exp === "number") return payload.exp * 1e3;
|
|
4733
|
+
} catch {
|
|
4734
|
+
}
|
|
4735
|
+
return typeof tokens.expires_in === "number" ? Date.now() + tokens.expires_in * 1e3 : void 0;
|
|
4736
|
+
}
|
|
4737
|
+
|
|
4257
4738
|
// src/react/useMcp.ts
|
|
4258
4739
|
var DEFAULT_RECONNECT_DELAY = 3e3;
|
|
4259
4740
|
var DEFAULT_RETRY_DELAY = 5e3;
|
|
@@ -4269,6 +4750,7 @@ function useMcp(options) {
|
|
|
4269
4750
|
headers: headersOption,
|
|
4270
4751
|
proxyConfig,
|
|
4271
4752
|
oauthProxyUrl: oauthProxyUrlOption,
|
|
4753
|
+
connectionMode,
|
|
4272
4754
|
autoProxyFallback = false,
|
|
4273
4755
|
logLevel: logLevelOption = "silent",
|
|
4274
4756
|
autoRetry = false,
|
|
@@ -4292,6 +4774,7 @@ function useMcp(options) {
|
|
|
4292
4774
|
oauth: oauthOptions
|
|
4293
4775
|
} = options;
|
|
4294
4776
|
const transportType = "http";
|
|
4777
|
+
const requestedProxyAddress = proxyConfig?.proxyAddress;
|
|
4295
4778
|
const oauthClientId = oauthOptions?.clientId?.trim() || void 0;
|
|
4296
4779
|
const oauthClientMetadataUrl = oauthOptions?.clientMetadataUrl?.trim() || void 0;
|
|
4297
4780
|
const oauthScope = oauthOptions?.scope?.trim() || void 0;
|
|
@@ -4339,6 +4822,9 @@ function useMcp(options) {
|
|
|
4339
4822
|
);
|
|
4340
4823
|
const oauthClientConfig = derivedOAuthClientConfig;
|
|
4341
4824
|
const autoProxyFallbackConfig = useMemo(() => {
|
|
4825
|
+
if (connectionMode === "direct" || connectionMode === "proxy") {
|
|
4826
|
+
return { enabled: false, proxyAddress: void 0 };
|
|
4827
|
+
}
|
|
4342
4828
|
if (!autoProxyFallback) {
|
|
4343
4829
|
return { enabled: false, proxyAddress: void 0 };
|
|
4344
4830
|
}
|
|
@@ -4354,7 +4840,7 @@ function useMcp(options) {
|
|
|
4354
4840
|
enabled: autoProxyFallback.enabled !== false && Boolean(proxyAddress),
|
|
4355
4841
|
proxyAddress
|
|
4356
4842
|
};
|
|
4357
|
-
}, [autoProxyFallback, proxyConfig]);
|
|
4843
|
+
}, [autoProxyFallback, connectionMode, proxyConfig]);
|
|
4358
4844
|
const autoReconnectConfig = useMemo(() => {
|
|
4359
4845
|
if (autoReconnect === false) {
|
|
4360
4846
|
return {
|
|
@@ -4390,20 +4876,33 @@ function useMcp(options) {
|
|
|
4390
4876
|
const [effectiveProxyConfig, setEffectiveProxyConfig] = useState(void 0);
|
|
4391
4877
|
useEffect(() => {
|
|
4392
4878
|
setEffectiveProxyConfig(void 0);
|
|
4393
|
-
}, [
|
|
4879
|
+
}, [
|
|
4880
|
+
url,
|
|
4881
|
+
requestedProxyAddress,
|
|
4882
|
+
connectionMode,
|
|
4883
|
+
autoProxyFallbackConfig.proxyAddress
|
|
4884
|
+
]);
|
|
4394
4885
|
const activeProxyConfig = useMemo(() => {
|
|
4395
|
-
|
|
4396
|
-
|
|
4886
|
+
const hasCurrentAutoFallback = autoProxyFallbackConfig.enabled && effectiveProxyConfig?.proxyAddress === autoProxyFallbackConfig.proxyAddress;
|
|
4887
|
+
if (hasCurrentAutoFallback && effectiveProxyConfig) {
|
|
4888
|
+
const latestHeaders = proxyConfig?.headers ?? {};
|
|
4889
|
+
return {
|
|
4890
|
+
...effectiveProxyConfig,
|
|
4891
|
+
headers: {
|
|
4892
|
+
...latestHeaders,
|
|
4893
|
+
...effectiveProxyConfig.headers ?? {}
|
|
4894
|
+
}
|
|
4895
|
+
};
|
|
4397
4896
|
}
|
|
4398
|
-
const
|
|
4399
|
-
return
|
|
4400
|
-
|
|
4401
|
-
|
|
4402
|
-
|
|
4403
|
-
|
|
4404
|
-
|
|
4405
|
-
|
|
4406
|
-
|
|
4897
|
+
const startsDirect = connectionMode === "auto" || connectionMode === "direct" || connectionMode === void 0 && autoProxyFallbackConfig.enabled;
|
|
4898
|
+
return startsDirect ? void 0 : proxyConfig;
|
|
4899
|
+
}, [
|
|
4900
|
+
effectiveProxyConfig,
|
|
4901
|
+
proxyConfig,
|
|
4902
|
+
connectionMode,
|
|
4903
|
+
autoProxyFallbackConfig.enabled,
|
|
4904
|
+
autoProxyFallbackConfig.proxyAddress
|
|
4905
|
+
]);
|
|
4407
4906
|
const gatewayUrl = activeProxyConfig?.proxyAddress;
|
|
4408
4907
|
const proxyHeaders = activeProxyConfig?.headers ?? {};
|
|
4409
4908
|
const effectiveOAuthUrl = useMemo(() => {
|
|
@@ -4735,8 +5234,9 @@ function useMcp(options) {
|
|
|
4735
5234
|
clientInfo: mergedClientInfo,
|
|
4736
5235
|
// Pass a fetch that scopes OAuth-proxy routing to this server's
|
|
4737
5236
|
// transport/auth calls. getProxyFetch wraps `customFetch` (e.g. the
|
|
4738
|
-
// OAuth retry fetch for scope step-up)
|
|
4739
|
-
//
|
|
5237
|
+
// OAuth retry fetch for scope step-up), bypasses the browser cache
|
|
5238
|
+
// for OAuth metadata, and optionally routes OAuth through the BFF.
|
|
5239
|
+
// It never mutates the global fetch.
|
|
4740
5240
|
...(() => {
|
|
4741
5241
|
const scopedFetch = authProviderRef.current?.getProxyFetch?.(customFetch) ?? customFetch;
|
|
4742
5242
|
return scopedFetch ? { fetch: scopedFetch } : {};
|
|
@@ -4874,9 +5374,22 @@ function useMcp(options) {
|
|
|
4874
5374
|
});
|
|
4875
5375
|
setTools(connection.tools || []);
|
|
4876
5376
|
const [resourcesResult, promptsResult, templatesResult] = await Promise.all([
|
|
4877
|
-
connection.listAllResources()
|
|
4878
|
-
|
|
4879
|
-
|
|
5377
|
+
connection.listAllResources().catch((error2) => {
|
|
5378
|
+
addLog("warn", "Failed to load initial resources:", error2);
|
|
5379
|
+
return { resources: [] };
|
|
5380
|
+
}),
|
|
5381
|
+
connection.listPrompts().catch((error2) => {
|
|
5382
|
+
addLog("warn", "Failed to load initial prompts:", error2);
|
|
5383
|
+
return { prompts: [] };
|
|
5384
|
+
}),
|
|
5385
|
+
connection.supports("resources") ? connection.listResourceTemplates().catch((error2) => {
|
|
5386
|
+
addLog(
|
|
5387
|
+
"warn",
|
|
5388
|
+
"Failed to load initial resource templates:",
|
|
5389
|
+
error2
|
|
5390
|
+
);
|
|
5391
|
+
return { resourceTemplates: [] };
|
|
5392
|
+
}) : Promise.resolve({ resourceTemplates: [] })
|
|
4880
5393
|
]);
|
|
4881
5394
|
if (!isMountedRef.current) {
|
|
4882
5395
|
addLog(
|
|
@@ -4935,7 +5448,7 @@ function useMcp(options) {
|
|
|
4935
5448
|
return "failed";
|
|
4936
5449
|
}
|
|
4937
5450
|
if (tokens?.access_token) {
|
|
4938
|
-
const expiresAt = tokens
|
|
5451
|
+
const expiresAt = getOAuthTokenExpiry(tokens);
|
|
4939
5452
|
let tokenEndpoint = null;
|
|
4940
5453
|
let resource = null;
|
|
4941
5454
|
let clientCreds = null;
|
|
@@ -5045,8 +5558,10 @@ function useMcp(options) {
|
|
|
5045
5558
|
fetchFn: authProviderRef.current.getProxyFetch?.()
|
|
5046
5559
|
});
|
|
5047
5560
|
if (authResult === "REDIRECT") {
|
|
5048
|
-
const
|
|
5049
|
-
|
|
5561
|
+
const flowProvider = authProviderRef.current;
|
|
5562
|
+
const authResponse = await flowProvider.getAuthorizationResponse?.();
|
|
5563
|
+
const authCode = authResponse?.code ?? await flowProvider.getAuthorizationCode?.();
|
|
5564
|
+
if (typeof authCode !== "string") {
|
|
5050
5565
|
throw new Error(
|
|
5051
5566
|
"Authorization code not captured by headless provider"
|
|
5052
5567
|
);
|
|
@@ -5054,6 +5569,7 @@ function useMcp(options) {
|
|
|
5054
5569
|
await auth2(authProviderRef.current, {
|
|
5055
5570
|
serverUrl: url,
|
|
5056
5571
|
authorizationCode: authCode,
|
|
5572
|
+
...authResponse?.iss !== void 0 ? { iss: authResponse.iss } : {},
|
|
5057
5573
|
fetchFn: authProviderRef.current.getProxyFetch?.()
|
|
5058
5574
|
});
|
|
5059
5575
|
}
|
|
@@ -5210,19 +5726,20 @@ function useMcp(options) {
|
|
|
5210
5726
|
addLog("info", "Triggering fresh OAuth authorization...");
|
|
5211
5727
|
const parsedUrl = new URL(url);
|
|
5212
5728
|
const baseUrl = parsedUrl.origin + parsedUrl.pathname.replace(/\/+$/, "");
|
|
5213
|
-
|
|
5214
|
-
|
|
5215
|
-
|
|
5216
|
-
|
|
5217
|
-
|
|
5729
|
+
const authResult = await auth2(freshAuthProvider, {
|
|
5730
|
+
serverUrl: baseUrl,
|
|
5731
|
+
fetchFn: freshAuthProvider.getProxyFetch?.()
|
|
5732
|
+
});
|
|
5733
|
+
if (authResult === "AUTHORIZED") {
|
|
5218
5734
|
addLog("info", "OAuth flow completed (tokens obtained)");
|
|
5219
|
-
|
|
5220
|
-
|
|
5221
|
-
|
|
5222
|
-
"OAuth flow initiated (popup/redirect):",
|
|
5223
|
-
err instanceof Error ? err.message : "Redirecting..."
|
|
5224
|
-
);
|
|
5735
|
+
connectingRef.current = false;
|
|
5736
|
+
connectRef.current?.();
|
|
5737
|
+
return;
|
|
5225
5738
|
}
|
|
5739
|
+
if (authResult !== "REDIRECT") {
|
|
5740
|
+
throw new Error(`Unexpected OAuth auth() result: ${authResult}`);
|
|
5741
|
+
}
|
|
5742
|
+
addLog("info", "OAuth authorization redirect initiated");
|
|
5226
5743
|
const newAuthUrl = freshAuthProvider.getLastAttemptedAuthUrl?.();
|
|
5227
5744
|
if (newAuthUrl) {
|
|
5228
5745
|
setAuthUrl(newAuthUrl);
|
|
@@ -5288,11 +5805,8 @@ function useMcp(options) {
|
|
|
5288
5805
|
}
|
|
5289
5806
|
} catch (authError) {
|
|
5290
5807
|
if (!isMountedRef.current) return;
|
|
5291
|
-
|
|
5292
|
-
|
|
5293
|
-
"error",
|
|
5294
|
-
`Manual authentication failed: ${authError instanceof Error ? authError.message : String(authError)}`
|
|
5295
|
-
);
|
|
5808
|
+
const error2 = authError instanceof Error ? authError : new Error(String(authError));
|
|
5809
|
+
failConnection(`Manual authentication failed: ${error2.message}`, error2);
|
|
5296
5810
|
}
|
|
5297
5811
|
} else if (currentState === "authenticating") {
|
|
5298
5812
|
addLog(
|
|
@@ -5500,6 +6014,7 @@ function useMcp(options) {
|
|
|
5500
6014
|
// Triggers reconnection when proxy fallback changes OAuth URL
|
|
5501
6015
|
proxyConfig,
|
|
5502
6016
|
// Triggers reconnection when proxy config (including headers) changes
|
|
6017
|
+
autoProxyFallbackConfig.proxyAddress,
|
|
5503
6018
|
providedAuthProvider
|
|
5504
6019
|
]);
|
|
5505
6020
|
const retryRef = useRef(retry);
|
|
@@ -5649,20 +6164,14 @@ function renderResult(title, message, error, returnUrl) {
|
|
|
5649
6164
|
}
|
|
5650
6165
|
document.body.appendChild(container);
|
|
5651
6166
|
}
|
|
5652
|
-
function findStoredState(state) {
|
|
5653
|
-
const
|
|
5654
|
-
|
|
5655
|
-
|
|
5656
|
-
|
|
5657
|
-
|
|
5658
|
-
|
|
5659
|
-
|
|
5660
|
-
key = candidate;
|
|
5661
|
-
break;
|
|
5662
|
-
}
|
|
5663
|
-
}
|
|
5664
|
-
}
|
|
5665
|
-
const serialized = key ? localStorage.getItem(key) : null;
|
|
6167
|
+
async function findStoredState(state) {
|
|
6168
|
+
const store = new LocalStorageKVStore();
|
|
6169
|
+
const legacySuffix = `:state_${state}`;
|
|
6170
|
+
const scopedSuffix = `_state_${state}`;
|
|
6171
|
+
const key = (await store.keys()).find(
|
|
6172
|
+
(candidate) => candidate.endsWith(legacySuffix) || candidate.endsWith(scopedSuffix)
|
|
6173
|
+
);
|
|
6174
|
+
const serialized = key ? await store.get(key) : null;
|
|
5666
6175
|
if (!key || !serialized) {
|
|
5667
6176
|
throw new Error(`Invalid or expired OAuth state "${state}".`);
|
|
5668
6177
|
}
|
|
@@ -5670,10 +6179,10 @@ function findStoredState(state) {
|
|
|
5670
6179
|
try {
|
|
5671
6180
|
value = JSON.parse(serialized);
|
|
5672
6181
|
} catch {
|
|
5673
|
-
|
|
6182
|
+
await store.remove(key);
|
|
5674
6183
|
throw new Error("Failed to parse stored OAuth state.");
|
|
5675
6184
|
}
|
|
5676
|
-
return { key, value };
|
|
6185
|
+
return { key, value, store };
|
|
5677
6186
|
}
|
|
5678
6187
|
function redirectWithError(returnUrl, message) {
|
|
5679
6188
|
const url = new URL(returnUrl);
|
|
@@ -5732,17 +6241,19 @@ async function completeAuthorization() {
|
|
|
5732
6241
|
const callbackParams = new URLSearchParams(window.location.search);
|
|
5733
6242
|
const state = callbackParams.get("state");
|
|
5734
6243
|
let stateKey = null;
|
|
6244
|
+
let stateStore = null;
|
|
5735
6245
|
let storedState = null;
|
|
5736
6246
|
let provider = null;
|
|
5737
6247
|
try {
|
|
5738
6248
|
if (!state) {
|
|
5739
6249
|
throw new Error("OAuth callback is missing the state parameter.");
|
|
5740
6250
|
}
|
|
5741
|
-
const stored = findStoredState(state);
|
|
6251
|
+
const stored = await findStoredState(state);
|
|
5742
6252
|
stateKey = stored.key;
|
|
6253
|
+
stateStore = stored.store;
|
|
5743
6254
|
storedState = stored.value;
|
|
5744
6255
|
if (!storedState.expiry || storedState.expiry < Date.now()) {
|
|
5745
|
-
|
|
6256
|
+
await stateStore.remove(stateKey);
|
|
5746
6257
|
throw new Error(
|
|
5747
6258
|
"OAuth state has expired. Please start authentication again."
|
|
5748
6259
|
);
|
|
@@ -5757,7 +6268,7 @@ async function completeAuthorization() {
|
|
|
5757
6268
|
fetch: provider.getProxyFetch()
|
|
5758
6269
|
});
|
|
5759
6270
|
await transport.finishAuth(callbackParams);
|
|
5760
|
-
|
|
6271
|
+
await stateStore.remove(stateKey);
|
|
5761
6272
|
signalResult(true, void 0, storedState, {
|
|
5762
6273
|
state,
|
|
5763
6274
|
serverUrlHash: storedState.serverUrlHash
|
|
@@ -5765,8 +6276,12 @@ async function completeAuthorization() {
|
|
|
5765
6276
|
} catch (error) {
|
|
5766
6277
|
const message = error instanceof Error ? error.message : String(error);
|
|
5767
6278
|
console.error("[mcp-callback] OAuth callback failed:", error);
|
|
5768
|
-
if (stateKey)
|
|
5769
|
-
if (provider)
|
|
6279
|
+
if (stateKey && stateStore) await stateStore.remove(stateKey);
|
|
6280
|
+
if (provider) {
|
|
6281
|
+
await (stateStore ?? new LocalStorageKVStore()).remove(
|
|
6282
|
+
provider.getKey("last_auth_url")
|
|
6283
|
+
);
|
|
6284
|
+
}
|
|
5770
6285
|
signalResult(false, message, storedState, {
|
|
5771
6286
|
state,
|
|
5772
6287
|
serverUrlHash: storedState?.serverUrlHash
|
|
@@ -5789,45 +6304,6 @@ import React, {
|
|
|
5789
6304
|
useState as useState3
|
|
5790
6305
|
} from "react";
|
|
5791
6306
|
|
|
5792
|
-
// src/react/types.ts
|
|
5793
|
-
var PERSISTED_SERVER_CONFIG_KEYS = [
|
|
5794
|
-
"url",
|
|
5795
|
-
"displayName",
|
|
5796
|
-
"enabled",
|
|
5797
|
-
"proxyConfig",
|
|
5798
|
-
"oauthProxyUrl",
|
|
5799
|
-
"connectionMode",
|
|
5800
|
-
"autoProxyFallback",
|
|
5801
|
-
"callbackUrl",
|
|
5802
|
-
"storageKeyPrefix",
|
|
5803
|
-
"headers",
|
|
5804
|
-
"logLevel",
|
|
5805
|
-
"autoRetry",
|
|
5806
|
-
"autoReconnect",
|
|
5807
|
-
"reconnectionOptions",
|
|
5808
|
-
"popupFeatures",
|
|
5809
|
-
"preventAutoAuth",
|
|
5810
|
-
"useRedirectFlow",
|
|
5811
|
-
"clientOptions",
|
|
5812
|
-
"protocolNegotiation",
|
|
5813
|
-
"timeout",
|
|
5814
|
-
"clientInfo",
|
|
5815
|
-
"oauth"
|
|
5816
|
-
];
|
|
5817
|
-
function pickPersistedServerConfig(source) {
|
|
5818
|
-
const out = {};
|
|
5819
|
-
for (const key of PERSISTED_SERVER_CONFIG_KEYS) {
|
|
5820
|
-
const value = source[key];
|
|
5821
|
-
if (value !== void 0) {
|
|
5822
|
-
out[key] = value;
|
|
5823
|
-
}
|
|
5824
|
-
}
|
|
5825
|
-
return out;
|
|
5826
|
-
}
|
|
5827
|
-
function toPersistedServerConfig(config) {
|
|
5828
|
-
return pickPersistedServerConfig(config);
|
|
5829
|
-
}
|
|
5830
|
-
|
|
5831
6307
|
// src/react/useMcpServerQueues.ts
|
|
5832
6308
|
import { useCallback as useCallback3, useEffect as useEffect2, useRef as useRef2, useState as useState2 } from "react";
|
|
5833
6309
|
var MAX_NOTIFICATIONS = 500;
|
|
@@ -6014,8 +6490,8 @@ function sameSerializedValue(left, right) {
|
|
|
6014
6490
|
}
|
|
6015
6491
|
function isSameMcpServer(left, right) {
|
|
6016
6492
|
return left.id === right.id && sameSerializedValue(
|
|
6017
|
-
|
|
6018
|
-
|
|
6493
|
+
pickLiveServerConfig(left),
|
|
6494
|
+
pickLiveServerConfig(right)
|
|
6019
6495
|
) && 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(
|
|
6020
6496
|
left.pendingSamplingRequests,
|
|
6021
6497
|
right.pendingSamplingRequests
|
|
@@ -6149,7 +6625,7 @@ function McpServerWrapper({
|
|
|
6149
6625
|
}, [onUpdate]);
|
|
6150
6626
|
useEffect3(() => {
|
|
6151
6627
|
const server = {
|
|
6152
|
-
...
|
|
6628
|
+
...pickLiveServerConfig(options),
|
|
6153
6629
|
...mcp,
|
|
6154
6630
|
id,
|
|
6155
6631
|
displayName: displayName || options.displayName || id,
|
|
@@ -6496,8 +6972,8 @@ function McpClientProvider({
|
|
|
6496
6972
|
...options
|
|
6497
6973
|
};
|
|
6498
6974
|
if (sameSerializedValue(
|
|
6499
|
-
|
|
6500
|
-
|
|
6975
|
+
pickLiveServerConfig(currentConfig.options),
|
|
6976
|
+
pickLiveServerConfig(updatedOptions)
|
|
6501
6977
|
)) {
|
|
6502
6978
|
return;
|
|
6503
6979
|
}
|
|
@@ -6656,70 +7132,115 @@ function useMcpServer(id) {
|
|
|
6656
7132
|
|
|
6657
7133
|
// src/react/storage.ts
|
|
6658
7134
|
var LocalStorageProvider = class {
|
|
7135
|
+
/**
|
|
7136
|
+
* Creates a browser storage provider.
|
|
7137
|
+
*
|
|
7138
|
+
* @param storageKey - Key used for configurations. Metadata uses the same key
|
|
7139
|
+
* with a `-metadata` suffix. Defaults to `"mcp-client-servers"`.
|
|
7140
|
+
*/
|
|
6659
7141
|
constructor(storageKey = "mcp-client-servers") {
|
|
6660
7142
|
this.storageKey = storageKey;
|
|
6661
7143
|
this.metadataKey = `${storageKey}-metadata`;
|
|
6662
7144
|
}
|
|
6663
7145
|
metadataKey;
|
|
7146
|
+
/** Returns sanitized server configurations from `localStorage`. */
|
|
6664
7147
|
getServers() {
|
|
6665
7148
|
try {
|
|
6666
7149
|
const stored = localStorage.getItem(this.storageKey);
|
|
6667
|
-
|
|
6668
|
-
|
|
6669
|
-
|
|
7150
|
+
if (!stored) return {};
|
|
7151
|
+
const parsed = JSON.parse(stored);
|
|
7152
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
7153
|
+
return {};
|
|
7154
|
+
}
|
|
7155
|
+
const sanitized = Object.fromEntries(
|
|
7156
|
+
Object.entries(parsed).flatMap(
|
|
7157
|
+
([id, config]) => config && typeof config === "object" && !Array.isArray(config) ? [
|
|
7158
|
+
[
|
|
7159
|
+
id,
|
|
7160
|
+
toPersistedServerConfig(config)
|
|
7161
|
+
]
|
|
7162
|
+
] : []
|
|
7163
|
+
)
|
|
7164
|
+
);
|
|
7165
|
+
const serialized = JSON.stringify(sanitized);
|
|
7166
|
+
if (serialized !== stored) {
|
|
7167
|
+
try {
|
|
7168
|
+
localStorage.setItem(this.storageKey, serialized);
|
|
7169
|
+
} catch {
|
|
7170
|
+
console.error(
|
|
7171
|
+
"[LocalStorageProvider] Failed to persist sanitized servers."
|
|
7172
|
+
);
|
|
7173
|
+
}
|
|
7174
|
+
}
|
|
7175
|
+
return sanitized;
|
|
7176
|
+
} catch {
|
|
7177
|
+
console.error("[LocalStorageProvider] Failed to load servers.");
|
|
6670
7178
|
return {};
|
|
6671
7179
|
}
|
|
6672
7180
|
}
|
|
7181
|
+
/** Replaces all saved server configurations. */
|
|
6673
7182
|
setServers(servers) {
|
|
6674
7183
|
try {
|
|
6675
|
-
|
|
6676
|
-
|
|
6677
|
-
|
|
7184
|
+
const sanitized = Object.fromEntries(
|
|
7185
|
+
Object.entries(servers).map(([id, config]) => [
|
|
7186
|
+
id,
|
|
7187
|
+
toPersistedServerConfig(config)
|
|
7188
|
+
])
|
|
7189
|
+
);
|
|
7190
|
+
localStorage.setItem(this.storageKey, JSON.stringify(sanitized));
|
|
7191
|
+
} catch {
|
|
7192
|
+
console.error("[LocalStorageProvider] Failed to save servers.");
|
|
6678
7193
|
}
|
|
6679
7194
|
}
|
|
7195
|
+
/** Saves one server configuration. */
|
|
6680
7196
|
setServer(id, config) {
|
|
6681
7197
|
const servers = this.getServers();
|
|
6682
7198
|
servers[id] = config;
|
|
6683
7199
|
this.setServers(servers);
|
|
6684
7200
|
}
|
|
7201
|
+
/** Removes one server and its cached metadata. */
|
|
6685
7202
|
removeServer(id) {
|
|
6686
7203
|
const servers = this.getServers();
|
|
6687
7204
|
delete servers[id];
|
|
6688
7205
|
this.setServers(servers);
|
|
6689
7206
|
this.removeServerMetadata(id);
|
|
6690
7207
|
}
|
|
7208
|
+
/** Removes all saved configurations and metadata. */
|
|
6691
7209
|
clear() {
|
|
6692
7210
|
try {
|
|
6693
7211
|
localStorage.removeItem(this.storageKey);
|
|
6694
7212
|
localStorage.removeItem(this.metadataKey);
|
|
6695
|
-
} catch
|
|
6696
|
-
console.error("[LocalStorageProvider] Failed to clear
|
|
7213
|
+
} catch {
|
|
7214
|
+
console.error("[LocalStorageProvider] Failed to clear.");
|
|
6697
7215
|
}
|
|
6698
7216
|
}
|
|
6699
7217
|
getAllMetadata() {
|
|
6700
7218
|
try {
|
|
6701
7219
|
const stored = localStorage.getItem(this.metadataKey);
|
|
6702
7220
|
return stored ? JSON.parse(stored) : {};
|
|
6703
|
-
} catch
|
|
6704
|
-
console.error("[LocalStorageProvider] Failed to load metadata
|
|
7221
|
+
} catch {
|
|
7222
|
+
console.error("[LocalStorageProvider] Failed to load metadata.");
|
|
6705
7223
|
return {};
|
|
6706
7224
|
}
|
|
6707
7225
|
}
|
|
6708
7226
|
setAllMetadata(metadata) {
|
|
6709
7227
|
try {
|
|
6710
7228
|
localStorage.setItem(this.metadataKey, JSON.stringify(metadata));
|
|
6711
|
-
} catch
|
|
6712
|
-
console.error("[LocalStorageProvider] Failed to save metadata
|
|
7229
|
+
} catch {
|
|
7230
|
+
console.error("[LocalStorageProvider] Failed to save metadata.");
|
|
6713
7231
|
}
|
|
6714
7232
|
}
|
|
7233
|
+
/** Returns cached presentation metadata for a server. */
|
|
6715
7234
|
getServerMetadata(id) {
|
|
6716
7235
|
return this.getAllMetadata()[id];
|
|
6717
7236
|
}
|
|
7237
|
+
/** Saves presentation metadata and stamps the current cache time. */
|
|
6718
7238
|
setServerMetadata(id, metadata) {
|
|
6719
7239
|
const allMetadata = this.getAllMetadata();
|
|
6720
7240
|
allMetadata[id] = { ...metadata, cachedAt: Date.now() };
|
|
6721
7241
|
this.setAllMetadata(allMetadata);
|
|
6722
7242
|
}
|
|
7243
|
+
/** Removes cached presentation metadata for a server. */
|
|
6723
7244
|
removeServerMetadata(id) {
|
|
6724
7245
|
const allMetadata = this.getAllMetadata();
|
|
6725
7246
|
delete allMetadata[id];
|
|
@@ -6729,29 +7250,42 @@ var LocalStorageProvider = class {
|
|
|
6729
7250
|
var MemoryStorageProvider = class {
|
|
6730
7251
|
storage = {};
|
|
6731
7252
|
metadata = {};
|
|
7253
|
+
/** Returns a shallow copy of all stored server configurations. */
|
|
6732
7254
|
getServers() {
|
|
6733
7255
|
return { ...this.storage };
|
|
6734
7256
|
}
|
|
7257
|
+
/** Replaces all stored server configurations. */
|
|
6735
7258
|
setServers(servers) {
|
|
6736
|
-
this.storage =
|
|
7259
|
+
this.storage = Object.fromEntries(
|
|
7260
|
+
Object.entries(servers).map(([id, config]) => [
|
|
7261
|
+
id,
|
|
7262
|
+
toPersistedServerConfig(config)
|
|
7263
|
+
])
|
|
7264
|
+
);
|
|
6737
7265
|
}
|
|
7266
|
+
/** Stores one server configuration. */
|
|
6738
7267
|
setServer(id, config) {
|
|
6739
|
-
this.storage[id] = config;
|
|
7268
|
+
this.storage[id] = toPersistedServerConfig(config);
|
|
6740
7269
|
}
|
|
7270
|
+
/** Removes one server and its cached metadata. */
|
|
6741
7271
|
removeServer(id) {
|
|
6742
7272
|
delete this.storage[id];
|
|
6743
7273
|
this.removeServerMetadata(id);
|
|
6744
7274
|
}
|
|
7275
|
+
/** Removes all configurations and metadata. */
|
|
6745
7276
|
clear() {
|
|
6746
7277
|
this.storage = {};
|
|
6747
7278
|
this.metadata = {};
|
|
6748
7279
|
}
|
|
7280
|
+
/** Returns cached presentation metadata for a server. */
|
|
6749
7281
|
getServerMetadata(id) {
|
|
6750
7282
|
return this.metadata[id];
|
|
6751
7283
|
}
|
|
7284
|
+
/** Saves presentation metadata and stamps the current cache time. */
|
|
6752
7285
|
setServerMetadata(id, metadata) {
|
|
6753
7286
|
this.metadata[id] = { ...metadata, cachedAt: Date.now() };
|
|
6754
7287
|
}
|
|
7288
|
+
/** Removes cached presentation metadata for a server. */
|
|
6755
7289
|
removeServerMetadata(id) {
|
|
6756
7290
|
delete this.metadata[id];
|
|
6757
7291
|
}
|
|
@@ -6816,32 +7350,58 @@ var OPENAI_FILE_APIS_SCRIPT = `<script>
|
|
|
6816
7350
|
})();
|
|
6817
7351
|
</script>`;
|
|
6818
7352
|
function injectOpenAiFileApis(html) {
|
|
6819
|
-
|
|
6820
|
-
|
|
6821
|
-
|
|
6822
|
-
|
|
6823
|
-
|
|
6824
|
-
|
|
6825
|
-
|
|
6826
|
-
|
|
6827
|
-
|
|
6828
|
-
"<
|
|
7353
|
+
const headEnd = findOpeningConstructEnd(html, "<head");
|
|
7354
|
+
if (headEnd !== void 0) {
|
|
7355
|
+
return insertAt(html, headEnd, OPENAI_FILE_APIS_SCRIPT);
|
|
7356
|
+
}
|
|
7357
|
+
const htmlEnd = findOpeningConstructEnd(html, "<html");
|
|
7358
|
+
if (htmlEnd !== void 0) {
|
|
7359
|
+
return insertAt(
|
|
7360
|
+
html,
|
|
7361
|
+
htmlEnd,
|
|
7362
|
+
"<head>" + OPENAI_FILE_APIS_SCRIPT + "</head>"
|
|
6829
7363
|
);
|
|
6830
7364
|
}
|
|
6831
|
-
|
|
6832
|
-
|
|
6833
|
-
|
|
6834
|
-
|
|
6835
|
-
|
|
6836
|
-
|
|
6837
|
-
if (html.includes("<!DOCTYPE") || html.includes("<!doctype")) {
|
|
6838
|
-
return html.replace(
|
|
6839
|
-
/(<!DOCTYPE[^>]*>|<!doctype[^>]*>)/i,
|
|
6840
|
-
"$1<head>" + OPENAI_FILE_APIS_SCRIPT + "</head>"
|
|
7365
|
+
const doctypeEnd = findOpeningConstructEnd(html, "<!doctype");
|
|
7366
|
+
if (doctypeEnd !== void 0) {
|
|
7367
|
+
return insertAt(
|
|
7368
|
+
html,
|
|
7369
|
+
doctypeEnd,
|
|
7370
|
+
"<head>" + OPENAI_FILE_APIS_SCRIPT + "</head>"
|
|
6841
7371
|
);
|
|
6842
7372
|
}
|
|
6843
7373
|
return OPENAI_FILE_APIS_SCRIPT + html;
|
|
6844
7374
|
}
|
|
7375
|
+
function findOpeningConstructEnd(html, lowercasePrefix) {
|
|
7376
|
+
const lowercaseHtml = html.toLowerCase();
|
|
7377
|
+
let searchFrom = 0;
|
|
7378
|
+
while (searchFrom < lowercaseHtml.length) {
|
|
7379
|
+
const start = lowercaseHtml.indexOf(lowercasePrefix, searchFrom);
|
|
7380
|
+
if (start === -1) return void 0;
|
|
7381
|
+
const boundary = lowercaseHtml[start + lowercasePrefix.length];
|
|
7382
|
+
if (boundary === ">" || boundary === " " || boundary === " " || boundary === "\n" || boundary === "\r" || boundary === "\f") {
|
|
7383
|
+
let quote;
|
|
7384
|
+
for (let index = start + lowercasePrefix.length; index < html.length; index++) {
|
|
7385
|
+
const character = html[index];
|
|
7386
|
+
if (quote) {
|
|
7387
|
+
if (character === quote) quote = void 0;
|
|
7388
|
+
continue;
|
|
7389
|
+
}
|
|
7390
|
+
if (character === '"' || character === "'") {
|
|
7391
|
+
quote = character;
|
|
7392
|
+
continue;
|
|
7393
|
+
}
|
|
7394
|
+
if (character === ">") return index + 1;
|
|
7395
|
+
}
|
|
7396
|
+
return void 0;
|
|
7397
|
+
}
|
|
7398
|
+
searchFrom = start + lowercasePrefix.length;
|
|
7399
|
+
}
|
|
7400
|
+
return void 0;
|
|
7401
|
+
}
|
|
7402
|
+
function insertAt(value, index, addition) {
|
|
7403
|
+
return value.slice(0, index) + addition + value.slice(index);
|
|
7404
|
+
}
|
|
6845
7405
|
|
|
6846
7406
|
// src/react/view/resolve-view-resource.ts
|
|
6847
7407
|
function resolveViewResource(options) {
|
|
@@ -6890,6 +7450,11 @@ function resolveViewResource(options) {
|
|
|
6890
7450
|
}
|
|
6891
7451
|
|
|
6892
7452
|
// src/react/view/sandbox-blob-url.ts
|
|
7453
|
+
function buildViewSandboxUrl(sandboxDocumentUrl, options) {
|
|
7454
|
+
const url = new URL(sandboxDocumentUrl.href);
|
|
7455
|
+
applySandboxSearchParams(url, options);
|
|
7456
|
+
return url;
|
|
7457
|
+
}
|
|
6893
7458
|
function applySandboxSearchParams(url, options) {
|
|
6894
7459
|
const { cspMode, permissions, widgetCsp } = options;
|
|
6895
7460
|
url.searchParams.set(
|
|
@@ -7256,6 +7821,67 @@ var VIEW_DIMENSIONS = {
|
|
|
7256
7821
|
FULLSCREEN_HEADER_HEIGHT: 50
|
|
7257
7822
|
};
|
|
7258
7823
|
|
|
7824
|
+
// src/react/view/view-host-policy.ts
|
|
7825
|
+
function buildDefaultHostCapabilities({
|
|
7826
|
+
hasConnection,
|
|
7827
|
+
hasMessageHandler,
|
|
7828
|
+
hasModelContextHandler,
|
|
7829
|
+
hasLogHandler,
|
|
7830
|
+
hasSamplingHandler,
|
|
7831
|
+
hasDownloadHandler,
|
|
7832
|
+
messageCapabilities,
|
|
7833
|
+
modelContextCapabilities
|
|
7834
|
+
}) {
|
|
7835
|
+
return {
|
|
7836
|
+
openLinks: {},
|
|
7837
|
+
...hasConnection ? {
|
|
7838
|
+
serverTools: {},
|
|
7839
|
+
serverResources: {}
|
|
7840
|
+
} : {},
|
|
7841
|
+
...hasLogHandler ? { logging: {} } : {},
|
|
7842
|
+
...hasSamplingHandler ? { sampling: {} } : {},
|
|
7843
|
+
...hasDownloadHandler ? { downloadFile: {} } : {},
|
|
7844
|
+
...hasModelContextHandler ? { updateModelContext: modelContextCapabilities ?? { text: {} } } : {},
|
|
7845
|
+
...hasMessageHandler ? { message: messageCapabilities ?? { text: {} } } : {}
|
|
7846
|
+
};
|
|
7847
|
+
}
|
|
7848
|
+
function isToolVisibleToModel(tool) {
|
|
7849
|
+
if (!tool._meta || typeof tool._meta !== "object") return true;
|
|
7850
|
+
const ui = tool._meta.ui;
|
|
7851
|
+
if (!ui || typeof ui !== "object") return true;
|
|
7852
|
+
const visibility = ui.visibility;
|
|
7853
|
+
return !Array.isArray(visibility) || visibility.some((value) => value === "model");
|
|
7854
|
+
}
|
|
7855
|
+
async function dispatchUiMessage(handler, content) {
|
|
7856
|
+
if (!handler) {
|
|
7857
|
+
throw new Error("This host surface does not support ui/message");
|
|
7858
|
+
}
|
|
7859
|
+
if (content.length === 0) {
|
|
7860
|
+
throw new Error("ui/message requires at least one content block");
|
|
7861
|
+
}
|
|
7862
|
+
await handler(content);
|
|
7863
|
+
}
|
|
7864
|
+
function resolveRequestedDisplayMode({
|
|
7865
|
+
requested,
|
|
7866
|
+
current,
|
|
7867
|
+
hostAvailable,
|
|
7868
|
+
appAvailable
|
|
7869
|
+
}) {
|
|
7870
|
+
const hostModes = hostAvailable ?? ["inline"];
|
|
7871
|
+
const appModes = appAvailable ?? ["inline"];
|
|
7872
|
+
return hostModes.includes(requested) && appModes.includes(requested) ? requested : current;
|
|
7873
|
+
}
|
|
7874
|
+
function assertAppCanCallTool(tools, name) {
|
|
7875
|
+
const tool = tools?.find((candidate) => candidate.name === name);
|
|
7876
|
+
if (!tool) {
|
|
7877
|
+
throw new Error(`Tool "${name}" is not available to this app`);
|
|
7878
|
+
}
|
|
7879
|
+
const visibility = tool._meta?.ui?.visibility;
|
|
7880
|
+
if (visibility && !visibility.includes("app")) {
|
|
7881
|
+
throw new Error(`Tool "${name}" is not available to this app`);
|
|
7882
|
+
}
|
|
7883
|
+
}
|
|
7884
|
+
|
|
7259
7885
|
// src/react/view/view-detection.ts
|
|
7260
7886
|
function getViewResourceUri(toolMeta) {
|
|
7261
7887
|
const uri = toolMeta?.ui;
|
|
@@ -7275,15 +7901,6 @@ function isViewResource(mimeType) {
|
|
|
7275
7901
|
var DEFAULT_HOST_INFO = { name: "mcp-use-client", version: "2.0.0" };
|
|
7276
7902
|
var DEFAULT_TOOL_CALL_TIMEOUT = 6e5;
|
|
7277
7903
|
var SANDBOX_PROXY_READY = "ui/notifications/sandbox-proxy-ready";
|
|
7278
|
-
var DEFAULT_HOST_CAPABILITIES = {
|
|
7279
|
-
openLinks: {},
|
|
7280
|
-
serverTools: {},
|
|
7281
|
-
serverResources: {},
|
|
7282
|
-
logging: {},
|
|
7283
|
-
updateModelContext: { text: {} },
|
|
7284
|
-
// ponytail: always advertised; bridge.onmessage no-ops when onMessage unset
|
|
7285
|
-
message: { text: {} }
|
|
7286
|
-
};
|
|
7287
7904
|
function CloseIcon() {
|
|
7288
7905
|
return /* @__PURE__ */ React2.createElement(
|
|
7289
7906
|
"svg",
|
|
@@ -7347,12 +7964,17 @@ function ViewRendererBase({
|
|
|
7347
7964
|
hostInfo = DEFAULT_HOST_INFO,
|
|
7348
7965
|
hostContext,
|
|
7349
7966
|
hostCapabilities,
|
|
7967
|
+
messageCapabilities,
|
|
7968
|
+
modelContextCapabilities,
|
|
7350
7969
|
cspMode = "widget-declared",
|
|
7351
7970
|
displayMode: displayModeProp,
|
|
7352
7971
|
onDisplayModeChange,
|
|
7353
7972
|
inlineMaxWidth = 768,
|
|
7354
7973
|
chromeless,
|
|
7355
7974
|
onMessage,
|
|
7975
|
+
onSamplingRequest,
|
|
7976
|
+
onDownloadFile,
|
|
7977
|
+
onAppToolsChanged,
|
|
7356
7978
|
onModelContextUpdate,
|
|
7357
7979
|
onLog,
|
|
7358
7980
|
onReady,
|
|
@@ -7384,12 +8006,42 @@ function ViewRendererBase({
|
|
|
7384
8006
|
const [activeSandboxUrl, setActiveSandboxUrl] = useState4(null);
|
|
7385
8007
|
const [loadError, setLoadError] = useState4(null);
|
|
7386
8008
|
const [initCount, setInitCount] = useState4(0);
|
|
7387
|
-
const [showSpinner, setShowSpinner] = useState4(true);
|
|
7388
8009
|
const [inlineHeight, setInlineHeight] = useState4(
|
|
7389
8010
|
VIEW_DIMENSIONS.DEFAULT_HEIGHT
|
|
7390
8011
|
);
|
|
7391
8012
|
const [internalDisplayMode, setInternalDisplayMode] = useState4("inline");
|
|
7392
8013
|
const displayMode = displayModeProp ?? internalDisplayMode;
|
|
8014
|
+
const hasMessageHandler = onMessage !== void 0;
|
|
8015
|
+
const hasModelContextHandler = onModelContextUpdate !== void 0;
|
|
8016
|
+
const hasLogHandler = onLog !== void 0;
|
|
8017
|
+
const hasSamplingHandler = onSamplingRequest !== void 0;
|
|
8018
|
+
const hasDownloadHandler = onDownloadFile !== void 0;
|
|
8019
|
+
const effectiveHostCapabilities = useMemo3(
|
|
8020
|
+
() => ({
|
|
8021
|
+
...buildDefaultHostCapabilities({
|
|
8022
|
+
hasConnection: source.kind === "live",
|
|
8023
|
+
hasMessageHandler,
|
|
8024
|
+
hasModelContextHandler,
|
|
8025
|
+
hasLogHandler,
|
|
8026
|
+
hasSamplingHandler,
|
|
8027
|
+
hasDownloadHandler,
|
|
8028
|
+
messageCapabilities,
|
|
8029
|
+
modelContextCapabilities
|
|
8030
|
+
}),
|
|
8031
|
+
...hostCapabilities
|
|
8032
|
+
}),
|
|
8033
|
+
[
|
|
8034
|
+
hostCapabilities,
|
|
8035
|
+
hasLogHandler,
|
|
8036
|
+
hasSamplingHandler,
|
|
8037
|
+
hasDownloadHandler,
|
|
8038
|
+
hasMessageHandler,
|
|
8039
|
+
hasModelContextHandler,
|
|
8040
|
+
messageCapabilities,
|
|
8041
|
+
modelContextCapabilities,
|
|
8042
|
+
source.kind
|
|
8043
|
+
]
|
|
8044
|
+
);
|
|
7393
8045
|
const effectiveHostContext = useMemo3(() => {
|
|
7394
8046
|
if (!hostContext) return hostContext;
|
|
7395
8047
|
if (hostContext.displayMode === displayMode) return hostContext;
|
|
@@ -7399,6 +8051,12 @@ function ViewRendererBase({
|
|
|
7399
8051
|
hostContextRef.current = effectiveHostContext;
|
|
7400
8052
|
const onMessageRef = useRef4(onMessage);
|
|
7401
8053
|
onMessageRef.current = onMessage;
|
|
8054
|
+
const onSamplingRequestRef = useRef4(onSamplingRequest);
|
|
8055
|
+
onSamplingRequestRef.current = onSamplingRequest;
|
|
8056
|
+
const onDownloadFileRef = useRef4(onDownloadFile);
|
|
8057
|
+
onDownloadFileRef.current = onDownloadFile;
|
|
8058
|
+
const onAppToolsChangedRef = useRef4(onAppToolsChanged);
|
|
8059
|
+
onAppToolsChangedRef.current = onAppToolsChanged;
|
|
7402
8060
|
const toolInputRef = useRef4(toolInput);
|
|
7403
8061
|
toolInputRef.current = toolInput;
|
|
7404
8062
|
const partialToolInputRef = useRef4(partialToolInput);
|
|
@@ -7615,7 +8273,7 @@ function ViewRendererBase({
|
|
|
7615
8273
|
await readyPromise;
|
|
7616
8274
|
if (disposed) return;
|
|
7617
8275
|
const capabilities = {
|
|
7618
|
-
...
|
|
8276
|
+
...effectiveHostCapabilities,
|
|
7619
8277
|
sandbox: {
|
|
7620
8278
|
csp: cspMode === "permissive" ? void 0 : resolved.csp,
|
|
7621
8279
|
permissions: resolved.permissions
|
|
@@ -7624,75 +8282,110 @@ function ViewRendererBase({
|
|
|
7624
8282
|
bridge = new AppBridge(null, hostInfo, capabilities, {
|
|
7625
8283
|
hostContext: hostContextRef.current
|
|
7626
8284
|
});
|
|
7627
|
-
|
|
7628
|
-
|
|
7629
|
-
|
|
7630
|
-
|
|
7631
|
-
onMessageRef.current
|
|
7632
|
-
|
|
7633
|
-
|
|
7634
|
-
}
|
|
8285
|
+
if (capabilities.message) {
|
|
8286
|
+
bridge.onmessage = async ({
|
|
8287
|
+
content
|
|
8288
|
+
}) => {
|
|
8289
|
+
await dispatchUiMessage(onMessageRef.current, content);
|
|
8290
|
+
return {};
|
|
8291
|
+
};
|
|
8292
|
+
}
|
|
8293
|
+
if (capabilities.sampling) {
|
|
8294
|
+
bridge.oncreatesamplingmessage = async (params) => {
|
|
8295
|
+
const handler = onSamplingRequestRef.current;
|
|
8296
|
+
if (!handler) {
|
|
8297
|
+
throw new Error("This host surface does not support sampling");
|
|
8298
|
+
}
|
|
8299
|
+
return handler(params);
|
|
8300
|
+
};
|
|
8301
|
+
}
|
|
8302
|
+
if (capabilities.downloadFile) {
|
|
8303
|
+
bridge.ondownloadfile = async (params) => {
|
|
8304
|
+
const handler = onDownloadFileRef.current;
|
|
8305
|
+
if (!handler) {
|
|
8306
|
+
throw new Error("This host surface does not support downloads");
|
|
8307
|
+
}
|
|
8308
|
+
return handler(params);
|
|
8309
|
+
};
|
|
8310
|
+
}
|
|
7635
8311
|
bridge.onopenlink = async ({ url }) => {
|
|
7636
8312
|
if (url) window.open(url, "_blank", "noopener,noreferrer");
|
|
7637
8313
|
return {};
|
|
7638
8314
|
};
|
|
7639
|
-
|
|
7640
|
-
|
|
7641
|
-
|
|
7642
|
-
|
|
7643
|
-
|
|
7644
|
-
|
|
7645
|
-
|
|
7646
|
-
|
|
7647
|
-
|
|
7648
|
-
|
|
7649
|
-
|
|
7650
|
-
|
|
7651
|
-
|
|
7652
|
-
|
|
7653
|
-
|
|
7654
|
-
|
|
7655
|
-
|
|
7656
|
-
|
|
7657
|
-
|
|
7658
|
-
|
|
7659
|
-
}
|
|
7660
|
-
|
|
7661
|
-
|
|
7662
|
-
|
|
7663
|
-
|
|
7664
|
-
|
|
7665
|
-
|
|
7666
|
-
|
|
7667
|
-
|
|
7668
|
-
|
|
8315
|
+
if (capabilities.serverTools) {
|
|
8316
|
+
bridge.oncalltool = (async ({
|
|
8317
|
+
name,
|
|
8318
|
+
arguments: args
|
|
8319
|
+
}) => {
|
|
8320
|
+
const conn = connectionRef.current;
|
|
8321
|
+
if (!conn) throw new Error("Server connection not available");
|
|
8322
|
+
assertAppCanCallTool(conn.tools, name);
|
|
8323
|
+
try {
|
|
8324
|
+
return await conn.callTool(name, args || {}, {
|
|
8325
|
+
timeout: toolCallTimeout,
|
|
8326
|
+
resetTimeoutOnProgress: true
|
|
8327
|
+
});
|
|
8328
|
+
} catch (error) {
|
|
8329
|
+
bridge?.sendToolCancelled({
|
|
8330
|
+
reason: error instanceof Error ? error.message : String(error)
|
|
8331
|
+
});
|
|
8332
|
+
throw error;
|
|
8333
|
+
}
|
|
8334
|
+
});
|
|
8335
|
+
}
|
|
8336
|
+
if (capabilities.serverResources) {
|
|
8337
|
+
bridge.onreadresource = (async ({
|
|
8338
|
+
uri
|
|
8339
|
+
}) => {
|
|
8340
|
+
const conn = connectionRef.current;
|
|
8341
|
+
if (!conn) throw new Error("Server connection not available");
|
|
8342
|
+
return await conn.readResource(uri);
|
|
8343
|
+
});
|
|
8344
|
+
bridge.onlistresources = (async () => {
|
|
8345
|
+
const conn = connectionRef.current;
|
|
8346
|
+
if (!conn) throw new Error("Server connection not available");
|
|
8347
|
+
return { resources: [...conn.resources ?? []] };
|
|
8348
|
+
});
|
|
8349
|
+
}
|
|
7669
8350
|
bridge.onrequestdisplaymode = async ({
|
|
7670
8351
|
mode
|
|
7671
8352
|
}) => {
|
|
7672
8353
|
const requested = mode ?? "inline";
|
|
7673
|
-
const
|
|
7674
|
-
|
|
7675
|
-
|
|
7676
|
-
|
|
7677
|
-
|
|
7678
|
-
|
|
8354
|
+
const effective = resolveRequestedDisplayMode({
|
|
8355
|
+
requested,
|
|
8356
|
+
current: displayModeRef.current,
|
|
8357
|
+
hostAvailable: hostContextRef.current?.availableDisplayModes,
|
|
8358
|
+
appAvailable: bridge?.getAppCapabilities()?.availableDisplayModes
|
|
8359
|
+
});
|
|
7679
8360
|
await handleDisplayModeChangeRef.current(effective);
|
|
7680
8361
|
return { mode: effective };
|
|
7681
8362
|
};
|
|
7682
|
-
|
|
7683
|
-
|
|
7684
|
-
|
|
7685
|
-
|
|
7686
|
-
|
|
7687
|
-
|
|
7688
|
-
|
|
7689
|
-
|
|
7690
|
-
|
|
7691
|
-
|
|
7692
|
-
|
|
7693
|
-
|
|
7694
|
-
|
|
7695
|
-
|
|
8363
|
+
if (capabilities.updateModelContext) {
|
|
8364
|
+
bridge.onupdatemodelcontext = async ({
|
|
8365
|
+
content,
|
|
8366
|
+
structuredContent
|
|
8367
|
+
}) => {
|
|
8368
|
+
if (!onModelContextUpdateRef.current) {
|
|
8369
|
+
throw new Error(
|
|
8370
|
+
"This host surface does not support model context updates"
|
|
8371
|
+
);
|
|
8372
|
+
}
|
|
8373
|
+
await onModelContextUpdateRef.current({
|
|
8374
|
+
content,
|
|
8375
|
+
structuredContent
|
|
8376
|
+
});
|
|
8377
|
+
return {};
|
|
8378
|
+
};
|
|
8379
|
+
}
|
|
8380
|
+
if (capabilities.logging) {
|
|
8381
|
+
bridge.onloggingmessage = async ({
|
|
8382
|
+
level,
|
|
8383
|
+
data
|
|
8384
|
+
}) => {
|
|
8385
|
+
onLogRef.current?.({ level, data });
|
|
8386
|
+
return {};
|
|
8387
|
+
};
|
|
8388
|
+
}
|
|
7696
8389
|
bridge.onsizechange = async ({
|
|
7697
8390
|
height
|
|
7698
8391
|
}) => {
|
|
@@ -7702,6 +8395,35 @@ function ViewRendererBase({
|
|
|
7702
8395
|
onInlineHeightChangeRef.current?.(height);
|
|
7703
8396
|
}
|
|
7704
8397
|
};
|
|
8398
|
+
let publishedAppToolsSignature = null;
|
|
8399
|
+
const publishAppTools = async () => {
|
|
8400
|
+
const handler = onAppToolsChangedRef.current;
|
|
8401
|
+
if (!bridge || !handler) return;
|
|
8402
|
+
const appCapabilities = bridge.getAppCapabilities();
|
|
8403
|
+
if (!appCapabilities?.tools) {
|
|
8404
|
+
handler(null);
|
|
8405
|
+
return;
|
|
8406
|
+
}
|
|
8407
|
+
const result = await bridge.listTools({});
|
|
8408
|
+
if (disposed || !bridge) return;
|
|
8409
|
+
const signature = JSON.stringify(result.tools);
|
|
8410
|
+
if (signature === publishedAppToolsSignature) return;
|
|
8411
|
+
publishedAppToolsSignature = signature;
|
|
8412
|
+
const currentBridge = bridge;
|
|
8413
|
+
handler({
|
|
8414
|
+
tools: result.tools,
|
|
8415
|
+
callTool: (name, args) => currentBridge.callTool({
|
|
8416
|
+
name,
|
|
8417
|
+
arguments: args ?? {}
|
|
8418
|
+
})
|
|
8419
|
+
});
|
|
8420
|
+
};
|
|
8421
|
+
bridge.setNotificationHandler(
|
|
8422
|
+
"notifications/tools/list_changed",
|
|
8423
|
+
async () => {
|
|
8424
|
+
await publishAppTools();
|
|
8425
|
+
}
|
|
8426
|
+
);
|
|
7705
8427
|
const initPromise = hookInitialized(bridge);
|
|
7706
8428
|
let transport = new PostMessageTransport(
|
|
7707
8429
|
iframe.contentWindow,
|
|
@@ -7722,9 +8444,11 @@ function ViewRendererBase({
|
|
|
7722
8444
|
bridgeRef.current = bridge;
|
|
7723
8445
|
setInitCount((c) => c + 1);
|
|
7724
8446
|
onLifecycleChangeRef.current?.({ status: "initialized" });
|
|
8447
|
+
await publishAppTools();
|
|
7725
8448
|
const currentPartialToolInput = partialToolInputRef.current;
|
|
7726
|
-
|
|
7727
|
-
|
|
8449
|
+
const hasCompletedToolResult = toolOutputRef.current !== void 0 && toolOutputRef.current !== null;
|
|
8450
|
+
if (currentPartialToolInput && !hasCompletedToolResult) {
|
|
8451
|
+
await bridge.sendToolInputPartial({
|
|
7728
8452
|
arguments: currentPartialToolInput
|
|
7729
8453
|
});
|
|
7730
8454
|
} else {
|
|
@@ -7732,14 +8456,14 @@ function ViewRendererBase({
|
|
|
7732
8456
|
...toolInputRef.current,
|
|
7733
8457
|
...parseCustomProps(customPropsRef.current)
|
|
7734
8458
|
};
|
|
7735
|
-
bridge.sendToolInput({ arguments: mergedArgs });
|
|
8459
|
+
await bridge.sendToolInput({ arguments: mergedArgs });
|
|
7736
8460
|
}
|
|
7737
8461
|
const toolResultPayload = buildToolResultPayload(
|
|
7738
8462
|
toolOutputRef.current,
|
|
7739
8463
|
customPropsRef.current
|
|
7740
8464
|
);
|
|
7741
8465
|
if (toolResultPayload) {
|
|
7742
|
-
bridge.sendToolResult(toolResultPayload);
|
|
8466
|
+
await bridge.sendToolResult(toolResultPayload);
|
|
7743
8467
|
}
|
|
7744
8468
|
onLifecycleChangeRef.current?.({ status: "ready" });
|
|
7745
8469
|
} catch (err) {
|
|
@@ -7756,6 +8480,7 @@ function ViewRendererBase({
|
|
|
7756
8480
|
disposed = true;
|
|
7757
8481
|
const toClose = bridge;
|
|
7758
8482
|
bridgeRef.current = null;
|
|
8483
|
+
onAppToolsChangedRef.current?.(null);
|
|
7759
8484
|
if (!toClose) return;
|
|
7760
8485
|
onLifecycleChangeRef.current?.({ status: "tearing-down" });
|
|
7761
8486
|
void (async () => {
|
|
@@ -7778,7 +8503,7 @@ function ViewRendererBase({
|
|
|
7778
8503
|
resolved,
|
|
7779
8504
|
activeSandboxUrl,
|
|
7780
8505
|
hostInfo,
|
|
7781
|
-
|
|
8506
|
+
effectiveHostCapabilities,
|
|
7782
8507
|
cspMode,
|
|
7783
8508
|
viewId,
|
|
7784
8509
|
wrapTransport,
|
|
@@ -7788,33 +8513,37 @@ function ViewRendererBase({
|
|
|
7788
8513
|
useEffect5(() => {
|
|
7789
8514
|
const bridge = bridgeRef.current;
|
|
7790
8515
|
if (!bridge || initCount === 0 || !effectiveHostContext) return;
|
|
7791
|
-
bridge.setHostContext(effectiveHostContext);
|
|
8516
|
+
void bridge.setHostContext(effectiveHostContext);
|
|
7792
8517
|
}, [effectiveHostContext, initCount]);
|
|
7793
8518
|
useEffect5(() => {
|
|
7794
8519
|
const bridge = bridgeRef.current;
|
|
7795
|
-
if (!bridge || initCount === 0 || !partialToolInput)
|
|
7796
|
-
|
|
7797
|
-
|
|
8520
|
+
if (!bridge || initCount === 0 || !partialToolInput || toolOutput !== void 0 && toolOutput !== null) {
|
|
8521
|
+
return;
|
|
8522
|
+
}
|
|
8523
|
+
void bridge.sendToolInputPartial({ arguments: partialToolInput });
|
|
8524
|
+
}, [initCount, partialToolInput, toolOutput]);
|
|
7798
8525
|
useEffect5(() => {
|
|
7799
8526
|
const bridge = bridgeRef.current;
|
|
7800
|
-
if (!bridge || initCount === 0 || partialToolInput)
|
|
8527
|
+
if (!bridge || initCount === 0 || partialToolInput && (toolOutput === void 0 || toolOutput === null)) {
|
|
8528
|
+
return;
|
|
8529
|
+
}
|
|
7801
8530
|
const mergedArgs = {
|
|
7802
8531
|
...toolInput,
|
|
7803
8532
|
...parseCustomProps(customProps)
|
|
7804
8533
|
};
|
|
7805
|
-
bridge.sendToolInput({ arguments: mergedArgs });
|
|
7806
|
-
}, [initCount, toolInput, partialToolInput, customProps]);
|
|
8534
|
+
void bridge.sendToolInput({ arguments: mergedArgs });
|
|
8535
|
+
}, [initCount, toolInput, partialToolInput, customProps, toolOutput]);
|
|
7807
8536
|
useEffect5(() => {
|
|
7808
8537
|
const bridge = bridgeRef.current;
|
|
7809
8538
|
if (!bridge || initCount === 0) return;
|
|
7810
8539
|
const toolResultPayload = buildToolResultPayload(toolOutput, customProps);
|
|
7811
8540
|
if (!toolResultPayload) return;
|
|
7812
|
-
bridge.sendToolResult(toolResultPayload);
|
|
8541
|
+
void bridge.sendToolResult(toolResultPayload);
|
|
7813
8542
|
}, [initCount, toolOutput, customProps]);
|
|
7814
8543
|
useEffect5(() => {
|
|
7815
8544
|
const bridge = bridgeRef.current;
|
|
7816
8545
|
if (!bridge || initCount === 0 || !cancelled) return;
|
|
7817
|
-
bridge.sendToolCancelled({ reason: "Cancelled by user" });
|
|
8546
|
+
void bridge.sendToolCancelled({ reason: "Cancelled by user" });
|
|
7818
8547
|
}, [cancelled, initCount]);
|
|
7819
8548
|
const readyFiredRef = useRef4(false);
|
|
7820
8549
|
useEffect5(() => {
|
|
@@ -7822,11 +8551,6 @@ function ViewRendererBase({
|
|
|
7822
8551
|
readyFiredRef.current = true;
|
|
7823
8552
|
onReadyRef.current?.();
|
|
7824
8553
|
}, [initCount]);
|
|
7825
|
-
useEffect5(() => {
|
|
7826
|
-
if (initCount === 0 || !showSpinner) return;
|
|
7827
|
-
const timer = setTimeout(() => setShowSpinner(false), 300);
|
|
7828
|
-
return () => clearTimeout(timer);
|
|
7829
|
-
}, [initCount, showSpinner]);
|
|
7830
8554
|
const showHostBorder = resolved !== null && resolved.prefersBorder && displayMode !== "fullscreen";
|
|
7831
8555
|
if (loadError) {
|
|
7832
8556
|
return /* @__PURE__ */ React2.createElement("div", { className }, /* @__PURE__ */ React2.createElement("div", { className: "border border-red-200/50 dark:border-red-800/50 bg-red-50/30 dark:bg-red-950/20 rounded-lg p-4" }, /* @__PURE__ */ React2.createElement("p", { className: "text-sm text-red-600 dark:text-red-400" }, "Failed to load view: ", loadError)));
|
|
@@ -7909,7 +8633,6 @@ function ViewRendererBase({
|
|
|
7909
8633
|
{
|
|
7910
8634
|
className: isFullscreen ? "relative flex min-h-0 w-full flex-1 flex-col" : isPip ? "relative w-full h-full min-h-0 flex flex-1 flex-col" : "relative w-full flex flex-1 justify-center items-center"
|
|
7911
8635
|
},
|
|
7912
|
-
showSpinner && /* @__PURE__ */ React2.createElement("div", { className: "flex absolute inset-0 items-center justify-center z-10" }, /* @__PURE__ */ React2.createElement("span", { className: "text-sm text-muted-foreground" }, "Loading\u2026")),
|
|
7913
8636
|
!isPip && !isFullscreen && (invoking || invoked) && /* @__PURE__ */ React2.createElement("div", { className: "absolute -top-8 left-2 z-10 whitespace-nowrap pointer-events-none text-xs text-muted-foreground" }, invoking && !toolOutput ? invoking : invoked),
|
|
7914
8637
|
/* @__PURE__ */ React2.createElement(
|
|
7915
8638
|
"div",
|
|
@@ -7944,6 +8667,14 @@ function viewRendererAreEqual(prev, next) {
|
|
|
7944
8667
|
if (prev.customProps !== next.customProps) return false;
|
|
7945
8668
|
if (prev.hostContext !== next.hostContext) return false;
|
|
7946
8669
|
if (prev.hostCapabilities !== next.hostCapabilities) return false;
|
|
8670
|
+
if (prev.messageCapabilities !== next.messageCapabilities) return false;
|
|
8671
|
+
if (prev.modelContextCapabilities !== next.modelContextCapabilities)
|
|
8672
|
+
return false;
|
|
8673
|
+
if (prev.onMessage !== next.onMessage) return false;
|
|
8674
|
+
if (prev.onSamplingRequest !== next.onSamplingRequest) return false;
|
|
8675
|
+
if (prev.onDownloadFile !== next.onDownloadFile) return false;
|
|
8676
|
+
if (prev.onAppToolsChanged !== next.onAppToolsChanged) return false;
|
|
8677
|
+
if (prev.onModelContextUpdate !== next.onModelContextUpdate) return false;
|
|
7947
8678
|
if (prev.cspMode !== next.cspMode) return false;
|
|
7948
8679
|
if (prev.mockOpenAiFileApis !== next.mockOpenAiFileApis) return false;
|
|
7949
8680
|
if (prev.onInlineHeightChange !== next.onInlineHeightChange) return false;
|
|
@@ -7962,20 +8693,25 @@ export {
|
|
|
7962
8693
|
Tel,
|
|
7963
8694
|
Telemetry,
|
|
7964
8695
|
ViewRenderer,
|
|
8696
|
+
buildSandboxProxyBlobHtml,
|
|
7965
8697
|
buildViewSandboxBlobUrl,
|
|
8698
|
+
buildViewSandboxUrl,
|
|
7966
8699
|
clearRpcLogs,
|
|
7967
8700
|
detectFavicon,
|
|
7968
8701
|
getAllRpcLogs,
|
|
7969
8702
|
getRpcLogs,
|
|
7970
8703
|
getViewResourceUri,
|
|
8704
|
+
isToolVisibleToModel,
|
|
7971
8705
|
isViewResource,
|
|
7972
8706
|
isViewTool,
|
|
7973
8707
|
onMcpAuthorization,
|
|
7974
8708
|
parseCustomProps,
|
|
8709
|
+
pickPersistedServerConfig,
|
|
7975
8710
|
resolveViewResource,
|
|
7976
8711
|
setTelemetrySource,
|
|
7977
8712
|
specTypeSchemas,
|
|
7978
8713
|
subscribeToRpcLogs,
|
|
8714
|
+
toPersistedServerConfig,
|
|
7979
8715
|
useMcp,
|
|
7980
8716
|
useMcpClient,
|
|
7981
8717
|
useMcpServer
|