@mcp-use/client 2.0.0-beta.9 → 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.
Files changed (91) hide show
  1. package/README.md +18 -19
  2. package/dist/.tsbuildinfo +1 -1
  3. package/dist/auth/browser.d.ts +62 -6
  4. package/dist/auth/browser.d.ts.map +1 -1
  5. package/dist/auth/callback.d.ts.map +1 -1
  6. package/dist/auth/flow.d.ts.map +1 -1
  7. package/dist/auth/node.d.ts +115 -5
  8. package/dist/auth/node.d.ts.map +1 -1
  9. package/dist/auth/session-store.d.ts +7 -1
  10. package/dist/auth/session-store.d.ts.map +1 -1
  11. package/dist/auth/storage.d.ts +12 -3
  12. package/dist/auth/storage.d.ts.map +1 -1
  13. package/dist/code-mode/executor-e2b.d.ts.map +1 -1
  14. package/dist/core/base.d.ts +4 -8
  15. package/dist/core/base.d.ts.map +1 -1
  16. package/dist/core/browser.d.ts +14 -2
  17. package/dist/core/browser.d.ts.map +1 -1
  18. package/dist/core/config.d.ts +82 -20
  19. package/dist/core/config.d.ts.map +1 -1
  20. package/dist/core/node.d.ts +7 -10
  21. package/dist/core/node.d.ts.map +1 -1
  22. package/dist/core/session.d.ts +102 -25
  23. package/dist/core/session.d.ts.map +1 -1
  24. package/dist/index-browser.d.ts +1 -0
  25. package/dist/index-browser.d.ts.map +1 -1
  26. package/dist/index-browser.js +568 -163
  27. package/dist/index-browser.js.map +1 -1
  28. package/dist/index.d.ts +2 -2
  29. package/dist/index.d.ts.map +1 -1
  30. package/dist/index.js +407 -92
  31. package/dist/index.js.map +1 -1
  32. package/dist/react/McpClientProvider.d.ts +22 -10
  33. package/dist/react/McpClientProvider.d.ts.map +1 -1
  34. package/dist/react/index.d.ts +5 -2
  35. package/dist/react/index.d.ts.map +1 -1
  36. package/dist/react/index.js +1090 -352
  37. package/dist/react/index.js.map +1 -1
  38. package/dist/react/rpc-logger.d.ts +5 -0
  39. package/dist/react/rpc-logger.d.ts.map +1 -1
  40. package/dist/react/storage.d.ts +57 -10
  41. package/dist/react/storage.d.ts.map +1 -1
  42. package/dist/react/token-expiry.d.ts +9 -0
  43. package/dist/react/token-expiry.d.ts.map +1 -0
  44. package/dist/react/types.d.ts +134 -22
  45. package/dist/react/types.d.ts.map +1 -1
  46. package/dist/react/useMcp-helpers.d.ts.map +1 -1
  47. package/dist/react/useMcp-operations.d.ts +24 -6
  48. package/dist/react/useMcp-operations.d.ts.map +1 -1
  49. package/dist/react/useMcp.d.ts.map +1 -1
  50. package/dist/react/useMcpServerQueues.d.ts +36 -9
  51. package/dist/react/useMcpServerQueues.d.ts.map +1 -1
  52. package/dist/react/view/ViewRenderer.d.ts +12 -4
  53. package/dist/react/view/ViewRenderer.d.ts.map +1 -1
  54. package/dist/react/view/ext-apps-bridge.d.ts +1 -1
  55. package/dist/react/view/ext-apps-bridge.d.ts.map +1 -1
  56. package/dist/react/view/inject-openai-file-apis.d.ts.map +1 -1
  57. package/dist/react/view/parse-custom-props.d.ts +8 -0
  58. package/dist/react/view/parse-custom-props.d.ts.map +1 -1
  59. package/dist/react/view/resolve-view-resource.d.ts +10 -0
  60. package/dist/react/view/resolve-view-resource.d.ts.map +1 -1
  61. package/dist/react/view/sandbox-blob-url.d.ts +2 -0
  62. package/dist/react/view/sandbox-blob-url.d.ts.map +1 -1
  63. package/dist/react/view/types.d.ts +111 -4
  64. package/dist/react/view/types.d.ts.map +1 -1
  65. package/dist/react/view/view-detection.d.ts +18 -0
  66. package/dist/react/view/view-detection.d.ts.map +1 -1
  67. package/dist/react/view/view-host-policy.d.ts +61 -0
  68. package/dist/react/view/view-host-policy.d.ts.map +1 -0
  69. package/dist/sandbox.d.ts +2 -0
  70. package/dist/sandbox.d.ts.map +1 -0
  71. package/dist/sandbox.js +283 -0
  72. package/dist/sandbox.js.map +1 -0
  73. package/dist/telemetry/events.d.ts.map +1 -1
  74. package/dist/telemetry/tel-fetch.d.ts.map +1 -1
  75. package/dist/telemetry/telemetry.d.ts +5 -1
  76. package/dist/telemetry/telemetry.d.ts.map +1 -1
  77. package/dist/transport/base.d.ts +169 -34
  78. package/dist/transport/base.d.ts.map +1 -1
  79. package/dist/transport/connection-manager.d.ts +5 -1
  80. package/dist/transport/connection-manager.d.ts.map +1 -1
  81. package/dist/transport/http.d.ts +51 -2
  82. package/dist/transport/http.d.ts.map +1 -1
  83. package/dist/transport/stdio.d.ts +29 -1
  84. package/dist/transport/stdio.d.ts.map +1 -1
  85. package/dist/utils/elicitation.d.ts +4 -2
  86. package/dist/utils/elicitation.d.ts.map +1 -1
  87. package/dist/utils/logging.d.ts +1 -0
  88. package/dist/utils/logging.d.ts.map +1 -1
  89. package/dist/utils/version.d.ts +6 -0
  90. package/dist/utils/version.d.ts.map +1 -1
  91. package/package.json +9 -3
@@ -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
- * Get the current roots.
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
- /** Disconnect and release resources. */
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
- /** Check if the client is connected */
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
- /** Lazily expose the cached tools list. */
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
- /** Expose cached server capabilities. */
855
+ /** Capabilities cached during initialization, or an empty object. */
776
856
  get serverCapabilities() {
777
857
  return this.capabilitiesCache || {};
778
858
  }
779
- /** Expose cached server info. */
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
- /** Call a tool on the server. */
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
- /** Read a resource by URI. */
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
- /** Send a raw request through the client. */
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
- /** Establish connection to the MCP implementation via streamable HTTP. */
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
- /** Get the active transport type (`streamable-http` once connected). */
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-beta.8";
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.store.remove(key);
1707
- if (ctx) await this.store.remove(this.credentialKey("client_info"));
2010
+ await this.invalidateCredentials("registration");
1708
2011
  console.warn(
1709
- `[${this.storageKeyPrefix}] Removed OAuth client information containing a browser client_secret.`
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}] Invalidating cached OAuth client info due to redirect URI mismatch.`
2020
+ `[${this.storageKeyPrefix}] Recovering cached OAuth credentials after a redirect URI change.`
1718
2021
  );
1719
- await this.store.remove(key);
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 serialized = JSON.stringify(clientInformation);
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}:state_${state}`;
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
- new LocalStorageKVStore()
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.replace(/\/+$/, "");
1958
- const targetPath = target.pathname.replace(/\/+$/, "");
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
- * When this provider is not configured to proxy OAuth requests (no
1978
- * `oauthProxyUrl`, or `proxyOAuthRequests` disabled), the provided
1979
- * `baseFetch` is returned as-is (or `undefined` when none is given, letting
1980
- * the SDK fall back to its default `fetch`).
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 body = init?.body ? await serializeBody(init.body) : void 0;
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: init?.method || "POST",
2063
- headers: init?.headers ? Object.fromEntries(new Headers(init.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(data.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
- return this.session.saveClientInformation(clientInformation, ctx);
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
- return this.session.storeAuthorizationState(authorizationUrl, {
2144
- extraProviderOptions: {
2145
- oauthProxyUrl: this.oauthProxyUrl,
2146
- ...this.clientMetadataUrl ? { clientMetadataUrl: this.clientMetadataUrl } : {},
2147
- ...this.staticClientInfo ? { staticClientInfo: this.staticClientInfo } : {},
2148
- ...this.scope ? { scope: this.scope } : {}
2149
- },
2150
- flowType: this.useRedirectFlow ? "redirect" : "popup",
2151
- returnUrl: typeof window !== "undefined" ? window.location.href : void 0
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
- const storedUrl = localStorage.getItem(this.getKey("last_auth_url"));
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 (let i = 0; i < localStorage.length; i++) {
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
- localStorage.removeItem(key);
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
- function capturePostHog(params) {
2386
- const host = params.host ?? POSTHOG_HOST;
2387
- const apiKey = params.apiKey ?? POSTHOG_API_KEY;
2388
- return telFetch(`${host}/i/v0/e/`, {
2389
- method: "POST",
2390
- headers: { "Content-Type": "application/json" },
2391
- keepalive: true,
2392
- body: JSON.stringify({
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 code = await flowProvider.getAuthorizationCode();
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
- const result = await runAuthPopup({
2766
- popup: null,
2767
- state,
2768
- tokensKey,
2769
- timeoutMs
2770
- });
2771
- switch (result.kind) {
2772
- case "success":
2773
- return;
2774
- case "cancelled":
2775
- throw new Error("OAuth authentication was cancelled.");
2776
- case "timeout":
2777
- throw new Error(
2778
- `OAuth callback not received within ${timeoutMs}ms. Ensure /oauth/callback calls onMcpAuthorization().`
2779
- );
2780
- case "error":
2781
- throw new Error(result.error);
2782
- default:
2783
- throw new Error("Unexpected OAuth popup result");
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 {Error} When called before protocol negotiation completes.
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 {Error} If called on the base class instead of a concrete implementation
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 {Error} If the server is not found in the configuration
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 {Error} If the session is not found
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
- * Get the mcp-use package version.
3784
- * Works in all environments (Node.js, browser, Cloudflare Workers, Deno, etc.)
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: { ...params.allHeaders, ...authHeaders },
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
- }, [url, proxyConfig]);
4879
+ }, [
4880
+ url,
4881
+ requestedProxyAddress,
4882
+ connectionMode,
4883
+ autoProxyFallbackConfig.proxyAddress
4884
+ ]);
4394
4885
  const activeProxyConfig = useMemo(() => {
4395
- if (!effectiveProxyConfig?.proxyAddress) {
4396
- return proxyConfig;
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 latestHeaders = proxyConfig?.headers ?? {};
4399
- return {
4400
- ...effectiveProxyConfig,
4401
- headers: {
4402
- ...latestHeaders,
4403
- ...effectiveProxyConfig.headers ?? {}
4404
- }
4405
- };
4406
- }, [effectiveProxyConfig, proxyConfig]);
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) when proxying, or returns it
4739
- // unchanged otherwise. Never mutates the global fetch.
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
- connection.listPrompts(),
4879
- connection.supports("resources") ? connection.listResourceTemplates() : Promise.resolve({ resourceTemplates: [] })
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.expires_in ? Date.now() + tokens.expires_in * 1e3 : void 0;
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 authCode = await authProviderRef.current.getAuthorizationCode?.();
5049
- if (!authCode) {
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
  }
@@ -5498,6 +6014,7 @@ function useMcp(options) {
5498
6014
  // Triggers reconnection when proxy fallback changes OAuth URL
5499
6015
  proxyConfig,
5500
6016
  // Triggers reconnection when proxy config (including headers) changes
6017
+ autoProxyFallbackConfig.proxyAddress,
5501
6018
  providedAuthProvider
5502
6019
  ]);
5503
6020
  const retryRef = useRef(retry);
@@ -5647,20 +6164,14 @@ function renderResult(title, message, error, returnUrl) {
5647
6164
  }
5648
6165
  document.body.appendChild(container);
5649
6166
  }
5650
- function findStoredState(state) {
5651
- const defaultKey = `mcp:auth:state_${state}`;
5652
- let key = localStorage.getItem(defaultKey) ? defaultKey : null;
5653
- if (!key) {
5654
- const suffix = `:state_${state}`;
5655
- for (let index = 0; index < localStorage.length; index++) {
5656
- const candidate = localStorage.key(index);
5657
- if (candidate?.endsWith(suffix)) {
5658
- key = candidate;
5659
- break;
5660
- }
5661
- }
5662
- }
5663
- 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;
5664
6175
  if (!key || !serialized) {
5665
6176
  throw new Error(`Invalid or expired OAuth state "${state}".`);
5666
6177
  }
@@ -5668,10 +6179,10 @@ function findStoredState(state) {
5668
6179
  try {
5669
6180
  value = JSON.parse(serialized);
5670
6181
  } catch {
5671
- localStorage.removeItem(key);
6182
+ await store.remove(key);
5672
6183
  throw new Error("Failed to parse stored OAuth state.");
5673
6184
  }
5674
- return { key, value };
6185
+ return { key, value, store };
5675
6186
  }
5676
6187
  function redirectWithError(returnUrl, message) {
5677
6188
  const url = new URL(returnUrl);
@@ -5730,17 +6241,19 @@ async function completeAuthorization() {
5730
6241
  const callbackParams = new URLSearchParams(window.location.search);
5731
6242
  const state = callbackParams.get("state");
5732
6243
  let stateKey = null;
6244
+ let stateStore = null;
5733
6245
  let storedState = null;
5734
6246
  let provider = null;
5735
6247
  try {
5736
6248
  if (!state) {
5737
6249
  throw new Error("OAuth callback is missing the state parameter.");
5738
6250
  }
5739
- const stored = findStoredState(state);
6251
+ const stored = await findStoredState(state);
5740
6252
  stateKey = stored.key;
6253
+ stateStore = stored.store;
5741
6254
  storedState = stored.value;
5742
6255
  if (!storedState.expiry || storedState.expiry < Date.now()) {
5743
- localStorage.removeItem(stateKey);
6256
+ await stateStore.remove(stateKey);
5744
6257
  throw new Error(
5745
6258
  "OAuth state has expired. Please start authentication again."
5746
6259
  );
@@ -5755,7 +6268,7 @@ async function completeAuthorization() {
5755
6268
  fetch: provider.getProxyFetch()
5756
6269
  });
5757
6270
  await transport.finishAuth(callbackParams);
5758
- localStorage.removeItem(stateKey);
6271
+ await stateStore.remove(stateKey);
5759
6272
  signalResult(true, void 0, storedState, {
5760
6273
  state,
5761
6274
  serverUrlHash: storedState.serverUrlHash
@@ -5763,8 +6276,12 @@ async function completeAuthorization() {
5763
6276
  } catch (error) {
5764
6277
  const message = error instanceof Error ? error.message : String(error);
5765
6278
  console.error("[mcp-callback] OAuth callback failed:", error);
5766
- if (stateKey) localStorage.removeItem(stateKey);
5767
- if (provider) localStorage.removeItem(provider.getKey("last_auth_url"));
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
+ }
5768
6285
  signalResult(false, message, storedState, {
5769
6286
  state,
5770
6287
  serverUrlHash: storedState?.serverUrlHash
@@ -5787,45 +6304,6 @@ import React, {
5787
6304
  useState as useState3
5788
6305
  } from "react";
5789
6306
 
5790
- // src/react/types.ts
5791
- var PERSISTED_SERVER_CONFIG_KEYS = [
5792
- "url",
5793
- "displayName",
5794
- "enabled",
5795
- "proxyConfig",
5796
- "oauthProxyUrl",
5797
- "connectionMode",
5798
- "autoProxyFallback",
5799
- "callbackUrl",
5800
- "storageKeyPrefix",
5801
- "headers",
5802
- "logLevel",
5803
- "autoRetry",
5804
- "autoReconnect",
5805
- "reconnectionOptions",
5806
- "popupFeatures",
5807
- "preventAutoAuth",
5808
- "useRedirectFlow",
5809
- "clientOptions",
5810
- "protocolNegotiation",
5811
- "timeout",
5812
- "clientInfo",
5813
- "oauth"
5814
- ];
5815
- function pickPersistedServerConfig(source) {
5816
- const out = {};
5817
- for (const key of PERSISTED_SERVER_CONFIG_KEYS) {
5818
- const value = source[key];
5819
- if (value !== void 0) {
5820
- out[key] = value;
5821
- }
5822
- }
5823
- return out;
5824
- }
5825
- function toPersistedServerConfig(config) {
5826
- return pickPersistedServerConfig(config);
5827
- }
5828
-
5829
6307
  // src/react/useMcpServerQueues.ts
5830
6308
  import { useCallback as useCallback3, useEffect as useEffect2, useRef as useRef2, useState as useState2 } from "react";
5831
6309
  var MAX_NOTIFICATIONS = 500;
@@ -6012,8 +6490,8 @@ function sameSerializedValue(left, right) {
6012
6490
  }
6013
6491
  function isSameMcpServer(left, right) {
6014
6492
  return left.id === right.id && sameSerializedValue(
6015
- pickPersistedServerConfig(left),
6016
- pickPersistedServerConfig(right)
6493
+ pickLiveServerConfig(left),
6494
+ pickLiveServerConfig(right)
6017
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(
6018
6496
  left.pendingSamplingRequests,
6019
6497
  right.pendingSamplingRequests
@@ -6147,7 +6625,7 @@ function McpServerWrapper({
6147
6625
  }, [onUpdate]);
6148
6626
  useEffect3(() => {
6149
6627
  const server = {
6150
- ...toPersistedServerConfig(options),
6628
+ ...pickLiveServerConfig(options),
6151
6629
  ...mcp,
6152
6630
  id,
6153
6631
  displayName: displayName || options.displayName || id,
@@ -6494,8 +6972,8 @@ function McpClientProvider({
6494
6972
  ...options
6495
6973
  };
6496
6974
  if (sameSerializedValue(
6497
- pickPersistedServerConfig(currentConfig.options),
6498
- pickPersistedServerConfig(updatedOptions)
6975
+ pickLiveServerConfig(currentConfig.options),
6976
+ pickLiveServerConfig(updatedOptions)
6499
6977
  )) {
6500
6978
  return;
6501
6979
  }
@@ -6654,70 +7132,115 @@ function useMcpServer(id) {
6654
7132
 
6655
7133
  // src/react/storage.ts
6656
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
+ */
6657
7141
  constructor(storageKey = "mcp-client-servers") {
6658
7142
  this.storageKey = storageKey;
6659
7143
  this.metadataKey = `${storageKey}-metadata`;
6660
7144
  }
6661
7145
  metadataKey;
7146
+ /** Returns sanitized server configurations from `localStorage`. */
6662
7147
  getServers() {
6663
7148
  try {
6664
7149
  const stored = localStorage.getItem(this.storageKey);
6665
- return stored ? JSON.parse(stored) : {};
6666
- } catch (error) {
6667
- console.error("[LocalStorageProvider] Failed to load servers:", error);
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.");
6668
7178
  return {};
6669
7179
  }
6670
7180
  }
7181
+ /** Replaces all saved server configurations. */
6671
7182
  setServers(servers) {
6672
7183
  try {
6673
- localStorage.setItem(this.storageKey, JSON.stringify(servers));
6674
- } catch (error) {
6675
- console.error("[LocalStorageProvider] Failed to save servers:", error);
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.");
6676
7193
  }
6677
7194
  }
7195
+ /** Saves one server configuration. */
6678
7196
  setServer(id, config) {
6679
7197
  const servers = this.getServers();
6680
7198
  servers[id] = config;
6681
7199
  this.setServers(servers);
6682
7200
  }
7201
+ /** Removes one server and its cached metadata. */
6683
7202
  removeServer(id) {
6684
7203
  const servers = this.getServers();
6685
7204
  delete servers[id];
6686
7205
  this.setServers(servers);
6687
7206
  this.removeServerMetadata(id);
6688
7207
  }
7208
+ /** Removes all saved configurations and metadata. */
6689
7209
  clear() {
6690
7210
  try {
6691
7211
  localStorage.removeItem(this.storageKey);
6692
7212
  localStorage.removeItem(this.metadataKey);
6693
- } catch (error) {
6694
- console.error("[LocalStorageProvider] Failed to clear:", error);
7213
+ } catch {
7214
+ console.error("[LocalStorageProvider] Failed to clear.");
6695
7215
  }
6696
7216
  }
6697
7217
  getAllMetadata() {
6698
7218
  try {
6699
7219
  const stored = localStorage.getItem(this.metadataKey);
6700
7220
  return stored ? JSON.parse(stored) : {};
6701
- } catch (error) {
6702
- console.error("[LocalStorageProvider] Failed to load metadata:", error);
7221
+ } catch {
7222
+ console.error("[LocalStorageProvider] Failed to load metadata.");
6703
7223
  return {};
6704
7224
  }
6705
7225
  }
6706
7226
  setAllMetadata(metadata) {
6707
7227
  try {
6708
7228
  localStorage.setItem(this.metadataKey, JSON.stringify(metadata));
6709
- } catch (error) {
6710
- console.error("[LocalStorageProvider] Failed to save metadata:", error);
7229
+ } catch {
7230
+ console.error("[LocalStorageProvider] Failed to save metadata.");
6711
7231
  }
6712
7232
  }
7233
+ /** Returns cached presentation metadata for a server. */
6713
7234
  getServerMetadata(id) {
6714
7235
  return this.getAllMetadata()[id];
6715
7236
  }
7237
+ /** Saves presentation metadata and stamps the current cache time. */
6716
7238
  setServerMetadata(id, metadata) {
6717
7239
  const allMetadata = this.getAllMetadata();
6718
7240
  allMetadata[id] = { ...metadata, cachedAt: Date.now() };
6719
7241
  this.setAllMetadata(allMetadata);
6720
7242
  }
7243
+ /** Removes cached presentation metadata for a server. */
6721
7244
  removeServerMetadata(id) {
6722
7245
  const allMetadata = this.getAllMetadata();
6723
7246
  delete allMetadata[id];
@@ -6727,29 +7250,42 @@ var LocalStorageProvider = class {
6727
7250
  var MemoryStorageProvider = class {
6728
7251
  storage = {};
6729
7252
  metadata = {};
7253
+ /** Returns a shallow copy of all stored server configurations. */
6730
7254
  getServers() {
6731
7255
  return { ...this.storage };
6732
7256
  }
7257
+ /** Replaces all stored server configurations. */
6733
7258
  setServers(servers) {
6734
- this.storage = { ...servers };
7259
+ this.storage = Object.fromEntries(
7260
+ Object.entries(servers).map(([id, config]) => [
7261
+ id,
7262
+ toPersistedServerConfig(config)
7263
+ ])
7264
+ );
6735
7265
  }
7266
+ /** Stores one server configuration. */
6736
7267
  setServer(id, config) {
6737
- this.storage[id] = config;
7268
+ this.storage[id] = toPersistedServerConfig(config);
6738
7269
  }
7270
+ /** Removes one server and its cached metadata. */
6739
7271
  removeServer(id) {
6740
7272
  delete this.storage[id];
6741
7273
  this.removeServerMetadata(id);
6742
7274
  }
7275
+ /** Removes all configurations and metadata. */
6743
7276
  clear() {
6744
7277
  this.storage = {};
6745
7278
  this.metadata = {};
6746
7279
  }
7280
+ /** Returns cached presentation metadata for a server. */
6747
7281
  getServerMetadata(id) {
6748
7282
  return this.metadata[id];
6749
7283
  }
7284
+ /** Saves presentation metadata and stamps the current cache time. */
6750
7285
  setServerMetadata(id, metadata) {
6751
7286
  this.metadata[id] = { ...metadata, cachedAt: Date.now() };
6752
7287
  }
7288
+ /** Removes cached presentation metadata for a server. */
6753
7289
  removeServerMetadata(id) {
6754
7290
  delete this.metadata[id];
6755
7291
  }
@@ -6814,32 +7350,58 @@ var OPENAI_FILE_APIS_SCRIPT = `<script>
6814
7350
  })();
6815
7351
  </script>`;
6816
7352
  function injectOpenAiFileApis(html) {
6817
- if (html.includes("<head>")) {
6818
- return html.replace("<head>", "<head>" + OPENAI_FILE_APIS_SCRIPT);
6819
- }
6820
- if (html.includes("<HEAD>")) {
6821
- return html.replace("<HEAD>", "<HEAD>" + OPENAI_FILE_APIS_SCRIPT);
6822
- }
6823
- if (html.includes("<html>")) {
6824
- return html.replace(
6825
- "<html>",
6826
- "<html><head>" + OPENAI_FILE_APIS_SCRIPT + "</head>"
6827
- );
6828
- }
6829
- if (html.includes("<HTML>")) {
6830
- return html.replace(
6831
- "<HTML>",
6832
- "<HTML><head>" + OPENAI_FILE_APIS_SCRIPT + "</head>"
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>"
6833
7363
  );
6834
7364
  }
6835
- if (html.includes("<!DOCTYPE") || html.includes("<!doctype")) {
6836
- return html.replace(
6837
- /(<!DOCTYPE[^>]*>|<!doctype[^>]*>)/i,
6838
- "$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>"
6839
7371
  );
6840
7372
  }
6841
7373
  return OPENAI_FILE_APIS_SCRIPT + html;
6842
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
+ }
6843
7405
 
6844
7406
  // src/react/view/resolve-view-resource.ts
6845
7407
  function resolveViewResource(options) {
@@ -6888,6 +7450,11 @@ function resolveViewResource(options) {
6888
7450
  }
6889
7451
 
6890
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
+ }
6891
7458
  function applySandboxSearchParams(url, options) {
6892
7459
  const { cspMode, permissions, widgetCsp } = options;
6893
7460
  url.searchParams.set(
@@ -7254,6 +7821,67 @@ var VIEW_DIMENSIONS = {
7254
7821
  FULLSCREEN_HEADER_HEIGHT: 50
7255
7822
  };
7256
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
+
7257
7885
  // src/react/view/view-detection.ts
7258
7886
  function getViewResourceUri(toolMeta) {
7259
7887
  const uri = toolMeta?.ui;
@@ -7273,15 +7901,6 @@ function isViewResource(mimeType) {
7273
7901
  var DEFAULT_HOST_INFO = { name: "mcp-use-client", version: "2.0.0" };
7274
7902
  var DEFAULT_TOOL_CALL_TIMEOUT = 6e5;
7275
7903
  var SANDBOX_PROXY_READY = "ui/notifications/sandbox-proxy-ready";
7276
- var DEFAULT_HOST_CAPABILITIES = {
7277
- openLinks: {},
7278
- serverTools: {},
7279
- serverResources: {},
7280
- logging: {},
7281
- updateModelContext: { text: {} },
7282
- // ponytail: always advertised; bridge.onmessage no-ops when onMessage unset
7283
- message: { text: {} }
7284
- };
7285
7904
  function CloseIcon() {
7286
7905
  return /* @__PURE__ */ React2.createElement(
7287
7906
  "svg",
@@ -7345,12 +7964,17 @@ function ViewRendererBase({
7345
7964
  hostInfo = DEFAULT_HOST_INFO,
7346
7965
  hostContext,
7347
7966
  hostCapabilities,
7967
+ messageCapabilities,
7968
+ modelContextCapabilities,
7348
7969
  cspMode = "widget-declared",
7349
7970
  displayMode: displayModeProp,
7350
7971
  onDisplayModeChange,
7351
7972
  inlineMaxWidth = 768,
7352
7973
  chromeless,
7353
7974
  onMessage,
7975
+ onSamplingRequest,
7976
+ onDownloadFile,
7977
+ onAppToolsChanged,
7354
7978
  onModelContextUpdate,
7355
7979
  onLog,
7356
7980
  onReady,
@@ -7382,12 +8006,42 @@ function ViewRendererBase({
7382
8006
  const [activeSandboxUrl, setActiveSandboxUrl] = useState4(null);
7383
8007
  const [loadError, setLoadError] = useState4(null);
7384
8008
  const [initCount, setInitCount] = useState4(0);
7385
- const [showSpinner, setShowSpinner] = useState4(true);
7386
8009
  const [inlineHeight, setInlineHeight] = useState4(
7387
8010
  VIEW_DIMENSIONS.DEFAULT_HEIGHT
7388
8011
  );
7389
8012
  const [internalDisplayMode, setInternalDisplayMode] = useState4("inline");
7390
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
+ );
7391
8045
  const effectiveHostContext = useMemo3(() => {
7392
8046
  if (!hostContext) return hostContext;
7393
8047
  if (hostContext.displayMode === displayMode) return hostContext;
@@ -7397,6 +8051,12 @@ function ViewRendererBase({
7397
8051
  hostContextRef.current = effectiveHostContext;
7398
8052
  const onMessageRef = useRef4(onMessage);
7399
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;
7400
8060
  const toolInputRef = useRef4(toolInput);
7401
8061
  toolInputRef.current = toolInput;
7402
8062
  const partialToolInputRef = useRef4(partialToolInput);
@@ -7613,7 +8273,7 @@ function ViewRendererBase({
7613
8273
  await readyPromise;
7614
8274
  if (disposed) return;
7615
8275
  const capabilities = {
7616
- ...hostCapabilities ?? DEFAULT_HOST_CAPABILITIES,
8276
+ ...effectiveHostCapabilities,
7617
8277
  sandbox: {
7618
8278
  csp: cspMode === "permissive" ? void 0 : resolved.csp,
7619
8279
  permissions: resolved.permissions
@@ -7622,75 +8282,110 @@ function ViewRendererBase({
7622
8282
  bridge = new AppBridge(null, hostInfo, capabilities, {
7623
8283
  hostContext: hostContextRef.current
7624
8284
  });
7625
- bridge.onmessage = async ({
7626
- content
7627
- }) => {
7628
- if (content.length > 0 && onMessageRef.current) {
7629
- onMessageRef.current(content);
7630
- }
7631
- return {};
7632
- };
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
+ }
7633
8311
  bridge.onopenlink = async ({ url }) => {
7634
8312
  if (url) window.open(url, "_blank", "noopener,noreferrer");
7635
8313
  return {};
7636
8314
  };
7637
- bridge.oncalltool = (async ({
7638
- name,
7639
- arguments: args
7640
- }) => {
7641
- const conn = connectionRef.current;
7642
- if (!conn) throw new Error("Server connection not available");
7643
- try {
7644
- return await conn.callTool(name, args || {}, {
7645
- timeout: toolCallTimeout,
7646
- resetTimeoutOnProgress: true
7647
- });
7648
- } catch (error) {
7649
- bridge?.sendToolCancelled({
7650
- reason: error instanceof Error ? error.message : String(error)
7651
- });
7652
- throw error;
7653
- }
7654
- });
7655
- bridge.onreadresource = (async ({
7656
- uri
7657
- }) => {
7658
- const conn = connectionRef.current;
7659
- if (!conn) throw new Error("Server connection not available");
7660
- return await conn.readResource(uri);
7661
- });
7662
- bridge.onlistresources = (async () => {
7663
- const conn = connectionRef.current;
7664
- if (!conn) throw new Error("Server connection not available");
7665
- return { resources: [...conn.resources ?? []] };
7666
- });
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
+ }
7667
8350
  bridge.onrequestdisplaymode = async ({
7668
8351
  mode
7669
8352
  }) => {
7670
8353
  const requested = mode ?? "inline";
7671
- const available = hostContextRef.current?.availableDisplayModes ?? [
7672
- "inline",
7673
- "pip",
7674
- "fullscreen"
7675
- ];
7676
- const effective = available.includes(requested) ? requested : displayModeRef.current;
8354
+ const effective = resolveRequestedDisplayMode({
8355
+ requested,
8356
+ current: displayModeRef.current,
8357
+ hostAvailable: hostContextRef.current?.availableDisplayModes,
8358
+ appAvailable: bridge?.getAppCapabilities()?.availableDisplayModes
8359
+ });
7677
8360
  await handleDisplayModeChangeRef.current(effective);
7678
8361
  return { mode: effective };
7679
8362
  };
7680
- bridge.onupdatemodelcontext = async ({
7681
- content,
7682
- structuredContent
7683
- }) => {
7684
- onModelContextUpdateRef.current?.({ content, structuredContent });
7685
- return {};
7686
- };
7687
- bridge.onloggingmessage = async ({
7688
- level,
7689
- data
7690
- }) => {
7691
- onLogRef.current?.({ level, data });
7692
- return {};
7693
- };
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
+ }
7694
8389
  bridge.onsizechange = async ({
7695
8390
  height
7696
8391
  }) => {
@@ -7700,6 +8395,35 @@ function ViewRendererBase({
7700
8395
  onInlineHeightChangeRef.current?.(height);
7701
8396
  }
7702
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
+ );
7703
8427
  const initPromise = hookInitialized(bridge);
7704
8428
  let transport = new PostMessageTransport(
7705
8429
  iframe.contentWindow,
@@ -7720,9 +8444,11 @@ function ViewRendererBase({
7720
8444
  bridgeRef.current = bridge;
7721
8445
  setInitCount((c) => c + 1);
7722
8446
  onLifecycleChangeRef.current?.({ status: "initialized" });
8447
+ await publishAppTools();
7723
8448
  const currentPartialToolInput = partialToolInputRef.current;
7724
- if (currentPartialToolInput) {
7725
- bridge.sendToolInputPartial({
8449
+ const hasCompletedToolResult = toolOutputRef.current !== void 0 && toolOutputRef.current !== null;
8450
+ if (currentPartialToolInput && !hasCompletedToolResult) {
8451
+ await bridge.sendToolInputPartial({
7726
8452
  arguments: currentPartialToolInput
7727
8453
  });
7728
8454
  } else {
@@ -7730,14 +8456,14 @@ function ViewRendererBase({
7730
8456
  ...toolInputRef.current,
7731
8457
  ...parseCustomProps(customPropsRef.current)
7732
8458
  };
7733
- bridge.sendToolInput({ arguments: mergedArgs });
8459
+ await bridge.sendToolInput({ arguments: mergedArgs });
7734
8460
  }
7735
8461
  const toolResultPayload = buildToolResultPayload(
7736
8462
  toolOutputRef.current,
7737
8463
  customPropsRef.current
7738
8464
  );
7739
8465
  if (toolResultPayload) {
7740
- bridge.sendToolResult(toolResultPayload);
8466
+ await bridge.sendToolResult(toolResultPayload);
7741
8467
  }
7742
8468
  onLifecycleChangeRef.current?.({ status: "ready" });
7743
8469
  } catch (err) {
@@ -7754,6 +8480,7 @@ function ViewRendererBase({
7754
8480
  disposed = true;
7755
8481
  const toClose = bridge;
7756
8482
  bridgeRef.current = null;
8483
+ onAppToolsChangedRef.current?.(null);
7757
8484
  if (!toClose) return;
7758
8485
  onLifecycleChangeRef.current?.({ status: "tearing-down" });
7759
8486
  void (async () => {
@@ -7776,7 +8503,7 @@ function ViewRendererBase({
7776
8503
  resolved,
7777
8504
  activeSandboxUrl,
7778
8505
  hostInfo,
7779
- hostCapabilities,
8506
+ effectiveHostCapabilities,
7780
8507
  cspMode,
7781
8508
  viewId,
7782
8509
  wrapTransport,
@@ -7786,33 +8513,37 @@ function ViewRendererBase({
7786
8513
  useEffect5(() => {
7787
8514
  const bridge = bridgeRef.current;
7788
8515
  if (!bridge || initCount === 0 || !effectiveHostContext) return;
7789
- bridge.setHostContext(effectiveHostContext);
8516
+ void bridge.setHostContext(effectiveHostContext);
7790
8517
  }, [effectiveHostContext, initCount]);
7791
8518
  useEffect5(() => {
7792
8519
  const bridge = bridgeRef.current;
7793
- if (!bridge || initCount === 0 || !partialToolInput) return;
7794
- bridge.sendToolInputPartial({ arguments: partialToolInput });
7795
- }, [initCount, partialToolInput]);
8520
+ if (!bridge || initCount === 0 || !partialToolInput || toolOutput !== void 0 && toolOutput !== null) {
8521
+ return;
8522
+ }
8523
+ void bridge.sendToolInputPartial({ arguments: partialToolInput });
8524
+ }, [initCount, partialToolInput, toolOutput]);
7796
8525
  useEffect5(() => {
7797
8526
  const bridge = bridgeRef.current;
7798
- if (!bridge || initCount === 0 || partialToolInput) return;
8527
+ if (!bridge || initCount === 0 || partialToolInput && (toolOutput === void 0 || toolOutput === null)) {
8528
+ return;
8529
+ }
7799
8530
  const mergedArgs = {
7800
8531
  ...toolInput,
7801
8532
  ...parseCustomProps(customProps)
7802
8533
  };
7803
- bridge.sendToolInput({ arguments: mergedArgs });
7804
- }, [initCount, toolInput, partialToolInput, customProps]);
8534
+ void bridge.sendToolInput({ arguments: mergedArgs });
8535
+ }, [initCount, toolInput, partialToolInput, customProps, toolOutput]);
7805
8536
  useEffect5(() => {
7806
8537
  const bridge = bridgeRef.current;
7807
8538
  if (!bridge || initCount === 0) return;
7808
8539
  const toolResultPayload = buildToolResultPayload(toolOutput, customProps);
7809
8540
  if (!toolResultPayload) return;
7810
- bridge.sendToolResult(toolResultPayload);
8541
+ void bridge.sendToolResult(toolResultPayload);
7811
8542
  }, [initCount, toolOutput, customProps]);
7812
8543
  useEffect5(() => {
7813
8544
  const bridge = bridgeRef.current;
7814
8545
  if (!bridge || initCount === 0 || !cancelled) return;
7815
- bridge.sendToolCancelled({ reason: "Cancelled by user" });
8546
+ void bridge.sendToolCancelled({ reason: "Cancelled by user" });
7816
8547
  }, [cancelled, initCount]);
7817
8548
  const readyFiredRef = useRef4(false);
7818
8549
  useEffect5(() => {
@@ -7820,11 +8551,6 @@ function ViewRendererBase({
7820
8551
  readyFiredRef.current = true;
7821
8552
  onReadyRef.current?.();
7822
8553
  }, [initCount]);
7823
- useEffect5(() => {
7824
- if (initCount === 0 || !showSpinner) return;
7825
- const timer = setTimeout(() => setShowSpinner(false), 300);
7826
- return () => clearTimeout(timer);
7827
- }, [initCount, showSpinner]);
7828
8554
  const showHostBorder = resolved !== null && resolved.prefersBorder && displayMode !== "fullscreen";
7829
8555
  if (loadError) {
7830
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)));
@@ -7907,7 +8633,6 @@ function ViewRendererBase({
7907
8633
  {
7908
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"
7909
8635
  },
7910
- 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")),
7911
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),
7912
8637
  /* @__PURE__ */ React2.createElement(
7913
8638
  "div",
@@ -7942,6 +8667,14 @@ function viewRendererAreEqual(prev, next) {
7942
8667
  if (prev.customProps !== next.customProps) return false;
7943
8668
  if (prev.hostContext !== next.hostContext) return false;
7944
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;
7945
8678
  if (prev.cspMode !== next.cspMode) return false;
7946
8679
  if (prev.mockOpenAiFileApis !== next.mockOpenAiFileApis) return false;
7947
8680
  if (prev.onInlineHeightChange !== next.onInlineHeightChange) return false;
@@ -7960,20 +8693,25 @@ export {
7960
8693
  Tel,
7961
8694
  Telemetry,
7962
8695
  ViewRenderer,
8696
+ buildSandboxProxyBlobHtml,
7963
8697
  buildViewSandboxBlobUrl,
8698
+ buildViewSandboxUrl,
7964
8699
  clearRpcLogs,
7965
8700
  detectFavicon,
7966
8701
  getAllRpcLogs,
7967
8702
  getRpcLogs,
7968
8703
  getViewResourceUri,
8704
+ isToolVisibleToModel,
7969
8705
  isViewResource,
7970
8706
  isViewTool,
7971
8707
  onMcpAuthorization,
7972
8708
  parseCustomProps,
8709
+ pickPersistedServerConfig,
7973
8710
  resolveViewResource,
7974
8711
  setTelemetrySource,
7975
8712
  specTypeSchemas,
7976
8713
  subscribeToRpcLogs,
8714
+ toPersistedServerConfig,
7977
8715
  useMcp,
7978
8716
  useMcpClient,
7979
8717
  useMcpServer