@modelcontextprotocol/inspector 2.8.0 → 2.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -5
- package/clients/cli/build/index.js +1565 -329
- package/clients/tui/build/index.js +1651 -576
- package/clients/web/build/index.js +2481 -1216
- package/clients/web/dist/assets/index-DgXruQhz.js +1598 -0
- package/clients/web/dist/assets/{prism-light-CGBvpchP.js → prism-light-leP1dqtE.js} +1 -1
- package/clients/web/dist/index.html +1 -1
- package/package.json +9 -7
- package/clients/web/dist/assets/index-9ZWu1ACP.js +0 -1598
|
@@ -90,6 +90,19 @@ var SECRET_FIELD_OAUTH_CLIENT_SECRET = "oauth-client-secret";
|
|
|
90
90
|
var SECRET_FIELD_IDP_CLIENT_SECRET = "idp-client-secret";
|
|
91
91
|
var envSecretField = (envKey) => `env:${envKey}`;
|
|
92
92
|
|
|
93
|
+
// ../../core/storage/own-entry.ts
|
|
94
|
+
function setOwnEntry(map, key, value) {
|
|
95
|
+
Object.defineProperty(map, key, {
|
|
96
|
+
value,
|
|
97
|
+
enumerable: true,
|
|
98
|
+
writable: true,
|
|
99
|
+
configurable: true
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
function getOwnEntry(map, key) {
|
|
103
|
+
return map !== void 0 && Object.hasOwn(map, key) ? map[key] : void 0;
|
|
104
|
+
}
|
|
105
|
+
|
|
93
106
|
// ../../core/auth/node/secret-store.ts
|
|
94
107
|
var SERVICE_NAME = "mcp-inspector";
|
|
95
108
|
var keyringLoad;
|
|
@@ -142,6 +155,12 @@ var SecretStoreUnavailableError = class extends Error {
|
|
|
142
155
|
this.name = "SecretStoreUnavailableError";
|
|
143
156
|
}
|
|
144
157
|
};
|
|
158
|
+
var SecretFileLockHeldError = class extends SecretStoreUnavailableError {
|
|
159
|
+
constructor(message, options) {
|
|
160
|
+
super(message, options);
|
|
161
|
+
this.name = "SecretFileLockHeldError";
|
|
162
|
+
}
|
|
163
|
+
};
|
|
145
164
|
var KeychainUnavailableError = class extends SecretStoreUnavailableError {
|
|
146
165
|
constructor(cause) {
|
|
147
166
|
const message = cause instanceof Error ? cause.message : String(cause);
|
|
@@ -172,6 +191,7 @@ async function probeKeyringAvailable() {
|
|
|
172
191
|
try {
|
|
173
192
|
const entry = new keyring.mod.AsyncEntry(SERVICE_NAME, PROBE_ACCOUNT);
|
|
174
193
|
await entry.getPassword();
|
|
194
|
+
await keyring.mod.findCredentialsAsync(SERVICE_NAME);
|
|
175
195
|
return { available: true };
|
|
176
196
|
} catch (err) {
|
|
177
197
|
return {
|
|
@@ -181,6 +201,7 @@ async function probeKeyringAvailable() {
|
|
|
181
201
|
}
|
|
182
202
|
}
|
|
183
203
|
var PROBE_ACCOUNT = "__inspector:probe";
|
|
204
|
+
var isNoEntryError = (err) => err instanceof Error && /no (matching )?entry/i.test(err.message);
|
|
184
205
|
var KeyringSecretStore = class {
|
|
185
206
|
/**
|
|
186
207
|
* The intolerant read. Same lookup as {@link get}, minus the catch — a
|
|
@@ -232,23 +253,27 @@ var KeyringSecretStore = class {
|
|
|
232
253
|
async delete(serverId, field) {
|
|
233
254
|
try {
|
|
234
255
|
const keyring = await loadKeyring();
|
|
235
|
-
if (!keyring.ok)
|
|
256
|
+
if (!keyring.ok) throw new KeychainUnavailableError(keyring.err);
|
|
236
257
|
const entry = new keyring.mod.AsyncEntry(
|
|
237
258
|
SERVICE_NAME,
|
|
238
259
|
buildAccount(serverId, field)
|
|
239
260
|
);
|
|
240
261
|
await entry.deleteCredential();
|
|
241
|
-
} catch {
|
|
262
|
+
} catch (err) {
|
|
263
|
+
if (isNoEntryError(err)) return;
|
|
264
|
+
if (err instanceof KeychainUnavailableError) throw err;
|
|
265
|
+
throw new KeychainUnavailableError(err);
|
|
242
266
|
}
|
|
243
267
|
}
|
|
244
268
|
async deleteAllForServer(serverId) {
|
|
245
269
|
let creds;
|
|
246
270
|
try {
|
|
247
271
|
const keyring = await loadKeyring();
|
|
248
|
-
if (!keyring.ok)
|
|
272
|
+
if (!keyring.ok) throw new KeychainUnavailableError(keyring.err);
|
|
249
273
|
creds = await keyring.mod.findCredentialsAsync(SERVICE_NAME);
|
|
250
|
-
} catch {
|
|
251
|
-
|
|
274
|
+
} catch (err) {
|
|
275
|
+
if (err instanceof KeychainUnavailableError) throw err;
|
|
276
|
+
throw new KeychainUnavailableError(err);
|
|
252
277
|
}
|
|
253
278
|
const prefix = `${serverId}:`;
|
|
254
279
|
for (const c of creds) {
|
|
@@ -262,6 +287,25 @@ var KeyringSecretStore = class {
|
|
|
262
287
|
async function secretStoreGetStrict(store, serverId, field) {
|
|
263
288
|
return store.getStrict ? store.getStrict(serverId, field) : store.get(serverId, field);
|
|
264
289
|
}
|
|
290
|
+
async function snapshotSecretFields(store, serverId, fields) {
|
|
291
|
+
return Promise.all(
|
|
292
|
+
fields.map(async (field) => ({
|
|
293
|
+
serverId,
|
|
294
|
+
field,
|
|
295
|
+
value: await secretStoreGetStrict(store, serverId, field)
|
|
296
|
+
}))
|
|
297
|
+
);
|
|
298
|
+
}
|
|
299
|
+
async function restoreSecretFields(store, snapshot, onRestoreFailure) {
|
|
300
|
+
for (const { serverId, field, value } of snapshot) {
|
|
301
|
+
try {
|
|
302
|
+
if (value === null) await store.delete(serverId, field);
|
|
303
|
+
else await store.set(serverId, field, value);
|
|
304
|
+
} catch (error) {
|
|
305
|
+
onRestoreFailure(error);
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
}
|
|
265
309
|
async function secretStoreGetMany(store, requests) {
|
|
266
310
|
if (store.getMany) return store.getMany(requests);
|
|
267
311
|
const out = {};
|
|
@@ -274,16 +318,44 @@ async function secretStoreGetMany(store, requests) {
|
|
|
274
318
|
);
|
|
275
319
|
const found = {};
|
|
276
320
|
for (const [field, value] of entries) {
|
|
277
|
-
if (value !== null) found
|
|
321
|
+
if (value !== null) setOwnEntry(found, field, value);
|
|
278
322
|
}
|
|
279
|
-
out
|
|
323
|
+
setOwnEntry(out, serverId, found);
|
|
280
324
|
})
|
|
281
325
|
);
|
|
282
326
|
return out;
|
|
283
327
|
}
|
|
328
|
+
async function secretStoreGetManyStrict(store, requests) {
|
|
329
|
+
if (store.getManyStrict) return store.getManyStrict(requests);
|
|
330
|
+
const out = {};
|
|
331
|
+
await Promise.all(
|
|
332
|
+
requests.map(async ({ serverId, fields }) => {
|
|
333
|
+
const entries = await Promise.all(
|
|
334
|
+
fields.map(
|
|
335
|
+
async (field) => [
|
|
336
|
+
field,
|
|
337
|
+
await secretStoreGetStrict(store, serverId, field)
|
|
338
|
+
]
|
|
339
|
+
)
|
|
340
|
+
);
|
|
341
|
+
const found = {};
|
|
342
|
+
for (const [field, value] of entries) {
|
|
343
|
+
if (value !== null) setOwnEntry(found, field, value);
|
|
344
|
+
}
|
|
345
|
+
setOwnEntry(out, serverId, found);
|
|
346
|
+
})
|
|
347
|
+
);
|
|
348
|
+
return out;
|
|
349
|
+
}
|
|
350
|
+
async function settleStoreMutations(mutations) {
|
|
351
|
+
const results = await Promise.allSettled(mutations);
|
|
352
|
+
for (const result of results) {
|
|
353
|
+
if (result.status === "rejected") throw result.reason;
|
|
354
|
+
}
|
|
355
|
+
}
|
|
284
356
|
async function secretStoreSetMany(store, serverId, values) {
|
|
285
357
|
if (store.setMany) return store.setMany(serverId, values);
|
|
286
|
-
await
|
|
358
|
+
await settleStoreMutations(
|
|
287
359
|
Object.entries(values).map(
|
|
288
360
|
([field, value]) => store.set(serverId, field, value)
|
|
289
361
|
)
|
|
@@ -430,6 +502,10 @@ var ModernListSkillsResultSchema = ListSkillsResultSchema.extend({
|
|
|
430
502
|
var GetSkillEnvelopeSchema = z.looseObject({
|
|
431
503
|
skill: SkillEntrySchema
|
|
432
504
|
});
|
|
505
|
+
var ModernGetSkillEnvelopeSchema = GetSkillEnvelopeSchema.extend({
|
|
506
|
+
ttlMs: z.int().min(0),
|
|
507
|
+
cacheScope: z.enum(["public", "private"])
|
|
508
|
+
});
|
|
433
509
|
var GetSkillResultSchema = GetSkillEnvelopeSchema.transform(
|
|
434
510
|
(result) => result.skill
|
|
435
511
|
);
|
|
@@ -2601,7 +2677,7 @@ async function openSecretFileLock(filePath) {
|
|
|
2601
2677
|
});
|
|
2602
2678
|
} catch (err) {
|
|
2603
2679
|
if (isStuckOrHeld(err, lockPathOf(target))) {
|
|
2604
|
-
throw new
|
|
2680
|
+
throw new SecretFileLockHeldError(
|
|
2605
2681
|
`Could not save to the secrets file at ${target}: its lock (${lockPathOf(target)}) was still held after the ${Math.round(RETRY_BUDGET_MS / 1e3)} seconds this save waited. Its secrets are intact; the value you just entered was not saved. If no other Inspector is running, remove that lock and try again.`
|
|
2606
2682
|
);
|
|
2607
2683
|
}
|
|
@@ -2710,6 +2786,13 @@ function resolveSecretPassphrase(env = process.env, secretFilePath) {
|
|
|
2710
2786
|
return { passphrase };
|
|
2711
2787
|
}
|
|
2712
2788
|
var buildAccount2 = (serverId, field) => `${serverId}:${field}`;
|
|
2789
|
+
function assertSecretString(field, value) {
|
|
2790
|
+
if (typeof value !== "string") {
|
|
2791
|
+
throw new TypeError(
|
|
2792
|
+
`Refusing to store a non-string secret value (${typeof value}) for "${field}": the secrets file holds only strings, and writing this would make the whole file unreadable.`
|
|
2793
|
+
);
|
|
2794
|
+
}
|
|
2795
|
+
}
|
|
2713
2796
|
function asSecretMap(value, filePath) {
|
|
2714
2797
|
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
2715
2798
|
throw new SecretStoreUnavailableError(
|
|
@@ -2968,7 +3051,9 @@ var FileSecretStore = class {
|
|
|
2968
3051
|
let plain;
|
|
2969
3052
|
try {
|
|
2970
3053
|
const key = await this.deriveKey(parsed.kdf);
|
|
2971
|
-
const decipher = crypto2.createDecipheriv(CIPHER, key, parts.iv
|
|
3054
|
+
const decipher = crypto2.createDecipheriv(CIPHER, key, parts.iv, {
|
|
3055
|
+
authTagLength: GCM_TAG_BYTES
|
|
3056
|
+
});
|
|
2972
3057
|
decipher.setAuthTag(parts.tag);
|
|
2973
3058
|
plain = Buffer.concat([
|
|
2974
3059
|
decipher.update(parts.body),
|
|
@@ -3002,7 +3087,9 @@ var FileSecretStore = class {
|
|
|
3002
3087
|
};
|
|
3003
3088
|
const key = await this.deriveKey(kdf);
|
|
3004
3089
|
const iv = crypto2.randomBytes(IV_BYTES);
|
|
3005
|
-
const cipher = crypto2.createCipheriv(CIPHER, key, iv
|
|
3090
|
+
const cipher = crypto2.createCipheriv(CIPHER, key, iv, {
|
|
3091
|
+
authTagLength: GCM_TAG_BYTES
|
|
3092
|
+
});
|
|
3006
3093
|
const body = Buffer.concat([
|
|
3007
3094
|
cipher.update(JSON.stringify(map), "utf-8"),
|
|
3008
3095
|
cipher.final()
|
|
@@ -3167,9 +3254,36 @@ var FileSecretStore = class {
|
|
|
3167
3254
|
const found = {};
|
|
3168
3255
|
for (const field of fields) {
|
|
3169
3256
|
const value = map?.[buildAccount2(serverId, field)];
|
|
3170
|
-
if (value !== void 0) found
|
|
3257
|
+
if (value !== void 0) setOwnEntry(found, field, value);
|
|
3258
|
+
}
|
|
3259
|
+
setOwnEntry(out, serverId, found);
|
|
3260
|
+
}
|
|
3261
|
+
return out;
|
|
3262
|
+
}
|
|
3263
|
+
/**
|
|
3264
|
+
* Bulk twin of {@link getStrict}: the same single-pass read as
|
|
3265
|
+
* {@link getMany}, but an unreadable store throws (with the typed advice
|
|
3266
|
+
* {@link getStrict} attaches) instead of yielding no fields — required by
|
|
3267
|
+
* OAuth read hydration, whose result later drives store deletions.
|
|
3268
|
+
*/
|
|
3269
|
+
async getManyStrict(requests) {
|
|
3270
|
+
let map;
|
|
3271
|
+
try {
|
|
3272
|
+
map = await this.serialize(() => this.readMap());
|
|
3273
|
+
} catch (err) {
|
|
3274
|
+
if (err instanceof SecretStoreUnavailableError) throw err;
|
|
3275
|
+
throw new SecretStoreUnavailableError(
|
|
3276
|
+
`Could not read the secrets file at ${this.filePath}: ${err instanceof Error ? err.message : String(err)}`
|
|
3277
|
+
);
|
|
3278
|
+
}
|
|
3279
|
+
const out = {};
|
|
3280
|
+
for (const { serverId, fields } of requests) {
|
|
3281
|
+
const found = {};
|
|
3282
|
+
for (const field of fields) {
|
|
3283
|
+
const value = map?.[buildAccount2(serverId, field)];
|
|
3284
|
+
if (value !== void 0) setOwnEntry(found, field, value);
|
|
3171
3285
|
}
|
|
3172
|
-
out
|
|
3286
|
+
setOwnEntry(out, serverId, found);
|
|
3173
3287
|
}
|
|
3174
3288
|
return out;
|
|
3175
3289
|
}
|
|
@@ -3213,6 +3327,9 @@ var FileSecretStore = class {
|
|
|
3213
3327
|
*/
|
|
3214
3328
|
async setMany(serverId, values) {
|
|
3215
3329
|
if (Object.keys(values).length === 0) return;
|
|
3330
|
+
for (const [field, value] of Object.entries(values)) {
|
|
3331
|
+
assertSecretString(field, value);
|
|
3332
|
+
}
|
|
3216
3333
|
await this.serialize(
|
|
3217
3334
|
() => this.mutate((map) => {
|
|
3218
3335
|
const next = { ...map };
|
|
@@ -3224,6 +3341,7 @@ var FileSecretStore = class {
|
|
|
3224
3341
|
);
|
|
3225
3342
|
}
|
|
3226
3343
|
async set(serverId, field, value) {
|
|
3344
|
+
assertSecretString(field, value);
|
|
3227
3345
|
await this.serialize(
|
|
3228
3346
|
() => this.mutate((map) => ({
|
|
3229
3347
|
...map,
|
|
@@ -3239,9 +3357,11 @@ var FileSecretStore = class {
|
|
|
3239
3357
|
await this.deleteWhere((key) => key.startsWith(prefix));
|
|
3240
3358
|
}
|
|
3241
3359
|
/**
|
|
3242
|
-
* Shared delete body.
|
|
3243
|
-
*
|
|
3244
|
-
*
|
|
3360
|
+
* Shared delete body. Same confirmed-delete contract as the keyring
|
|
3361
|
+
* store: a deletion that cannot be confirmed (unreadable file, failed
|
|
3362
|
+
* decrypt, failed write) must escape, because reporting success would
|
|
3363
|
+
* let a caller commit state that assumes the entry is gone — once the
|
|
3364
|
+
* file becomes readable again, a later read would resurrect it.
|
|
3245
3365
|
*/
|
|
3246
3366
|
async deleteWhere(match) {
|
|
3247
3367
|
try {
|
|
@@ -3254,7 +3374,12 @@ var FileSecretStore = class {
|
|
|
3254
3374
|
return next;
|
|
3255
3375
|
})
|
|
3256
3376
|
);
|
|
3257
|
-
} catch {
|
|
3377
|
+
} catch (err) {
|
|
3378
|
+
if (err instanceof SecretStoreUnavailableError) throw err;
|
|
3379
|
+
throw new SecretStoreUnavailableError(
|
|
3380
|
+
`Could not delete from the secrets file: ${err instanceof Error ? err.message : String(err)}`,
|
|
3381
|
+
{ cause: err }
|
|
3382
|
+
);
|
|
3258
3383
|
}
|
|
3259
3384
|
}
|
|
3260
3385
|
};
|
|
@@ -3630,6 +3755,9 @@ var DeferredSecretStore = class {
|
|
|
3630
3755
|
async getMany(requests) {
|
|
3631
3756
|
return secretStoreGetMany(await this.target(), requests);
|
|
3632
3757
|
}
|
|
3758
|
+
async getManyStrict(requests) {
|
|
3759
|
+
return secretStoreGetManyStrict(await this.target(), requests);
|
|
3760
|
+
}
|
|
3633
3761
|
async get(serverId, field) {
|
|
3634
3762
|
return (await this.target()).get(serverId, field);
|
|
3635
3763
|
}
|
|
@@ -3868,12 +3996,31 @@ function redactJsonValue(value) {
|
|
|
3868
3996
|
}
|
|
3869
3997
|
return value;
|
|
3870
3998
|
}
|
|
3999
|
+
function joinSensitiveContinuations(body) {
|
|
4000
|
+
const out = [];
|
|
4001
|
+
let sensitiveAt = -1;
|
|
4002
|
+
for (const pair of body.split("&")) {
|
|
4003
|
+
const eq = pair.indexOf("=");
|
|
4004
|
+
if (eq === -1) {
|
|
4005
|
+
if (sensitiveAt !== -1 && pair.length > 0) {
|
|
4006
|
+
out[sensitiveAt] += `%26${pair}`;
|
|
4007
|
+
} else {
|
|
4008
|
+
out.push(pair);
|
|
4009
|
+
}
|
|
4010
|
+
continue;
|
|
4011
|
+
}
|
|
4012
|
+
const [key] = new URLSearchParams(pair).keys();
|
|
4013
|
+
sensitiveAt = isSensitiveField(key) && eq < pair.length - 1 ? out.length : -1;
|
|
4014
|
+
out.push(pair);
|
|
4015
|
+
}
|
|
4016
|
+
return out.join("&");
|
|
4017
|
+
}
|
|
3871
4018
|
function redactBody(body, contentType) {
|
|
3872
4019
|
if (!body) return body;
|
|
3873
4020
|
const type = (contentType ?? "").toLowerCase();
|
|
3874
4021
|
if (type.includes("application/x-www-form-urlencoded")) {
|
|
3875
4022
|
try {
|
|
3876
|
-
const params = new URLSearchParams(body);
|
|
4023
|
+
const params = new URLSearchParams(joinSensitiveContinuations(body));
|
|
3877
4024
|
let changed = false;
|
|
3878
4025
|
for (const key of new Set(params.keys())) {
|
|
3879
4026
|
if (isSensitiveField(key)) {
|
|
@@ -4904,42 +5051,57 @@ import {
|
|
|
4904
5051
|
function withIssuer(value, issuer) {
|
|
4905
5052
|
return issuer === void 0 ? value : { ...value, issuer };
|
|
4906
5053
|
}
|
|
4907
|
-
var
|
|
5054
|
+
var OAuthStorageCoordination = class {
|
|
4908
5055
|
loaded = false;
|
|
4909
5056
|
loadPromise;
|
|
4910
5057
|
/** Serializes persist writes so concurrent mutators cannot reorder POSTs. */
|
|
4911
5058
|
persistQueue = Promise.resolve();
|
|
5059
|
+
};
|
|
5060
|
+
var OAuthStorageBase = class {
|
|
5061
|
+
coordination;
|
|
4912
5062
|
memory;
|
|
4913
5063
|
backend;
|
|
4914
|
-
constructor(memory, backend) {
|
|
5064
|
+
constructor(memory, backend, coordination = new OAuthStorageCoordination()) {
|
|
4915
5065
|
this.memory = memory;
|
|
4916
5066
|
this.backend = backend;
|
|
5067
|
+
this.coordination = coordination;
|
|
4917
5068
|
}
|
|
4918
5069
|
load() {
|
|
4919
|
-
|
|
4920
|
-
|
|
5070
|
+
const coordination = this.coordination;
|
|
5071
|
+
if (!coordination.loadPromise) {
|
|
5072
|
+
coordination.loadPromise = this.doLoad().catch((error) => {
|
|
5073
|
+
coordination.loadPromise = void 0;
|
|
5074
|
+
throw error;
|
|
5075
|
+
});
|
|
4921
5076
|
}
|
|
4922
|
-
return
|
|
5077
|
+
return coordination.loadPromise;
|
|
4923
5078
|
}
|
|
4924
5079
|
async doLoad() {
|
|
4925
|
-
if (this.loaded) {
|
|
5080
|
+
if (this.coordination.loaded) {
|
|
4926
5081
|
return;
|
|
4927
5082
|
}
|
|
4928
5083
|
const snapshot = await this.backend.read();
|
|
4929
5084
|
if (snapshot) {
|
|
4930
5085
|
this.memory.replace(snapshot);
|
|
4931
5086
|
}
|
|
4932
|
-
this.loaded = true;
|
|
5087
|
+
this.coordination.loaded = true;
|
|
4933
5088
|
}
|
|
4934
5089
|
async ensureLoaded() {
|
|
4935
5090
|
await this.load();
|
|
4936
5091
|
}
|
|
4937
|
-
|
|
4938
|
-
|
|
4939
|
-
|
|
5092
|
+
/**
|
|
5093
|
+
* Flush memory to the backend, naming the sections the calling mutation
|
|
5094
|
+
* touched so shared-store backends can merge just those entries over a
|
|
5095
|
+
* fresh read (the clobber fix — see {@link OAuthPersistSections}). The
|
|
5096
|
+
* snapshot is taken *inside* the queued closure, so a write that waited in
|
|
5097
|
+
* the queue carries the state as of when it actually runs, coalescing with
|
|
5098
|
+
* any mutations that landed in memory while it waited.
|
|
5099
|
+
*/
|
|
5100
|
+
async persist(sections) {
|
|
5101
|
+
const prior = this.coordination.persistQueue;
|
|
4940
5102
|
const tracked = prior.catch(() => {
|
|
4941
|
-
}).then(() => this.backend.write(snapshot));
|
|
4942
|
-
this.persistQueue = tracked;
|
|
5103
|
+
}).then(() => this.backend.write(this.memory.snapshot(), sections));
|
|
5104
|
+
this.coordination.persistQueue = tracked;
|
|
4943
5105
|
await tracked;
|
|
4944
5106
|
}
|
|
4945
5107
|
/**
|
|
@@ -4953,7 +5115,7 @@ var OAuthStorageBase = class {
|
|
|
4953
5115
|
/** Read the issuer-bound slot for a read, if one exists. */
|
|
4954
5116
|
issuerSlot(state, issuer) {
|
|
4955
5117
|
const key = this.resolveReadIssuer(state, issuer);
|
|
4956
|
-
return key ? state.byIssuer
|
|
5118
|
+
return key ? getOwnEntry(state.byIssuer, key) : void 0;
|
|
4957
5119
|
}
|
|
4958
5120
|
/**
|
|
4959
5121
|
* Read-modify-write the `byIssuer[issuer]` slot, set `activeIssuer`, and clear
|
|
@@ -4964,7 +5126,9 @@ var OAuthStorageBase = class {
|
|
|
4964
5126
|
const state = this.memory.getState().getServerState(serverUrl);
|
|
4965
5127
|
const byIssuer = {
|
|
4966
5128
|
...state.byIssuer,
|
|
4967
|
-
|
|
5129
|
+
// The computed key defines an own property; the merge-base read needs
|
|
5130
|
+
// the own guard so an absent `__proto__` slot reads as undefined.
|
|
5131
|
+
[issuer]: { ...getOwnEntry(state.byIssuer, issuer), ...updates }
|
|
4968
5132
|
};
|
|
4969
5133
|
this.memory.getState().setServerState(serverUrl, {
|
|
4970
5134
|
byIssuer,
|
|
@@ -4976,7 +5140,7 @@ var OAuthStorageBase = class {
|
|
|
4976
5140
|
mapIssuerSlots(state, fn) {
|
|
4977
5141
|
const byIssuer = {};
|
|
4978
5142
|
for (const [key, slot] of Object.entries(state.byIssuer ?? {})) {
|
|
4979
|
-
byIssuer
|
|
5143
|
+
setOwnEntry(byIssuer, key, { ...slot, ...fn(slot) });
|
|
4980
5144
|
}
|
|
4981
5145
|
return byIssuer;
|
|
4982
5146
|
}
|
|
@@ -5022,7 +5186,7 @@ var OAuthStorageBase = class {
|
|
|
5022
5186
|
clientRegistrationKind: options.registrationKind
|
|
5023
5187
|
});
|
|
5024
5188
|
}
|
|
5025
|
-
await this.persist();
|
|
5189
|
+
await this.persist({ servers: [serverUrl] });
|
|
5026
5190
|
}
|
|
5027
5191
|
async savePreregisteredClientInformation(serverUrl, clientInformation) {
|
|
5028
5192
|
await this.ensureLoaded();
|
|
@@ -5030,7 +5194,7 @@ var OAuthStorageBase = class {
|
|
|
5030
5194
|
preregisteredClientInformation: clientInformation,
|
|
5031
5195
|
clientRegistrationKind: "static"
|
|
5032
5196
|
});
|
|
5033
|
-
await this.persist();
|
|
5197
|
+
await this.persist({ servers: [serverUrl] });
|
|
5034
5198
|
}
|
|
5035
5199
|
async clearClientInformation(serverUrl, isPreregistered, issuer) {
|
|
5036
5200
|
await this.ensureLoaded();
|
|
@@ -5038,7 +5202,7 @@ var OAuthStorageBase = class {
|
|
|
5038
5202
|
this.memory.getState().setServerState(serverUrl, {
|
|
5039
5203
|
preregisteredClientInformation: void 0
|
|
5040
5204
|
});
|
|
5041
|
-
await this.persist();
|
|
5205
|
+
await this.persist({ servers: [serverUrl] });
|
|
5042
5206
|
return;
|
|
5043
5207
|
}
|
|
5044
5208
|
if (issuer !== void 0) {
|
|
@@ -5061,7 +5225,7 @@ var OAuthStorageBase = class {
|
|
|
5061
5225
|
clientRegistrationKind: void 0
|
|
5062
5226
|
});
|
|
5063
5227
|
}
|
|
5064
|
-
await this.persist();
|
|
5228
|
+
await this.persist({ servers: [serverUrl] });
|
|
5065
5229
|
}
|
|
5066
5230
|
async getTokens(serverUrl, issuer) {
|
|
5067
5231
|
await this.ensureLoaded();
|
|
@@ -5072,9 +5236,12 @@ var OAuthStorageBase = class {
|
|
|
5072
5236
|
if (!tokens) {
|
|
5073
5237
|
return void 0;
|
|
5074
5238
|
}
|
|
5075
|
-
const
|
|
5239
|
+
const result = await OAuthTokensSchema.safeParseAsync(tokens);
|
|
5240
|
+
if (!result.success) {
|
|
5241
|
+
return void 0;
|
|
5242
|
+
}
|
|
5076
5243
|
return withIssuer(
|
|
5077
|
-
|
|
5244
|
+
result.data,
|
|
5078
5245
|
fromSlot ? this.resolveReadIssuer(state, issuer) : void 0
|
|
5079
5246
|
);
|
|
5080
5247
|
}
|
|
@@ -5098,7 +5265,7 @@ var OAuthStorageBase = class {
|
|
|
5098
5265
|
...options?.enterpriseManaged === true && { enterpriseManaged: true }
|
|
5099
5266
|
});
|
|
5100
5267
|
}
|
|
5101
|
-
await this.persist();
|
|
5268
|
+
await this.persist({ servers: [serverUrl] });
|
|
5102
5269
|
}
|
|
5103
5270
|
async clearTokens(serverUrl, issuer) {
|
|
5104
5271
|
await this.ensureLoaded();
|
|
@@ -5117,7 +5284,7 @@ var OAuthStorageBase = class {
|
|
|
5117
5284
|
}));
|
|
5118
5285
|
this.memory.getState().setServerState(serverUrl, { byIssuer, tokens: void 0 });
|
|
5119
5286
|
}
|
|
5120
|
-
await this.persist();
|
|
5287
|
+
await this.persist({ servers: [serverUrl] });
|
|
5121
5288
|
}
|
|
5122
5289
|
async getCodeVerifier(serverUrl) {
|
|
5123
5290
|
await this.ensureLoaded();
|
|
@@ -5127,12 +5294,12 @@ var OAuthStorageBase = class {
|
|
|
5127
5294
|
async saveCodeVerifier(serverUrl, codeVerifier) {
|
|
5128
5295
|
await this.ensureLoaded();
|
|
5129
5296
|
this.memory.getState().setServerState(serverUrl, { codeVerifier });
|
|
5130
|
-
await this.persist();
|
|
5297
|
+
await this.persist({ servers: [serverUrl] });
|
|
5131
5298
|
}
|
|
5132
5299
|
async clearCodeVerifier(serverUrl) {
|
|
5133
5300
|
await this.ensureLoaded();
|
|
5134
5301
|
this.memory.getState().setServerState(serverUrl, { codeVerifier: void 0 });
|
|
5135
|
-
await this.persist();
|
|
5302
|
+
await this.persist({ servers: [serverUrl] });
|
|
5136
5303
|
}
|
|
5137
5304
|
async getScope(serverUrl) {
|
|
5138
5305
|
await this.ensureLoaded();
|
|
@@ -5142,12 +5309,12 @@ var OAuthStorageBase = class {
|
|
|
5142
5309
|
async saveScope(serverUrl, scope) {
|
|
5143
5310
|
await this.ensureLoaded();
|
|
5144
5311
|
this.memory.getState().setServerState(serverUrl, { scope });
|
|
5145
|
-
await this.persist();
|
|
5312
|
+
await this.persist({ servers: [serverUrl] });
|
|
5146
5313
|
}
|
|
5147
5314
|
async clearScope(serverUrl) {
|
|
5148
5315
|
await this.ensureLoaded();
|
|
5149
5316
|
this.memory.getState().setServerState(serverUrl, { scope: void 0 });
|
|
5150
|
-
await this.persist();
|
|
5317
|
+
await this.persist({ servers: [serverUrl] });
|
|
5151
5318
|
}
|
|
5152
5319
|
async getServerMetadata(serverUrl) {
|
|
5153
5320
|
await this.ensureLoaded();
|
|
@@ -5157,12 +5324,12 @@ var OAuthStorageBase = class {
|
|
|
5157
5324
|
async saveServerMetadata(serverUrl, metadata) {
|
|
5158
5325
|
await this.ensureLoaded();
|
|
5159
5326
|
this.memory.getState().setServerState(serverUrl, { serverMetadata: metadata });
|
|
5160
|
-
await this.persist();
|
|
5327
|
+
await this.persist({ servers: [serverUrl] });
|
|
5161
5328
|
}
|
|
5162
5329
|
async clearServerMetadata(serverUrl) {
|
|
5163
5330
|
await this.ensureLoaded();
|
|
5164
5331
|
this.memory.getState().setServerState(serverUrl, { serverMetadata: void 0 });
|
|
5165
|
-
await this.persist();
|
|
5332
|
+
await this.persist({ servers: [serverUrl] });
|
|
5166
5333
|
}
|
|
5167
5334
|
async getDiscoveryState(serverUrl) {
|
|
5168
5335
|
await this.ensureLoaded();
|
|
@@ -5171,12 +5338,12 @@ var OAuthStorageBase = class {
|
|
|
5171
5338
|
async saveDiscoveryState(serverUrl, state) {
|
|
5172
5339
|
await this.ensureLoaded();
|
|
5173
5340
|
this.memory.getState().setServerState(serverUrl, { discoveryState: state });
|
|
5174
|
-
await this.persist();
|
|
5341
|
+
await this.persist({ servers: [serverUrl] });
|
|
5175
5342
|
}
|
|
5176
5343
|
async clearDiscoveryState(serverUrl) {
|
|
5177
5344
|
await this.ensureLoaded();
|
|
5178
5345
|
this.memory.getState().setServerState(serverUrl, { discoveryState: void 0 });
|
|
5179
|
-
await this.persist();
|
|
5346
|
+
await this.persist({ servers: [serverUrl] });
|
|
5180
5347
|
}
|
|
5181
5348
|
async takeRevocationSnapshot(serverUrl) {
|
|
5182
5349
|
await this.ensureLoaded();
|
|
@@ -5194,13 +5361,13 @@ var OAuthStorageBase = class {
|
|
|
5194
5361
|
serverMetadata: state.serverMetadata
|
|
5195
5362
|
};
|
|
5196
5363
|
this.memory.getState().clearServerState(serverUrl);
|
|
5197
|
-
await this.persist();
|
|
5364
|
+
await this.persist({ servers: [serverUrl] });
|
|
5198
5365
|
return snapshot;
|
|
5199
5366
|
}
|
|
5200
5367
|
async clear(serverUrl) {
|
|
5201
5368
|
await this.ensureLoaded();
|
|
5202
5369
|
this.memory.getState().clearServerState(serverUrl);
|
|
5203
|
-
await this.persist();
|
|
5370
|
+
await this.persist({ servers: [serverUrl] });
|
|
5204
5371
|
}
|
|
5205
5372
|
async getIdpSession(issuer) {
|
|
5206
5373
|
await this.ensureLoaded();
|
|
@@ -5213,17 +5380,18 @@ var OAuthStorageBase = class {
|
|
|
5213
5380
|
async saveIdpSession(issuer, session) {
|
|
5214
5381
|
await this.ensureLoaded();
|
|
5215
5382
|
this.memory.getState().setIdpSession(issuer, session);
|
|
5216
|
-
await this.persist();
|
|
5383
|
+
await this.persist({ idpSessions: [issuer] });
|
|
5217
5384
|
}
|
|
5218
5385
|
async clearIdpSession(issuer) {
|
|
5219
5386
|
await this.ensureLoaded();
|
|
5220
5387
|
this.memory.getState().clearIdpSession(issuer);
|
|
5221
|
-
await this.persist();
|
|
5388
|
+
await this.persist({ idpSessions: [issuer] });
|
|
5222
5389
|
}
|
|
5223
5390
|
async clearEnterpriseManagedResourceServers() {
|
|
5224
5391
|
await this.ensureLoaded();
|
|
5392
|
+
const servers = Object.entries(this.memory.getState().servers).filter(([, state]) => state.enterpriseManaged === true).map(([url]) => url);
|
|
5225
5393
|
this.memory.getState().clearEnterpriseManagedResourceServers();
|
|
5226
|
-
await this.persist();
|
|
5394
|
+
await this.persist({ servers });
|
|
5227
5395
|
}
|
|
5228
5396
|
};
|
|
5229
5397
|
|
|
@@ -5241,13 +5409,15 @@ var OAuthMemoryStore = class {
|
|
|
5241
5409
|
servers: this.servers,
|
|
5242
5410
|
idpSessions: this.idpSessions,
|
|
5243
5411
|
getServerState: (serverUrl) => {
|
|
5244
|
-
return this.servers
|
|
5412
|
+
return getOwnEntry(this.servers, serverUrl) || {};
|
|
5245
5413
|
},
|
|
5246
5414
|
setServerState: (serverUrl, updates) => {
|
|
5247
5415
|
this.servers = {
|
|
5248
5416
|
...this.servers,
|
|
5417
|
+
// Computed keys define own properties, so this write is safe for
|
|
5418
|
+
// `__proto__`; only the merge-base read needs the own guard.
|
|
5249
5419
|
[serverUrl]: {
|
|
5250
|
-
...this.servers
|
|
5420
|
+
...getOwnEntry(this.servers, serverUrl),
|
|
5251
5421
|
...updates
|
|
5252
5422
|
}
|
|
5253
5423
|
};
|
|
@@ -5258,13 +5428,13 @@ var OAuthMemoryStore = class {
|
|
|
5258
5428
|
this.servers = rest;
|
|
5259
5429
|
},
|
|
5260
5430
|
getIdpSession: (issuer) => {
|
|
5261
|
-
return this.idpSessions
|
|
5431
|
+
return getOwnEntry(this.idpSessions, issuer) || {};
|
|
5262
5432
|
},
|
|
5263
5433
|
setIdpSession: (issuer, updates) => {
|
|
5264
5434
|
this.idpSessions = {
|
|
5265
5435
|
...this.idpSessions,
|
|
5266
5436
|
[issuer]: {
|
|
5267
|
-
...this.idpSessions
|
|
5437
|
+
...getOwnEntry(this.idpSessions, issuer),
|
|
5268
5438
|
...updates
|
|
5269
5439
|
}
|
|
5270
5440
|
};
|
|
@@ -5298,13 +5468,70 @@ var OAuthMemoryStore = class {
|
|
|
5298
5468
|
};
|
|
5299
5469
|
|
|
5300
5470
|
// ../../core/auth/oauth-persist.ts
|
|
5471
|
+
import { OAuthTokensSchema as OAuthTokensSchema2 } from "@modelcontextprotocol/core";
|
|
5472
|
+
function mergeOAuthSections(disk, snapshot, sections) {
|
|
5473
|
+
const merged = {
|
|
5474
|
+
servers: { ...disk?.servers },
|
|
5475
|
+
idpSessions: { ...disk?.idpSessions }
|
|
5476
|
+
};
|
|
5477
|
+
for (const url of sections.servers ?? []) {
|
|
5478
|
+
const value = getOwnEntry(snapshot.servers, url);
|
|
5479
|
+
if (value === void 0) {
|
|
5480
|
+
delete merged.servers[url];
|
|
5481
|
+
} else {
|
|
5482
|
+
setOwnEntry(merged.servers, url, value);
|
|
5483
|
+
}
|
|
5484
|
+
}
|
|
5485
|
+
for (const issuer of sections.idpSessions ?? []) {
|
|
5486
|
+
const value = getOwnEntry(snapshot.idpSessions, issuer);
|
|
5487
|
+
if (value === void 0) {
|
|
5488
|
+
delete merged.idpSessions[issuer];
|
|
5489
|
+
} else {
|
|
5490
|
+
setOwnEntry(merged.idpSessions, issuer, value);
|
|
5491
|
+
}
|
|
5492
|
+
}
|
|
5493
|
+
return merged;
|
|
5494
|
+
}
|
|
5301
5495
|
function isRecord3(value) {
|
|
5302
5496
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
5303
5497
|
}
|
|
5498
|
+
function isEntryMap(value) {
|
|
5499
|
+
return isRecord3(value) && Object.values(value).every(isRecord3);
|
|
5500
|
+
}
|
|
5501
|
+
var VERBATIM_SECRET_KEYS = [
|
|
5502
|
+
"client_secret",
|
|
5503
|
+
"registration_access_token"
|
|
5504
|
+
];
|
|
5505
|
+
function isValidClientInformation(value) {
|
|
5506
|
+
if (value === void 0) return true;
|
|
5507
|
+
if (!isRecord3(value)) return false;
|
|
5508
|
+
return VERBATIM_SECRET_KEYS.every(
|
|
5509
|
+
(key) => !Object.hasOwn(value, key) || typeof value[key] === "string"
|
|
5510
|
+
);
|
|
5511
|
+
}
|
|
5512
|
+
var PartialTokensSchema = OAuthTokensSchema2.partial();
|
|
5513
|
+
function isValidServerEntry(entry) {
|
|
5514
|
+
if (!isRecord3(entry)) return false;
|
|
5515
|
+
if (!isValidClientInformation(entry.clientInformation)) return false;
|
|
5516
|
+
if (!isValidClientInformation(entry.preregisteredClientInformation))
|
|
5517
|
+
return false;
|
|
5518
|
+
if (entry.byIssuer !== void 0) {
|
|
5519
|
+
if (!isRecord3(entry.byIssuer)) return false;
|
|
5520
|
+
for (const slot of Object.values(entry.byIssuer)) {
|
|
5521
|
+
if (!isRecord3(slot)) return false;
|
|
5522
|
+
if (!isValidClientInformation(slot.clientInformation)) return false;
|
|
5523
|
+
}
|
|
5524
|
+
}
|
|
5525
|
+
return true;
|
|
5526
|
+
}
|
|
5304
5527
|
function snapshotFromPayload(payload) {
|
|
5528
|
+
const servers = payload.servers ?? {};
|
|
5529
|
+
const idpSessions = payload.idpSessions ?? {};
|
|
5530
|
+
if (!isEntryMap(servers) || !isEntryMap(idpSessions)) return null;
|
|
5531
|
+
if (!Object.values(servers).every(isValidServerEntry)) return null;
|
|
5305
5532
|
return {
|
|
5306
|
-
servers
|
|
5307
|
-
idpSessions
|
|
5533
|
+
servers,
|
|
5534
|
+
idpSessions
|
|
5308
5535
|
};
|
|
5309
5536
|
}
|
|
5310
5537
|
function parseOAuthPersistBlob(raw) {
|
|
@@ -5327,123 +5554,884 @@ function serializeOAuthPersistBlob(snapshot) {
|
|
|
5327
5554
|
return serializeStore(snapshot);
|
|
5328
5555
|
}
|
|
5329
5556
|
|
|
5330
|
-
// ../../core/auth/node/oauth-
|
|
5331
|
-
|
|
5557
|
+
// ../../core/auth/node/oauth-secrets.ts
|
|
5558
|
+
import { OAuthTokensSchema as OAuthTokensSchema3 } from "@modelcontextprotocol/core";
|
|
5559
|
+
var PERSIST_TOKENS_ENV = "MCP_INSPECTOR_PERSIST_TOKENS";
|
|
5560
|
+
var warnedPolicyValues = /* @__PURE__ */ new Set();
|
|
5561
|
+
function getPersistTokensPolicy(env = process.env) {
|
|
5562
|
+
const raw = env[PERSIST_TOKENS_ENV];
|
|
5563
|
+
if (raw === void 0 || raw === "") return "all";
|
|
5564
|
+
if (raw === "all" || raw === "access" || raw === "none") return raw;
|
|
5565
|
+
if (!warnedPolicyValues.has(raw)) {
|
|
5566
|
+
warnedPolicyValues.add(raw);
|
|
5567
|
+
console.warn(
|
|
5568
|
+
`[mcp-inspector] Ignoring invalid ${PERSIST_TOKENS_ENV}=${JSON.stringify(raw)} (expected "all", "access", or "none"); persisting all tokens.`
|
|
5569
|
+
);
|
|
5570
|
+
}
|
|
5571
|
+
return "all";
|
|
5572
|
+
}
|
|
5573
|
+
var oauthSecretServerId = (serverUrl) => `oauth+${encodeURIComponent(serverUrl)}`;
|
|
5574
|
+
var oauthIdpSecretServerId = (issuer) => `oauth-idp+${encodeURIComponent(issuer)}`;
|
|
5575
|
+
var issuerTokensField = (issuer) => `tokens:${issuer}`;
|
|
5576
|
+
var issuerClientSecretField = (issuer) => `client-secret:${issuer}`;
|
|
5577
|
+
var issuerRegistrationTokenField = (issuer) => `registration-token:${issuer}`;
|
|
5578
|
+
var LEGACY_TOKENS_FIELD = "tokens";
|
|
5579
|
+
var LEGACY_CLIENT_SECRET_FIELD = "client-secret";
|
|
5580
|
+
var LEGACY_REGISTRATION_TOKEN_FIELD = "registration-token";
|
|
5581
|
+
var PREREG_CLIENT_SECRET_FIELD = "prereg-client-secret";
|
|
5582
|
+
var PREREG_REGISTRATION_TOKEN_FIELD = "prereg-registration-token";
|
|
5583
|
+
var IDP_SESSION_FIELD = "idp-session";
|
|
5584
|
+
function applyTokensPolicy(tokens, policy) {
|
|
5585
|
+
if (policy === "none") return void 0;
|
|
5586
|
+
if (policy === "access" && tokens.refresh_token !== void 0) {
|
|
5587
|
+
const { refresh_token: _stripped, ...rest } = tokens;
|
|
5588
|
+
return rest;
|
|
5589
|
+
}
|
|
5590
|
+
return tokens;
|
|
5591
|
+
}
|
|
5592
|
+
var StorableTokensSchema = OAuthTokensSchema3.partial();
|
|
5593
|
+
function hasSecretBearingTokenField(tokens) {
|
|
5594
|
+
return tokens.access_token !== void 0 || tokens.refresh_token !== void 0 || tokens.id_token !== void 0;
|
|
5595
|
+
}
|
|
5596
|
+
function splitTokens(tokens, policy) {
|
|
5597
|
+
const kept = applyTokensPolicy(tokens, policy);
|
|
5598
|
+
if (!kept) return {};
|
|
5599
|
+
if (!StorableTokensSchema.safeParse(kept).success) {
|
|
5600
|
+
return { plaintext: kept };
|
|
5601
|
+
}
|
|
5602
|
+
if (!hasSecretBearingTokenField(kept)) {
|
|
5603
|
+
return {};
|
|
5604
|
+
}
|
|
5605
|
+
return { secret: JSON.stringify(kept) };
|
|
5606
|
+
}
|
|
5607
|
+
function splitClientInformation(info, fields, secrets) {
|
|
5608
|
+
const { client_secret, registration_access_token, ...publicInfo } = info;
|
|
5609
|
+
if (client_secret === void 0 && registration_access_token === void 0) {
|
|
5610
|
+
return info;
|
|
5611
|
+
}
|
|
5612
|
+
if (client_secret !== void 0) {
|
|
5613
|
+
secrets[fields.clientSecret] = client_secret;
|
|
5614
|
+
}
|
|
5615
|
+
if (registration_access_token !== void 0) {
|
|
5616
|
+
secrets[fields.registrationToken] = registration_access_token;
|
|
5617
|
+
}
|
|
5618
|
+
return publicInfo;
|
|
5619
|
+
}
|
|
5620
|
+
function joinClientInformation(info, fields, secrets) {
|
|
5621
|
+
const clientSecret = secrets[fields.clientSecret];
|
|
5622
|
+
const registrationToken = secrets[fields.registrationToken];
|
|
5623
|
+
if (clientSecret === void 0 && registrationToken === void 0) {
|
|
5624
|
+
return info;
|
|
5625
|
+
}
|
|
5332
5626
|
return {
|
|
5333
|
-
|
|
5334
|
-
|
|
5335
|
-
|
|
5336
|
-
|
|
5337
|
-
async write(snapshot) {
|
|
5338
|
-
await writeStoreFile(
|
|
5339
|
-
options.filePath,
|
|
5340
|
-
serializeOAuthPersistBlob(snapshot)
|
|
5341
|
-
);
|
|
5342
|
-
},
|
|
5343
|
-
async remove() {
|
|
5344
|
-
await deleteStoreFile(options.filePath);
|
|
5627
|
+
...info,
|
|
5628
|
+
...clientSecret !== void 0 && { client_secret: clientSecret },
|
|
5629
|
+
...registrationToken !== void 0 && {
|
|
5630
|
+
registration_access_token: registrationToken
|
|
5345
5631
|
}
|
|
5346
5632
|
};
|
|
5347
5633
|
}
|
|
5348
|
-
|
|
5349
|
-
|
|
5350
|
-
|
|
5351
|
-
|
|
5352
|
-
|
|
5353
|
-
|
|
5354
|
-
|
|
5634
|
+
var issuerClientInfoFields = (issuer) => ({
|
|
5635
|
+
clientSecret: issuerClientSecretField(issuer),
|
|
5636
|
+
registrationToken: issuerRegistrationTokenField(issuer)
|
|
5637
|
+
});
|
|
5638
|
+
var LEGACY_CLIENT_INFO_FIELDS = {
|
|
5639
|
+
clientSecret: LEGACY_CLIENT_SECRET_FIELD,
|
|
5640
|
+
registrationToken: LEGACY_REGISTRATION_TOKEN_FIELD
|
|
5641
|
+
};
|
|
5642
|
+
var PREREG_CLIENT_INFO_FIELDS = {
|
|
5643
|
+
clientSecret: PREREG_CLIENT_SECRET_FIELD,
|
|
5644
|
+
registrationToken: PREREG_REGISTRATION_TOKEN_FIELD
|
|
5645
|
+
};
|
|
5646
|
+
function splitServerOAuthState(state, policy) {
|
|
5647
|
+
const secrets = {};
|
|
5648
|
+
const residue = { ...state };
|
|
5649
|
+
if (state.byIssuer) {
|
|
5650
|
+
const byIssuer = {};
|
|
5651
|
+
for (const [issuer, slot] of Object.entries(state.byIssuer)) {
|
|
5652
|
+
const slotResidue = { ...slot };
|
|
5653
|
+
if (slot.tokens) {
|
|
5654
|
+
delete slotResidue.tokens;
|
|
5655
|
+
const tokenSplit = splitTokens(slot.tokens, policy);
|
|
5656
|
+
if (tokenSplit.secret !== void 0) {
|
|
5657
|
+
secrets[issuerTokensField(issuer)] = tokenSplit.secret;
|
|
5658
|
+
}
|
|
5659
|
+
if (tokenSplit.plaintext) slotResidue.tokens = tokenSplit.plaintext;
|
|
5660
|
+
}
|
|
5661
|
+
if (slot.clientInformation) {
|
|
5662
|
+
slotResidue.clientInformation = splitClientInformation(
|
|
5663
|
+
slot.clientInformation,
|
|
5664
|
+
issuerClientInfoFields(issuer),
|
|
5665
|
+
secrets
|
|
5666
|
+
);
|
|
5667
|
+
}
|
|
5668
|
+
setOwnEntry(byIssuer, issuer, slotResidue);
|
|
5669
|
+
}
|
|
5670
|
+
residue.byIssuer = byIssuer;
|
|
5355
5671
|
}
|
|
5356
|
-
if (
|
|
5357
|
-
|
|
5672
|
+
if (state.tokens) {
|
|
5673
|
+
delete residue.tokens;
|
|
5674
|
+
const tokenSplit = splitTokens(state.tokens, policy);
|
|
5675
|
+
if (tokenSplit.secret !== void 0) {
|
|
5676
|
+
secrets[LEGACY_TOKENS_FIELD] = tokenSplit.secret;
|
|
5677
|
+
}
|
|
5678
|
+
if (tokenSplit.plaintext) residue.tokens = tokenSplit.plaintext;
|
|
5358
5679
|
}
|
|
5359
|
-
|
|
5680
|
+
if (state.clientInformation) {
|
|
5681
|
+
residue.clientInformation = splitClientInformation(
|
|
5682
|
+
state.clientInformation,
|
|
5683
|
+
LEGACY_CLIENT_INFO_FIELDS,
|
|
5684
|
+
secrets
|
|
5685
|
+
);
|
|
5686
|
+
}
|
|
5687
|
+
if (state.preregisteredClientInformation) {
|
|
5688
|
+
residue.preregisteredClientInformation = splitClientInformation(
|
|
5689
|
+
state.preregisteredClientInformation,
|
|
5690
|
+
PREREG_CLIENT_INFO_FIELDS,
|
|
5691
|
+
secrets
|
|
5692
|
+
);
|
|
5693
|
+
}
|
|
5694
|
+
return { residue, secrets };
|
|
5360
5695
|
}
|
|
5361
|
-
|
|
5362
|
-
|
|
5363
|
-
|
|
5364
|
-
|
|
5365
|
-
|
|
5366
|
-
|
|
5367
|
-
|
|
5368
|
-
memoryCache.set(key, memory);
|
|
5696
|
+
function parseStoredTokens(raw) {
|
|
5697
|
+
try {
|
|
5698
|
+
const parsed = JSON.parse(raw);
|
|
5699
|
+
if (StorableTokensSchema.safeParse(parsed).success && hasSecretBearingTokenField(parsed)) {
|
|
5700
|
+
return parsed;
|
|
5701
|
+
}
|
|
5702
|
+
} catch {
|
|
5369
5703
|
}
|
|
5370
|
-
return
|
|
5704
|
+
return void 0;
|
|
5371
5705
|
}
|
|
5372
|
-
function
|
|
5373
|
-
|
|
5374
|
-
|
|
5375
|
-
|
|
5706
|
+
function parseStoredIdpSession(raw) {
|
|
5707
|
+
try {
|
|
5708
|
+
const parsed = JSON.parse(raw);
|
|
5709
|
+
if (typeof parsed === "object" && parsed !== null) {
|
|
5710
|
+
const { idToken, refreshToken } = parsed;
|
|
5711
|
+
const usable = {
|
|
5712
|
+
...typeof idToken === "string" && { idToken },
|
|
5713
|
+
...typeof refreshToken === "string" && { refreshToken }
|
|
5714
|
+
};
|
|
5715
|
+
if (Object.keys(usable).length > 0) return usable;
|
|
5716
|
+
}
|
|
5717
|
+
} catch {
|
|
5718
|
+
}
|
|
5719
|
+
return void 0;
|
|
5376
5720
|
}
|
|
5377
|
-
|
|
5378
|
-
|
|
5379
|
-
|
|
5380
|
-
|
|
5381
|
-
|
|
5382
|
-
|
|
5383
|
-
|
|
5384
|
-
|
|
5385
|
-
|
|
5721
|
+
function isUsableStoredSecret(field, raw) {
|
|
5722
|
+
if (field === LEGACY_TOKENS_FIELD || field.startsWith("tokens:")) {
|
|
5723
|
+
return parseStoredTokens(raw) !== void 0;
|
|
5724
|
+
}
|
|
5725
|
+
if (field === IDP_SESSION_FIELD) {
|
|
5726
|
+
return parseStoredIdpSession(raw) !== void 0;
|
|
5727
|
+
}
|
|
5728
|
+
return true;
|
|
5729
|
+
}
|
|
5730
|
+
function joinServerOAuthState(residue, secrets) {
|
|
5731
|
+
const joined = { ...residue };
|
|
5732
|
+
if (residue.byIssuer) {
|
|
5733
|
+
const byIssuer = {};
|
|
5734
|
+
for (const [issuer, slot] of Object.entries(residue.byIssuer)) {
|
|
5735
|
+
const joinedSlot = { ...slot };
|
|
5736
|
+
const tokensRaw = secrets[issuerTokensField(issuer)];
|
|
5737
|
+
if (tokensRaw !== void 0) {
|
|
5738
|
+
const tokens = parseStoredTokens(tokensRaw);
|
|
5739
|
+
if (tokens) joinedSlot.tokens = tokens;
|
|
5740
|
+
}
|
|
5741
|
+
if (joinedSlot.clientInformation) {
|
|
5742
|
+
joinedSlot.clientInformation = joinClientInformation(
|
|
5743
|
+
joinedSlot.clientInformation,
|
|
5744
|
+
issuerClientInfoFields(issuer),
|
|
5745
|
+
secrets
|
|
5746
|
+
);
|
|
5747
|
+
}
|
|
5748
|
+
setOwnEntry(byIssuer, issuer, joinedSlot);
|
|
5749
|
+
}
|
|
5750
|
+
joined.byIssuer = byIssuer;
|
|
5751
|
+
}
|
|
5752
|
+
const legacyTokensRaw = secrets[LEGACY_TOKENS_FIELD];
|
|
5753
|
+
if (legacyTokensRaw !== void 0) {
|
|
5754
|
+
const tokens = parseStoredTokens(legacyTokensRaw);
|
|
5755
|
+
if (tokens) joined.tokens = tokens;
|
|
5756
|
+
}
|
|
5757
|
+
if (joined.clientInformation) {
|
|
5758
|
+
joined.clientInformation = joinClientInformation(
|
|
5759
|
+
joined.clientInformation,
|
|
5760
|
+
LEGACY_CLIENT_INFO_FIELDS,
|
|
5761
|
+
secrets
|
|
5386
5762
|
);
|
|
5387
5763
|
}
|
|
5388
|
-
|
|
5389
|
-
|
|
5390
|
-
|
|
5391
|
-
|
|
5392
|
-
|
|
5393
|
-
|
|
5394
|
-
OAuthTokensSchema as OAuthTokensSchema2
|
|
5395
|
-
} from "@modelcontextprotocol/core";
|
|
5396
|
-
var DEFAULT_REVOCATION_TIMEOUT_MS = 5e3;
|
|
5397
|
-
var MIN_REVOCATION_REQUEST_BUDGET_MS = 5;
|
|
5398
|
-
function selectRevocableToken(tokens) {
|
|
5399
|
-
if (!tokens) return null;
|
|
5400
|
-
if (tokens.refresh_token) {
|
|
5401
|
-
return { token: tokens.refresh_token, tokenTypeHint: "refresh_token" };
|
|
5764
|
+
if (joined.preregisteredClientInformation) {
|
|
5765
|
+
joined.preregisteredClientInformation = joinClientInformation(
|
|
5766
|
+
joined.preregisteredClientInformation,
|
|
5767
|
+
PREREG_CLIENT_INFO_FIELDS,
|
|
5768
|
+
secrets
|
|
5769
|
+
);
|
|
5402
5770
|
}
|
|
5403
|
-
|
|
5404
|
-
|
|
5771
|
+
return joined;
|
|
5772
|
+
}
|
|
5773
|
+
function splitIdpSession(session, policy) {
|
|
5774
|
+
const { idToken, refreshToken, ...residue } = session;
|
|
5775
|
+
const secrets = {};
|
|
5776
|
+
if (policy !== "none" && (idToken !== void 0 || refreshToken !== void 0)) {
|
|
5777
|
+
const kept = {
|
|
5778
|
+
...typeof idToken === "string" && { idToken },
|
|
5779
|
+
...policy === "all" && typeof refreshToken === "string" && { refreshToken }
|
|
5780
|
+
};
|
|
5781
|
+
if (Object.keys(kept).length > 0) {
|
|
5782
|
+
secrets[IDP_SESSION_FIELD] = JSON.stringify(kept);
|
|
5783
|
+
}
|
|
5405
5784
|
}
|
|
5406
|
-
return
|
|
5785
|
+
return { residue, secrets };
|
|
5407
5786
|
}
|
|
5408
|
-
function
|
|
5409
|
-
|
|
5410
|
-
|
|
5411
|
-
|
|
5787
|
+
function joinIdpSession(residue, secrets) {
|
|
5788
|
+
const raw = secrets[IDP_SESSION_FIELD];
|
|
5789
|
+
if (raw === void 0) return { ...residue };
|
|
5790
|
+
const stored = parseStoredIdpSession(raw);
|
|
5791
|
+
return stored ? { ...residue, ...stored } : { ...residue };
|
|
5412
5792
|
}
|
|
5413
|
-
function
|
|
5414
|
-
const
|
|
5415
|
-
|
|
5416
|
-
|
|
5417
|
-
|
|
5418
|
-
|
|
5419
|
-
|
|
5420
|
-
|
|
5421
|
-
}
|
|
5422
|
-
|
|
5423
|
-
|
|
5424
|
-
|
|
5425
|
-
if (method === "client_secret_basic" && client.client_secret) {
|
|
5426
|
-
const credentials = `${encodeURIComponent(client.client_id)}:${encodeURIComponent(client.client_secret)}`;
|
|
5427
|
-
headers.Authorization = `Basic ${base64Encode(credentials)}`;
|
|
5428
|
-
} else if (method === "client_secret_post" && client.client_secret) {
|
|
5429
|
-
body.set("client_id", client.client_id);
|
|
5430
|
-
body.set("client_secret", client.client_secret);
|
|
5431
|
-
} else {
|
|
5432
|
-
body.set("client_id", client.client_id);
|
|
5433
|
-
}
|
|
5793
|
+
function serverSecretFields(state) {
|
|
5794
|
+
const fields = /* @__PURE__ */ new Set([
|
|
5795
|
+
LEGACY_TOKENS_FIELD,
|
|
5796
|
+
LEGACY_CLIENT_SECRET_FIELD,
|
|
5797
|
+
LEGACY_REGISTRATION_TOKEN_FIELD,
|
|
5798
|
+
PREREG_CLIENT_SECRET_FIELD,
|
|
5799
|
+
PREREG_REGISTRATION_TOKEN_FIELD
|
|
5800
|
+
]);
|
|
5801
|
+
for (const issuer of Object.keys(state?.byIssuer ?? {})) {
|
|
5802
|
+
fields.add(issuerTokensField(issuer));
|
|
5803
|
+
fields.add(issuerClientSecretField(issuer));
|
|
5804
|
+
fields.add(issuerRegistrationTokenField(issuer));
|
|
5434
5805
|
}
|
|
5435
|
-
return
|
|
5436
|
-
url: params.endpoint,
|
|
5437
|
-
init: { method: "POST", headers, body: body.toString() }
|
|
5438
|
-
};
|
|
5806
|
+
return [...fields];
|
|
5439
5807
|
}
|
|
5440
|
-
function
|
|
5441
|
-
|
|
5442
|
-
return Buffer.from(value, "utf8").toString("base64");
|
|
5808
|
+
function clientInfoHasPlaintext(info) {
|
|
5809
|
+
return info !== void 0 && (info.client_secret !== void 0 || info.registration_access_token !== void 0);
|
|
5443
5810
|
}
|
|
5444
|
-
|
|
5445
|
-
const
|
|
5446
|
-
|
|
5811
|
+
function snapshotHasPlaintextSecrets(snapshot) {
|
|
5812
|
+
for (const state of Object.values(snapshot.servers)) {
|
|
5813
|
+
if (state.tokens) return true;
|
|
5814
|
+
if (clientInfoHasPlaintext(state.clientInformation)) return true;
|
|
5815
|
+
if (clientInfoHasPlaintext(state.preregisteredClientInformation)) {
|
|
5816
|
+
return true;
|
|
5817
|
+
}
|
|
5818
|
+
for (const slot of Object.values(state.byIssuer ?? {})) {
|
|
5819
|
+
if (slot.tokens) return true;
|
|
5820
|
+
if (clientInfoHasPlaintext(slot.clientInformation)) return true;
|
|
5821
|
+
}
|
|
5822
|
+
}
|
|
5823
|
+
for (const session of Object.values(snapshot.idpSessions)) {
|
|
5824
|
+
if (session.idToken !== void 0 || session.refreshToken !== void 0) {
|
|
5825
|
+
return true;
|
|
5826
|
+
}
|
|
5827
|
+
}
|
|
5828
|
+
return false;
|
|
5829
|
+
}
|
|
5830
|
+
|
|
5831
|
+
// ../../core/auth/node/oauth-persist-file.ts
|
|
5832
|
+
var warnedStoreFailures = /* @__PURE__ */ new Set();
|
|
5833
|
+
function warnStoreWriteFailure(error) {
|
|
5834
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
5835
|
+
if (warnedStoreFailures.has(reason)) return;
|
|
5836
|
+
warnedStoreFailures.add(reason);
|
|
5837
|
+
console.warn(
|
|
5838
|
+
`[mcp-inspector] Could not write OAuth tokens to the secret store (${reason}). Tokens will be kept in memory for this session only and will NOT be persisted; expect to re-authorize after a restart.`
|
|
5839
|
+
);
|
|
5840
|
+
}
|
|
5841
|
+
function warnRestoreFailure(error) {
|
|
5842
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
5843
|
+
const key = `restore:${reason}`;
|
|
5844
|
+
if (warnedStoreFailures.has(key)) return;
|
|
5845
|
+
warnedStoreFailures.add(key);
|
|
5846
|
+
console.warn(
|
|
5847
|
+
`[mcp-inspector] Could not restore secret-store entries after a failed OAuth state write (${reason}). The secret store may be inconsistent with oauth.json; if a server's stored credentials stop working, re-authorize it.`
|
|
5848
|
+
);
|
|
5849
|
+
}
|
|
5850
|
+
function warnUnconfirmedFile(error) {
|
|
5851
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
5852
|
+
const key = `unconfirmed:${reason}`;
|
|
5853
|
+
if (warnedStoreFailures.has(key)) return;
|
|
5854
|
+
warnedStoreFailures.add(key);
|
|
5855
|
+
console.warn(
|
|
5856
|
+
`[mcp-inspector] Could not re-read oauth.json after a failed OAuth state save (${reason}). The file may still contain the interrupted save while the secret store was restored to the previous values; if a server's stored credentials stop working, re-authorize it.`
|
|
5857
|
+
);
|
|
5858
|
+
}
|
|
5859
|
+
function warnMigrationFailure(error) {
|
|
5860
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
5861
|
+
const key = `migration:${reason}`;
|
|
5862
|
+
if (warnedStoreFailures.has(key)) return;
|
|
5863
|
+
warnedStoreFailures.add(key);
|
|
5864
|
+
console.warn(
|
|
5865
|
+
`[mcp-inspector] Could not migrate plaintext OAuth secrets into the secret store (${reason}). The plaintext copy in oauth.json was kept and keeps working; migration will be retried on the next read.`
|
|
5866
|
+
);
|
|
5867
|
+
}
|
|
5868
|
+
function rethrowLockError(filePath, error, action = "save") {
|
|
5869
|
+
if (error instanceof SecretFileLockHeldError) {
|
|
5870
|
+
throw new SecretFileLockHeldError(
|
|
5871
|
+
`Could not ${action} OAuth state: the state file at ${filePath} is locked by another Inspector process and did not become available.`,
|
|
5872
|
+
{ cause: error }
|
|
5873
|
+
);
|
|
5874
|
+
}
|
|
5875
|
+
throw error;
|
|
5876
|
+
}
|
|
5877
|
+
async function withOAuthStateLock(filePath, action, body) {
|
|
5878
|
+
let entered = false;
|
|
5879
|
+
try {
|
|
5880
|
+
return await withSecretFileLock(filePath, async () => {
|
|
5881
|
+
entered = true;
|
|
5882
|
+
return body();
|
|
5883
|
+
});
|
|
5884
|
+
} catch (error) {
|
|
5885
|
+
if (!entered) rethrowLockError(filePath, error, action);
|
|
5886
|
+
throw error;
|
|
5887
|
+
}
|
|
5888
|
+
}
|
|
5889
|
+
var OAuthStateFileUnrecognizedError = class extends Error {
|
|
5890
|
+
constructor(filePath, action) {
|
|
5891
|
+
super(
|
|
5892
|
+
`Refusing to ${action} OAuth state: ${filePath} exists but is not a recognized OAuth state file (it may be corrupt, truncated, or written by something else). Its entries are the only index of credentials in the OS keychain / secret store, so overwriting it would strand them. Restore the file from a backup or fix its JSON; deleting it starts fresh but abandons any credentials it indexed.`
|
|
5893
|
+
);
|
|
5894
|
+
this.name = "OAuthStateFileUnrecognizedError";
|
|
5895
|
+
}
|
|
5896
|
+
};
|
|
5897
|
+
async function readDiskForMutation(filePath, action) {
|
|
5898
|
+
const raw = await readStoreFile(filePath);
|
|
5899
|
+
const parsed = parseOAuthPersistBlob(raw);
|
|
5900
|
+
if (raw !== null && parsed === null) {
|
|
5901
|
+
throw new OAuthStateFileUnrecognizedError(filePath, action);
|
|
5902
|
+
}
|
|
5903
|
+
return parsed;
|
|
5904
|
+
}
|
|
5905
|
+
async function persistEntrySecrets(store, serverId, candidates, secrets, prior, allowDegrade) {
|
|
5906
|
+
const priorValue = new Map(prior.map(({ field, value }) => [field, value]));
|
|
5907
|
+
const changed = {};
|
|
5908
|
+
for (const [field, value] of Object.entries(secrets)) {
|
|
5909
|
+
if (priorValue.get(field) !== value) changed[field] = value;
|
|
5910
|
+
}
|
|
5911
|
+
try {
|
|
5912
|
+
if (Object.keys(changed).length > 0) {
|
|
5913
|
+
await secretStoreSetMany(store, serverId, changed);
|
|
5914
|
+
}
|
|
5915
|
+
} catch (error) {
|
|
5916
|
+
if (!allowDegrade) throw error;
|
|
5917
|
+
warnStoreWriteFailure(error);
|
|
5918
|
+
const touched = prior.filter(({ field }) => changed[field] !== void 0);
|
|
5919
|
+
let restoreFailure;
|
|
5920
|
+
await restoreSecretFields(store, touched, (err) => {
|
|
5921
|
+
restoreFailure = err;
|
|
5922
|
+
});
|
|
5923
|
+
if (restoreFailure !== void 0) throw restoreFailure;
|
|
5924
|
+
return false;
|
|
5925
|
+
}
|
|
5926
|
+
await settleStoreMutations(
|
|
5927
|
+
candidates.filter(
|
|
5928
|
+
(field) => secrets[field] === void 0 && priorValue.get(field) !== null
|
|
5929
|
+
).map((field) => store.delete(serverId, field))
|
|
5930
|
+
);
|
|
5931
|
+
return true;
|
|
5932
|
+
}
|
|
5933
|
+
function revertEntryToDisk(merged, disk, key) {
|
|
5934
|
+
const diskEntry = getOwnEntry(disk, key);
|
|
5935
|
+
if (diskEntry === void 0) {
|
|
5936
|
+
if (Object.hasOwn(merged, key)) delete merged[key];
|
|
5937
|
+
} else {
|
|
5938
|
+
setOwnEntry(merged, key, diskEntry);
|
|
5939
|
+
}
|
|
5940
|
+
}
|
|
5941
|
+
function preserveNonDurableSecrets(diskSecrets, secrets) {
|
|
5942
|
+
const keep = {};
|
|
5943
|
+
for (const [field, value] of Object.entries(secrets)) {
|
|
5944
|
+
if (diskSecrets[field] === value) keep[field] = value;
|
|
5945
|
+
}
|
|
5946
|
+
return keep;
|
|
5947
|
+
}
|
|
5948
|
+
async function writeOAuthSections(filePath, snapshot, sections, secretStore = defaultSecretStore()) {
|
|
5949
|
+
const policy = getPersistTokensPolicy();
|
|
5950
|
+
const durable = await secretStoreIsDurable(secretStore);
|
|
5951
|
+
await withOAuthStateLock(filePath, "save", async () => {
|
|
5952
|
+
const restoreBaseline = /* @__PURE__ */ new Map();
|
|
5953
|
+
const ourWrites = /* @__PURE__ */ new Map();
|
|
5954
|
+
const priorKey = (p) => `${p.serverId}\0${p.field}`;
|
|
5955
|
+
const foldPriors = (priors) => {
|
|
5956
|
+
for (const prior of priors) {
|
|
5957
|
+
const key = priorKey(prior);
|
|
5958
|
+
if (restoreBaseline.has(key) && ourWrites.get(key) !== void 0 && prior.value === ourWrites.get(key)) {
|
|
5959
|
+
continue;
|
|
5960
|
+
}
|
|
5961
|
+
restoreBaseline.set(key, prior);
|
|
5962
|
+
}
|
|
5963
|
+
};
|
|
5964
|
+
const restoreToBaseline = () => restoreSecretFields(
|
|
5965
|
+
secretStore,
|
|
5966
|
+
[...restoreBaseline.values()],
|
|
5967
|
+
warnRestoreFailure
|
|
5968
|
+
);
|
|
5969
|
+
let unconfirmed = null;
|
|
5970
|
+
const reconcileFailure = async () => {
|
|
5971
|
+
if (unconfirmed !== null) {
|
|
5972
|
+
const committed = unconfirmed;
|
|
5973
|
+
let observed = null;
|
|
5974
|
+
let readable = true;
|
|
5975
|
+
try {
|
|
5976
|
+
observed = await readStoreFile(filePath);
|
|
5977
|
+
} catch (error) {
|
|
5978
|
+
readable = false;
|
|
5979
|
+
warnUnconfirmedFile(error);
|
|
5980
|
+
}
|
|
5981
|
+
if (readable && observed === committed.written) {
|
|
5982
|
+
const target = new Map(
|
|
5983
|
+
[...restoreBaseline].map(([key, { serverId, field, value }]) => [
|
|
5984
|
+
key,
|
|
5985
|
+
{ serverId, field, value }
|
|
5986
|
+
])
|
|
5987
|
+
);
|
|
5988
|
+
for (const [key, write] of committed.writes) target.set(key, write);
|
|
5989
|
+
try {
|
|
5990
|
+
await settleStoreMutations(
|
|
5991
|
+
[...target.values()].map(
|
|
5992
|
+
({ serverId, field, value }) => value === null ? secretStore.delete(serverId, field) : secretStore.set(serverId, field, value)
|
|
5993
|
+
)
|
|
5994
|
+
);
|
|
5995
|
+
return true;
|
|
5996
|
+
} catch (error) {
|
|
5997
|
+
warnRestoreFailure(error);
|
|
5998
|
+
return false;
|
|
5999
|
+
}
|
|
6000
|
+
}
|
|
6001
|
+
}
|
|
6002
|
+
await restoreToBaseline();
|
|
6003
|
+
return false;
|
|
6004
|
+
};
|
|
6005
|
+
for (let attempt = 0; attempt < MAX_WRITE_ATTEMPTS; attempt++) {
|
|
6006
|
+
const priorSecrets = [];
|
|
6007
|
+
const attemptWrites = /* @__PURE__ */ new Map();
|
|
6008
|
+
let written;
|
|
6009
|
+
try {
|
|
6010
|
+
const disk = await readDiskForMutation(filePath, "save");
|
|
6011
|
+
const effective = sections ? {
|
|
6012
|
+
servers: sections.servers && [...new Set(sections.servers)],
|
|
6013
|
+
idpSessions: sections.idpSessions && [
|
|
6014
|
+
...new Set(sections.idpSessions)
|
|
6015
|
+
]
|
|
6016
|
+
} : {
|
|
6017
|
+
servers: [
|
|
6018
|
+
.../* @__PURE__ */ new Set([
|
|
6019
|
+
...Object.keys(disk?.servers ?? {}),
|
|
6020
|
+
...Object.keys(snapshot.servers)
|
|
6021
|
+
])
|
|
6022
|
+
],
|
|
6023
|
+
idpSessions: [
|
|
6024
|
+
.../* @__PURE__ */ new Set([
|
|
6025
|
+
...Object.keys(disk?.idpSessions ?? {}),
|
|
6026
|
+
...Object.keys(snapshot.idpSessions)
|
|
6027
|
+
])
|
|
6028
|
+
]
|
|
6029
|
+
};
|
|
6030
|
+
const merged = mergeOAuthSections(disk, snapshot, effective);
|
|
6031
|
+
for (const url of effective.servers ?? []) {
|
|
6032
|
+
const serverId = oauthSecretServerId(url);
|
|
6033
|
+
const next = getOwnEntry(snapshot.servers, url);
|
|
6034
|
+
const candidates = [
|
|
6035
|
+
.../* @__PURE__ */ new Set([
|
|
6036
|
+
...serverSecretFields(getOwnEntry(disk?.servers, url)),
|
|
6037
|
+
...serverSecretFields(next)
|
|
6038
|
+
])
|
|
6039
|
+
];
|
|
6040
|
+
const entryPrior = await snapshotSecretFields(
|
|
6041
|
+
secretStore,
|
|
6042
|
+
serverId,
|
|
6043
|
+
candidates
|
|
6044
|
+
);
|
|
6045
|
+
priorSecrets.push(...entryPrior);
|
|
6046
|
+
if (next === void 0) {
|
|
6047
|
+
for (const field of candidates) {
|
|
6048
|
+
ourWrites.set(priorKey({ serverId, field }), null);
|
|
6049
|
+
}
|
|
6050
|
+
await secretStore.deleteAllForServer(serverId);
|
|
6051
|
+
for (const field of candidates) {
|
|
6052
|
+
attemptWrites.set(priorKey({ serverId, field }), {
|
|
6053
|
+
serverId,
|
|
6054
|
+
field,
|
|
6055
|
+
value: null
|
|
6056
|
+
});
|
|
6057
|
+
}
|
|
6058
|
+
continue;
|
|
6059
|
+
}
|
|
6060
|
+
const { residue, secrets } = splitServerOAuthState(next, policy);
|
|
6061
|
+
for (const field of candidates) {
|
|
6062
|
+
ourWrites.set(
|
|
6063
|
+
priorKey({ serverId, field }),
|
|
6064
|
+
secrets[field] ?? null
|
|
6065
|
+
);
|
|
6066
|
+
}
|
|
6067
|
+
setOwnEntry(merged.servers, url, residue);
|
|
6068
|
+
if (!durable) {
|
|
6069
|
+
const diskEntry = getOwnEntry(disk?.servers, url);
|
|
6070
|
+
const keep = preserveNonDurableSecrets(
|
|
6071
|
+
diskEntry ? splitServerOAuthState(diskEntry, policy).secrets : {},
|
|
6072
|
+
secrets
|
|
6073
|
+
);
|
|
6074
|
+
if (Object.keys(keep).length > 0) {
|
|
6075
|
+
setOwnEntry(
|
|
6076
|
+
merged.servers,
|
|
6077
|
+
url,
|
|
6078
|
+
joinServerOAuthState(residue, keep)
|
|
6079
|
+
);
|
|
6080
|
+
}
|
|
6081
|
+
}
|
|
6082
|
+
const persisted = await persistEntrySecrets(
|
|
6083
|
+
secretStore,
|
|
6084
|
+
serverId,
|
|
6085
|
+
candidates,
|
|
6086
|
+
secrets,
|
|
6087
|
+
entryPrior,
|
|
6088
|
+
// Degrading is only sound when the disk entry is the pre-call
|
|
6089
|
+
// state this attempt's priors pair with; on a retry it may be an
|
|
6090
|
+
// earlier attempt's committed residue, so escalate into the
|
|
6091
|
+
// reconciling exit instead.
|
|
6092
|
+
attempt === 0
|
|
6093
|
+
);
|
|
6094
|
+
if (!persisted) {
|
|
6095
|
+
revertEntryToDisk(merged.servers, disk?.servers, url);
|
|
6096
|
+
continue;
|
|
6097
|
+
}
|
|
6098
|
+
for (const field of candidates) {
|
|
6099
|
+
attemptWrites.set(priorKey({ serverId, field }), {
|
|
6100
|
+
serverId,
|
|
6101
|
+
field,
|
|
6102
|
+
value: secrets[field] ?? null
|
|
6103
|
+
});
|
|
6104
|
+
}
|
|
6105
|
+
}
|
|
6106
|
+
for (const issuer of effective.idpSessions ?? []) {
|
|
6107
|
+
const serverId = oauthIdpSecretServerId(issuer);
|
|
6108
|
+
const next = getOwnEntry(snapshot.idpSessions, issuer);
|
|
6109
|
+
const entryPrior = await snapshotSecretFields(secretStore, serverId, [
|
|
6110
|
+
IDP_SESSION_FIELD
|
|
6111
|
+
]);
|
|
6112
|
+
priorSecrets.push(...entryPrior);
|
|
6113
|
+
if (next === void 0) {
|
|
6114
|
+
ourWrites.set(
|
|
6115
|
+
priorKey({ serverId, field: IDP_SESSION_FIELD }),
|
|
6116
|
+
null
|
|
6117
|
+
);
|
|
6118
|
+
await secretStore.deleteAllForServer(serverId);
|
|
6119
|
+
attemptWrites.set(
|
|
6120
|
+
priorKey({ serverId, field: IDP_SESSION_FIELD }),
|
|
6121
|
+
{
|
|
6122
|
+
serverId,
|
|
6123
|
+
field: IDP_SESSION_FIELD,
|
|
6124
|
+
value: null
|
|
6125
|
+
}
|
|
6126
|
+
);
|
|
6127
|
+
continue;
|
|
6128
|
+
}
|
|
6129
|
+
const { residue, secrets } = splitIdpSession(next, policy);
|
|
6130
|
+
ourWrites.set(
|
|
6131
|
+
priorKey({ serverId, field: IDP_SESSION_FIELD }),
|
|
6132
|
+
secrets[IDP_SESSION_FIELD] ?? null
|
|
6133
|
+
);
|
|
6134
|
+
setOwnEntry(merged.idpSessions, issuer, residue);
|
|
6135
|
+
if (!durable) {
|
|
6136
|
+
const diskSession = getOwnEntry(disk?.idpSessions, issuer);
|
|
6137
|
+
const keep = preserveNonDurableSecrets(
|
|
6138
|
+
diskSession ? splitIdpSession(diskSession, policy).secrets : {},
|
|
6139
|
+
secrets
|
|
6140
|
+
);
|
|
6141
|
+
if (Object.keys(keep).length > 0) {
|
|
6142
|
+
setOwnEntry(
|
|
6143
|
+
merged.idpSessions,
|
|
6144
|
+
issuer,
|
|
6145
|
+
joinIdpSession(residue, keep)
|
|
6146
|
+
);
|
|
6147
|
+
}
|
|
6148
|
+
}
|
|
6149
|
+
const persisted = await persistEntrySecrets(
|
|
6150
|
+
secretStore,
|
|
6151
|
+
serverId,
|
|
6152
|
+
[IDP_SESSION_FIELD],
|
|
6153
|
+
secrets,
|
|
6154
|
+
entryPrior,
|
|
6155
|
+
// Same as the server loop: degrade on the first attempt only.
|
|
6156
|
+
attempt === 0
|
|
6157
|
+
);
|
|
6158
|
+
if (!persisted) {
|
|
6159
|
+
revertEntryToDisk(merged.idpSessions, disk?.idpSessions, issuer);
|
|
6160
|
+
continue;
|
|
6161
|
+
}
|
|
6162
|
+
attemptWrites.set(priorKey({ serverId, field: IDP_SESSION_FIELD }), {
|
|
6163
|
+
serverId,
|
|
6164
|
+
field: IDP_SESSION_FIELD,
|
|
6165
|
+
value: secrets[IDP_SESSION_FIELD] ?? null
|
|
6166
|
+
});
|
|
6167
|
+
}
|
|
6168
|
+
written = serializeOAuthPersistBlob(merged);
|
|
6169
|
+
await writeStoreFile(filePath, written);
|
|
6170
|
+
unconfirmed = { written, writes: attemptWrites };
|
|
6171
|
+
} catch (error) {
|
|
6172
|
+
foldPriors(priorSecrets);
|
|
6173
|
+
if (await reconcileFailure()) return;
|
|
6174
|
+
throw error;
|
|
6175
|
+
}
|
|
6176
|
+
foldPriors(priorSecrets);
|
|
6177
|
+
let observed;
|
|
6178
|
+
try {
|
|
6179
|
+
observed = await readStoreFile(filePath);
|
|
6180
|
+
} catch {
|
|
6181
|
+
continue;
|
|
6182
|
+
}
|
|
6183
|
+
if (observed === written) return;
|
|
6184
|
+
unconfirmed = null;
|
|
6185
|
+
}
|
|
6186
|
+
if (await reconcileFailure()) return;
|
|
6187
|
+
throw new SecretStoreUnavailableError(
|
|
6188
|
+
`Could not save OAuth state: another process kept overwriting ${filePath} (gave up after ${MAX_WRITE_ATTEMPTS} attempts). Re-run the action to retry.`
|
|
6189
|
+
);
|
|
6190
|
+
});
|
|
6191
|
+
}
|
|
6192
|
+
function secretRequestsFor(snapshot) {
|
|
6193
|
+
const requests = [];
|
|
6194
|
+
for (const [url, state] of Object.entries(snapshot.servers)) {
|
|
6195
|
+
requests.push({
|
|
6196
|
+
serverId: oauthSecretServerId(url),
|
|
6197
|
+
fields: serverSecretFields(state)
|
|
6198
|
+
});
|
|
6199
|
+
}
|
|
6200
|
+
for (const issuer of Object.keys(snapshot.idpSessions)) {
|
|
6201
|
+
requests.push({
|
|
6202
|
+
serverId: oauthIdpSecretServerId(issuer),
|
|
6203
|
+
fields: [IDP_SESSION_FIELD]
|
|
6204
|
+
});
|
|
6205
|
+
}
|
|
6206
|
+
return requests;
|
|
6207
|
+
}
|
|
6208
|
+
async function joinSnapshot(snapshot, secretStore) {
|
|
6209
|
+
const requests = secretRequestsFor(snapshot);
|
|
6210
|
+
const values = requests.length > 0 ? await secretStoreGetManyStrict(secretStore, requests) : {};
|
|
6211
|
+
return {
|
|
6212
|
+
servers: Object.fromEntries(
|
|
6213
|
+
Object.entries(snapshot.servers).map(([url, state]) => [
|
|
6214
|
+
url,
|
|
6215
|
+
joinServerOAuthState(state, values[oauthSecretServerId(url)] ?? {})
|
|
6216
|
+
])
|
|
6217
|
+
),
|
|
6218
|
+
idpSessions: Object.fromEntries(
|
|
6219
|
+
Object.entries(snapshot.idpSessions).map(([issuer, session]) => [
|
|
6220
|
+
issuer,
|
|
6221
|
+
joinIdpSession(session, values[oauthIdpSecretServerId(issuer)] ?? {})
|
|
6222
|
+
])
|
|
6223
|
+
)
|
|
6224
|
+
};
|
|
6225
|
+
}
|
|
6226
|
+
async function migratePlaintextSecrets(filePath, secretStore) {
|
|
6227
|
+
const raw = await readStoreFile(filePath);
|
|
6228
|
+
const fresh = parseOAuthPersistBlob(raw);
|
|
6229
|
+
if (!fresh || !snapshotHasPlaintextSecrets(fresh)) return;
|
|
6230
|
+
const migrateEntrySecrets = async (serverId, secrets) => {
|
|
6231
|
+
const absent = {};
|
|
6232
|
+
for (const [field, value] of Object.entries(secrets)) {
|
|
6233
|
+
const existing = await secretStoreGetStrict(secretStore, serverId, field);
|
|
6234
|
+
if (existing === null || !isUsableStoredSecret(field, existing)) {
|
|
6235
|
+
absent[field] = value;
|
|
6236
|
+
}
|
|
6237
|
+
}
|
|
6238
|
+
if (Object.keys(absent).length > 0) {
|
|
6239
|
+
await secretStoreSetMany(secretStore, serverId, absent);
|
|
6240
|
+
}
|
|
6241
|
+
};
|
|
6242
|
+
const residue = { servers: {}, idpSessions: {} };
|
|
6243
|
+
for (const [url, state] of Object.entries(fresh.servers)) {
|
|
6244
|
+
const split = splitServerOAuthState(state, "all");
|
|
6245
|
+
setOwnEntry(residue.servers, url, split.residue);
|
|
6246
|
+
await migrateEntrySecrets(oauthSecretServerId(url), split.secrets);
|
|
6247
|
+
}
|
|
6248
|
+
for (const [issuer, session] of Object.entries(fresh.idpSessions)) {
|
|
6249
|
+
const split = splitIdpSession(session, "all");
|
|
6250
|
+
setOwnEntry(residue.idpSessions, issuer, split.residue);
|
|
6251
|
+
await migrateEntrySecrets(oauthIdpSecretServerId(issuer), split.secrets);
|
|
6252
|
+
}
|
|
6253
|
+
const stripped = serializeOAuthPersistBlob(residue);
|
|
6254
|
+
if (stripped !== raw) {
|
|
6255
|
+
await writeStoreFile(filePath, stripped);
|
|
6256
|
+
}
|
|
6257
|
+
}
|
|
6258
|
+
async function readOAuthStore(filePath, secretStore = defaultSecretStore()) {
|
|
6259
|
+
return withOAuthStateLock(filePath, "read", async () => {
|
|
6260
|
+
let snapshot = parseOAuthPersistBlob(await readStoreFile(filePath));
|
|
6261
|
+
if (snapshot === null) return null;
|
|
6262
|
+
if (snapshotHasPlaintextSecrets(snapshot) && await secretStoreIsDurable(secretStore)) {
|
|
6263
|
+
try {
|
|
6264
|
+
await migratePlaintextSecrets(filePath, secretStore);
|
|
6265
|
+
snapshot = parseOAuthPersistBlob(await readStoreFile(filePath)) ?? snapshot;
|
|
6266
|
+
} catch (error) {
|
|
6267
|
+
warnMigrationFailure(error);
|
|
6268
|
+
}
|
|
6269
|
+
}
|
|
6270
|
+
return joinSnapshot(snapshot, secretStore);
|
|
6271
|
+
});
|
|
6272
|
+
}
|
|
6273
|
+
async function removeOAuthStore(filePath, secretStore = defaultSecretStore()) {
|
|
6274
|
+
await withOAuthStateLock(filePath, "remove", async () => {
|
|
6275
|
+
const snapshot = await readDiskForMutation(filePath, "remove");
|
|
6276
|
+
if (snapshot) {
|
|
6277
|
+
const targets = [
|
|
6278
|
+
...Object.entries(snapshot.servers).map(([url, state]) => ({
|
|
6279
|
+
id: oauthSecretServerId(url),
|
|
6280
|
+
fields: serverSecretFields(state)
|
|
6281
|
+
})),
|
|
6282
|
+
...Object.keys(snapshot.idpSessions).map((issuer) => ({
|
|
6283
|
+
id: oauthIdpSecretServerId(issuer),
|
|
6284
|
+
fields: [IDP_SESSION_FIELD]
|
|
6285
|
+
}))
|
|
6286
|
+
];
|
|
6287
|
+
const priorSecrets = [];
|
|
6288
|
+
try {
|
|
6289
|
+
for (const { id, fields } of targets) {
|
|
6290
|
+
priorSecrets.push(
|
|
6291
|
+
...await snapshotSecretFields(secretStore, id, fields)
|
|
6292
|
+
);
|
|
6293
|
+
await secretStore.deleteAllForServer(id);
|
|
6294
|
+
}
|
|
6295
|
+
await deleteStoreFile(filePath);
|
|
6296
|
+
} catch (error) {
|
|
6297
|
+
await restoreSecretFields(
|
|
6298
|
+
secretStore,
|
|
6299
|
+
priorSecrets,
|
|
6300
|
+
warnRestoreFailure
|
|
6301
|
+
);
|
|
6302
|
+
throw error;
|
|
6303
|
+
}
|
|
6304
|
+
} else {
|
|
6305
|
+
await deleteStoreFile(filePath);
|
|
6306
|
+
}
|
|
6307
|
+
});
|
|
6308
|
+
}
|
|
6309
|
+
function createFileOAuthPersistBackend(options) {
|
|
6310
|
+
const secretStore = options.secretStore ?? defaultSecretStore();
|
|
6311
|
+
return {
|
|
6312
|
+
async read() {
|
|
6313
|
+
return readOAuthStore(options.filePath, secretStore);
|
|
6314
|
+
},
|
|
6315
|
+
async write(snapshot, sections) {
|
|
6316
|
+
await writeOAuthSections(
|
|
6317
|
+
options.filePath,
|
|
6318
|
+
snapshot,
|
|
6319
|
+
sections,
|
|
6320
|
+
secretStore
|
|
6321
|
+
);
|
|
6322
|
+
},
|
|
6323
|
+
async remove() {
|
|
6324
|
+
await removeOAuthStore(options.filePath, secretStore);
|
|
6325
|
+
}
|
|
6326
|
+
};
|
|
6327
|
+
}
|
|
6328
|
+
|
|
6329
|
+
// ../../core/auth/node/storage-node.ts
|
|
6330
|
+
var DEFAULT_STATE_PATH = getStoreFilePath(getDefaultStorageDir(), "oauth");
|
|
6331
|
+
function getStateFilePath(customPath) {
|
|
6332
|
+
if (customPath) return customPath;
|
|
6333
|
+
if (process.env.MCP_INSPECTOR_OAUTH_STATE_PATH) {
|
|
6334
|
+
return process.env.MCP_INSPECTOR_OAUTH_STATE_PATH;
|
|
6335
|
+
}
|
|
6336
|
+
if (process.env.MCP_STORAGE_DIR) {
|
|
6337
|
+
return getStoreFilePath(process.env.MCP_STORAGE_DIR, "oauth");
|
|
6338
|
+
}
|
|
6339
|
+
return DEFAULT_STATE_PATH;
|
|
6340
|
+
}
|
|
6341
|
+
var sharedStateCache = /* @__PURE__ */ new Map();
|
|
6342
|
+
var storageCache = /* @__PURE__ */ new Map();
|
|
6343
|
+
function getSharedState(stateFilePath) {
|
|
6344
|
+
const key = getStateFilePath(stateFilePath);
|
|
6345
|
+
let shared = sharedStateCache.get(key);
|
|
6346
|
+
if (!shared) {
|
|
6347
|
+
shared = {
|
|
6348
|
+
memory: new OAuthMemoryStore(),
|
|
6349
|
+
coordination: new OAuthStorageCoordination()
|
|
6350
|
+
};
|
|
6351
|
+
sharedStateCache.set(key, shared);
|
|
6352
|
+
}
|
|
6353
|
+
return shared;
|
|
6354
|
+
}
|
|
6355
|
+
function resetNodeOAuthStorageCache(stateFilePath) {
|
|
6356
|
+
const key = getStateFilePath(stateFilePath);
|
|
6357
|
+
sharedStateCache.delete(key);
|
|
6358
|
+
storageCache.delete(key);
|
|
6359
|
+
}
|
|
6360
|
+
var NodeOAuthStorage = class extends OAuthStorageBase {
|
|
6361
|
+
/**
|
|
6362
|
+
* @param storagePath - Optional path to state file. Default: ~/.mcp-inspector/storage/oauth.json
|
|
6363
|
+
* @param secretStore - Optional secret store for tokens/client secrets.
|
|
6364
|
+
* Default: the process-wide selected store. Tests inject an in-memory
|
|
6365
|
+
* double here so they never touch the OS keychain.
|
|
6366
|
+
*/
|
|
6367
|
+
constructor(storagePath, secretStore) {
|
|
6368
|
+
const filePath = getStateFilePath(storagePath);
|
|
6369
|
+
const shared = getSharedState(storagePath);
|
|
6370
|
+
super(
|
|
6371
|
+
shared.memory,
|
|
6372
|
+
createFileOAuthPersistBackend({ filePath, secretStore }),
|
|
6373
|
+
shared.coordination
|
|
6374
|
+
);
|
|
6375
|
+
}
|
|
6376
|
+
};
|
|
6377
|
+
|
|
6378
|
+
// ../../core/auth/revocation.ts
|
|
6379
|
+
import { selectClientAuthMethod } from "@modelcontextprotocol/client";
|
|
6380
|
+
import {
|
|
6381
|
+
OAuthClientInformationSchema as OAuthClientInformationSchema2,
|
|
6382
|
+
OAuthTokensSchema as OAuthTokensSchema4
|
|
6383
|
+
} from "@modelcontextprotocol/core";
|
|
6384
|
+
var DEFAULT_REVOCATION_TIMEOUT_MS = 5e3;
|
|
6385
|
+
var MIN_REVOCATION_REQUEST_BUDGET_MS = 5;
|
|
6386
|
+
function selectRevocableToken(tokens) {
|
|
6387
|
+
if (!tokens) return null;
|
|
6388
|
+
if (tokens.refresh_token) {
|
|
6389
|
+
return { token: tokens.refresh_token, tokenTypeHint: "refresh_token" };
|
|
6390
|
+
}
|
|
6391
|
+
if (tokens.access_token) {
|
|
6392
|
+
return { token: tokens.access_token, tokenTypeHint: "access_token" };
|
|
6393
|
+
}
|
|
6394
|
+
return null;
|
|
6395
|
+
}
|
|
6396
|
+
function revocationAuthMethods(metadata) {
|
|
6397
|
+
return metadata.revocation_endpoint_auth_methods_supported ?? [
|
|
6398
|
+
"client_secret_basic"
|
|
6399
|
+
];
|
|
6400
|
+
}
|
|
6401
|
+
function buildRevocationRequest(params) {
|
|
6402
|
+
const body = new URLSearchParams({
|
|
6403
|
+
token: params.token,
|
|
6404
|
+
token_type_hint: params.tokenTypeHint
|
|
6405
|
+
});
|
|
6406
|
+
const headers = {
|
|
6407
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
|
6408
|
+
Accept: "application/json"
|
|
6409
|
+
};
|
|
6410
|
+
const client = params.clientInformation;
|
|
6411
|
+
if (client) {
|
|
6412
|
+
const method = selectClientAuthMethod(client, params.supportedAuthMethods);
|
|
6413
|
+
if (method === "client_secret_basic" && client.client_secret) {
|
|
6414
|
+
const credentials = `${encodeURIComponent(client.client_id)}:${encodeURIComponent(client.client_secret)}`;
|
|
6415
|
+
headers.Authorization = `Basic ${base64Encode(credentials)}`;
|
|
6416
|
+
} else if (method === "client_secret_post" && client.client_secret) {
|
|
6417
|
+
body.set("client_id", client.client_id);
|
|
6418
|
+
body.set("client_secret", client.client_secret);
|
|
6419
|
+
} else {
|
|
6420
|
+
body.set("client_id", client.client_id);
|
|
6421
|
+
}
|
|
6422
|
+
}
|
|
6423
|
+
return {
|
|
6424
|
+
url: params.endpoint,
|
|
6425
|
+
init: { method: "POST", headers, body: body.toString() }
|
|
6426
|
+
};
|
|
6427
|
+
}
|
|
6428
|
+
function base64Encode(value) {
|
|
6429
|
+
if (typeof btoa === "function") return btoa(value);
|
|
6430
|
+
return Buffer.from(value, "utf8").toString("base64");
|
|
6431
|
+
}
|
|
6432
|
+
async function revokeToken(params) {
|
|
6433
|
+
const timeoutMs = Math.max(
|
|
6434
|
+
0,
|
|
5447
6435
|
Math.round(params.timeoutMs ?? DEFAULT_REVOCATION_TIMEOUT_MS)
|
|
5448
6436
|
);
|
|
5449
6437
|
try {
|
|
@@ -5619,7 +6607,7 @@ async function collectGrants(snapshot) {
|
|
|
5619
6607
|
return { grants, failures };
|
|
5620
6608
|
}
|
|
5621
6609
|
async function parseTokens(raw) {
|
|
5622
|
-
return raw === void 0 || raw === null ? void 0 : await
|
|
6610
|
+
return raw === void 0 || raw === null ? void 0 : await OAuthTokensSchema4.partial().parseAsync(raw);
|
|
5623
6611
|
}
|
|
5624
6612
|
async function parseClient(raw) {
|
|
5625
6613
|
return raw === void 0 || raw === null ? void 0 : await OAuthClientInformationSchema2.parseAsync(raw);
|
|
@@ -6122,10 +7110,13 @@ var ADVERTISABLE_EXTENSIONS = [
|
|
|
6122
7110
|
{
|
|
6123
7111
|
key: UI_EXTENSION_KEY,
|
|
6124
7112
|
label: "MCP Apps UI (io.modelcontextprotocol/ui)",
|
|
6125
|
-
// The MCP Apps UI extension
|
|
6126
|
-
//
|
|
6127
|
-
// a
|
|
7113
|
+
// The MCP Apps UI extension, advertised with the App resource MIME type the
|
|
7114
|
+
// Inspector renders — a conforming server checks the `mimeTypes` before
|
|
7115
|
+
// serving a UI resource. Default-on only where Apps can actually be
|
|
7116
|
+
// rendered (the web client); the CLI and TUI cannot, so they leave it off
|
|
7117
|
+
// unless explicitly overridden (#2403).
|
|
6128
7118
|
defaultAdvertised: true,
|
|
7119
|
+
requiresAppRenderer: true,
|
|
6129
7120
|
advertisement: { mimeTypes: [MCP_APP_MIME_TYPE] }
|
|
6130
7121
|
},
|
|
6131
7122
|
{
|
|
@@ -6141,10 +7132,13 @@ var ADVERTISABLE_EXTENSIONS = [
|
|
|
6141
7132
|
defaultAdvertised: true
|
|
6142
7133
|
}
|
|
6143
7134
|
];
|
|
7135
|
+
function isAdvertisedByDefault(ext, rendersApps) {
|
|
7136
|
+
return ext.defaultAdvertised && (!ext.requiresAppRenderer || rendersApps);
|
|
7137
|
+
}
|
|
6144
7138
|
function buildClientExtensions(input) {
|
|
6145
7139
|
const map = {};
|
|
6146
7140
|
for (const ext of ADVERTISABLE_EXTENSIONS) {
|
|
6147
|
-
const advertised = input.advertised?.[ext.key] ?? ext.
|
|
7141
|
+
const advertised = input.advertised?.[ext.key] ?? isAdvertisedByDefault(ext, input.rendersApps === true);
|
|
6148
7142
|
if (advertised) {
|
|
6149
7143
|
map[ext.key] = ext.advertisement ? structuredClone(ext.advertisement) : {};
|
|
6150
7144
|
}
|
|
@@ -7089,6 +8083,14 @@ function exemptMcpEndpoint(getServerUrl, negotiatesEndpoint) {
|
|
|
7089
8083
|
};
|
|
7090
8084
|
}
|
|
7091
8085
|
var REQUEST_DEADLINES = /* @__PURE__ */ new WeakMap();
|
|
8086
|
+
function stampRequestDeadline(init, budget) {
|
|
8087
|
+
if (REQUEST_DEADLINES.has(init)) {
|
|
8088
|
+
throw new Error(
|
|
8089
|
+
"stampRequestDeadline: this RequestInit already carries a deadline \u2014 withOAuthRequestTimeout must build a fresh init per call (#2418)"
|
|
8090
|
+
);
|
|
8091
|
+
}
|
|
8092
|
+
REQUEST_DEADLINES.set(init, budget);
|
|
8093
|
+
}
|
|
7092
8094
|
function requestHeadersOf(input, init) {
|
|
7093
8095
|
if (init?.headers) return new Headers(init.headers);
|
|
7094
8096
|
if (typeof input !== "string" && !(input instanceof URL)) {
|
|
@@ -7146,7 +8148,7 @@ function withOAuthRequestTimeout(fetchFn, timeoutMs = DEFAULT_OAUTH_REQUEST_TIME
|
|
|
7146
8148
|
}
|
|
7147
8149
|
});
|
|
7148
8150
|
const nextInit = { ...init, signal };
|
|
7149
|
-
|
|
8151
|
+
stampRequestDeadline(nextInit, budget);
|
|
7150
8152
|
let settled;
|
|
7151
8153
|
let drainReader;
|
|
7152
8154
|
try {
|
|
@@ -8243,7 +9245,7 @@ async function discoverResourceAsMetadata(resourceAsUrl, fetchFn) {
|
|
|
8243
9245
|
|
|
8244
9246
|
// ../../core/auth/ema/wire.ts
|
|
8245
9247
|
import { discoverAndRequestJwtAuthGrant } from "@modelcontextprotocol/client";
|
|
8246
|
-
import { OAuthTokensSchema as
|
|
9248
|
+
import { OAuthTokensSchema as OAuthTokensSchema5 } from "@modelcontextprotocol/core";
|
|
8247
9249
|
async function exchangeIdJag(params) {
|
|
8248
9250
|
const issuer = normalizeIdpIssuer(params.idp.issuer);
|
|
8249
9251
|
const resource = params.resource.trim();
|
|
@@ -8322,7 +9324,7 @@ async function redeemIdJagForAccessToken(params) {
|
|
|
8322
9324
|
"EMA leg 3 (resource AS JWT bearer grant)"
|
|
8323
9325
|
);
|
|
8324
9326
|
}
|
|
8325
|
-
return
|
|
9327
|
+
return OAuthTokensSchema5.parse(await response.json());
|
|
8326
9328
|
}
|
|
8327
9329
|
|
|
8328
9330
|
// ../../core/auth/ema/emaFlow.ts
|
|
@@ -9547,6 +10549,14 @@ function progressTokenOf(message) {
|
|
|
9547
10549
|
}
|
|
9548
10550
|
return void 0;
|
|
9549
10551
|
}
|
|
10552
|
+
function waitForProgressToken(waits, token) {
|
|
10553
|
+
const exact = waits.get(token);
|
|
10554
|
+
if (exact !== void 0 || typeof token === "number") {
|
|
10555
|
+
return exact;
|
|
10556
|
+
}
|
|
10557
|
+
const numeric = Number(token);
|
|
10558
|
+
return Number.isSafeInteger(numeric) ? waits.get(numeric) : void 0;
|
|
10559
|
+
}
|
|
9550
10560
|
|
|
9551
10561
|
// ../../core/mcp/remote/remoteClientTransport.ts
|
|
9552
10562
|
var DEFAULT_SSE_RESPONSE_TIMEOUT_MS = 6e4;
|
|
@@ -10023,8 +11033,8 @@ var RemoteClientTransport = class {
|
|
|
10023
11033
|
});
|
|
10024
11034
|
}
|
|
10025
11035
|
/** Re-arm a pending request's SSE wait timeout on a matching progress note. */
|
|
10026
|
-
resetSseResponseWait(
|
|
10027
|
-
this.sseResponseWaits
|
|
11036
|
+
resetSseResponseWait(progressToken) {
|
|
11037
|
+
waitForProgressToken(this.sseResponseWaits, progressToken)?.resetTimeout();
|
|
10028
11038
|
}
|
|
10029
11039
|
async postSend(message, options, retryCount) {
|
|
10030
11040
|
const requestId = requestIdForMessage(message);
|
|
@@ -10229,6 +11239,11 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
|
|
|
10229
11239
|
requestTimeout;
|
|
10230
11240
|
defaultMetadata;
|
|
10231
11241
|
serverSettings;
|
|
11242
|
+
// The settings the current transport was built from. `serverSettings` is
|
|
11243
|
+
// replaced live on every settings save (#1444), but transport-level inputs
|
|
11244
|
+
// such as custom headers are fixed when the transport is created, so this
|
|
11245
|
+
// is what the open connection actually sends (#2460).
|
|
11246
|
+
transportSettings;
|
|
10232
11247
|
versionNegotiation;
|
|
10233
11248
|
status = "disconnected";
|
|
10234
11249
|
// True only while an explicit disconnect() owns the teardown. close() can
|
|
@@ -10583,6 +11598,10 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
|
|
|
10583
11598
|
const advertisedExtensions = buildClientExtensions({
|
|
10584
11599
|
enterpriseManaged: options.oauth?.enterpriseManaged ?? false,
|
|
10585
11600
|
advertised: this.advertisedExtensions,
|
|
11601
|
+
// Only a client that can render Apps claims the UI extension by default
|
|
11602
|
+
// (#2403). An app-elicitation renderer is itself a claim to host an App,
|
|
11603
|
+
// so supplying one implies it.
|
|
11604
|
+
rendersApps: options.rendersApps === true || this.appElicitationRenderer !== void 0,
|
|
10586
11605
|
// Read off the built `capabilities.elicitation.form` rather than
|
|
10587
11606
|
// re-deriving from `options.elicit`: the nested MCP Apps `elicitation`
|
|
10588
11607
|
// setting must never be advertised without the core form capability it
|
|
@@ -11008,6 +12027,7 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
|
|
|
11008
12027
|
}
|
|
11009
12028
|
this.baseTransport = null;
|
|
11010
12029
|
this.transport = null;
|
|
12030
|
+
this.transportSettings = void 0;
|
|
11011
12031
|
this.transportHasAuthProvider = false;
|
|
11012
12032
|
}
|
|
11013
12033
|
/**
|
|
@@ -11481,6 +12501,7 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
|
|
|
11481
12501
|
transportOptions
|
|
11482
12502
|
);
|
|
11483
12503
|
this.baseTransport = baseTransport;
|
|
12504
|
+
this.transportSettings = transportOptions.settings;
|
|
11484
12505
|
if (this.directAuthRecovery) {
|
|
11485
12506
|
this.directAuthRecoveryActive = !(baseTransport instanceof RemoteClientTransport);
|
|
11486
12507
|
}
|
|
@@ -11682,6 +12703,7 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
|
|
|
11682
12703
|
}
|
|
11683
12704
|
this.baseTransport = null;
|
|
11684
12705
|
this.transport = null;
|
|
12706
|
+
this.transportSettings = void 0;
|
|
11685
12707
|
this.transportHasAuthProvider = false;
|
|
11686
12708
|
this.clearPendingPeerRequests();
|
|
11687
12709
|
if (this.status !== "disconnected") {
|
|
@@ -12414,6 +13436,13 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
|
|
|
12414
13436
|
getClientCapabilities() {
|
|
12415
13437
|
return this.clientCapabilities;
|
|
12416
13438
|
}
|
|
13439
|
+
/**
|
|
13440
|
+
* Get the client identity (name, version) this client reports to servers —
|
|
13441
|
+
* the caller's `clientIdentity`, or core's neutral fallback without one.
|
|
13442
|
+
*/
|
|
13443
|
+
getClientInfo() {
|
|
13444
|
+
return this.clientInfo;
|
|
13445
|
+
}
|
|
12417
13446
|
/**
|
|
12418
13447
|
* Get server info (name, version)
|
|
12419
13448
|
*/
|
|
@@ -12465,6 +13494,16 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
|
|
|
12465
13494
|
getServerSettings() {
|
|
12466
13495
|
return this.serverSettings;
|
|
12467
13496
|
}
|
|
13497
|
+
/**
|
|
13498
|
+
* The settings the current transport was built from, or `undefined` when no
|
|
13499
|
+
* transport exists. Unlike {@link getServerSettings}, a live settings edit
|
|
13500
|
+
* does not change this: transport-level inputs (custom headers) apply only
|
|
13501
|
+
* when the next transport is created, so comparing the two tells a caller
|
|
13502
|
+
* whether an edit is still waiting on a reconnect (#2460).
|
|
13503
|
+
*/
|
|
13504
|
+
getTransportSettings() {
|
|
13505
|
+
return this.transportSettings;
|
|
13506
|
+
}
|
|
12468
13507
|
/**
|
|
12469
13508
|
* Replace the in-memory per-server settings on a live client. Lets a settings
|
|
12470
13509
|
* edit (e.g. toggling auto-refresh-on-list-changed) take effect on the
|
|
@@ -13986,10 +15025,9 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
|
|
|
13986
15025
|
* removes it before the result gets here (#2373).
|
|
13987
15026
|
*
|
|
13988
15027
|
* Separate from {@link getSkill} because the callers differ: the UIs want the
|
|
13989
|
-
* entry, while the CLI prints the result and must not reshape it.
|
|
13990
|
-
*
|
|
13991
|
-
*
|
|
13992
|
-
* those (Copilot).
|
|
15028
|
+
* entry, while the CLI prints the result and must not reshape it. A modern
|
|
15029
|
+
* result carries `ttlMs` / `cacheScope` (#2404), and unwrapping to the entry
|
|
15030
|
+
* discards exactly those (Copilot).
|
|
13993
15031
|
*/
|
|
13994
15032
|
async getSkillResult(uri, metadata) {
|
|
13995
15033
|
if (!this.client) {
|
|
@@ -14000,11 +15038,12 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
|
|
|
14000
15038
|
uri,
|
|
14001
15039
|
...effectiveMeta ? { _meta: effectiveMeta } : {}
|
|
14002
15040
|
};
|
|
15041
|
+
const resultSchema = this.isModernEra() ? ModernGetSkillEnvelopeSchema : GetSkillEnvelopeSchema;
|
|
14003
15042
|
try {
|
|
14004
15043
|
return await this.invokeMcpClient(
|
|
14005
15044
|
() => this.client.request(
|
|
14006
15045
|
{ method: SKILLS_GET_METHOD, params },
|
|
14007
|
-
|
|
15046
|
+
resultSchema,
|
|
14008
15047
|
this.getRequestOptions(this.progressTokenOf(metadata))
|
|
14009
15048
|
),
|
|
14010
15049
|
{ method: SKILLS_GET_METHOD }
|
|
@@ -15141,18 +16180,83 @@ function parseAddressPart(part) {
|
|
|
15141
16180
|
if (/^[0-9]+$/.test(part)) return parseInt(part, 10);
|
|
15142
16181
|
return NaN;
|
|
15143
16182
|
}
|
|
15144
|
-
function isAllZeroIpv4(value) {
|
|
15145
|
-
const parts = value.split(".");
|
|
15146
|
-
if (parts.length > 4) return false;
|
|
15147
|
-
return parts.every((part) => parseAddressPart(part) === 0);
|
|
16183
|
+
function isAllZeroIpv4(value) {
|
|
16184
|
+
const parts = value.split(".");
|
|
16185
|
+
if (parts.length > 4) return false;
|
|
16186
|
+
return parts.every((part) => parseAddressPart(part) === 0);
|
|
16187
|
+
}
|
|
16188
|
+
function isAllInterfacesHost(host) {
|
|
16189
|
+
const normalized = canonicalizeIpv6(stripBrackets(canonicalUrlHost(host)));
|
|
16190
|
+
return ALL_INTERFACES_LITERALS.has(normalized) || isAllZeroIpv4(normalized);
|
|
16191
|
+
}
|
|
16192
|
+
function isLoopbackHost(host) {
|
|
16193
|
+
const h = canonicalUrlHost(host).replace(/\.$/, "");
|
|
16194
|
+
return h === "localhost" || h === "[::1]" || /^127(\.(25[0-5]|2[0-4]\d|1?\d?\d)){3}$/.test(h);
|
|
16195
|
+
}
|
|
16196
|
+
|
|
16197
|
+
// ../../core/auth/issuerBinding.ts
|
|
16198
|
+
import { AuthorizationServerMismatchError } from "@modelcontextprotocol/client";
|
|
16199
|
+
var MISSING_DISCOVERY_STATE_PHRASE = "discoveryState was not available";
|
|
16200
|
+
function isAuthorizationServerMismatchShape(err) {
|
|
16201
|
+
if (err === null || typeof err !== "object") {
|
|
16202
|
+
return false;
|
|
16203
|
+
}
|
|
16204
|
+
const candidate = err;
|
|
16205
|
+
if (typeof candidate.recordedIssuer !== "string" || typeof candidate.currentIssuer !== "string") {
|
|
16206
|
+
return false;
|
|
16207
|
+
}
|
|
16208
|
+
return AuthorizationServerMismatchError.isInstance(err) || candidate.name === "AuthorizationServerMismatchError";
|
|
16209
|
+
}
|
|
16210
|
+
function isMissingDiscoveryStatePlaceholder(recordedIssuer) {
|
|
16211
|
+
if (recordedIssuer.includes(MISSING_DISCOVERY_STATE_PHRASE)) {
|
|
16212
|
+
return true;
|
|
16213
|
+
}
|
|
16214
|
+
try {
|
|
16215
|
+
const url = new URL(recordedIssuer);
|
|
16216
|
+
return url.protocol !== "http:" && url.protocol !== "https:";
|
|
16217
|
+
} catch {
|
|
16218
|
+
return true;
|
|
16219
|
+
}
|
|
16220
|
+
}
|
|
16221
|
+
function findIssuerBindingFailure(err) {
|
|
16222
|
+
return findIssuerBindingFailureDeep(err, /* @__PURE__ */ new Set());
|
|
15148
16223
|
}
|
|
15149
|
-
function
|
|
15150
|
-
|
|
15151
|
-
|
|
16224
|
+
function findIssuerBindingFailureDeep(err, seen) {
|
|
16225
|
+
if (err === null || typeof err !== "object" || seen.has(err)) {
|
|
16226
|
+
return void 0;
|
|
16227
|
+
}
|
|
16228
|
+
seen.add(err);
|
|
16229
|
+
if (isAuthorizationServerMismatchShape(err)) {
|
|
16230
|
+
return classifyAuthorizationServerMismatch(err);
|
|
16231
|
+
}
|
|
16232
|
+
const nested = findIssuerBindingFailureDeep(
|
|
16233
|
+
err.cause,
|
|
16234
|
+
seen
|
|
16235
|
+
);
|
|
16236
|
+
if (nested) {
|
|
16237
|
+
return nested;
|
|
16238
|
+
}
|
|
16239
|
+
const data = err.data;
|
|
16240
|
+
if (data !== null && typeof data === "object") {
|
|
16241
|
+
return findIssuerBindingFailureDeep(
|
|
16242
|
+
data.cause,
|
|
16243
|
+
seen
|
|
16244
|
+
);
|
|
16245
|
+
}
|
|
16246
|
+
return void 0;
|
|
15152
16247
|
}
|
|
15153
|
-
function
|
|
15154
|
-
|
|
15155
|
-
|
|
16248
|
+
function classifyAuthorizationServerMismatch(err) {
|
|
16249
|
+
if (isMissingDiscoveryStatePlaceholder(err.recordedIssuer)) {
|
|
16250
|
+
return {
|
|
16251
|
+
kind: "lost_authorization_state",
|
|
16252
|
+
currentIssuer: err.currentIssuer
|
|
16253
|
+
};
|
|
16254
|
+
}
|
|
16255
|
+
return {
|
|
16256
|
+
kind: "issuer_mismatch",
|
|
16257
|
+
recordedIssuer: err.recordedIssuer,
|
|
16258
|
+
currentIssuer: err.currentIssuer
|
|
16259
|
+
};
|
|
15156
16260
|
}
|
|
15157
16261
|
|
|
15158
16262
|
// src/error-handler.ts
|
|
@@ -15186,7 +16290,20 @@ var EXIT_CODES = {
|
|
|
15186
16290
|
* job that wants to tolerate oversized catalogs can allow 8 and still fail
|
|
15187
16291
|
* on 7.
|
|
15188
16292
|
*/
|
|
15189
|
-
SKILL_INCOMPLETE: 8
|
|
16293
|
+
SKILL_INCOMPLETE: 8,
|
|
16294
|
+
/**
|
|
16295
|
+
* `--verify --require-digests` found a skill whose `resources` is
|
|
16296
|
+
* `"dynamic"`, so no digest was advertised and nothing was hashed (#2405).
|
|
16297
|
+
*
|
|
16298
|
+
* Only ever produced under `--require-digests`: `"dynamic"` is a conforming
|
|
16299
|
+
* wire form, and SEP-2640 leaves declining such skills to the host ("Hosts
|
|
16300
|
+
* MAY decline to load such skills"). The flag is how a CI job standing in for
|
|
16301
|
+
* a host that declines them says so; without it the run exits 0 and reports
|
|
16302
|
+
* `outcome: "unverifiable"`. Its own code, like 8, so a job can tell "no
|
|
16303
|
+
* digests to check" apart from "a digest was wrong" and "the walk was cut
|
|
16304
|
+
* short".
|
|
16305
|
+
*/
|
|
16306
|
+
SKILL_UNVERIFIABLE: 9
|
|
15190
16307
|
};
|
|
15191
16308
|
var CliExitCodeError = class extends Error {
|
|
15192
16309
|
constructor(exitCode, message, envelope) {
|
|
@@ -15221,7 +16338,44 @@ function statusOf(error) {
|
|
|
15221
16338
|
return void 0;
|
|
15222
16339
|
}
|
|
15223
16340
|
var UNREACHABLE_PATTERN = /ENOTFOUND|ECONNREFUSED|ECONNRESET|EAI_AGAIN|ETIMEDOUT|fetch failed|getaddrinfo|connect(?:ion)? timed out|aborted/i;
|
|
16341
|
+
var EMBEDDED_URL_PATTERN = /\bhttps?:\/\/(?:(?!https?:\/\/)[^\s"<>])+/gi;
|
|
16342
|
+
var TRAILING_PUNCTUATION = /* @__PURE__ */ new Set([
|
|
16343
|
+
".",
|
|
16344
|
+
",",
|
|
16345
|
+
";",
|
|
16346
|
+
":",
|
|
16347
|
+
"!",
|
|
16348
|
+
"?",
|
|
16349
|
+
")",
|
|
16350
|
+
"]",
|
|
16351
|
+
"'"
|
|
16352
|
+
]);
|
|
16353
|
+
function trailingPunctuationStart(match) {
|
|
16354
|
+
let end = match.length;
|
|
16355
|
+
while (end > 0 && TRAILING_PUNCTUATION.has(match.charAt(end - 1))) end--;
|
|
16356
|
+
return end;
|
|
16357
|
+
}
|
|
16358
|
+
function redactUrlsInText(text) {
|
|
16359
|
+
return text.replace(EMBEDDED_URL_PATTERN, (match) => {
|
|
16360
|
+
const end = trailingPunctuationStart(match);
|
|
16361
|
+
return redactUrlQuery(match.slice(0, end)) + match.slice(end);
|
|
16362
|
+
});
|
|
16363
|
+
}
|
|
16364
|
+
function redactEnvelope(envelope) {
|
|
16365
|
+
return {
|
|
16366
|
+
...envelope,
|
|
16367
|
+
message: redactUrlsInText(envelope.message),
|
|
16368
|
+
...envelope.cause !== void 0 && {
|
|
16369
|
+
cause: redactUrlsInText(envelope.cause)
|
|
16370
|
+
},
|
|
16371
|
+
...envelope.url !== void 0 && { url: redactUrlQuery(envelope.url) }
|
|
16372
|
+
};
|
|
16373
|
+
}
|
|
15224
16374
|
function classifyError(error, context) {
|
|
16375
|
+
const { exitCode, envelope } = classifyUnredacted(error, context);
|
|
16376
|
+
return { exitCode, envelope: redactEnvelope(envelope) };
|
|
16377
|
+
}
|
|
16378
|
+
function classifyUnredacted(error, context) {
|
|
15225
16379
|
const message = error instanceof Error ? error.message : typeof error === "string" ? error : "Unknown error";
|
|
15226
16380
|
const cause = causeOf(error);
|
|
15227
16381
|
const status = statusOf(error);
|
|
@@ -15242,9 +16396,29 @@ function classifyError(error, context) {
|
|
|
15242
16396
|
}
|
|
15243
16397
|
};
|
|
15244
16398
|
}
|
|
15245
|
-
if (
|
|
15246
|
-
|
|
15247
|
-
|
|
16399
|
+
if (error instanceof SecretStoreUnavailableError) {
|
|
16400
|
+
return {
|
|
16401
|
+
exitCode: EXIT_CODES.USAGE,
|
|
16402
|
+
envelope: {
|
|
16403
|
+
code: "store_unavailable",
|
|
16404
|
+
message,
|
|
16405
|
+
...cause !== void 0 && { cause },
|
|
16406
|
+
...url !== void 0 && { url }
|
|
16407
|
+
}
|
|
16408
|
+
};
|
|
16409
|
+
}
|
|
16410
|
+
if (error instanceof OAuthStateFileUnrecognizedError) {
|
|
16411
|
+
return {
|
|
16412
|
+
exitCode: EXIT_CODES.USAGE,
|
|
16413
|
+
envelope: {
|
|
16414
|
+
code: "oauth_state_unrecognized",
|
|
16415
|
+
message,
|
|
16416
|
+
...cause !== void 0 && { cause },
|
|
16417
|
+
...url !== void 0 && { url }
|
|
16418
|
+
}
|
|
16419
|
+
};
|
|
16420
|
+
}
|
|
16421
|
+
if (status === 401 || status === 403 || isUnauthorizedError(error)) {
|
|
15248
16422
|
return {
|
|
15249
16423
|
exitCode: EXIT_CODES.AUTH_REQUIRED,
|
|
15250
16424
|
envelope: {
|
|
@@ -15686,6 +16860,9 @@ async function emitResult(result, appInfo, args) {
|
|
|
15686
16860
|
}
|
|
15687
16861
|
|
|
15688
16862
|
// src/handlers/consume-outcome.ts
|
|
16863
|
+
function isBrokenPipe(err) {
|
|
16864
|
+
return err instanceof Error && err.code === "EPIPE";
|
|
16865
|
+
}
|
|
15689
16866
|
async function consumeMethodOutcome(outcome, args) {
|
|
15690
16867
|
if (outcome.kind === "result") {
|
|
15691
16868
|
await emitResult(outcome.result, outcome.appInfo, args);
|
|
@@ -15701,23 +16878,37 @@ async function consumeMethodOutcome(outcome, args) {
|
|
|
15701
16878
|
throw new CliExitCodeError(outcome.exitCode, outcome.summary ?? "", {
|
|
15702
16879
|
// The envelope's `code` follows the exit code, so a caller reading one
|
|
15703
16880
|
// never has to reconcile it against the other.
|
|
15704
|
-
code: outcome.exitCode === EXIT_CODES.SKILL_INCOMPLETE ? "skills_incomplete" : "skills_nonconformant"
|
|
16881
|
+
code: outcome.exitCode === EXIT_CODES.SKILL_INCOMPLETE ? "skills_incomplete" : outcome.exitCode === EXIT_CODES.SKILL_UNVERIFIABLE ? "skills_unverifiable" : "skills_nonconformant"
|
|
15705
16882
|
});
|
|
15706
16883
|
}
|
|
15707
16884
|
return;
|
|
15708
16885
|
}
|
|
15709
|
-
await new Promise((resolve7) => {
|
|
15710
|
-
|
|
15711
|
-
|
|
15712
|
-
});
|
|
15713
|
-
const onSignal = () => {
|
|
15714
|
-
stop();
|
|
16886
|
+
await new Promise((resolve7, reject) => {
|
|
16887
|
+
let stop;
|
|
16888
|
+
const detach = () => {
|
|
15715
16889
|
process.off("SIGINT", onSignal);
|
|
15716
16890
|
process.off("SIGTERM", onSignal);
|
|
15717
|
-
|
|
16891
|
+
process.stdout.off("error", onStdoutError);
|
|
15718
16892
|
};
|
|
16893
|
+
const finish = (err) => {
|
|
16894
|
+
detach();
|
|
16895
|
+
stop?.();
|
|
16896
|
+
if (err === void 0) resolve7();
|
|
16897
|
+
else reject(err);
|
|
16898
|
+
};
|
|
16899
|
+
const onSignal = () => finish();
|
|
16900
|
+
const onStdoutError = (err) => finish(isBrokenPipe(err) ? void 0 : err);
|
|
16901
|
+
process.stdout.on("error", onStdoutError);
|
|
15719
16902
|
process.on("SIGINT", onSignal);
|
|
15720
16903
|
process.on("SIGTERM", onSignal);
|
|
16904
|
+
try {
|
|
16905
|
+
stop = outcome.start((obj) => {
|
|
16906
|
+
void awaitableLog(JSON.stringify(obj) + "\n");
|
|
16907
|
+
});
|
|
16908
|
+
} catch (err) {
|
|
16909
|
+
detach();
|
|
16910
|
+
reject(err);
|
|
16911
|
+
}
|
|
15721
16912
|
});
|
|
15722
16913
|
}
|
|
15723
16914
|
|
|
@@ -16538,24 +17729,6 @@ async function collectAppInfo(client, tool, metadata) {
|
|
|
16538
17729
|
}
|
|
16539
17730
|
}
|
|
16540
17731
|
|
|
16541
|
-
// src/handlers/skills-verify.ts
|
|
16542
|
-
function summarizeSkillVerification(reports) {
|
|
16543
|
-
const failed = reports.filter((report) => report.outcome === "failed").length;
|
|
16544
|
-
const incomplete = reports.filter(
|
|
16545
|
-
(report) => report.outcome === "incomplete"
|
|
16546
|
-
).length;
|
|
16547
|
-
const files = reports.reduce((sum, report) => sum + report.files.length, 0);
|
|
16548
|
-
const mismatched = reports.reduce(
|
|
16549
|
-
(sum, report) => sum + report.files.filter((file) => file.status === "mismatch").length,
|
|
16550
|
-
0
|
|
16551
|
-
);
|
|
16552
|
-
const skillWord = reports.length === 1 ? "skill" : "skills";
|
|
16553
|
-
const fileWord = files === 1 ? "file" : "files";
|
|
16554
|
-
const incompleteClause = incomplete === 0 ? "" : ` ${incomplete} of ${reports.length} ${skillWord} could not be fully checked: the read bounds stopped the walk.`;
|
|
16555
|
-
const headline = failed === 0 ? incomplete === 0 ? `Verified ${reports.length} ${skillWord} and ${files} ${fileWord}: no conformance errors.` : `Checked ${reports.length} ${skillWord} and ${files} ${fileWord}: no conformance errors in what was read.` : `${failed} of ${reports.length} ${skillWord} failed verification (${mismatched} digest/size mismatch across ${files} ${fileWord}).`;
|
|
16556
|
-
return `${headline}${incompleteClause}`;
|
|
16557
|
-
}
|
|
16558
|
-
|
|
16559
17732
|
// ../../core/mcp/skillsVerification.ts
|
|
16560
17733
|
function reasonOf(err) {
|
|
16561
17734
|
return err instanceof Error ? err.message : String(err);
|
|
@@ -16748,7 +17921,11 @@ async function verifySkills(client, entries, metadata) {
|
|
|
16748
17921
|
files,
|
|
16749
17922
|
...incomplete ? { incomplete } : {},
|
|
16750
17923
|
ok: !hasError && !fileFailed,
|
|
16751
|
-
|
|
17924
|
+
// Precedence runs from the loudest verdict down: a broken MUST outranks
|
|
17925
|
+
// an unfinished walk, and an unfinished walk outranks a skill that had
|
|
17926
|
+
// nothing to hash — a `"dynamic"` skill past the run's catalog budget
|
|
17927
|
+
// was not read at all, which `incomplete` already says more precisely.
|
|
17928
|
+
outcome: hasError || fileFailed ? "failed" : incomplete !== void 0 ? "incomplete" : entry.resources === DYNAMIC_RESOURCES ? "unverifiable" : "verified"
|
|
16752
17929
|
});
|
|
16753
17930
|
}
|
|
16754
17931
|
return reports;
|
|
@@ -16759,6 +17936,38 @@ function allSkillsVerified(reports) {
|
|
|
16759
17936
|
function anySkillFailed(reports) {
|
|
16760
17937
|
return reports.some((report) => report.outcome === "failed");
|
|
16761
17938
|
}
|
|
17939
|
+
function anySkillUnverifiable(reports) {
|
|
17940
|
+
return reports.some((report) => report.outcome === "unverifiable");
|
|
17941
|
+
}
|
|
17942
|
+
|
|
17943
|
+
// src/handlers/skills-verify.ts
|
|
17944
|
+
function skillVerificationExitCode(reports, requireDigests) {
|
|
17945
|
+
if (allSkillsVerified(reports)) return void 0;
|
|
17946
|
+
if (anySkillFailed(reports)) return EXIT_CODES.SKILL_NONCONFORMANT;
|
|
17947
|
+
if (reports.some((report) => report.outcome === "incomplete"))
|
|
17948
|
+
return EXIT_CODES.SKILL_INCOMPLETE;
|
|
17949
|
+
return requireDigests && anySkillUnverifiable(reports) ? EXIT_CODES.SKILL_UNVERIFIABLE : void 0;
|
|
17950
|
+
}
|
|
17951
|
+
function summarizeSkillVerification(reports) {
|
|
17952
|
+
const failed = reports.filter((report) => report.outcome === "failed").length;
|
|
17953
|
+
const incomplete = reports.filter(
|
|
17954
|
+
(report) => report.outcome === "incomplete"
|
|
17955
|
+
).length;
|
|
17956
|
+
const unverifiable = reports.filter(
|
|
17957
|
+
(report) => report.outcome === "unverifiable"
|
|
17958
|
+
).length;
|
|
17959
|
+
const files = reports.reduce((sum, report) => sum + report.files.length, 0);
|
|
17960
|
+
const mismatched = reports.reduce(
|
|
17961
|
+
(sum, report) => sum + report.files.filter((file) => file.status === "mismatch").length,
|
|
17962
|
+
0
|
|
17963
|
+
);
|
|
17964
|
+
const skillWord = reports.length === 1 ? "skill" : "skills";
|
|
17965
|
+
const fileWord = files === 1 ? "file" : "files";
|
|
17966
|
+
const incompleteClause = incomplete === 0 ? "" : ` ${incomplete} of ${reports.length} ${skillWord} could not be fully checked: the read bounds stopped the walk.`;
|
|
17967
|
+
const unverifiableClause = unverifiable === 0 ? "" : ` ${unverifiable} of ${reports.length} ${skillWord} advertised no digests (resources: "dynamic"), so ${unverifiable === 1 ? "its" : "their"} integrity was not checked.`;
|
|
17968
|
+
const headline = failed !== 0 ? `${failed} of ${reports.length} ${skillWord} failed verification (${mismatched} digest/size mismatch across ${files} ${fileWord}).` : incomplete !== 0 ? `Checked ${reports.length} ${skillWord} and ${files} ${fileWord}: no conformance errors in what was read.` : unverifiable !== 0 ? `Checked ${reports.length} ${skillWord} and ${files} ${fileWord}: no conformance errors.` : `Verified ${reports.length} ${skillWord} and ${files} ${fileWord}: no conformance errors.`;
|
|
17969
|
+
return `${headline}${incompleteClause}${unverifiableClause}`;
|
|
17970
|
+
}
|
|
16762
17971
|
|
|
16763
17972
|
// src/handlers/run-method.ts
|
|
16764
17973
|
function assertSkillsSupported(inspectorClient, method) {
|
|
@@ -17009,13 +18218,10 @@ async function runMethod(inspectorClient, args) {
|
|
|
17009
18218
|
kind: "ndjson",
|
|
17010
18219
|
lines: reports,
|
|
17011
18220
|
summary: summarizeSkillVerification(reports),
|
|
17012
|
-
|
|
17013
|
-
|
|
17014
|
-
|
|
17015
|
-
|
|
17016
|
-
...allSkillsVerified(reports) ? {} : {
|
|
17017
|
-
exitCode: anySkillFailed(reports) ? EXIT_CODES.SKILL_NONCONFORMANT : EXIT_CODES.SKILL_INCOMPLETE
|
|
17018
|
-
}
|
|
18221
|
+
exitCode: skillVerificationExitCode(
|
|
18222
|
+
reports,
|
|
18223
|
+
args.requireDigests === true
|
|
18224
|
+
)
|
|
17019
18225
|
};
|
|
17020
18226
|
}
|
|
17021
18227
|
result = { skills };
|
|
@@ -17041,13 +18247,10 @@ async function runMethod(inspectorClient, args) {
|
|
|
17041
18247
|
kind: "ndjson",
|
|
17042
18248
|
lines: reports,
|
|
17043
18249
|
summary: summarizeSkillVerification(reports),
|
|
17044
|
-
|
|
17045
|
-
|
|
17046
|
-
|
|
17047
|
-
|
|
17048
|
-
...allSkillsVerified(reports) ? {} : {
|
|
17049
|
-
exitCode: anySkillFailed(reports) ? EXIT_CODES.SKILL_NONCONFORMANT : EXIT_CODES.SKILL_INCOMPLETE
|
|
17050
|
-
}
|
|
18250
|
+
exitCode: skillVerificationExitCode(
|
|
18251
|
+
reports,
|
|
18252
|
+
args.requireDigests === true
|
|
18253
|
+
)
|
|
17051
18254
|
};
|
|
17052
18255
|
}
|
|
17053
18256
|
result = envelope;
|
|
@@ -17129,71 +18332,6 @@ import {
|
|
|
17129
18332
|
discoverAuthorizationServerMetadata as discoverAuthorizationServerMetadata4
|
|
17130
18333
|
} from "@modelcontextprotocol/client";
|
|
17131
18334
|
|
|
17132
|
-
// ../../core/auth/issuerBinding.ts
|
|
17133
|
-
import { AuthorizationServerMismatchError } from "@modelcontextprotocol/client";
|
|
17134
|
-
var MISSING_DISCOVERY_STATE_PHRASE = "discoveryState was not available";
|
|
17135
|
-
function isAuthorizationServerMismatchShape(err) {
|
|
17136
|
-
if (err === null || typeof err !== "object") {
|
|
17137
|
-
return false;
|
|
17138
|
-
}
|
|
17139
|
-
const candidate = err;
|
|
17140
|
-
if (typeof candidate.recordedIssuer !== "string" || typeof candidate.currentIssuer !== "string") {
|
|
17141
|
-
return false;
|
|
17142
|
-
}
|
|
17143
|
-
return AuthorizationServerMismatchError.isInstance(err) || candidate.name === "AuthorizationServerMismatchError";
|
|
17144
|
-
}
|
|
17145
|
-
function isMissingDiscoveryStatePlaceholder(recordedIssuer) {
|
|
17146
|
-
if (recordedIssuer.includes(MISSING_DISCOVERY_STATE_PHRASE)) {
|
|
17147
|
-
return true;
|
|
17148
|
-
}
|
|
17149
|
-
try {
|
|
17150
|
-
const url = new URL(recordedIssuer);
|
|
17151
|
-
return url.protocol !== "http:" && url.protocol !== "https:";
|
|
17152
|
-
} catch {
|
|
17153
|
-
return true;
|
|
17154
|
-
}
|
|
17155
|
-
}
|
|
17156
|
-
function findIssuerBindingFailure(err) {
|
|
17157
|
-
return findIssuerBindingFailureDeep(err, /* @__PURE__ */ new Set());
|
|
17158
|
-
}
|
|
17159
|
-
function findIssuerBindingFailureDeep(err, seen) {
|
|
17160
|
-
if (err === null || typeof err !== "object" || seen.has(err)) {
|
|
17161
|
-
return void 0;
|
|
17162
|
-
}
|
|
17163
|
-
seen.add(err);
|
|
17164
|
-
if (isAuthorizationServerMismatchShape(err)) {
|
|
17165
|
-
return classifyAuthorizationServerMismatch(err);
|
|
17166
|
-
}
|
|
17167
|
-
const nested = findIssuerBindingFailureDeep(
|
|
17168
|
-
err.cause,
|
|
17169
|
-
seen
|
|
17170
|
-
);
|
|
17171
|
-
if (nested) {
|
|
17172
|
-
return nested;
|
|
17173
|
-
}
|
|
17174
|
-
const data = err.data;
|
|
17175
|
-
if (data !== null && typeof data === "object") {
|
|
17176
|
-
return findIssuerBindingFailureDeep(
|
|
17177
|
-
data.cause,
|
|
17178
|
-
seen
|
|
17179
|
-
);
|
|
17180
|
-
}
|
|
17181
|
-
return void 0;
|
|
17182
|
-
}
|
|
17183
|
-
function classifyAuthorizationServerMismatch(err) {
|
|
17184
|
-
if (isMissingDiscoveryStatePlaceholder(err.recordedIssuer)) {
|
|
17185
|
-
return {
|
|
17186
|
-
kind: "lost_authorization_state",
|
|
17187
|
-
currentIssuer: err.currentIssuer
|
|
17188
|
-
};
|
|
17189
|
-
}
|
|
17190
|
-
return {
|
|
17191
|
-
kind: "issuer_mismatch",
|
|
17192
|
-
recordedIssuer: err.recordedIssuer,
|
|
17193
|
-
currentIssuer: err.currentIssuer
|
|
17194
|
-
};
|
|
17195
|
-
}
|
|
17196
|
-
|
|
17197
18335
|
// ../../core/auth/node/oauth-callback-server.ts
|
|
17198
18336
|
import { createServer } from "http";
|
|
17199
18337
|
var DEFAULT_HOSTNAME = "127.0.0.1";
|
|
@@ -17489,8 +18627,28 @@ async function runRunnerInteractiveOAuth(options) {
|
|
|
17489
18627
|
|
|
17490
18628
|
// src/open-url.ts
|
|
17491
18629
|
import open from "open";
|
|
17492
|
-
|
|
17493
|
-
|
|
18630
|
+
var OPEN_URL_TIMEOUT_MS = 5e3;
|
|
18631
|
+
function launched(child) {
|
|
18632
|
+
return new Promise((resolve7, reject) => {
|
|
18633
|
+
child.on("error", reject);
|
|
18634
|
+
child.once("spawn", () => resolve7());
|
|
18635
|
+
});
|
|
18636
|
+
}
|
|
18637
|
+
async function openUrl(url, timeoutMs = OPEN_URL_TIMEOUT_MS) {
|
|
18638
|
+
const href = typeof url === "string" ? url : url.href;
|
|
18639
|
+
let timer;
|
|
18640
|
+
const timeout = new Promise((_, reject) => {
|
|
18641
|
+
timer = setTimeout(
|
|
18642
|
+
() => reject(new Error(`browser did not open within ${timeoutMs / 1e3}s`)),
|
|
18643
|
+
timeoutMs
|
|
18644
|
+
);
|
|
18645
|
+
});
|
|
18646
|
+
try {
|
|
18647
|
+
const opened = Promise.resolve().then(() => open(href)).then(launched);
|
|
18648
|
+
await Promise.race([opened, timeout]);
|
|
18649
|
+
} finally {
|
|
18650
|
+
clearTimeout(timer);
|
|
18651
|
+
}
|
|
17494
18652
|
}
|
|
17495
18653
|
|
|
17496
18654
|
// src/style.ts
|
|
@@ -17556,7 +18714,12 @@ function createCliOAuthNavigation(options = {}) {
|
|
|
17556
18714
|
if (!forced && !tty) return;
|
|
17557
18715
|
try {
|
|
17558
18716
|
await (options.openBrowser ?? openUrl)(href);
|
|
17559
|
-
} catch {
|
|
18717
|
+
} catch (error) {
|
|
18718
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
18719
|
+
write(
|
|
18720
|
+
`Could not open a browser automatically (${reason}). Open the URL above manually.
|
|
18721
|
+
`
|
|
18722
|
+
);
|
|
17560
18723
|
}
|
|
17561
18724
|
});
|
|
17562
18725
|
}
|
|
@@ -17699,6 +18862,23 @@ function mergeSecretsIntoClientConfig(config, secrets) {
|
|
|
17699
18862
|
}
|
|
17700
18863
|
|
|
17701
18864
|
// ../../core/client/node-persistence.ts
|
|
18865
|
+
async function withClientConfigLock(filePath, action, body) {
|
|
18866
|
+
let entered = false;
|
|
18867
|
+
try {
|
|
18868
|
+
return await withSecretFileLock(filePath, async () => {
|
|
18869
|
+
entered = true;
|
|
18870
|
+
return body();
|
|
18871
|
+
});
|
|
18872
|
+
} catch (error) {
|
|
18873
|
+
if (!entered && error instanceof SecretFileLockHeldError) {
|
|
18874
|
+
throw new SecretFileLockHeldError(
|
|
18875
|
+
`Could not ${action} the client configuration: the file at ${filePath} is locked by another Inspector process and did not become available.`,
|
|
18876
|
+
{ cause: error }
|
|
18877
|
+
);
|
|
18878
|
+
}
|
|
18879
|
+
throw error;
|
|
18880
|
+
}
|
|
18881
|
+
}
|
|
17702
18882
|
async function readIdpSecretFromKeychain(secretStore) {
|
|
17703
18883
|
const secret = await secretStore.get(
|
|
17704
18884
|
CLIENT_KEYCHAIN_ID,
|
|
@@ -17708,29 +18888,39 @@ async function readIdpSecretFromKeychain(secretStore) {
|
|
|
17708
18888
|
return { [SECRET_FIELD_IDP_CLIENT_SECRET]: secret };
|
|
17709
18889
|
}
|
|
17710
18890
|
async function migrateClientPlaintextSecret(filePath, config, secretStore) {
|
|
17711
|
-
const { stripped, secrets } = extractSecretsFromClientConfig(config);
|
|
17712
|
-
const value = secrets[SECRET_FIELD_IDP_CLIENT_SECRET];
|
|
17713
|
-
if (!value) return config;
|
|
17714
18891
|
try {
|
|
17715
|
-
|
|
17716
|
-
|
|
17717
|
-
|
|
17718
|
-
|
|
17719
|
-
|
|
17720
|
-
|
|
17721
|
-
|
|
17722
|
-
|
|
17723
|
-
|
|
17724
|
-
|
|
17725
|
-
|
|
17726
|
-
|
|
17727
|
-
|
|
17728
|
-
|
|
17729
|
-
|
|
18892
|
+
return await withClientConfigLock(filePath, "migrate", async () => {
|
|
18893
|
+
const raw = await readStoreFile(filePath);
|
|
18894
|
+
if (raw === null) return {};
|
|
18895
|
+
const fresh = parseClientConfig(parseStore(raw));
|
|
18896
|
+
const { stripped, secrets } = extractSecretsFromClientConfig(fresh);
|
|
18897
|
+
const value = secrets[SECRET_FIELD_IDP_CLIENT_SECRET];
|
|
18898
|
+
if (!value) return fresh;
|
|
18899
|
+
try {
|
|
18900
|
+
const existing = await secretStoreGetStrict(
|
|
18901
|
+
secretStore,
|
|
18902
|
+
CLIENT_KEYCHAIN_ID,
|
|
18903
|
+
SECRET_FIELD_IDP_CLIENT_SECRET
|
|
18904
|
+
);
|
|
18905
|
+
if (existing === null) {
|
|
18906
|
+
await secretStore.set(
|
|
18907
|
+
CLIENT_KEYCHAIN_ID,
|
|
18908
|
+
SECRET_FIELD_IDP_CLIENT_SECRET,
|
|
18909
|
+
value
|
|
18910
|
+
);
|
|
18911
|
+
}
|
|
18912
|
+
if (!await secretStoreIsDurable(secretStore)) return fresh;
|
|
18913
|
+
await writeStoreFile(filePath, serializeStore(stripped));
|
|
18914
|
+
return stripped;
|
|
18915
|
+
} catch (err) {
|
|
18916
|
+
if (err instanceof SecretStoreUnavailableError) {
|
|
18917
|
+
return fresh;
|
|
18918
|
+
}
|
|
18919
|
+
throw err;
|
|
18920
|
+
}
|
|
18921
|
+
});
|
|
17730
18922
|
} catch (err) {
|
|
17731
|
-
if (err instanceof
|
|
17732
|
-
return config;
|
|
17733
|
-
}
|
|
18923
|
+
if (err instanceof SecretFileLockHeldError) return config;
|
|
17734
18924
|
throw err;
|
|
17735
18925
|
}
|
|
17736
18926
|
}
|
|
@@ -18193,6 +19383,13 @@ async function callMethod(serverConfig, serverSettings, args, clientConfig, cliA
|
|
|
18193
19383
|
...serverSettings?.protocolEra && {
|
|
18194
19384
|
versionNegotiation: eraToVersionNegotiation(serverSettings.protocolEra)
|
|
18195
19385
|
},
|
|
19386
|
+
// The CLI cannot render an MCP App, so it does not advertise the UI
|
|
19387
|
+
// extension by default (#2403). `--advertise-apps` claims it explicitly,
|
|
19388
|
+
// for a server that only exposes its App tools to a client that does —
|
|
19389
|
+
// which is what an `--app-info` probe against such a server needs.
|
|
19390
|
+
...args.advertiseApps && {
|
|
19391
|
+
advertisedExtensions: { [UI_EXTENSION_KEY]: true }
|
|
19392
|
+
},
|
|
18196
19393
|
...clientAuthOptions
|
|
18197
19394
|
});
|
|
18198
19395
|
try {
|
|
@@ -18226,14 +19423,8 @@ function normalizeServerUrl2(serverUrl) {
|
|
|
18226
19423
|
}
|
|
18227
19424
|
}
|
|
18228
19425
|
async function readOAuthSnapshot(statePath) {
|
|
18229
|
-
const
|
|
18230
|
-
|
|
18231
|
-
const text = await readFile2(statePath, "utf8");
|
|
18232
|
-
const snapshot = parseOAuthPersistBlob(text);
|
|
18233
|
-
if (snapshot) return snapshot;
|
|
18234
|
-
} catch {
|
|
18235
|
-
}
|
|
18236
|
-
return { servers: {}, idpSessions: {} };
|
|
19426
|
+
const snapshot = await readOAuthStore(statePath);
|
|
19427
|
+
return snapshot ?? { servers: {}, idpSessions: {} };
|
|
18237
19428
|
}
|
|
18238
19429
|
async function readOAuthServers(statePath) {
|
|
18239
19430
|
return (await readOAuthSnapshot(statePath)).servers;
|
|
@@ -18308,17 +19499,44 @@ async function refreshStoredAuthToken(serverUrl, statePath, deps = {}) {
|
|
|
18308
19499
|
);
|
|
18309
19500
|
}
|
|
18310
19501
|
servers[found.key] = { ...found.state, tokens };
|
|
18311
|
-
await
|
|
19502
|
+
await writeOAuthSections(statePath, snapshot, { servers: [found.key] });
|
|
18312
19503
|
return tokens.access_token;
|
|
18313
19504
|
}
|
|
18314
|
-
|
|
19505
|
+
var DEADLINE_ELAPSED = /* @__PURE__ */ Symbol("deadline-elapsed");
|
|
19506
|
+
async function raceDeadline(promise, deadline) {
|
|
19507
|
+
promise.catch(() => {
|
|
19508
|
+
});
|
|
19509
|
+
const remaining = deadline - Date.now();
|
|
19510
|
+
if (remaining <= 0) return DEADLINE_ELAPSED;
|
|
19511
|
+
let timer;
|
|
19512
|
+
const elapsed = new Promise((resolve7) => {
|
|
19513
|
+
timer = setTimeout(() => resolve7(DEADLINE_ELAPSED), remaining);
|
|
19514
|
+
});
|
|
19515
|
+
try {
|
|
19516
|
+
return await Promise.race([promise, elapsed]);
|
|
19517
|
+
} finally {
|
|
19518
|
+
clearTimeout(timer);
|
|
19519
|
+
}
|
|
19520
|
+
}
|
|
19521
|
+
async function waitForStoredToken(serverUrl, statePath, timeoutSec, readServers = readOAuthServers) {
|
|
18315
19522
|
const key = normalizeServerUrl2(serverUrl);
|
|
18316
19523
|
const deadline = Date.now() + timeoutSec * 1e3;
|
|
19524
|
+
let servers = {};
|
|
19525
|
+
let lastError;
|
|
18317
19526
|
for (; ; ) {
|
|
18318
|
-
|
|
19527
|
+
try {
|
|
19528
|
+
const read = await raceDeadline(readServers(statePath), deadline);
|
|
19529
|
+
if (read !== DEADLINE_ELAPSED) {
|
|
19530
|
+
servers = read;
|
|
19531
|
+
lastError = void 0;
|
|
19532
|
+
}
|
|
19533
|
+
} catch (error) {
|
|
19534
|
+
if (!(error instanceof SecretFileLockHeldError)) lastError = error;
|
|
19535
|
+
}
|
|
18319
19536
|
const token = findStoredToken(servers, serverUrl);
|
|
18320
19537
|
if (token) return token;
|
|
18321
19538
|
if (Date.now() >= deadline) {
|
|
19539
|
+
if (lastError !== void 0) throw lastError;
|
|
18322
19540
|
const stored = Object.keys(servers);
|
|
18323
19541
|
throw new CliExitCodeError(
|
|
18324
19542
|
EXIT_CODES.AUTH_REQUIRED,
|
|
@@ -18326,7 +19544,9 @@ async function waitForStoredToken(serverUrl, statePath, timeoutSec) {
|
|
|
18326
19544
|
{ code: "auth_wait_timeout", url: serverUrl }
|
|
18327
19545
|
);
|
|
18328
19546
|
}
|
|
18329
|
-
await new Promise(
|
|
19547
|
+
await new Promise(
|
|
19548
|
+
(r) => setTimeout(r, Math.min(500, deadline - Date.now()))
|
|
19549
|
+
);
|
|
18330
19550
|
}
|
|
18331
19551
|
}
|
|
18332
19552
|
function deepLinkTransport(serverUrl, transport) {
|
|
@@ -18482,12 +19702,18 @@ async function parseArgs(argv) {
|
|
|
18482
19702
|
).option(
|
|
18483
19703
|
"--app-info",
|
|
18484
19704
|
"Probe the tool's MCP App UI metadata (resourceUri, csp, permissions, domain) and emit it as one JSON line; exit 2 when the tool has no app. Use with --method tools/call --tool-name <name> (the tool itself is not invoked) or --method tools/list (one NDJSON line per tool)."
|
|
19705
|
+
).option(
|
|
19706
|
+
"--advertise-apps",
|
|
19707
|
+
"Advertise the MCP Apps UI extension (io.modelcontextprotocol/ui) at initialize. Off by default because the CLI cannot render an App; set it when a server only exposes its App tools to a client that claims App support, e.g. for an --app-info probe."
|
|
18485
19708
|
).option(
|
|
18486
19709
|
"--strict",
|
|
18487
19710
|
"Report tool-schema portability problems in full (path, issue, suggested fix) on stderr, and exit 6 if any is error-severity. Use with --method tools/list. Without it, a one-line count is printed instead."
|
|
18488
19711
|
).option(
|
|
18489
19712
|
"--verify",
|
|
18490
19713
|
"Run the SEP-2640 conformance and digest checks over the skills returned, emit one JSON report per skill on stdout, and exit 7 if any fails or 8 if any could not be fully checked within the read bounds. Use with --method skills/list or --method skills/get."
|
|
19714
|
+
).option(
|
|
19715
|
+
"--require-digests",
|
|
19716
|
+
'With --verify: exit 9 when a skill advertises no digests (resources: "dynamic"), instead of reporting it as unverifiable and exiting 0.'
|
|
18491
19717
|
).option(
|
|
18492
19718
|
"--connect-timeout <ms>",
|
|
18493
19719
|
`Connection timeout in ms (default ${DEFAULT_CONNECT_TIMEOUT_MS} for ad-hoc --server-url / target invocations; 0 = no timeout).`,
|
|
@@ -18602,6 +19828,14 @@ async function parseArgs(argv) {
|
|
|
18602
19828
|
);
|
|
18603
19829
|
}
|
|
18604
19830
|
}
|
|
19831
|
+
if (options.requireDigests && !options.verify) {
|
|
19832
|
+
throw new Error("--require-digests requires --verify.");
|
|
19833
|
+
}
|
|
19834
|
+
if (options.advertiseApps && (options.listStoredAuth || options.printHandoff || options.method === "servers/list" || options.method === "servers/show")) {
|
|
19835
|
+
throw new Error(
|
|
19836
|
+
"--advertise-apps requires a command that connects to a server; it has no effect with --list-stored-auth, --print-handoff, or --method servers/list / servers/show."
|
|
19837
|
+
);
|
|
19838
|
+
}
|
|
18605
19839
|
const oauthStatePath = getStateFilePath();
|
|
18606
19840
|
if (options.listStoredAuth) {
|
|
18607
19841
|
const servers = await readOAuthServers(oauthStatePath);
|
|
@@ -18767,8 +20001,10 @@ async function parseArgs(argv) {
|
|
|
18767
20001
|
metadata: options.metadata,
|
|
18768
20002
|
toolMeta: options.toolMetadata,
|
|
18769
20003
|
appInfo: options.appInfo === true,
|
|
20004
|
+
advertiseApps: options.advertiseApps === true,
|
|
18770
20005
|
strict: options.strict === true,
|
|
18771
20006
|
verify: options.verify === true,
|
|
20007
|
+
requireDigests: options.requireDigests === true,
|
|
18772
20008
|
cursor: options.cursor,
|
|
18773
20009
|
format: options.format
|
|
18774
20010
|
};
|