@mcp-use/client 2.0.0-beta.12 → 2.0.0-beta.14

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 (43) hide show
  1. package/README.md +1 -1
  2. package/dist/.tsbuildinfo +1 -1
  3. package/dist/auth/browser.d.ts +5 -0
  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 +16 -1
  8. package/dist/auth/node.d.ts.map +1 -1
  9. package/dist/auth/storage.d.ts +12 -3
  10. package/dist/auth/storage.d.ts.map +1 -1
  11. package/dist/index-browser.js +337 -143
  12. package/dist/index-browser.js.map +1 -1
  13. package/dist/index.d.ts +1 -1
  14. package/dist/index.d.ts.map +1 -1
  15. package/dist/index.js +137 -47
  16. package/dist/index.js.map +1 -1
  17. package/dist/react/McpClientProvider.d.ts.map +1 -1
  18. package/dist/react/index.d.ts +3 -2
  19. package/dist/react/index.d.ts.map +1 -1
  20. package/dist/react/index.js +681 -293
  21. package/dist/react/index.js.map +1 -1
  22. package/dist/react/storage.d.ts +10 -10
  23. package/dist/react/storage.d.ts.map +1 -1
  24. package/dist/react/token-expiry.d.ts +9 -0
  25. package/dist/react/token-expiry.d.ts.map +1 -0
  26. package/dist/react/types.d.ts +22 -5
  27. package/dist/react/types.d.ts.map +1 -1
  28. package/dist/react/useMcp-operations.d.ts.map +1 -1
  29. package/dist/react/useMcp.d.ts.map +1 -1
  30. package/dist/react/view/ViewRenderer.d.ts +3 -2
  31. package/dist/react/view/ViewRenderer.d.ts.map +1 -1
  32. package/dist/react/view/ext-apps-bridge.d.ts +1 -1
  33. package/dist/react/view/ext-apps-bridge.d.ts.map +1 -1
  34. package/dist/react/view/inject-openai-file-apis.d.ts.map +1 -1
  35. package/dist/react/view/types.d.ts +14 -4
  36. package/dist/react/view/types.d.ts.map +1 -1
  37. package/dist/react/view/view-host-policy.d.ts +24 -0
  38. package/dist/react/view/view-host-policy.d.ts.map +1 -0
  39. package/dist/telemetry/events.d.ts.map +1 -1
  40. package/dist/telemetry/tel-fetch.d.ts.map +1 -1
  41. package/dist/transport/connection-manager.d.ts +4 -0
  42. package/dist/transport/connection-manager.d.ts.map +1 -1
  43. package/package.json +2 -1
@@ -128,7 +128,7 @@ var Logger = class {
128
128
  var logger = Logger.get();
129
129
 
130
130
  // src/utils/version.ts
131
- var VERSION = "2.0.0-beta.11";
131
+ var VERSION = "2.0.0-beta.14";
132
132
  function getPackageVersion() {
133
133
  return VERSION;
134
134
  }
@@ -148,16 +148,13 @@ var MCPAgentExecutionEvent = class extends BaseTelemetryEvent {
148
148
  return {
149
149
  // Core execution info
150
150
  execution_method: this.data.executionMethod,
151
- query: this.data.query,
152
151
  query_length: this.data.query.length,
153
152
  success: this.data.success,
154
153
  // Agent configuration
155
154
  model_provider: this.data.modelProvider,
156
155
  model_name: this.data.modelName,
157
156
  server_count: this.data.serverCount,
158
- server_identifiers: this.data.serverIdentifiers,
159
157
  total_tools_available: this.data.totalToolsAvailable,
160
- tools_available_names: this.data.toolsAvailableNames,
161
158
  max_steps_configured: this.data.maxStepsConfigured,
162
159
  memory_enabled: this.data.memoryEnabled,
163
160
  use_server_manager: this.data.useServerManager,
@@ -168,8 +165,6 @@ var MCPAgentExecutionEvent = class extends BaseTelemetryEvent {
168
165
  // Execution results (always include, even if null)
169
166
  steps_taken: this.data.stepsTaken ?? null,
170
167
  tools_used_count: this.data.toolsUsedCount ?? null,
171
- tools_used_names: this.data.toolsUsedNames ?? null,
172
- response: this.data.response ?? null,
173
168
  response_length: this.data.response ? this.data.response.length : null,
174
169
  execution_time_ms: this.data.executionTimeMs ?? null,
175
170
  error_type: this.data.errorType ?? null,
@@ -265,21 +260,72 @@ async function telFetch(url, init) {
265
260
  }
266
261
  var POSTHOG_HOST = "https://eu.i.posthog.com";
267
262
  var POSTHOG_API_KEY = "phc_lyTtbYwvkdSbrcMQNPiKiiRWrrM1seyKIMjycSvItEI";
268
- function capturePostHog(params) {
269
- const host = params.host ?? POSTHOG_HOST;
270
- const apiKey = params.apiKey ?? POSTHOG_API_KEY;
271
- return telFetch(`${host}/i/v0/e/`, {
272
- method: "POST",
273
- headers: { "Content-Type": "application/json" },
274
- keepalive: true,
275
- body: JSON.stringify({
263
+ var CONTENT_PROPERTY = /(^|_)(arguments?|args|body|command|headers?|location|message|query|response|secret|subject|token|uri|url|user_agent)(_|$)/i;
264
+ var IDENTIFYING_PROPERTY = /(^|_)(server_identifiers?|server_names?|servers|tool_names?|tools_(available|used)_names)(_|$)/i;
265
+ var AGGREGATE_PROPERTY = /(_count|_length|_duration(?:_ms)?|_time_ms|(^|_)num_[a-z0-9_]+)$/i;
266
+ function normalizePropertyKey(key) {
267
+ return key.replace(/([a-z0-9])([A-Z])/g, "$1_$2").replace(/[^a-z0-9_$]+/gi, "_").toLowerCase();
268
+ }
269
+ function sanitizeValue(value, seen) {
270
+ if (Array.isArray(value)) {
271
+ if (seen.has(value)) {
272
+ throw new TypeError("Cyclic telemetry properties are not supported");
273
+ }
274
+ seen.add(value);
275
+ const sanitized = value.map((item) => sanitizeValue(item, seen));
276
+ seen.delete(value);
277
+ return sanitized;
278
+ }
279
+ if (value !== null && typeof value === "object" && (Object.getPrototypeOf(value) === Object.prototype || Object.getPrototypeOf(value) === null)) {
280
+ if (seen.has(value)) {
281
+ throw new TypeError("Cyclic telemetry properties are not supported");
282
+ }
283
+ seen.add(value);
284
+ const sanitized = sanitizeProperties(
285
+ value,
286
+ seen
287
+ );
288
+ seen.delete(value);
289
+ return sanitized;
290
+ }
291
+ return value;
292
+ }
293
+ function sanitizeProperties(properties, seen = /* @__PURE__ */ new WeakSet()) {
294
+ const sanitized = {};
295
+ for (const [key, value] of Object.entries(properties)) {
296
+ const normalizedKey = normalizePropertyKey(key);
297
+ if (AGGREGATE_PROPERTY.test(normalizedKey)) {
298
+ if (value === null || typeof value === "number") {
299
+ sanitized[key] = value;
300
+ }
301
+ continue;
302
+ }
303
+ if (IDENTIFYING_PROPERTY.test(normalizedKey) || CONTENT_PROPERTY.test(normalizedKey)) {
304
+ continue;
305
+ }
306
+ sanitized[key] = sanitizeValue(value, seen);
307
+ }
308
+ return sanitized;
309
+ }
310
+ async function capturePostHog(params) {
311
+ try {
312
+ const host = params.host ?? POSTHOG_HOST;
313
+ const apiKey = params.apiKey ?? POSTHOG_API_KEY;
314
+ const body = JSON.stringify({
276
315
  api_key: apiKey,
277
316
  event: params.event,
278
317
  distinct_id: params.distinctId,
279
- properties: params.properties,
318
+ properties: sanitizeProperties(params.properties),
280
319
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
281
- })
282
- });
320
+ });
321
+ await telFetch(`${host}/i/v0/e/`, {
322
+ method: "POST",
323
+ headers: { "Content-Type": "application/json" },
324
+ keepalive: true,
325
+ body
326
+ });
327
+ } catch {
328
+ }
283
329
  }
284
330
 
285
331
  // src/telemetry/telemetry.ts
@@ -589,6 +635,186 @@ setConnectorTelemetryTracker(
589
635
  (data) => Telemetry.getInstance().trackConnectorInit(data).catch(() => void 0)
590
636
  );
591
637
 
638
+ // src/auth/storage.ts
639
+ var AUTH_CRYPTO_DATABASE = "mcp-use-oauth-crypto";
640
+ var AUTH_CRYPTO_STORE = "keys";
641
+ var AUTH_CRYPTO_KEY = "aes-gcm-v1";
642
+ var textEncoder = new TextEncoder();
643
+ var textDecoder = new TextDecoder();
644
+ var LocalStorageKVStore = class {
645
+ fallback = /* @__PURE__ */ new Map();
646
+ keyPromise;
647
+ durable = true;
648
+ async get(key) {
649
+ if (!this.durable) return this.fallback.get(key) ?? null;
650
+ let stored;
651
+ try {
652
+ stored = localStorage.getItem(key);
653
+ } catch {
654
+ this.durable = false;
655
+ return this.fallback.get(key) ?? null;
656
+ }
657
+ if (stored === null) return null;
658
+ const envelope = parseEncryptedEnvelope(stored);
659
+ if (!envelope) {
660
+ await this.set(key, stored);
661
+ return stored;
662
+ }
663
+ try {
664
+ const cryptoKey = await this.getCryptoKey();
665
+ const plaintext = await globalThis.crypto.subtle.decrypt(
666
+ {
667
+ name: "AES-GCM",
668
+ iv: decodeBase64(envelope.iv),
669
+ additionalData: textEncoder.encode(key)
670
+ },
671
+ cryptoKey,
672
+ decodeBase64(envelope.ciphertext)
673
+ );
674
+ return textDecoder.decode(plaintext);
675
+ } catch {
676
+ await this.remove(key);
677
+ return null;
678
+ }
679
+ }
680
+ async set(key, value) {
681
+ if (!this.durable) {
682
+ this.fallback.set(key, value);
683
+ return;
684
+ }
685
+ try {
686
+ const cryptoKey = await this.getCryptoKey();
687
+ const iv = globalThis.crypto.getRandomValues(new Uint8Array(12));
688
+ const ciphertext = await globalThis.crypto.subtle.encrypt(
689
+ {
690
+ name: "AES-GCM",
691
+ iv,
692
+ additionalData: textEncoder.encode(key)
693
+ },
694
+ cryptoKey,
695
+ textEncoder.encode(value)
696
+ );
697
+ const envelope = {
698
+ v: 1,
699
+ alg: "A256GCM",
700
+ iv: encodeBase64(iv),
701
+ ciphertext: encodeBase64(new Uint8Array(ciphertext))
702
+ };
703
+ localStorage.setItem(key, JSON.stringify(envelope));
704
+ this.fallback.delete(key);
705
+ } catch {
706
+ this.durable = false;
707
+ try {
708
+ localStorage.removeItem(key);
709
+ } catch {
710
+ }
711
+ this.fallback.set(key, value);
712
+ }
713
+ }
714
+ remove(key) {
715
+ this.fallback.delete(key);
716
+ try {
717
+ localStorage.removeItem(key);
718
+ } catch {
719
+ this.durable = false;
720
+ }
721
+ }
722
+ keys() {
723
+ const out = new Set(this.fallback.keys());
724
+ if (this.durable) {
725
+ try {
726
+ for (let i = 0; i < localStorage.length; i++) {
727
+ const key = localStorage.key(i);
728
+ if (key) out.add(key);
729
+ }
730
+ } catch {
731
+ this.durable = false;
732
+ }
733
+ }
734
+ return [...out];
735
+ }
736
+ getCryptoKey() {
737
+ this.keyPromise ??= getOrCreateCryptoKey();
738
+ return this.keyPromise;
739
+ }
740
+ };
741
+ function parseEncryptedEnvelope(value) {
742
+ try {
743
+ const parsed = JSON.parse(value);
744
+ 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") {
745
+ return void 0;
746
+ }
747
+ return parsed;
748
+ } catch {
749
+ return void 0;
750
+ }
751
+ }
752
+ async function getOrCreateCryptoKey() {
753
+ if (!globalThis.crypto?.subtle || typeof indexedDB === "undefined") {
754
+ throw new Error("Durable browser cryptography is unavailable");
755
+ }
756
+ const candidate = await globalThis.crypto.subtle.generateKey(
757
+ { name: "AES-GCM", length: 256 },
758
+ false,
759
+ ["encrypt", "decrypt"]
760
+ );
761
+ const database = await openCryptoDatabase();
762
+ try {
763
+ return await new Promise((resolve, reject) => {
764
+ const transaction = database.transaction(AUTH_CRYPTO_STORE, "readwrite");
765
+ const store = transaction.objectStore(AUTH_CRYPTO_STORE);
766
+ const request = store.get(AUTH_CRYPTO_KEY);
767
+ let selected;
768
+ request.onsuccess = () => {
769
+ selected = request.result;
770
+ if (!selected) {
771
+ selected = candidate;
772
+ store.put(candidate, AUTH_CRYPTO_KEY);
773
+ }
774
+ };
775
+ request.onerror = () => reject(request.error);
776
+ transaction.oncomplete = () => {
777
+ if (selected) resolve(selected);
778
+ else reject(new Error("OAuth encryption key was not initialized"));
779
+ };
780
+ transaction.onerror = () => reject(transaction.error);
781
+ transaction.onabort = () => reject(transaction.error);
782
+ });
783
+ } finally {
784
+ database.close();
785
+ }
786
+ }
787
+ function openCryptoDatabase() {
788
+ return new Promise((resolve, reject) => {
789
+ const request = indexedDB.open(AUTH_CRYPTO_DATABASE, 1);
790
+ request.onupgradeneeded = () => {
791
+ const database = request.result;
792
+ if (!database.objectStoreNames.contains(AUTH_CRYPTO_STORE)) {
793
+ database.createObjectStore(AUTH_CRYPTO_STORE);
794
+ }
795
+ };
796
+ request.onsuccess = () => resolve(request.result);
797
+ request.onerror = () => reject(request.error);
798
+ request.onblocked = () => reject(new Error("OAuth encryption database is blocked"));
799
+ });
800
+ }
801
+ function encodeBase64(bytes) {
802
+ let binary = "";
803
+ for (const byte of bytes) binary += String.fromCharCode(byte);
804
+ return btoa(binary);
805
+ }
806
+ function decodeBase64(value) {
807
+ const binary = atob(value);
808
+ const bytes = new Uint8Array(binary.length);
809
+ for (let index = 0; index < binary.length; index++) {
810
+ bytes[index] = binary.charCodeAt(index);
811
+ }
812
+ return bytes;
813
+ }
814
+
815
+ // src/auth/session-store.ts
816
+ import { validateClientMetadataUrl } from "@modelcontextprotocol/client";
817
+
592
818
  // src/auth/url.ts
593
819
  function sanitizeUrl(raw) {
594
820
  const abort = () => {
@@ -613,29 +839,7 @@ function sanitizeParam([k, v]) {
613
839
  return `${encodeURIComponent(k)}${v.length > 0 ? `=${encodeURIComponent(v)}` : ""}`;
614
840
  }
615
841
 
616
- // src/auth/storage.ts
617
- var LocalStorageKVStore = class {
618
- get(key) {
619
- return localStorage.getItem(key);
620
- }
621
- set(key, value) {
622
- localStorage.setItem(key, value);
623
- }
624
- remove(key) {
625
- localStorage.removeItem(key);
626
- }
627
- keys() {
628
- const out = [];
629
- for (let i = 0; i < localStorage.length; i++) {
630
- const k = localStorage.key(i);
631
- if (k) out.push(k);
632
- }
633
- return out;
634
- }
635
- };
636
-
637
842
  // src/auth/session-store.ts
638
- import { validateClientMetadataUrl } from "@modelcontextprotocol/client";
639
843
  var OAuthSessionStore = class _OAuthSessionStore {
640
844
  serverUrl;
641
845
  storageKeyPrefix;
@@ -894,7 +1098,7 @@ var OAuthSessionStore = class _OAuthSessionStore {
894
1098
  */
895
1099
  async storeAuthorizationState(authorizationUrl, opts = {}) {
896
1100
  const state = globalThis.crypto.randomUUID();
897
- const stateKey = `${this.storageKeyPrefix}:state_${state}`;
1101
+ const stateKey = `${this.storageKeyPrefix}_${this.serverUrlHash}_state_${state}`;
898
1102
  const stateData = {
899
1103
  serverUrlHash: this.serverUrlHash,
900
1104
  expiry: Date.now() + 1e3 * 60 * 10,
@@ -947,16 +1151,26 @@ async function serializeBody(body) {
947
1151
  if (body instanceof Blob) return await body.text();
948
1152
  return body;
949
1153
  }
1154
+ function trimTrailingSlashes(value) {
1155
+ let end = value.length;
1156
+ while (end > 0 && value.charCodeAt(end - 1) === 47) {
1157
+ end--;
1158
+ }
1159
+ return value.slice(0, end);
1160
+ }
950
1161
  var BrowserOAuthClientProvider = class {
951
1162
  serverUrl;
952
1163
  staticClientInfo;
953
1164
  session;
1165
+ storage;
954
1166
  // Browser-only state
955
1167
  preventAutoAuth;
956
1168
  useRedirectFlow;
957
1169
  oauthProxyUrl;
958
1170
  connectionUrl;
959
1171
  proxyOAuthRequests;
1172
+ lastAttemptedAuthUrl = null;
1173
+ authorizationPending = false;
960
1174
  onPopupWindow;
961
1175
  constructor(serverUrl, options = {}) {
962
1176
  if (options.staticClientInfo?.client_secret) {
@@ -965,10 +1179,11 @@ var BrowserOAuthClientProvider = class {
965
1179
  );
966
1180
  }
967
1181
  this.serverUrl = serverUrl;
1182
+ this.storage = new LocalStorageKVStore();
968
1183
  this.session = new OAuthSessionStore(
969
1184
  serverUrl,
970
1185
  { ...options, allowClientSecret: false },
971
- new LocalStorageKVStore()
1186
+ this.storage
972
1187
  );
973
1188
  this.preventAutoAuth = options.preventAutoAuth;
974
1189
  this.useRedirectFlow = options.useRedirectFlow;
@@ -1006,6 +1221,12 @@ var BrowserOAuthClientProvider = class {
1006
1221
  getKey(keySuffix) {
1007
1222
  return this.session.getKey(keySuffix);
1008
1223
  }
1224
+ get hasPendingFlow() {
1225
+ return this.authorizationPending;
1226
+ }
1227
+ markFlowComplete() {
1228
+ this.authorizationPending = false;
1229
+ }
1009
1230
  /**
1010
1231
  * Re-anchor an SDK-derived OAuth discovery URL from the MCP connection
1011
1232
  * (proxy) origin onto the actual MCP server.
@@ -1033,8 +1254,8 @@ var BrowserOAuthClientProvider = class {
1033
1254
  const [doc, ...suffixParts] = rest.split("/");
1034
1255
  if (!doc) return url;
1035
1256
  const suffix = suffixParts.length ? `/${suffixParts.join("/")}` : "";
1036
- const connectionPath = connection.pathname.replace(/\/+$/, "");
1037
- const targetPath = target.pathname.replace(/\/+$/, "");
1257
+ const connectionPath = trimTrailingSlashes(connection.pathname);
1258
+ const targetPath = trimTrailingSlashes(target.pathname);
1038
1259
  const newSuffix = suffix && suffix === connectionPath ? targetPath : suffix;
1039
1260
  return `${target.origin}/.well-known/${doc}${newSuffix}${requested.search}`;
1040
1261
  } catch {
@@ -1185,6 +1406,8 @@ var BrowserOAuthClientProvider = class {
1185
1406
  return this.session.tokens(ctx);
1186
1407
  }
1187
1408
  saveTokens(tokens, ctx) {
1409
+ this.lastAttemptedAuthUrl = null;
1410
+ this.authorizationPending = false;
1188
1411
  return this.session.saveTokens(tokens, ctx);
1189
1412
  }
1190
1413
  async clientInformation(ctx) {
@@ -1251,16 +1474,22 @@ var BrowserOAuthClientProvider = class {
1251
1474
  * use `redirectToAuthorization` for that.
1252
1475
  */
1253
1476
  async prepareAuthorizationUrl(authorizationUrl) {
1254
- return this.session.storeAuthorizationState(authorizationUrl, {
1255
- extraProviderOptions: {
1256
- oauthProxyUrl: this.oauthProxyUrl,
1257
- ...this.clientMetadataUrl ? { clientMetadataUrl: this.clientMetadataUrl } : {},
1258
- ...this.staticClientInfo ? { staticClientInfo: this.staticClientInfo } : {},
1259
- ...this.scope ? { scope: this.scope } : {}
1260
- },
1261
- flowType: this.useRedirectFlow ? "redirect" : "popup",
1262
- returnUrl: typeof window !== "undefined" ? window.location.href : void 0
1263
- });
1477
+ const prepared = await this.session.storeAuthorizationState(
1478
+ authorizationUrl,
1479
+ {
1480
+ extraProviderOptions: {
1481
+ oauthProxyUrl: this.oauthProxyUrl,
1482
+ ...this.clientMetadataUrl ? { clientMetadataUrl: this.clientMetadataUrl } : {},
1483
+ ...this.staticClientInfo ? { staticClientInfo: this.staticClientInfo } : {},
1484
+ ...this.scope ? { scope: this.scope } : {}
1485
+ },
1486
+ flowType: this.useRedirectFlow ? "redirect" : "popup",
1487
+ returnUrl: typeof window !== "undefined" ? window.location.href : void 0
1488
+ }
1489
+ );
1490
+ this.lastAttemptedAuthUrl = prepared;
1491
+ this.authorizationPending = true;
1492
+ return prepared;
1264
1493
  }
1265
1494
  /**
1266
1495
  * Redirects the user agent to the authorization URL, storing necessary state.
@@ -1312,64 +1541,22 @@ var BrowserOAuthClientProvider = class {
1312
1541
  * Retrieves the last URL passed to `redirectToAuthorization`. Useful for manual fallback.
1313
1542
  */
1314
1543
  getLastAttemptedAuthUrl() {
1315
- const storedUrl = localStorage.getItem(this.getKey("last_auth_url"));
1316
- if (!storedUrl) return null;
1317
- const storedCallbackUrl = localStorage.getItem(
1318
- this.getKey("last_auth_callback_url")
1319
- );
1320
- if (storedCallbackUrl !== this.callbackUrl) {
1321
- console.info(
1322
- `[${this.storageKeyPrefix}] Recovering stale OAuth state whose callback cannot be verified.`
1323
- );
1324
- this.clearStorage();
1325
- return null;
1326
- }
1327
- const sanitized = sanitizeUrl(storedUrl);
1328
- try {
1329
- const redirectUri = new URL(sanitized).searchParams.get("redirect_uri");
1330
- if (redirectUri && new URL(redirectUri).toString() !== new URL(this.callbackUrl).toString()) {
1331
- console.info(
1332
- `[${this.storageKeyPrefix}] Recovering stale OAuth state after the Inspector callback path changed.`
1333
- );
1334
- this.clearStorage();
1335
- return null;
1336
- }
1337
- } catch {
1338
- this.clearStorage();
1339
- return null;
1340
- }
1341
- return sanitized;
1544
+ return this.lastAttemptedAuthUrl;
1342
1545
  }
1343
1546
  clearStorage() {
1547
+ this.lastAttemptedAuthUrl = null;
1548
+ this.authorizationPending = false;
1344
1549
  const prefixPattern = `${this.storageKeyPrefix}_${this.serverUrlHash}_`;
1345
- const statePattern = `${this.storageKeyPrefix}:state_`;
1346
1550
  const keysToRemove = [];
1347
1551
  let count = 0;
1348
- for (let i = 0; i < localStorage.length; i++) {
1349
- const key = localStorage.key(i);
1350
- if (!key) continue;
1552
+ for (const key of this.storage.keys()) {
1351
1553
  if (key.startsWith(prefixPattern)) {
1352
1554
  keysToRemove.push(key);
1353
- } else if (key.startsWith(statePattern)) {
1354
- try {
1355
- const item = localStorage.getItem(key);
1356
- if (item) {
1357
- const state = JSON.parse(item);
1358
- if (state.serverUrlHash === this.serverUrlHash) {
1359
- keysToRemove.push(key);
1360
- }
1361
- }
1362
- } catch (e) {
1363
- console.warn(
1364
- `[${this.storageKeyPrefix}] Error parsing state key ${key} during clearStorage:`,
1365
- e
1366
- );
1367
- }
1368
1555
  }
1369
1556
  }
1370
1557
  const uniqueKeysToRemove = [...new Set(keysToRemove)];
1371
1558
  uniqueKeysToRemove.forEach((key) => {
1372
- localStorage.removeItem(key);
1559
+ this.storage.remove(key);
1373
1560
  count++;
1374
1561
  });
1375
1562
  return count;
@@ -1577,20 +1764,14 @@ function renderResult(title, message, error, returnUrl) {
1577
1764
  }
1578
1765
  document.body.appendChild(container);
1579
1766
  }
1580
- function findStoredState(state) {
1581
- const defaultKey = `mcp:auth:state_${state}`;
1582
- let key = localStorage.getItem(defaultKey) ? defaultKey : null;
1583
- if (!key) {
1584
- const suffix = `:state_${state}`;
1585
- for (let index = 0; index < localStorage.length; index++) {
1586
- const candidate = localStorage.key(index);
1587
- if (candidate?.endsWith(suffix)) {
1588
- key = candidate;
1589
- break;
1590
- }
1591
- }
1592
- }
1593
- const serialized = key ? localStorage.getItem(key) : null;
1767
+ async function findStoredState(state) {
1768
+ const store = new LocalStorageKVStore();
1769
+ const legacySuffix = `:state_${state}`;
1770
+ const scopedSuffix = `_state_${state}`;
1771
+ const key = (await store.keys()).find(
1772
+ (candidate) => candidate.endsWith(legacySuffix) || candidate.endsWith(scopedSuffix)
1773
+ );
1774
+ const serialized = key ? await store.get(key) : null;
1594
1775
  if (!key || !serialized) {
1595
1776
  throw new Error(`Invalid or expired OAuth state "${state}".`);
1596
1777
  }
@@ -1598,10 +1779,10 @@ function findStoredState(state) {
1598
1779
  try {
1599
1780
  value = JSON.parse(serialized);
1600
1781
  } catch {
1601
- localStorage.removeItem(key);
1782
+ await store.remove(key);
1602
1783
  throw new Error("Failed to parse stored OAuth state.");
1603
1784
  }
1604
- return { key, value };
1785
+ return { key, value, store };
1605
1786
  }
1606
1787
  function redirectWithError(returnUrl, message) {
1607
1788
  const url = new URL(returnUrl);
@@ -1660,17 +1841,19 @@ async function completeAuthorization() {
1660
1841
  const callbackParams = new URLSearchParams(window.location.search);
1661
1842
  const state = callbackParams.get("state");
1662
1843
  let stateKey = null;
1844
+ let stateStore = null;
1663
1845
  let storedState = null;
1664
1846
  let provider = null;
1665
1847
  try {
1666
1848
  if (!state) {
1667
1849
  throw new Error("OAuth callback is missing the state parameter.");
1668
1850
  }
1669
- const stored = findStoredState(state);
1851
+ const stored = await findStoredState(state);
1670
1852
  stateKey = stored.key;
1853
+ stateStore = stored.store;
1671
1854
  storedState = stored.value;
1672
1855
  if (!storedState.expiry || storedState.expiry < Date.now()) {
1673
- localStorage.removeItem(stateKey);
1856
+ await stateStore.remove(stateKey);
1674
1857
  throw new Error(
1675
1858
  "OAuth state has expired. Please start authentication again."
1676
1859
  );
@@ -1685,7 +1868,7 @@ async function completeAuthorization() {
1685
1868
  fetch: provider.getProxyFetch()
1686
1869
  });
1687
1870
  await transport.finishAuth(callbackParams);
1688
- localStorage.removeItem(stateKey);
1871
+ await stateStore.remove(stateKey);
1689
1872
  signalResult(true, void 0, storedState, {
1690
1873
  state,
1691
1874
  serverUrlHash: storedState.serverUrlHash
@@ -1693,8 +1876,12 @@ async function completeAuthorization() {
1693
1876
  } catch (error) {
1694
1877
  const message = error instanceof Error ? error.message : String(error);
1695
1878
  console.error("[mcp-callback] OAuth callback failed:", error);
1696
- if (stateKey) localStorage.removeItem(stateKey);
1697
- if (provider) localStorage.removeItem(provider.getKey("last_auth_url"));
1879
+ if (stateKey && stateStore) await stateStore.remove(stateKey);
1880
+ if (provider) {
1881
+ await (stateStore ?? new LocalStorageKVStore()).remove(
1882
+ provider.getKey("last_auth_url")
1883
+ );
1884
+ }
1698
1885
  signalResult(false, message, storedState, {
1699
1886
  state,
1700
1887
  serverUrlHash: storedState?.serverUrlHash
@@ -1736,11 +1923,12 @@ async function completeOAuthFlow(provider, serverUrl, options = {}) {
1736
1923
  throw new Error(`Unexpected OAuth auth() result: ${result}`);
1737
1924
  }
1738
1925
  }
1739
- if (typeof flowProvider.getAuthorizationCode === "function") {
1740
- const code = await flowProvider.getAuthorizationCode();
1926
+ if (typeof flowProvider.getAuthorizationResponse === "function" || typeof flowProvider.getAuthorizationCode === "function") {
1927
+ const response = typeof flowProvider.getAuthorizationResponse === "function" ? await flowProvider.getAuthorizationResponse() : { code: await flowProvider.getAuthorizationCode() };
1741
1928
  await auth(provider, {
1742
1929
  serverUrl,
1743
- authorizationCode: code,
1930
+ authorizationCode: response.code,
1931
+ ...response.iss !== void 0 ? { iss: response.iss } : {},
1744
1932
  fetchFn
1745
1933
  });
1746
1934
  return;
@@ -1754,6 +1942,8 @@ async function waitForBrowserAuthComplete(provider, timeoutMs) {
1754
1942
  );
1755
1943
  }
1756
1944
  if (provider.useRedirectFlow) {
1945
+ await new Promise(() => {
1946
+ });
1757
1947
  return;
1758
1948
  }
1759
1949
  const tokensKey = provider.getKey?.("tokens");
@@ -1770,25 +1960,29 @@ async function waitForBrowserAuthComplete(provider, timeoutMs) {
1770
1960
  } catch {
1771
1961
  }
1772
1962
  }
1773
- const result = await runAuthPopup({
1774
- popup: null,
1775
- state,
1776
- tokensKey,
1777
- timeoutMs
1778
- });
1779
- switch (result.kind) {
1780
- case "success":
1781
- return;
1782
- case "cancelled":
1783
- throw new Error("OAuth authentication was cancelled.");
1784
- case "timeout":
1785
- throw new Error(
1786
- `OAuth callback not received within ${timeoutMs}ms. Ensure /oauth/callback calls onMcpAuthorization().`
1787
- );
1788
- case "error":
1789
- throw new Error(result.error);
1790
- default:
1791
- throw new Error("Unexpected OAuth popup result");
1963
+ try {
1964
+ const result = await runAuthPopup({
1965
+ popup: null,
1966
+ state,
1967
+ tokensKey,
1968
+ timeoutMs
1969
+ });
1970
+ switch (result.kind) {
1971
+ case "success":
1972
+ return;
1973
+ case "cancelled":
1974
+ throw new Error("OAuth authentication was cancelled.");
1975
+ case "timeout":
1976
+ throw new Error(
1977
+ `OAuth callback not received within ${timeoutMs}ms. Ensure /oauth/callback calls onMcpAuthorization().`
1978
+ );
1979
+ case "error":
1980
+ throw new Error(result.error);
1981
+ default:
1982
+ throw new Error("Unexpected OAuth popup result");
1983
+ }
1984
+ } finally {
1985
+ provider.markFlowComplete?.();
1792
1986
  }
1793
1987
  }
1794
1988