@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.
- package/README.md +1 -1
- package/dist/.tsbuildinfo +1 -1
- package/dist/auth/browser.d.ts +5 -0
- package/dist/auth/browser.d.ts.map +1 -1
- package/dist/auth/callback.d.ts.map +1 -1
- package/dist/auth/flow.d.ts.map +1 -1
- package/dist/auth/node.d.ts +16 -1
- package/dist/auth/node.d.ts.map +1 -1
- package/dist/auth/storage.d.ts +12 -3
- package/dist/auth/storage.d.ts.map +1 -1
- package/dist/index-browser.js +337 -143
- package/dist/index-browser.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +137 -47
- package/dist/index.js.map +1 -1
- package/dist/react/McpClientProvider.d.ts.map +1 -1
- package/dist/react/index.d.ts +3 -2
- package/dist/react/index.d.ts.map +1 -1
- package/dist/react/index.js +681 -293
- package/dist/react/index.js.map +1 -1
- package/dist/react/storage.d.ts +10 -10
- package/dist/react/storage.d.ts.map +1 -1
- package/dist/react/token-expiry.d.ts +9 -0
- package/dist/react/token-expiry.d.ts.map +1 -0
- package/dist/react/types.d.ts +22 -5
- package/dist/react/types.d.ts.map +1 -1
- package/dist/react/useMcp-operations.d.ts.map +1 -1
- package/dist/react/useMcp.d.ts.map +1 -1
- package/dist/react/view/ViewRenderer.d.ts +3 -2
- package/dist/react/view/ViewRenderer.d.ts.map +1 -1
- package/dist/react/view/ext-apps-bridge.d.ts +1 -1
- package/dist/react/view/ext-apps-bridge.d.ts.map +1 -1
- package/dist/react/view/inject-openai-file-apis.d.ts.map +1 -1
- package/dist/react/view/types.d.ts +14 -4
- package/dist/react/view/types.d.ts.map +1 -1
- package/dist/react/view/view-host-policy.d.ts +24 -0
- package/dist/react/view/view-host-policy.d.ts.map +1 -0
- package/dist/telemetry/events.d.ts.map +1 -1
- package/dist/telemetry/tel-fetch.d.ts.map +1 -1
- package/dist/transport/connection-manager.d.ts +4 -0
- package/dist/transport/connection-manager.d.ts.map +1 -1
- package/package.json +2 -1
package/dist/index-browser.js
CHANGED
|
@@ -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.
|
|
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
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
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}
|
|
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
|
-
|
|
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
|
|
1037
|
-
const targetPath = target.pathname
|
|
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
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
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
|
-
|
|
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 (
|
|
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
|
-
|
|
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
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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)
|
|
1697
|
-
if (provider)
|
|
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
|
|
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
|
-
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
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
|
|