@modelcontextprotocol/inspector 2.11.0 → 2.12.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 +2 -0
- package/clients/cli/build/index.js +1149 -190
- package/clients/mcpdo/build/{chunk-O6RN6WMD.js → chunk-LBQDT5VV.js} +726 -101
- package/clients/mcpdo/build/mcp-bin.js +14 -1
- package/clients/mcpdo/build/mcpdod.js +1 -1
- package/clients/tui/build/index.js +734 -106
- package/clients/web/build/index.js +738 -89
- package/clients/web/dist/assets/{index-B79AqubU.js → index-qtX15-Bz.js} +111 -111
- package/clients/web/dist/assets/{prism-light-J_CKjs7N.js → prism-light-DfUe_Xlh.js} +1 -1
- package/clients/web/dist/index.html +1 -1
- package/package.json +2 -2
|
@@ -3177,6 +3177,13 @@ var BaseOAuthClientProvider = class {
|
|
|
3177
3177
|
requestRefreshToken;
|
|
3178
3178
|
/** Server-configured scope, for the #2068 `offline_access` distinction. */
|
|
3179
3179
|
configuredScope;
|
|
3180
|
+
/** Per-flow verifier store (#2726); absent → the shared storage slot. */
|
|
3181
|
+
flowStore;
|
|
3182
|
+
/**
|
|
3183
|
+
* The authId of the flow this provider is driving: set by {@link state} on the
|
|
3184
|
+
* redirect leg and by {@link resumeAuthorizationFlow} on the callback leg.
|
|
3185
|
+
*/
|
|
3186
|
+
flowId;
|
|
3180
3187
|
constructor(serverUrl, oauthConfig) {
|
|
3181
3188
|
this.serverUrl = serverUrl;
|
|
3182
3189
|
this.storage = oauthConfig.storage;
|
|
@@ -3186,6 +3193,7 @@ var BaseOAuthClientProvider = class {
|
|
|
3186
3193
|
this.authorizationParams = oauthConfig.authorizationParams;
|
|
3187
3194
|
this.requestRefreshToken = oauthConfig.requestRefreshToken ?? true;
|
|
3188
3195
|
this.configuredScope = oauthConfig.configuredScope;
|
|
3196
|
+
this.flowStore = oauthConfig.flowStore;
|
|
3189
3197
|
}
|
|
3190
3198
|
/**
|
|
3191
3199
|
* Load persisted scope into {@link cachedScope} before SDK `auth()` (which
|
|
@@ -3256,7 +3264,24 @@ var BaseOAuthClientProvider = class {
|
|
|
3256
3264
|
return metadata;
|
|
3257
3265
|
}
|
|
3258
3266
|
state() {
|
|
3259
|
-
|
|
3267
|
+
const state = generateOAuthState();
|
|
3268
|
+
this.flowId = state;
|
|
3269
|
+
return state;
|
|
3270
|
+
}
|
|
3271
|
+
/**
|
|
3272
|
+
* Bind this provider to the flow a callback's `state` names, so
|
|
3273
|
+
* {@link codeVerifier} reads that flow's verifier and no other (#2726). A
|
|
3274
|
+
* `state` that is not one of ours binds nothing — and, with a flow store, a
|
|
3275
|
+
* flow-less read then fails rather than reaching for the shared slot.
|
|
3276
|
+
*/
|
|
3277
|
+
resumeAuthorizationFlow(state) {
|
|
3278
|
+
this.flowId = parseOAuthState(state)?.authId;
|
|
3279
|
+
}
|
|
3280
|
+
/** Drop the bound flow's verifier once its exchange has completed. */
|
|
3281
|
+
async clearAuthorizationFlow() {
|
|
3282
|
+
if (this.flowStore && this.flowId) {
|
|
3283
|
+
await this.flowStore.clearCodeVerifier(this.flowId);
|
|
3284
|
+
}
|
|
3260
3285
|
}
|
|
3261
3286
|
async clientInformation(ctx) {
|
|
3262
3287
|
const preregistered = await this.storage.getClientInformation(
|
|
@@ -3401,9 +3426,26 @@ var BaseOAuthClientProvider = class {
|
|
|
3401
3426
|
}
|
|
3402
3427
|
}
|
|
3403
3428
|
async saveCodeVerifier(codeVerifier) {
|
|
3429
|
+
if (this.flowStore && this.flowId) {
|
|
3430
|
+
await this.flowStore.saveCodeVerifier(
|
|
3431
|
+
this.flowId,
|
|
3432
|
+
this.serverUrl,
|
|
3433
|
+
codeVerifier
|
|
3434
|
+
);
|
|
3435
|
+
return;
|
|
3436
|
+
}
|
|
3404
3437
|
await this.storage.saveCodeVerifier(this.serverUrl, codeVerifier);
|
|
3405
3438
|
}
|
|
3406
3439
|
async codeVerifier() {
|
|
3440
|
+
if (this.flowStore) {
|
|
3441
|
+
const verifier2 = this.flowId ? await this.flowStore.getCodeVerifier(this.flowId, this.serverUrl) : void 0;
|
|
3442
|
+
if (!verifier2) {
|
|
3443
|
+
throw new Error(
|
|
3444
|
+
"No code verifier saved for this authorization flow. It may have been started in another browser tab or already completed; please re-authenticate."
|
|
3445
|
+
);
|
|
3446
|
+
}
|
|
3447
|
+
return verifier2;
|
|
3448
|
+
}
|
|
3407
3449
|
const verifier = await this.storage.getCodeVerifier(this.serverUrl);
|
|
3408
3450
|
if (!verifier) {
|
|
3409
3451
|
throw new Error("No code verifier saved for session");
|
|
@@ -3450,6 +3492,10 @@ var BaseOAuthClientProvider = class {
|
|
|
3450
3492
|
await this.storage.clearTokens(this.serverUrl);
|
|
3451
3493
|
return;
|
|
3452
3494
|
case "verifier":
|
|
3495
|
+
if (this.flowStore) {
|
|
3496
|
+
await this.clearAuthorizationFlow();
|
|
3497
|
+
return;
|
|
3498
|
+
}
|
|
3453
3499
|
await this.storage.clearCodeVerifier(this.serverUrl);
|
|
3454
3500
|
return;
|
|
3455
3501
|
case "discovery":
|
|
@@ -4190,7 +4236,7 @@ function serializeOAuthPersistBlob(snapshot) {
|
|
|
4190
4236
|
import { resolve as resolve5 } from "path";
|
|
4191
4237
|
|
|
4192
4238
|
// ../../core/auth/node/oauth-secrets.ts
|
|
4193
|
-
import { randomUUID as randomUUID2 } from "crypto";
|
|
4239
|
+
import { createHmac, randomUUID as randomUUID2 } from "crypto";
|
|
4194
4240
|
import { OAuthTokensSchema as OAuthTokensSchema3 } from "@modelcontextprotocol/core";
|
|
4195
4241
|
var PERSIST_TOKENS_ENV = "MCP_INSPECTOR_PERSIST_TOKENS";
|
|
4196
4242
|
var warnedPolicyValues = /* @__PURE__ */ new Set();
|
|
@@ -4223,6 +4269,29 @@ var LEGACY_REGISTRATION_TOKEN_FIELD = "registration-token";
|
|
|
4223
4269
|
var PREREG_CLIENT_SECRET_FIELD = "prereg-client-secret";
|
|
4224
4270
|
var PREREG_REGISTRATION_TOKEN_FIELD = "prereg-registration-token";
|
|
4225
4271
|
var IDP_SESSION_FIELD = "idp-session";
|
|
4272
|
+
var clientCredentialStampField = (field) => `${field}#owner`;
|
|
4273
|
+
var credentialMac = (clientId, value) => createHmac("sha256", value).update(clientId).digest("hex");
|
|
4274
|
+
function clientCredentialStamp(clientId, value) {
|
|
4275
|
+
return JSON.stringify({
|
|
4276
|
+
client_id: clientId,
|
|
4277
|
+
mac: credentialMac(clientId, value)
|
|
4278
|
+
});
|
|
4279
|
+
}
|
|
4280
|
+
function stampedOwner(stamp, value) {
|
|
4281
|
+
if (stamp === void 0) return void 0;
|
|
4282
|
+
let parsed;
|
|
4283
|
+
try {
|
|
4284
|
+
parsed = JSON.parse(stamp);
|
|
4285
|
+
} catch {
|
|
4286
|
+
return void 0;
|
|
4287
|
+
}
|
|
4288
|
+
if (typeof parsed !== "object" || parsed === null) return void 0;
|
|
4289
|
+
const { client_id: clientId, mac } = parsed;
|
|
4290
|
+
if (typeof clientId !== "string" || mac !== credentialMac(clientId, value)) {
|
|
4291
|
+
return void 0;
|
|
4292
|
+
}
|
|
4293
|
+
return clientId;
|
|
4294
|
+
}
|
|
4226
4295
|
function applyTokensPolicy(tokens, policy) {
|
|
4227
4296
|
if (policy === "none") return void 0;
|
|
4228
4297
|
if (policy === "access" && tokens.refresh_token !== void 0) {
|
|
@@ -4251,12 +4320,18 @@ function splitClientInformation(info, fields, secrets) {
|
|
|
4251
4320
|
if (client_secret === void 0 && registration_access_token === void 0) {
|
|
4252
4321
|
return info;
|
|
4253
4322
|
}
|
|
4254
|
-
|
|
4255
|
-
|
|
4256
|
-
|
|
4257
|
-
|
|
4258
|
-
|
|
4259
|
-
|
|
4323
|
+
const put = (field, value) => {
|
|
4324
|
+
if (value === void 0) return;
|
|
4325
|
+
secrets[field] = value;
|
|
4326
|
+
if (fields.stamped && typeof info.client_id === "string") {
|
|
4327
|
+
secrets[clientCredentialStampField(field)] = clientCredentialStamp(
|
|
4328
|
+
info.client_id,
|
|
4329
|
+
value
|
|
4330
|
+
);
|
|
4331
|
+
}
|
|
4332
|
+
};
|
|
4333
|
+
put(fields.clientSecret, client_secret);
|
|
4334
|
+
put(fields.registrationToken, registration_access_token);
|
|
4260
4335
|
return publicInfo;
|
|
4261
4336
|
}
|
|
4262
4337
|
function joinClientInformation(info, fields, secrets) {
|
|
@@ -4275,15 +4350,18 @@ function joinClientInformation(info, fields, secrets) {
|
|
|
4275
4350
|
}
|
|
4276
4351
|
var issuerClientInfoFields = (issuer) => ({
|
|
4277
4352
|
clientSecret: issuerClientSecretField(issuer),
|
|
4278
|
-
registrationToken: issuerRegistrationTokenField(issuer)
|
|
4353
|
+
registrationToken: issuerRegistrationTokenField(issuer),
|
|
4354
|
+
stamped: true
|
|
4279
4355
|
});
|
|
4280
4356
|
var LEGACY_CLIENT_INFO_FIELDS = {
|
|
4281
4357
|
clientSecret: LEGACY_CLIENT_SECRET_FIELD,
|
|
4282
|
-
registrationToken: LEGACY_REGISTRATION_TOKEN_FIELD
|
|
4358
|
+
registrationToken: LEGACY_REGISTRATION_TOKEN_FIELD,
|
|
4359
|
+
stamped: true
|
|
4283
4360
|
};
|
|
4284
4361
|
var PREREG_CLIENT_INFO_FIELDS = {
|
|
4285
4362
|
clientSecret: PREREG_CLIENT_SECRET_FIELD,
|
|
4286
|
-
registrationToken: PREREG_REGISTRATION_TOKEN_FIELD
|
|
4363
|
+
registrationToken: PREREG_REGISTRATION_TOKEN_FIELD,
|
|
4364
|
+
stamped: false
|
|
4287
4365
|
};
|
|
4288
4366
|
function splitServerOAuthState(state, policy) {
|
|
4289
4367
|
const secrets = {};
|
|
@@ -4433,20 +4511,54 @@ function joinIdpSession(residue, secrets) {
|
|
|
4433
4511
|
return stored ? { ...residue, ...stored } : { ...residue };
|
|
4434
4512
|
}
|
|
4435
4513
|
function serverSecretFields(state) {
|
|
4514
|
+
const stamped = (field) => [
|
|
4515
|
+
field,
|
|
4516
|
+
clientCredentialStampField(field)
|
|
4517
|
+
];
|
|
4436
4518
|
const fields = /* @__PURE__ */ new Set([
|
|
4437
4519
|
LEGACY_TOKENS_FIELD,
|
|
4438
|
-
LEGACY_CLIENT_SECRET_FIELD,
|
|
4439
|
-
LEGACY_REGISTRATION_TOKEN_FIELD,
|
|
4520
|
+
...stamped(LEGACY_CLIENT_SECRET_FIELD),
|
|
4521
|
+
...stamped(LEGACY_REGISTRATION_TOKEN_FIELD),
|
|
4440
4522
|
PREREG_CLIENT_SECRET_FIELD,
|
|
4441
4523
|
PREREG_REGISTRATION_TOKEN_FIELD
|
|
4442
4524
|
]);
|
|
4443
4525
|
for (const issuer of Object.keys(state?.byIssuer ?? {})) {
|
|
4444
4526
|
fields.add(issuerTokensField(issuer));
|
|
4445
|
-
|
|
4446
|
-
|
|
4527
|
+
for (const field of stamped(issuerClientSecretField(issuer))) {
|
|
4528
|
+
fields.add(field);
|
|
4529
|
+
}
|
|
4530
|
+
for (const field of stamped(issuerRegistrationTokenField(issuer))) {
|
|
4531
|
+
fields.add(field);
|
|
4532
|
+
}
|
|
4447
4533
|
}
|
|
4448
4534
|
return [...fields];
|
|
4449
4535
|
}
|
|
4536
|
+
function clientCredentialOwners(state) {
|
|
4537
|
+
const owners = /* @__PURE__ */ new Map();
|
|
4538
|
+
const add = (info, fields) => {
|
|
4539
|
+
if (typeof info?.client_id !== "string") return;
|
|
4540
|
+
owners.set(fields.clientSecret, info.client_id);
|
|
4541
|
+
owners.set(fields.registrationToken, info.client_id);
|
|
4542
|
+
};
|
|
4543
|
+
add(state.clientInformation, LEGACY_CLIENT_INFO_FIELDS);
|
|
4544
|
+
for (const [issuer, slot] of Object.entries(state.byIssuer ?? {})) {
|
|
4545
|
+
add(slot.clientInformation, issuerClientInfoFields(issuer));
|
|
4546
|
+
}
|
|
4547
|
+
return owners;
|
|
4548
|
+
}
|
|
4549
|
+
function restampClientCredentials(state, secrets) {
|
|
4550
|
+
const out = { ...secrets };
|
|
4551
|
+
for (const [field, clientId] of clientCredentialOwners(state)) {
|
|
4552
|
+
const stampField = clientCredentialStampField(field);
|
|
4553
|
+
const value = Object.hasOwn(out, field) ? out[field] : void 0;
|
|
4554
|
+
if (value === void 0) {
|
|
4555
|
+
delete out[stampField];
|
|
4556
|
+
} else if (stampedOwner(out[stampField], value) === void 0) {
|
|
4557
|
+
setOwnEntry(out, stampField, clientCredentialStamp(clientId, value));
|
|
4558
|
+
}
|
|
4559
|
+
}
|
|
4560
|
+
return out;
|
|
4561
|
+
}
|
|
4450
4562
|
function clientInfoHasPlaintext(info) {
|
|
4451
4563
|
return info !== void 0 && (info.client_secret !== void 0 || info.registration_access_token !== void 0);
|
|
4452
4564
|
}
|
|
@@ -4487,6 +4599,44 @@ function coveredLocations(location, byLocation) {
|
|
|
4487
4599
|
([recorded]) => recorded === location || location === KEYRING_LOCATION && recorded.startsWith(FILE_LOCATION_PREFIX)
|
|
4488
4600
|
).map(([recorded, keys]) => ({ location: recorded, keys }));
|
|
4489
4601
|
}
|
|
4602
|
+
var emptyKeys = () => ({
|
|
4603
|
+
servers: /* @__PURE__ */ new Set(),
|
|
4604
|
+
idpSessions: /* @__PURE__ */ new Set(),
|
|
4605
|
+
owners: /* @__PURE__ */ new Map()
|
|
4606
|
+
});
|
|
4607
|
+
function candidateFields(location, byLocation) {
|
|
4608
|
+
const fields = /* @__PURE__ */ new Map();
|
|
4609
|
+
for (const { keys } of coveredLocations(location, byLocation)) {
|
|
4610
|
+
for (const [url, owned] of keys.owners) {
|
|
4611
|
+
const kept = fields.get(url) ?? /* @__PURE__ */ new Set();
|
|
4612
|
+
for (const field of owned.keys()) kept.add(field);
|
|
4613
|
+
fields.set(url, kept);
|
|
4614
|
+
}
|
|
4615
|
+
}
|
|
4616
|
+
return fields;
|
|
4617
|
+
}
|
|
4618
|
+
function addMissingOwners(target, source) {
|
|
4619
|
+
for (const [url, fields] of source) {
|
|
4620
|
+
const kept = target.get(url) ?? /* @__PURE__ */ new Map();
|
|
4621
|
+
for (const [field, clientId] of fields) {
|
|
4622
|
+
if (!kept.has(field)) kept.set(field, clientId);
|
|
4623
|
+
}
|
|
4624
|
+
if (kept.size > 0) target.set(url, kept);
|
|
4625
|
+
}
|
|
4626
|
+
}
|
|
4627
|
+
function mergeOwners(target, source) {
|
|
4628
|
+
let changed = false;
|
|
4629
|
+
for (const [url, fields] of source) {
|
|
4630
|
+
const kept = target.get(url) ?? /* @__PURE__ */ new Map();
|
|
4631
|
+
for (const [field, clientId] of fields) {
|
|
4632
|
+
if (kept.get(field) === clientId) continue;
|
|
4633
|
+
kept.set(field, clientId);
|
|
4634
|
+
changed = true;
|
|
4635
|
+
}
|
|
4636
|
+
if (kept.size > 0) target.set(url, kept);
|
|
4637
|
+
}
|
|
4638
|
+
return changed;
|
|
4639
|
+
}
|
|
4490
4640
|
var warnedLedgerFailures = /* @__PURE__ */ new Set();
|
|
4491
4641
|
function warnLedgerFailure(what, error) {
|
|
4492
4642
|
const reason = error instanceof Error ? error.message : String(error);
|
|
@@ -4500,6 +4650,19 @@ function warnLedgerFailure(what, error) {
|
|
|
4500
4650
|
function isRecord2(value) {
|
|
4501
4651
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
4502
4652
|
}
|
|
4653
|
+
function parseOwners(value) {
|
|
4654
|
+
const owners = /* @__PURE__ */ new Map();
|
|
4655
|
+
if (!isRecord2(value)) return owners;
|
|
4656
|
+
for (const [url, fields] of Object.entries(value)) {
|
|
4657
|
+
if (!isRecord2(fields)) continue;
|
|
4658
|
+
const byField = /* @__PURE__ */ new Map();
|
|
4659
|
+
for (const [field, clientId] of Object.entries(fields)) {
|
|
4660
|
+
if (typeof clientId === "string") byField.set(field, clientId);
|
|
4661
|
+
}
|
|
4662
|
+
if (byField.size > 0) owners.set(url, byField);
|
|
4663
|
+
}
|
|
4664
|
+
return owners;
|
|
4665
|
+
}
|
|
4503
4666
|
function stringsOf(value) {
|
|
4504
4667
|
return Array.isArray(value) ? value.filter((item) => typeof item === "string") : [];
|
|
4505
4668
|
}
|
|
@@ -4521,7 +4684,9 @@ function parseLedger(raw) {
|
|
|
4521
4684
|
if (!isRecord2(keys)) continue;
|
|
4522
4685
|
byLocation.set(location, {
|
|
4523
4686
|
servers: new Set(stringsOf(keys.servers)),
|
|
4524
|
-
idpSessions: new Set(stringsOf(keys.idpSessions))
|
|
4687
|
+
idpSessions: new Set(stringsOf(keys.idpSessions)),
|
|
4688
|
+
owners: parseOwners(keys.owners),
|
|
4689
|
+
...isValidSecretsNamespace(keys.owedTo) && { owedTo: keys.owedTo }
|
|
4525
4690
|
});
|
|
4526
4691
|
}
|
|
4527
4692
|
if (byLocation.size > 0) ledger.set(namespace, byLocation);
|
|
@@ -4535,7 +4700,19 @@ function serializeLedger(ledger) {
|
|
|
4535
4700
|
Object.fromEntries(
|
|
4536
4701
|
[...byLocation].map(([location, keys]) => [
|
|
4537
4702
|
location,
|
|
4538
|
-
{
|
|
4703
|
+
{
|
|
4704
|
+
servers: [...keys.servers],
|
|
4705
|
+
idpSessions: [...keys.idpSessions],
|
|
4706
|
+
...keys.owners.size > 0 && {
|
|
4707
|
+
owners: Object.fromEntries(
|
|
4708
|
+
[...keys.owners].map(([url, fields]) => [
|
|
4709
|
+
url,
|
|
4710
|
+
Object.fromEntries(fields)
|
|
4711
|
+
])
|
|
4712
|
+
)
|
|
4713
|
+
},
|
|
4714
|
+
...keys.owedTo !== void 0 && { owedTo: keys.owedTo }
|
|
4715
|
+
}
|
|
4539
4716
|
])
|
|
4540
4717
|
)
|
|
4541
4718
|
])
|
|
@@ -4547,7 +4724,7 @@ async function writeLedger(stateFilePath, ledger) {
|
|
|
4547
4724
|
if (ledger.size === 0) await deleteStoreFile(path7);
|
|
4548
4725
|
else await writeStoreFile(path7, serializeLedger(ledger));
|
|
4549
4726
|
}
|
|
4550
|
-
async function recordNamespaceKeys(stateFilePath, secretStore, namespace, servers, idpSessions) {
|
|
4727
|
+
async function recordNamespaceKeys(stateFilePath, secretStore, namespace, servers, idpSessions, owners = /* @__PURE__ */ new Map()) {
|
|
4551
4728
|
try {
|
|
4552
4729
|
const ledger = parseLedger(
|
|
4553
4730
|
await readStoreFile(namespaceLedgerPath(stateFilePath))
|
|
@@ -4561,7 +4738,7 @@ async function recordNamespaceKeys(stateFilePath, secretStore, namespace, server
|
|
|
4561
4738
|
let keys = byLocation.get(location);
|
|
4562
4739
|
let changed = false;
|
|
4563
4740
|
if (!keys) {
|
|
4564
|
-
keys =
|
|
4741
|
+
keys = emptyKeys();
|
|
4565
4742
|
byLocation.set(location, keys);
|
|
4566
4743
|
changed = true;
|
|
4567
4744
|
}
|
|
@@ -4575,11 +4752,93 @@ async function recordNamespaceKeys(stateFilePath, secretStore, namespace, server
|
|
|
4575
4752
|
keys.idpSessions.add(issuer);
|
|
4576
4753
|
changed = true;
|
|
4577
4754
|
}
|
|
4755
|
+
if (mergeOwners(keys.owners, owners)) changed = true;
|
|
4578
4756
|
if (changed) await writeLedger(stateFilePath, ledger);
|
|
4579
4757
|
} catch (error) {
|
|
4580
4758
|
warnLedgerFailure("update", error);
|
|
4581
4759
|
}
|
|
4582
4760
|
}
|
|
4761
|
+
async function markUncarriedNamespaces(stateFilePath, secretStore, into) {
|
|
4762
|
+
const ledger = parseLedger(
|
|
4763
|
+
await readStoreFile(namespaceLedgerPath(stateFilePath))
|
|
4764
|
+
);
|
|
4765
|
+
const location = await secretStoreLocation(secretStore);
|
|
4766
|
+
let changed = false;
|
|
4767
|
+
for (const [namespace, byLocation] of ledger) {
|
|
4768
|
+
if (namespace === into) continue;
|
|
4769
|
+
for (const [recorded, keys] of byLocation) {
|
|
4770
|
+
if (recorded === location || keys.owners.size === 0) continue;
|
|
4771
|
+
if (keys.owedTo === into) continue;
|
|
4772
|
+
keys.owedTo = into;
|
|
4773
|
+
changed = true;
|
|
4774
|
+
}
|
|
4775
|
+
}
|
|
4776
|
+
if (changed) await writeLedger(stateFilePath, ledger);
|
|
4777
|
+
}
|
|
4778
|
+
async function pendingCarries(stateFilePath, secretStore, current) {
|
|
4779
|
+
const ledger = parseLedger(
|
|
4780
|
+
await readStoreFile(namespaceLedgerPath(stateFilePath))
|
|
4781
|
+
);
|
|
4782
|
+
const location = await secretStoreLocation(secretStore);
|
|
4783
|
+
const carries = [];
|
|
4784
|
+
for (const [namespace, byLocation] of ledger) {
|
|
4785
|
+
const owed = coveredLocations(location, byLocation).some(
|
|
4786
|
+
({ keys }) => keys.owedTo === current
|
|
4787
|
+
);
|
|
4788
|
+
if (owed) {
|
|
4789
|
+
carries.push({
|
|
4790
|
+
namespace,
|
|
4791
|
+
fields: candidateFields(location, byLocation)
|
|
4792
|
+
});
|
|
4793
|
+
}
|
|
4794
|
+
}
|
|
4795
|
+
return carries.reverse();
|
|
4796
|
+
}
|
|
4797
|
+
async function carryOwed(stateFilePath, ledger, location, secretStore, namespace, current, recorded, now) {
|
|
4798
|
+
let carried = true;
|
|
4799
|
+
for (const [url, fields] of recorded) {
|
|
4800
|
+
for (const field of fields) {
|
|
4801
|
+
const clientId = now.get(url)?.get(field);
|
|
4802
|
+
if (clientId === void 0) continue;
|
|
4803
|
+
try {
|
|
4804
|
+
const from = oauthSecretServerId(url, namespace);
|
|
4805
|
+
const to = oauthSecretServerId(url, current);
|
|
4806
|
+
const stampField = clientCredentialStampField(field);
|
|
4807
|
+
const value = await secretStoreGetStrict(secretStore, from, field);
|
|
4808
|
+
if (value === null) continue;
|
|
4809
|
+
const stamp = await secretStoreGetStrict(secretStore, from, stampField);
|
|
4810
|
+
if (stamp === null || stampedOwner(stamp, value) !== clientId) {
|
|
4811
|
+
continue;
|
|
4812
|
+
}
|
|
4813
|
+
if (await secretStoreGetStrict(secretStore, to, field) !== null) {
|
|
4814
|
+
continue;
|
|
4815
|
+
}
|
|
4816
|
+
let byLocation = ledger.get(current);
|
|
4817
|
+
if (!byLocation) {
|
|
4818
|
+
byLocation = /* @__PURE__ */ new Map();
|
|
4819
|
+
ledger.set(current, byLocation);
|
|
4820
|
+
}
|
|
4821
|
+
const target = byLocation.get(location) ?? emptyKeys();
|
|
4822
|
+
byLocation.set(location, target);
|
|
4823
|
+
const isNew = !target.servers.has(url);
|
|
4824
|
+
target.servers.add(url);
|
|
4825
|
+
const owner = /* @__PURE__ */ new Map([[url, /* @__PURE__ */ new Map([[field, clientId]])]]);
|
|
4826
|
+
if (mergeOwners(target.owners, owner) || isNew) {
|
|
4827
|
+
await writeLedger(stateFilePath, ledger);
|
|
4828
|
+
}
|
|
4829
|
+
await secretStore.set(to, stampField, stamp);
|
|
4830
|
+
await secretStore.set(to, field, value);
|
|
4831
|
+
} catch (error) {
|
|
4832
|
+
carried = false;
|
|
4833
|
+
warnLedgerFailure(
|
|
4834
|
+
"carry over a superseded namespace recorded in",
|
|
4835
|
+
error
|
|
4836
|
+
);
|
|
4837
|
+
}
|
|
4838
|
+
}
|
|
4839
|
+
}
|
|
4840
|
+
return carried;
|
|
4841
|
+
}
|
|
4583
4842
|
async function recordedNamespaces(stateFilePath) {
|
|
4584
4843
|
try {
|
|
4585
4844
|
const ledger = parseLedger(
|
|
@@ -4591,7 +4850,7 @@ async function recordedNamespaces(stateFilePath) {
|
|
|
4591
4850
|
return [];
|
|
4592
4851
|
}
|
|
4593
4852
|
}
|
|
4594
|
-
async function purgeSupersededNamespaces(stateFilePath, secretStore, current) {
|
|
4853
|
+
async function purgeSupersededNamespaces(stateFilePath, secretStore, current, owners = /* @__PURE__ */ new Map()) {
|
|
4595
4854
|
let ledger;
|
|
4596
4855
|
let location;
|
|
4597
4856
|
try {
|
|
@@ -4604,8 +4863,22 @@ async function purgeSupersededNamespaces(stateFilePath, secretStore, current) {
|
|
|
4604
4863
|
return;
|
|
4605
4864
|
}
|
|
4606
4865
|
let changed = false;
|
|
4607
|
-
for (const [namespace, byLocation] of ledger) {
|
|
4866
|
+
for (const [namespace, byLocation] of [...ledger].reverse()) {
|
|
4608
4867
|
if (namespace === current) continue;
|
|
4868
|
+
if (current !== void 0 && coveredLocations(location, byLocation).some(
|
|
4869
|
+
({ keys }) => keys.owedTo === current
|
|
4870
|
+
) && !await carryOwed(
|
|
4871
|
+
stateFilePath,
|
|
4872
|
+
ledger,
|
|
4873
|
+
location,
|
|
4874
|
+
secretStore,
|
|
4875
|
+
namespace,
|
|
4876
|
+
current,
|
|
4877
|
+
candidateFields(location, byLocation),
|
|
4878
|
+
owners
|
|
4879
|
+
)) {
|
|
4880
|
+
break;
|
|
4881
|
+
}
|
|
4609
4882
|
for (const { location: recorded, keys } of coveredLocations(
|
|
4610
4883
|
location,
|
|
4611
4884
|
byLocation
|
|
@@ -4630,12 +4903,11 @@ async function purgeSupersededNamespaces(stateFilePath, secretStore, current) {
|
|
|
4630
4903
|
if (!purged || recorded !== location) continue;
|
|
4631
4904
|
byLocation.delete(recorded);
|
|
4632
4905
|
if (recorded.startsWith(FILE_LOCATION_PREFIX)) {
|
|
4633
|
-
const keyring = byLocation.get(KEYRING_LOCATION) ??
|
|
4634
|
-
|
|
4635
|
-
idpSessions: /* @__PURE__ */ new Set()
|
|
4636
|
-
};
|
|
4906
|
+
const keyring = byLocation.get(KEYRING_LOCATION) ?? emptyKeys();
|
|
4907
|
+
addMissingOwners(keyring.owners, keys.owners);
|
|
4637
4908
|
for (const url of keys.servers) keyring.servers.add(url);
|
|
4638
4909
|
for (const issuer of keys.idpSessions) keyring.idpSessions.add(issuer);
|
|
4910
|
+
keyring.owedTo ??= keys.owedTo;
|
|
4639
4911
|
byLocation.set(KEYRING_LOCATION, keyring);
|
|
4640
4912
|
}
|
|
4641
4913
|
changed = true;
|
|
@@ -4746,7 +5018,12 @@ async function adoptSecretsNamespace(filePath, secretStore, locked) {
|
|
|
4746
5018
|
const existing = parseSecretsNamespace(raw);
|
|
4747
5019
|
if (existing !== void 0) {
|
|
4748
5020
|
if (locked) {
|
|
4749
|
-
await purgeSupersededNamespaces(
|
|
5021
|
+
await purgeSupersededNamespaces(
|
|
5022
|
+
filePath,
|
|
5023
|
+
secretStore,
|
|
5024
|
+
existing,
|
|
5025
|
+
snapshotClientOwners(parseOAuthPersistBlob(raw))
|
|
5026
|
+
);
|
|
4750
5027
|
}
|
|
4751
5028
|
return existing;
|
|
4752
5029
|
}
|
|
@@ -4766,25 +5043,33 @@ async function adoptSecretsNamespace(filePath, secretStore, locked) {
|
|
|
4766
5043
|
`Could not save OAuth state: ${filePath} predates per-state-file secret namespaces, and migrating its secret-store entries needs the file lock, which is unavailable here (see the lock warning above). Migrating without it could lose credentials if two processes migrate at once. Nothing was changed; make the lock directory writable and retry \u2014 or, if you cannot, clear this file's stored OAuth state and re-authorize (clearing does not migrate, and the fresh state file mints its namespace without the lock).`
|
|
4767
5044
|
);
|
|
4768
5045
|
}
|
|
5046
|
+
const values = await readEntrySecrets(
|
|
5047
|
+
snapshot,
|
|
5048
|
+
secretStore,
|
|
5049
|
+
void 0,
|
|
5050
|
+
await recordedNamespaces(filePath)
|
|
5051
|
+
);
|
|
5052
|
+
const owners = snapshotClientOwners(snapshot);
|
|
4769
5053
|
await recordNamespaceKeys(
|
|
4770
5054
|
filePath,
|
|
4771
5055
|
secretStore,
|
|
4772
5056
|
namespace,
|
|
4773
5057
|
Object.keys(snapshot.servers),
|
|
4774
|
-
Object.keys(snapshot.idpSessions)
|
|
5058
|
+
Object.keys(snapshot.idpSessions),
|
|
5059
|
+
writtenClientOwners(snapshot, (url) => values.servers.get(url) ?? {})
|
|
4775
5060
|
);
|
|
5061
|
+
await markUncarriedNamespaces(filePath, secretStore, namespace);
|
|
4776
5062
|
const touched = [];
|
|
4777
5063
|
try {
|
|
4778
|
-
const values = await readEntrySecrets(
|
|
4779
|
-
snapshot,
|
|
4780
|
-
secretStore,
|
|
4781
|
-
void 0,
|
|
4782
|
-
await recordedNamespaces(filePath)
|
|
4783
|
-
);
|
|
4784
5064
|
const copies = [
|
|
5065
|
+
// Restamped: a value read from the legacy ids predates stamps, and a
|
|
5066
|
+
// fallback's stamp may belong to a value read from elsewhere (#2734).
|
|
4785
5067
|
...[...values.servers].map(([url, secrets]) => ({
|
|
4786
5068
|
scopedId: oauthSecretServerId(url, namespace),
|
|
4787
|
-
secrets
|
|
5069
|
+
secrets: restampClientCredentials(
|
|
5070
|
+
getOwnEntry(snapshot.servers, url) ?? {},
|
|
5071
|
+
secrets
|
|
5072
|
+
)
|
|
4788
5073
|
})),
|
|
4789
5074
|
...[...values.idpSessions].map(([issuer, secrets]) => ({
|
|
4790
5075
|
scopedId: oauthIdpSecretServerId(issuer, namespace),
|
|
@@ -4802,7 +5087,7 @@ async function adoptSecretsNamespace(filePath, secretStore, locked) {
|
|
|
4802
5087
|
await restoreSecretFields(secretStore, touched, warnRestoreFailure);
|
|
4803
5088
|
throw error;
|
|
4804
5089
|
}
|
|
4805
|
-
await purgeSupersededNamespaces(filePath, secretStore, namespace);
|
|
5090
|
+
await purgeSupersededNamespaces(filePath, secretStore, namespace, owners);
|
|
4806
5091
|
const legacyIds = [
|
|
4807
5092
|
...Object.keys(snapshot.servers).map((url) => oauthSecretServerId(url)),
|
|
4808
5093
|
...Object.keys(snapshot.idpSessions).map(
|
|
@@ -4979,7 +5264,22 @@ async function writeOAuthSectionsFrom(filePath, resolveSnapshot, sections, secre
|
|
|
4979
5264
|
secretStore,
|
|
4980
5265
|
namespace,
|
|
4981
5266
|
Object.keys(merged.servers),
|
|
4982
|
-
Object.keys(merged.idpSessions)
|
|
5267
|
+
Object.keys(merged.idpSessions),
|
|
5268
|
+
// Only the entries this save writes: another entry's secret in
|
|
5269
|
+
// this store was written for whatever client it was written for,
|
|
5270
|
+
// and relabeling it from the file would misattribute it (#2672).
|
|
5271
|
+
writtenClientOwners(
|
|
5272
|
+
{
|
|
5273
|
+
servers: Object.fromEntries(
|
|
5274
|
+
(effective.servers ?? []).flatMap((url) => {
|
|
5275
|
+
const entry = getOwnEntry(merged.servers, url);
|
|
5276
|
+
return entry === void 0 ? [] : [[url, entry]];
|
|
5277
|
+
})
|
|
5278
|
+
),
|
|
5279
|
+
idpSessions: {}
|
|
5280
|
+
},
|
|
5281
|
+
splitSecretsOf
|
|
5282
|
+
)
|
|
4983
5283
|
);
|
|
4984
5284
|
for (const url of effective.servers ?? []) {
|
|
4985
5285
|
const serverId = oauthSecretServerId(url, namespace);
|
|
@@ -5167,7 +5467,29 @@ function fillMissingSecrets(target, source) {
|
|
|
5167
5467
|
if (!Object.hasOwn(target, field)) setOwnEntry(target, field, value);
|
|
5168
5468
|
}
|
|
5169
5469
|
}
|
|
5170
|
-
|
|
5470
|
+
function writtenClientOwners(snapshot, secretsOf) {
|
|
5471
|
+
const owners = /* @__PURE__ */ new Map();
|
|
5472
|
+
for (const [url, state] of Object.entries(snapshot.servers)) {
|
|
5473
|
+
const secrets = secretsOf(url, state);
|
|
5474
|
+
const fields = new Map(
|
|
5475
|
+
[...clientCredentialOwners(state)].filter(
|
|
5476
|
+
([field]) => Object.hasOwn(secrets, field)
|
|
5477
|
+
)
|
|
5478
|
+
);
|
|
5479
|
+
if (fields.size > 0) owners.set(url, fields);
|
|
5480
|
+
}
|
|
5481
|
+
return owners;
|
|
5482
|
+
}
|
|
5483
|
+
var splitSecretsOf = (_url, state) => splitServerOAuthState(state, "all").secrets;
|
|
5484
|
+
function snapshotClientOwners(snapshot) {
|
|
5485
|
+
const owners = /* @__PURE__ */ new Map();
|
|
5486
|
+
for (const [url, state] of Object.entries(snapshot?.servers ?? {})) {
|
|
5487
|
+
const fields = clientCredentialOwners(state);
|
|
5488
|
+
if (fields.size > 0) owners.set(url, fields);
|
|
5489
|
+
}
|
|
5490
|
+
return owners;
|
|
5491
|
+
}
|
|
5492
|
+
async function readEntrySecrets(snapshot, secretStore, namespace, fallbacks, carries = []) {
|
|
5171
5493
|
const servers = /* @__PURE__ */ new Map();
|
|
5172
5494
|
const idpSessions = /* @__PURE__ */ new Map();
|
|
5173
5495
|
for (const url of Object.keys(snapshot.servers)) servers.set(url, {});
|
|
@@ -5188,14 +5510,49 @@ async function readEntrySecrets(snapshot, secretStore, namespace, fallbacks) {
|
|
|
5188
5510
|
);
|
|
5189
5511
|
}
|
|
5190
5512
|
}
|
|
5513
|
+
const owners = snapshotClientOwners(snapshot);
|
|
5514
|
+
for (const { namespace: from, fields: recorded } of carries) {
|
|
5515
|
+
const requests = [];
|
|
5516
|
+
for (const [url, fields] of recorded) {
|
|
5517
|
+
const target = servers.get(url);
|
|
5518
|
+
if (!target) continue;
|
|
5519
|
+
const wanted = [...fields].filter(
|
|
5520
|
+
(field) => owners.get(url)?.has(field) === true && !Object.hasOwn(target, field)
|
|
5521
|
+
);
|
|
5522
|
+
if (wanted.length > 0) {
|
|
5523
|
+
requests.push({
|
|
5524
|
+
serverId: oauthSecretServerId(url, from),
|
|
5525
|
+
fields: wanted.flatMap((field) => [
|
|
5526
|
+
field,
|
|
5527
|
+
clientCredentialStampField(field)
|
|
5528
|
+
])
|
|
5529
|
+
});
|
|
5530
|
+
}
|
|
5531
|
+
}
|
|
5532
|
+
if (requests.length === 0) continue;
|
|
5533
|
+
const values = await secretStoreGetManyStrict(secretStore, requests);
|
|
5534
|
+
for (const [url, target] of servers) {
|
|
5535
|
+
const found = values[oauthSecretServerId(url, from)] ?? {};
|
|
5536
|
+
for (const [field, clientId] of owners.get(url) ?? []) {
|
|
5537
|
+
const value = Object.hasOwn(found, field) ? found[field] : void 0;
|
|
5538
|
+
const stampField = clientCredentialStampField(field);
|
|
5539
|
+
if (value === void 0 || Object.hasOwn(target, field) || stampedOwner(found[stampField], value) !== clientId) {
|
|
5540
|
+
continue;
|
|
5541
|
+
}
|
|
5542
|
+
setOwnEntry(target, field, value);
|
|
5543
|
+
setOwnEntry(target, stampField, found[stampField]);
|
|
5544
|
+
}
|
|
5545
|
+
}
|
|
5546
|
+
}
|
|
5191
5547
|
return { servers, idpSessions };
|
|
5192
5548
|
}
|
|
5193
|
-
async function joinSnapshot(snapshot, secretStore, namespace, fallbacks) {
|
|
5549
|
+
async function joinSnapshot(snapshot, secretStore, namespace, fallbacks, carries = []) {
|
|
5194
5550
|
const values = await readEntrySecrets(
|
|
5195
5551
|
snapshot,
|
|
5196
5552
|
secretStore,
|
|
5197
5553
|
namespace,
|
|
5198
|
-
fallbacks
|
|
5554
|
+
fallbacks,
|
|
5555
|
+
carries
|
|
5199
5556
|
);
|
|
5200
5557
|
return {
|
|
5201
5558
|
servers: Object.fromEntries(
|
|
@@ -5223,7 +5580,8 @@ async function migratePlaintextSecrets(filePath, secretStore) {
|
|
|
5223
5580
|
secretStore,
|
|
5224
5581
|
namespace,
|
|
5225
5582
|
Object.keys(fresh.servers),
|
|
5226
|
-
Object.keys(fresh.idpSessions)
|
|
5583
|
+
Object.keys(fresh.idpSessions),
|
|
5584
|
+
writtenClientOwners(fresh, splitSecretsOf)
|
|
5227
5585
|
);
|
|
5228
5586
|
}
|
|
5229
5587
|
const migrateEntrySecrets = async (serverId, secrets) => {
|
|
@@ -5276,7 +5634,8 @@ async function readOAuthStore(filePath, secretStore = defaultSecretStore()) {
|
|
|
5276
5634
|
}
|
|
5277
5635
|
const namespace = parseSecretsNamespace(raw);
|
|
5278
5636
|
const fallbacks = namespace === void 0 ? await recordedNamespaces(filePath) : [];
|
|
5279
|
-
|
|
5637
|
+
const carries = namespace === void 0 ? [] : await pendingCarries(filePath, secretStore, namespace);
|
|
5638
|
+
return joinSnapshot(snapshot, secretStore, namespace, fallbacks, carries);
|
|
5280
5639
|
});
|
|
5281
5640
|
}
|
|
5282
5641
|
async function removeOAuthStore(filePath, secretStore = defaultSecretStore()) {
|
|
@@ -6198,6 +6557,16 @@ var MessageLogState = class extends TypedEventTarget {
|
|
|
6198
6557
|
return;
|
|
6199
6558
|
}
|
|
6200
6559
|
};
|
|
6560
|
+
const onRequestFailed = (event) => {
|
|
6561
|
+
const { id, reason } = event.detail;
|
|
6562
|
+
const requestEntry = this.pendingRequestEntries.get(id);
|
|
6563
|
+
if (!requestEntry) return;
|
|
6564
|
+
this.pendingRequestEntries.delete(id);
|
|
6565
|
+
requestEntry.clientError = reason;
|
|
6566
|
+
requestEntry.duration = Date.now() - requestEntry.timestamp.getTime();
|
|
6567
|
+
this.dispatchTypedEvent("message", requestEntry);
|
|
6568
|
+
this.dispatchTypedEvent("messagesChange", this.getMessages());
|
|
6569
|
+
};
|
|
6201
6570
|
const onStatusChange = () => {
|
|
6202
6571
|
if (isTerminalStatus(this.client?.getStatus())) {
|
|
6203
6572
|
this.messages = [];
|
|
@@ -6207,11 +6576,13 @@ var MessageLogState = class extends TypedEventTarget {
|
|
|
6207
6576
|
};
|
|
6208
6577
|
this.client.addEventListener("message", onMessage);
|
|
6209
6578
|
this.client.addEventListener("responseRejected", onResponseRejected);
|
|
6579
|
+
this.client.addEventListener("requestFailed", onRequestFailed);
|
|
6210
6580
|
this.client.addEventListener("statusChange", onStatusChange);
|
|
6211
6581
|
this.unsubscribe = () => {
|
|
6212
6582
|
if (this.client) {
|
|
6213
6583
|
this.client.removeEventListener("message", onMessage);
|
|
6214
6584
|
this.client.removeEventListener("responseRejected", onResponseRejected);
|
|
6585
|
+
this.client.removeEventListener("requestFailed", onRequestFailed);
|
|
6215
6586
|
this.client.removeEventListener("statusChange", onStatusChange);
|
|
6216
6587
|
}
|
|
6217
6588
|
this.client = null;
|
|
@@ -6571,6 +6942,12 @@ var DirectoryReadResultSchema = z2.looseObject({
|
|
|
6571
6942
|
resources: z2.array(DirectoryChildSchema),
|
|
6572
6943
|
nextCursor: z2.string().optional()
|
|
6573
6944
|
});
|
|
6945
|
+
var ModernDirectoryReadResultSchema = DirectoryReadResultSchema.extend(
|
|
6946
|
+
{
|
|
6947
|
+
ttlMs: z2.int().min(0),
|
|
6948
|
+
cacheScope: z2.enum(["public", "private"])
|
|
6949
|
+
}
|
|
6950
|
+
);
|
|
6574
6951
|
|
|
6575
6952
|
// ../../core/mcp/state/managedSkillsState.ts
|
|
6576
6953
|
var REPEATED_CURSOR_MESSAGE = "Server repeated a pagination cursor in skills/list; stopped to avoid an infinite walk.";
|
|
@@ -9823,6 +10200,23 @@ function subscriptionFailureMessage(error) {
|
|
|
9823
10200
|
return error instanceof Error ? error.message : String(error);
|
|
9824
10201
|
}
|
|
9825
10202
|
|
|
10203
|
+
// ../../core/mcp/subscriptionRefusal.ts
|
|
10204
|
+
import { SdkErrorCode as SdkErrorCode3, SdkHttpError } from "@modelcontextprotocol/client";
|
|
10205
|
+
var REFUSAL_STATUSES = /* @__PURE__ */ new Set([400, 403, 404, 405]);
|
|
10206
|
+
function subscriptionListenRefusal(error) {
|
|
10207
|
+
if (!SdkHttpError.isInstance(error) || error.code !== SdkErrorCode3.ClientHttpNotImplemented || !REFUSAL_STATUSES.has(error.status)) {
|
|
10208
|
+
return void 0;
|
|
10209
|
+
}
|
|
10210
|
+
return {
|
|
10211
|
+
status: error.status,
|
|
10212
|
+
...error.statusText ? { statusText: error.statusText } : {}
|
|
10213
|
+
};
|
|
10214
|
+
}
|
|
10215
|
+
function subscriptionListenRefusalMessage(refusal) {
|
|
10216
|
+
const status = refusal.statusText ? `${refusal.status} ${refusal.statusText}` : String(refusal.status);
|
|
10217
|
+
return `The server refused subscriptions/listen (HTTP ${status}). Not retrying \u2014 the server would answer the same way again.`;
|
|
10218
|
+
}
|
|
10219
|
+
|
|
9826
10220
|
// ../../core/mcp/messageTrackingTransport.ts
|
|
9827
10221
|
var MessageTrackingTransport = class {
|
|
9828
10222
|
baseTransport;
|
|
@@ -10052,7 +10446,7 @@ var TaskProgressRouter = class {
|
|
|
10052
10446
|
};
|
|
10053
10447
|
|
|
10054
10448
|
// ../../core/extension/tasks/rawWireChannel.ts
|
|
10055
|
-
import { SdkError as SdkError3, SdkErrorCode as
|
|
10449
|
+
import { SdkError as SdkError3, SdkErrorCode as SdkErrorCode4 } from "@modelcontextprotocol/client";
|
|
10056
10450
|
import { DispatchError as DispatchError2 } from "@modelcontextprotocol/ext-tasks/client";
|
|
10057
10451
|
var RAW_WIRE_ID_PREFIX = "inspector-ext-";
|
|
10058
10452
|
var RawWireChannel = class {
|
|
@@ -10125,8 +10519,8 @@ var RawWireChannel = class {
|
|
|
10125
10519
|
`Raw MCP request "${message.method}" timed out after ${timeoutMs} ms`,
|
|
10126
10520
|
false,
|
|
10127
10521
|
{
|
|
10128
|
-
cause: this.host.
|
|
10129
|
-
new SdkError3(
|
|
10522
|
+
cause: this.host.annotateError(
|
|
10523
|
+
new SdkError3(SdkErrorCode4.RequestTimeout, "Request timed out", {
|
|
10130
10524
|
timeout: timeoutMs
|
|
10131
10525
|
}),
|
|
10132
10526
|
message.method
|
|
@@ -10166,7 +10560,7 @@ var RawWireChannel = class {
|
|
|
10166
10560
|
if (!pending) return;
|
|
10167
10561
|
pending.cleanup();
|
|
10168
10562
|
reject(
|
|
10169
|
-
this.host.
|
|
10563
|
+
this.host.annotateError(
|
|
10170
10564
|
error instanceof Error ? error : new Error(String(error)),
|
|
10171
10565
|
message.method
|
|
10172
10566
|
)
|
|
@@ -10899,12 +11293,33 @@ var ToolCallCancelledError = class extends Error {
|
|
|
10899
11293
|
};
|
|
10900
11294
|
|
|
10901
11295
|
// ../../core/mcp/connectionDiagnostics.ts
|
|
10902
|
-
import { SdkError as SdkError4, SdkErrorCode as
|
|
11296
|
+
import { SdkError as SdkError4, SdkErrorCode as SdkErrorCode5 } from "@modelcontextprotocol/client";
|
|
10903
11297
|
function isRequestTimeoutData(value) {
|
|
10904
11298
|
return typeof value === "object" && value !== null && typeof value.timeout === "number";
|
|
10905
11299
|
}
|
|
10906
11300
|
function isRequestTimeoutError(err) {
|
|
10907
|
-
return SdkError4.isInstance(err) && err.code ===
|
|
11301
|
+
return SdkError4.isInstance(err) && err.code === SdkErrorCode5.RequestTimeout && isRequestTimeoutData(err.data);
|
|
11302
|
+
}
|
|
11303
|
+
function describeSendFailure(err) {
|
|
11304
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
11305
|
+
const status = httpStatusOf(err);
|
|
11306
|
+
if (status === void 0 || message.includes(`HTTP ${status}`)) {
|
|
11307
|
+
return message;
|
|
11308
|
+
}
|
|
11309
|
+
const statusText = httpStatusTextOf(err);
|
|
11310
|
+
return `HTTP ${status}${statusText ? ` ${statusText}` : ""}: ${message}`;
|
|
11311
|
+
}
|
|
11312
|
+
function httpStatusOf(err) {
|
|
11313
|
+
if (typeof err !== "object" || err === null || !("status" in err)) {
|
|
11314
|
+
return void 0;
|
|
11315
|
+
}
|
|
11316
|
+
return typeof err.status === "number" ? err.status : void 0;
|
|
11317
|
+
}
|
|
11318
|
+
function httpStatusTextOf(err) {
|
|
11319
|
+
if (typeof err !== "object" || err === null || !("statusText" in err)) {
|
|
11320
|
+
return void 0;
|
|
11321
|
+
}
|
|
11322
|
+
return typeof err.statusText === "string" ? err.statusText : void 0;
|
|
10908
11323
|
}
|
|
10909
11324
|
function formatDuration(ms) {
|
|
10910
11325
|
const clamped = Math.max(0, ms);
|
|
@@ -10966,11 +11381,108 @@ function annotateRequestTimeout(err, method, diagnostics) {
|
|
|
10966
11381
|
diagnostics
|
|
10967
11382
|
};
|
|
10968
11383
|
const message = `Request timed out after ${formatDuration(err.data.timeout)} (${method}). ${describeConnectionDiagnostics(diagnostics)}`;
|
|
10969
|
-
const annotated = new SdkError4(
|
|
11384
|
+
const annotated = new SdkError4(SdkErrorCode5.RequestTimeout, message, data);
|
|
10970
11385
|
annotated.cause = err;
|
|
10971
11386
|
return annotated;
|
|
10972
11387
|
}
|
|
10973
11388
|
|
|
11389
|
+
// ../../core/mcp/rateLimit.ts
|
|
11390
|
+
import {
|
|
11391
|
+
SdkHttpError as SdkHttpError2
|
|
11392
|
+
} from "@modelcontextprotocol/client";
|
|
11393
|
+
var HTTP_TOO_MANY_REQUESTS = 429;
|
|
11394
|
+
function requestIdsOf(body) {
|
|
11395
|
+
if (body === void 0) return [];
|
|
11396
|
+
let parsed;
|
|
11397
|
+
try {
|
|
11398
|
+
parsed = JSON.parse(body);
|
|
11399
|
+
} catch {
|
|
11400
|
+
return [];
|
|
11401
|
+
}
|
|
11402
|
+
const messages = Array.isArray(parsed) ? parsed : [parsed];
|
|
11403
|
+
const ids = [];
|
|
11404
|
+
for (const message of messages) {
|
|
11405
|
+
if (typeof message !== "object" || message === null) continue;
|
|
11406
|
+
if (!("method" in message) || !("id" in message)) continue;
|
|
11407
|
+
const { id } = message;
|
|
11408
|
+
if (typeof id === "string" || typeof id === "number") ids.push(id);
|
|
11409
|
+
}
|
|
11410
|
+
return ids;
|
|
11411
|
+
}
|
|
11412
|
+
var MAX_UNCLAIMED_RATE_LIMITS = 100;
|
|
11413
|
+
var RateLimitLedger = class {
|
|
11414
|
+
// `undefined` is a real value here: a 429 that carried no header.
|
|
11415
|
+
byRequestId = /* @__PURE__ */ new Map();
|
|
11416
|
+
/** File a tracked fetch, if it was a 429. Anything else is ignored. */
|
|
11417
|
+
record(entry) {
|
|
11418
|
+
if (entry.responseStatus !== HTTP_TOO_MANY_REQUESTS) return;
|
|
11419
|
+
const retryAfter = findHeader(entry.responseHeaders, "retry-after");
|
|
11420
|
+
for (const id of requestIdsOf(entry.requestBody)) {
|
|
11421
|
+
this.byRequestId.delete(id);
|
|
11422
|
+
this.byRequestId.set(id, retryAfter);
|
|
11423
|
+
if (this.byRequestId.size > MAX_UNCLAIMED_RATE_LIMITS) {
|
|
11424
|
+
const [oldest] = this.byRequestId.keys();
|
|
11425
|
+
if (oldest !== void 0) this.byRequestId.delete(oldest);
|
|
11426
|
+
}
|
|
11427
|
+
}
|
|
11428
|
+
}
|
|
11429
|
+
/** The `Retry-After` filed for `id`, removed as it is read. */
|
|
11430
|
+
take(id) {
|
|
11431
|
+
const retryAfter = this.byRequestId.get(id);
|
|
11432
|
+
this.byRequestId.delete(id);
|
|
11433
|
+
return retryAfter;
|
|
11434
|
+
}
|
|
11435
|
+
clear() {
|
|
11436
|
+
this.byRequestId.clear();
|
|
11437
|
+
}
|
|
11438
|
+
};
|
|
11439
|
+
var IMF_FIXDATE = /^[A-Z][a-z]{2}, \d{2} [A-Z][a-z]{2} \d{4} \d{2}:\d{2}:\d{2} GMT$/;
|
|
11440
|
+
function parseRetryAfter(value, now) {
|
|
11441
|
+
if (value === void 0) return void 0;
|
|
11442
|
+
const trimmed = value.trim();
|
|
11443
|
+
if (/^\d+$/.test(trimmed)) return Number(trimmed) * 1e3;
|
|
11444
|
+
if (!IMF_FIXDATE.test(trimmed)) return void 0;
|
|
11445
|
+
const at = Date.parse(trimmed);
|
|
11446
|
+
if (Number.isNaN(at) || new Date(at).toUTCString() !== trimmed) {
|
|
11447
|
+
return void 0;
|
|
11448
|
+
}
|
|
11449
|
+
return Math.max(0, at - now);
|
|
11450
|
+
}
|
|
11451
|
+
function isRateLimitError(err) {
|
|
11452
|
+
return SdkHttpError2.isInstance(err) && err.status === HTTP_TOO_MANY_REQUESTS;
|
|
11453
|
+
}
|
|
11454
|
+
function retryAfterOn(err) {
|
|
11455
|
+
const value = err.data.retryAfter;
|
|
11456
|
+
return typeof value === "string" ? value : void 0;
|
|
11457
|
+
}
|
|
11458
|
+
function attachRetryAfter(err, retryAfter) {
|
|
11459
|
+
if (!isRateLimitError(err) || retryAfter === void 0) return;
|
|
11460
|
+
if (retryAfterOn(err) !== void 0) return;
|
|
11461
|
+
err.data.retryAfter = retryAfter;
|
|
11462
|
+
}
|
|
11463
|
+
function describeRetry(retryAfter, retryAfterMs) {
|
|
11464
|
+
if (retryAfterMs !== void 0) {
|
|
11465
|
+
return `The server asked to retry after ${formatDuration(retryAfterMs)}.`;
|
|
11466
|
+
}
|
|
11467
|
+
if (retryAfter !== void 0) {
|
|
11468
|
+
return `The server sent an unparseable Retry-After (${retryAfter}).`;
|
|
11469
|
+
}
|
|
11470
|
+
return "The server sent no Retry-After header.";
|
|
11471
|
+
}
|
|
11472
|
+
function annotateRateLimit(err, method, now = Date.now()) {
|
|
11473
|
+
if (!isRateLimitError(err)) return err;
|
|
11474
|
+
const header = retryAfterOn(err);
|
|
11475
|
+
const retryAfterMs = parseRetryAfter(header, now);
|
|
11476
|
+
const data = {
|
|
11477
|
+
...err.data,
|
|
11478
|
+
method,
|
|
11479
|
+
...header !== void 0 && { retryAfter: header },
|
|
11480
|
+
...retryAfterMs !== void 0 && { retryAfterMs }
|
|
11481
|
+
};
|
|
11482
|
+
const message = `Rate limited by the server (HTTP 429) on ${method}. ${describeRetry(header, retryAfterMs)} ${err.message}`;
|
|
11483
|
+
return new SdkHttpError2(err.code, message, data, { cause: err });
|
|
11484
|
+
}
|
|
11485
|
+
|
|
10974
11486
|
// ../../core/auth/ema/clientConfigError.ts
|
|
10975
11487
|
function emaClientNotConfiguredMessage(reason) {
|
|
10976
11488
|
if (reason === "disabled") {
|
|
@@ -11406,11 +11918,6 @@ async function mintEmaResourceTokens(config, resourceContext) {
|
|
|
11406
11918
|
"EMA requires resource authorization server clientId (per-server oauth.clientId)"
|
|
11407
11919
|
);
|
|
11408
11920
|
}
|
|
11409
|
-
if (!config.resourceClientSecret?.trim()) {
|
|
11410
|
-
throw new Error(
|
|
11411
|
-
"EMA requires resource authorization server client secret in server OAuth settings (Test Client secret from xaa.dev resource registration)"
|
|
11412
|
-
);
|
|
11413
|
-
}
|
|
11414
11921
|
const idToken = await getValidIdToken({
|
|
11415
11922
|
idp: config.idp,
|
|
11416
11923
|
storage: config.storage,
|
|
@@ -11676,7 +12183,9 @@ var OAuthManager = class {
|
|
|
11676
12183
|
requestRefreshToken: this.oauthConfig.requestRefreshToken,
|
|
11677
12184
|
// #2068: lets the provider tell an `offline_access` the user asked for
|
|
11678
12185
|
// from one inherited from a previous grant's persisted scope.
|
|
11679
|
-
configuredScope: this.oauthConfig.scope
|
|
12186
|
+
configuredScope: this.oauthConfig.scope,
|
|
12187
|
+
// #2726: in-flight PKCE verifiers keyed by flow, not by server URL.
|
|
12188
|
+
flowStore: this.oauthConfig.flowStore
|
|
11680
12189
|
});
|
|
11681
12190
|
provider.setEventTarget(this.params.getEventTarget());
|
|
11682
12191
|
await provider.prepareForAuth();
|
|
@@ -11840,7 +12349,12 @@ var OAuthManager = class {
|
|
|
11840
12349
|
recordAuthorizationRequestScope(authorizationUrl, requestedScope) {
|
|
11841
12350
|
this.pendingAuthorizationScope = authorizationUrl.searchParams.get("scope")?.trim() || requestedScope;
|
|
11842
12351
|
}
|
|
11843
|
-
|
|
12352
|
+
/**
|
|
12353
|
+
* `state` is the OAuth `state` the callback was redirected back with. It
|
|
12354
|
+
* selects the flow whose PKCE verifier the exchange uses (#2726); without it
|
|
12355
|
+
* a provider with a flow store has no verifier to send.
|
|
12356
|
+
*/
|
|
12357
|
+
async completeOAuthFlow(authorizationCode, iss, state) {
|
|
11844
12358
|
try {
|
|
11845
12359
|
if (this.isEnterpriseManaged()) {
|
|
11846
12360
|
const emaConfig = await this.getEmaFlowConfig();
|
|
@@ -11864,6 +12378,9 @@ var OAuthManager = class {
|
|
|
11864
12378
|
}
|
|
11865
12379
|
const provider = await this.createOAuthProvider();
|
|
11866
12380
|
const serverUrl = this.getServerUrl();
|
|
12381
|
+
if (state !== void 0) {
|
|
12382
|
+
provider.resumeAuthorizationFlow(state);
|
|
12383
|
+
}
|
|
11867
12384
|
const result = await mcpAuth(provider, {
|
|
11868
12385
|
serverUrl,
|
|
11869
12386
|
authorizationCode,
|
|
@@ -11875,6 +12392,7 @@ var OAuthManager = class {
|
|
|
11875
12392
|
`Expected AUTHORIZED after providing authorization code, got: ${result}`
|
|
11876
12393
|
);
|
|
11877
12394
|
}
|
|
12395
|
+
await provider.clearAuthorizationFlow();
|
|
11878
12396
|
const tokens = await provider.tokens();
|
|
11879
12397
|
if (!tokens) {
|
|
11880
12398
|
throw new Error("Failed to retrieve tokens after authorization");
|
|
@@ -12006,15 +12524,21 @@ var OAuthManager = class {
|
|
|
12006
12524
|
* Re-read persisted OAuth state and determine whether `challenge` is already
|
|
12007
12525
|
* satisfied without an authorization-server round-trip.
|
|
12008
12526
|
*
|
|
12009
|
-
*
|
|
12010
|
-
*
|
|
12011
|
-
*
|
|
12012
|
-
*
|
|
12013
|
-
*
|
|
12014
|
-
*
|
|
12015
|
-
*
|
|
12527
|
+
* For `token_expired` and `insufficient_scope`, returns `true` only when
|
|
12528
|
+
* storage holds a *provably* unexpired token — a JWT whose `exp` is still in
|
|
12529
|
+
* the future. An opaque token carries no local expiry evidence, so the
|
|
12530
|
+
* resource server's verdict stands and re-authorization proceeds.
|
|
12531
|
+
* `insufficient_scope` additionally requires stored + token scope to cover
|
|
12532
|
+
* the SEP-2350 union: some resource servers answer an *expired* token with
|
|
12533
|
+
* `insufficient_scope`, and a scope-only check would replay that dead token
|
|
12534
|
+
* instead of refreshing it (#2724, the same gap #2051 closed for
|
|
12535
|
+
* `token_expired`). `invalid_token` and `unauthorized` always return
|
|
12536
|
+
* `false` — the resource server explicitly rejected the credential.
|
|
12537
|
+
*
|
|
12538
|
+
* A caller that has just completed a grant passes `scopeOnly` — see
|
|
12539
|
+
* {@link CheckAuthChallengeSatisfiedOptions}.
|
|
12016
12540
|
*/
|
|
12017
|
-
async checkAuthChallengeSatisfied(challenge) {
|
|
12541
|
+
async checkAuthChallengeSatisfied(challenge, options) {
|
|
12018
12542
|
const storage = this.oauthConfig.storage;
|
|
12019
12543
|
if (!storage) {
|
|
12020
12544
|
return false;
|
|
@@ -12027,6 +12551,14 @@ var OAuthManager = class {
|
|
|
12027
12551
|
if (challenge.reason !== "insufficient_scope") {
|
|
12028
12552
|
return challenge.reason === "token_expired" && isAccessTokenProvablyUnexpired(tokens);
|
|
12029
12553
|
}
|
|
12554
|
+
if (!options?.scopeOnly && !isAccessTokenProvablyUnexpired(tokens)) {
|
|
12555
|
+
return false;
|
|
12556
|
+
}
|
|
12557
|
+
return this.isStepUpScopeCovered(challenge, storage, tokens);
|
|
12558
|
+
}
|
|
12559
|
+
/** Stored + token scope cover the challenge's SEP-2350 union. */
|
|
12560
|
+
async isStepUpScopeCovered(challenge, storage, tokens) {
|
|
12561
|
+
const serverUrl = this.getServerUrl();
|
|
12030
12562
|
const enriched = await this.enrichChallengeWithAuthorizationScopes(
|
|
12031
12563
|
challenge,
|
|
12032
12564
|
tokens.scope
|
|
@@ -12130,14 +12662,14 @@ var OAuthManager = class {
|
|
|
12130
12662
|
}
|
|
12131
12663
|
async handleEnterpriseManagedAuthChallenge(challenge, options) {
|
|
12132
12664
|
const enriched = await this.enrichChallengeWithAuthorizationScopes(challenge);
|
|
12133
|
-
if (enriched.reason === "insufficient_scope" && !options?.confirmedStepUp) {
|
|
12665
|
+
if (enriched.reason === "insufficient_scope" && !options?.confirmedStepUp && !await this.checkAuthChallengeSatisfied(enriched, { scopeOnly: true })) {
|
|
12134
12666
|
return { kind: "step_up_confirm", challenge: enriched };
|
|
12135
12667
|
}
|
|
12136
12668
|
const config = await this.emaFlowConfigForChallenge(enriched);
|
|
12137
12669
|
if (enriched.reason === "insufficient_scope") {
|
|
12138
12670
|
const silent = await trySilentEmaAuth(config);
|
|
12139
12671
|
if (silent.status === "success") {
|
|
12140
|
-
if (await this.checkAuthChallengeSatisfied(enriched)) {
|
|
12672
|
+
if (await this.checkAuthChallengeSatisfied(enriched, { scopeOnly: true })) {
|
|
12141
12673
|
const minted = await this.requireStorage().getTokens(
|
|
12142
12674
|
this.getServerUrl()
|
|
12143
12675
|
);
|
|
@@ -12215,9 +12747,16 @@ var OAuthManager = class {
|
|
|
12215
12747
|
fetchFn: this.params.effectiveAuthFetch,
|
|
12216
12748
|
resourceMetadataUrl,
|
|
12217
12749
|
...enriched.reason === "insufficient_scope" && {
|
|
12750
|
+
// Judge coverage the way checkAuthChallengeSatisfied does: a token
|
|
12751
|
+
// response that omitted `scope` granted what was stored, so a
|
|
12752
|
+
// covered challenge on an expired token refreshes rather than
|
|
12753
|
+
// forcing interactive authorization (#2724).
|
|
12218
12754
|
forceReauthorization: isStrictScopeSuperset(
|
|
12219
12755
|
scopeForAuth,
|
|
12220
|
-
|
|
12756
|
+
resolveEffectiveGrantedScope(
|
|
12757
|
+
await this.oauthConfig.storage?.getScope(serverUrl),
|
|
12758
|
+
tokens?.scope
|
|
12759
|
+
)
|
|
12221
12760
|
)
|
|
12222
12761
|
}
|
|
12223
12762
|
});
|
|
@@ -12226,7 +12765,7 @@ var OAuthManager = class {
|
|
|
12226
12765
|
}
|
|
12227
12766
|
if (result === "AUTHORIZED") {
|
|
12228
12767
|
if (enriched.reason === "insufficient_scope") {
|
|
12229
|
-
if (await this.checkAuthChallengeSatisfied(enriched)) {
|
|
12768
|
+
if (await this.checkAuthChallengeSatisfied(enriched, { scopeOnly: true })) {
|
|
12230
12769
|
const freshTokens = await provider.tokens();
|
|
12231
12770
|
const scopeToPersist = resolvePersistedScopeAfterGrant(
|
|
12232
12771
|
freshTokens?.scope,
|
|
@@ -12328,7 +12867,7 @@ var OAuthManager = class {
|
|
|
12328
12867
|
if (result !== "AUTHORIZED") {
|
|
12329
12868
|
return null;
|
|
12330
12869
|
}
|
|
12331
|
-
if (await this.checkAuthChallengeSatisfied(enriched)) {
|
|
12870
|
+
if (await this.checkAuthChallengeSatisfied(enriched, { scopeOnly: true })) {
|
|
12332
12871
|
const freshTokens = await provider.tokens();
|
|
12333
12872
|
const scopeToPersist = resolvePersistedScopeAfterGrant(
|
|
12334
12873
|
freshTokens?.scope,
|
|
@@ -12360,8 +12899,8 @@ var OAuthManager = class {
|
|
|
12360
12899
|
// ../../core/mcp/remote/remoteClientTransport.ts
|
|
12361
12900
|
import {
|
|
12362
12901
|
SdkError as SdkError5,
|
|
12363
|
-
SdkErrorCode as
|
|
12364
|
-
SdkHttpError
|
|
12902
|
+
SdkErrorCode as SdkErrorCode6,
|
|
12903
|
+
SdkHttpError as SdkHttpError3
|
|
12365
12904
|
} from "@modelcontextprotocol/client";
|
|
12366
12905
|
|
|
12367
12906
|
// ../../core/mcp/remote/types.ts
|
|
@@ -12418,14 +12957,15 @@ function isConnectTransportError(json) {
|
|
|
12418
12957
|
return typeof json === "object" && json !== null && "ok" in json && json.ok === false && "kind" in json && json.kind === "transport_error";
|
|
12419
12958
|
}
|
|
12420
12959
|
var SDK_ERROR_CODES = new Set(
|
|
12421
|
-
Object.values(
|
|
12960
|
+
Object.values(SdkErrorCode6)
|
|
12422
12961
|
);
|
|
12423
12962
|
function toSdkHttpError(message, http) {
|
|
12424
|
-
const code = SDK_ERROR_CODES.has(http.code) ? http.code :
|
|
12425
|
-
return new
|
|
12963
|
+
const code = SDK_ERROR_CODES.has(http.code) ? http.code : SdkErrorCode6.ClientHttpNotImplemented;
|
|
12964
|
+
return new SdkHttpError3(code, message, {
|
|
12426
12965
|
status: http.status,
|
|
12427
12966
|
...http.statusText !== void 0 && { statusText: http.statusText },
|
|
12428
|
-
...http.text !== void 0 && { text: http.text }
|
|
12967
|
+
...http.text !== void 0 && { text: http.text },
|
|
12968
|
+
...http.retryAfter !== void 0 && { retryAfter: http.retryAfter }
|
|
12429
12969
|
});
|
|
12430
12970
|
}
|
|
12431
12971
|
function legacySessionId(json) {
|
|
@@ -12859,7 +13399,7 @@ var RemoteClientTransport = class {
|
|
|
12859
13399
|
timer = setTimeout(() => {
|
|
12860
13400
|
this.sseResponseWaits.delete(requestId);
|
|
12861
13401
|
reject(
|
|
12862
|
-
new SdkError5(
|
|
13402
|
+
new SdkError5(SdkErrorCode6.RequestTimeout, "Request timed out", {
|
|
12863
13403
|
timeout: this.sseResponseTimeoutMs
|
|
12864
13404
|
})
|
|
12865
13405
|
);
|
|
@@ -13071,7 +13611,7 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
|
|
|
13071
13611
|
transport: () => this.transport,
|
|
13072
13612
|
defaultTimeoutMs: () => this.requestTimeout ?? DEFAULT_REQUEST_TIMEOUT_MSEC,
|
|
13073
13613
|
resetTimeoutOnProgress: () => this.resetTimeoutOnProgress,
|
|
13074
|
-
|
|
13614
|
+
annotateError: (error, method) => this.annotateRequestError(error, method)
|
|
13075
13615
|
});
|
|
13076
13616
|
dispatchTaskRequest = (request, options) => this.rawWire.dispatch(request, options);
|
|
13077
13617
|
// Every outbound request still awaiting a response, keyed by JSON-RPC id.
|
|
@@ -13091,6 +13631,10 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
|
|
|
13091
13631
|
// `GET` on Streamable HTTP). Both per-session, reset with the maps above.
|
|
13092
13632
|
lastResponse;
|
|
13093
13633
|
notificationStream;
|
|
13634
|
+
// Each tracked `429`'s `Retry-After`, by the JSON-RPC id that drew it, until
|
|
13635
|
+
// that id's failed send claims it (#2442). Per-session, reset with the
|
|
13636
|
+
// diagnostics above.
|
|
13637
|
+
rateLimits = new RateLimitLedger();
|
|
13094
13638
|
/** True when the cached transport was built with an OAuth authProvider attached. */
|
|
13095
13639
|
transportHasAuthProvider = false;
|
|
13096
13640
|
/** Dedupes concurrent ambient auth challenges (reason + scopes). */
|
|
@@ -13235,6 +13779,10 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
|
|
|
13235
13779
|
// site to the two failure handlers. Cleared by any user-initiated refresh (a
|
|
13236
13780
|
// subscribe/unsubscribe is a fresh attempt, and the server may have changed).
|
|
13237
13781
|
modernNeverAcknowledged = false;
|
|
13782
|
+
// Set when the last `listen()` was refused at the HTTP layer with a status a
|
|
13783
|
+
// retry cannot change (#2722) — the sibling of `modernNeverAcknowledged`,
|
|
13784
|
+
// carried and cleared the same way, holding the HTTP answer the UI reports.
|
|
13785
|
+
modernListenRefusal = null;
|
|
13238
13786
|
/** Which in-flight task calls own each progress token (#2316). */
|
|
13239
13787
|
taskProgress = new TaskProgressRouter();
|
|
13240
13788
|
// Abort controller for the in-flight ordinary (non-task) tool call. Aborting
|
|
@@ -13492,7 +14040,7 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
|
|
|
13492
14040
|
this.clientInfo,
|
|
13493
14041
|
Object.keys(clientOptions).length > 0 ? clientOptions : void 0
|
|
13494
14042
|
);
|
|
13495
|
-
this.
|
|
14043
|
+
this.annotateSdkRequestErrors(this.client);
|
|
13496
14044
|
if (this.tasksCapabilityAdvertised) {
|
|
13497
14045
|
this.bindReceiverTasks();
|
|
13498
14046
|
}
|
|
@@ -13500,7 +14048,8 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
|
|
|
13500
14048
|
/**
|
|
13501
14049
|
* Decorate the SDK Client's `request` so a per-request timeout rejects with
|
|
13502
14050
|
* the connection's state at that moment instead of a bare `Request timed
|
|
13503
|
-
* out` (#2318)
|
|
14051
|
+
* out` (#2318), and a `429` rejects as rate limiting with the server's
|
|
14052
|
+
* `Retry-After` instead of a bare `Error POSTing to endpoint` (#2442).
|
|
13504
14053
|
*
|
|
13505
14054
|
* `Protocol.request` is the one funnel every SDK verb goes through —
|
|
13506
14055
|
* `listTools`, `callTool`, `readResource`, the handshake's `initialize`,
|
|
@@ -13518,21 +14067,30 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
|
|
|
13518
14067
|
* implementation signature serves both at runtime; the single cast on the
|
|
13519
14068
|
* way back is what the overloads cost.
|
|
13520
14069
|
*/
|
|
13521
|
-
|
|
14070
|
+
annotateSdkRequestErrors(client) {
|
|
13522
14071
|
const sdkRequest = client.request.bind(client);
|
|
13523
14072
|
const decorated = async (...args) => {
|
|
13524
14073
|
try {
|
|
13525
14074
|
return await sdkRequest(...args);
|
|
13526
14075
|
} catch (err) {
|
|
13527
|
-
throw
|
|
13528
|
-
err,
|
|
13529
|
-
args[0].method,
|
|
13530
|
-
this.getConnectionDiagnostics()
|
|
13531
|
-
);
|
|
14076
|
+
throw this.annotateRequestError(err, args[0].method);
|
|
13532
14077
|
}
|
|
13533
14078
|
};
|
|
13534
14079
|
client.request = decorated;
|
|
13535
14080
|
}
|
|
14081
|
+
/**
|
|
14082
|
+
* The one place a failed request's error is decorated, shared by the SDK
|
|
14083
|
+
* funnel above and the raw-wire channel that bypasses it (modern-era tool
|
|
14084
|
+
* calls and task operations), so a caller sees the same error whichever
|
|
14085
|
+
* path its request took: a timeout with the connection's state (#2318), a
|
|
14086
|
+
* `429` named as rate limiting (#2442), anything else untouched.
|
|
14087
|
+
*/
|
|
14088
|
+
annotateRequestError(err, method) {
|
|
14089
|
+
return annotateRateLimit(
|
|
14090
|
+
annotateRequestTimeout(err, method, this.getConnectionDiagnostics()),
|
|
14091
|
+
method
|
|
14092
|
+
);
|
|
14093
|
+
}
|
|
13536
14094
|
/**
|
|
13537
14095
|
* @param base - the composed OAuth-path fetch, deadline innermost (#2319).
|
|
13538
14096
|
*
|
|
@@ -13576,9 +14134,14 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
|
|
|
13576
14134
|
},
|
|
13577
14135
|
trackSendFailure: (message, error) => {
|
|
13578
14136
|
if (isRequestTimeoutError(error)) return;
|
|
14137
|
+
attachRetryAfter(error, this.rateLimits.take(message.id));
|
|
13579
14138
|
if (this.outstandingRequests.delete(message.id)) {
|
|
13580
14139
|
this.dispatchConnectionDiagnosticsChange();
|
|
13581
14140
|
}
|
|
14141
|
+
this.dispatchTypedEvent("requestFailed", {
|
|
14142
|
+
id: message.id,
|
|
14143
|
+
reason: describeSendFailure(error)
|
|
14144
|
+
});
|
|
13582
14145
|
},
|
|
13583
14146
|
trackResponse: (message, origin) => {
|
|
13584
14147
|
const responseId = message.id;
|
|
@@ -13644,6 +14207,7 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
|
|
|
13644
14207
|
};
|
|
13645
14208
|
baseTransport.onerror = (error) => {
|
|
13646
14209
|
if (this.status === "connecting") return;
|
|
14210
|
+
if (subscriptionListenRefusal(error) !== void 0) return;
|
|
13647
14211
|
this.status = "error";
|
|
13648
14212
|
this.dispatchTypedEvent("statusChange", this.status);
|
|
13649
14213
|
this.dispatchTypedEvent("error", error);
|
|
@@ -13889,6 +14453,7 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
|
|
|
13889
14453
|
this.clearModernReconnectTimer();
|
|
13890
14454
|
this.modernReconnectAttempts = 0;
|
|
13891
14455
|
this.modernNeverAcknowledged = false;
|
|
14456
|
+
this.modernListenRefusal = null;
|
|
13892
14457
|
this.modernSubscription = null;
|
|
13893
14458
|
this.setModernStreamState(INACTIVE_SUBSCRIPTION_STREAM_STATE);
|
|
13894
14459
|
this.dispatchSubscriptionsChange();
|
|
@@ -13927,6 +14492,7 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
|
|
|
13927
14492
|
this.taskProgress.clear();
|
|
13928
14493
|
this.lastResponse = void 0;
|
|
13929
14494
|
this.notificationStream = void 0;
|
|
14495
|
+
this.rateLimits.clear();
|
|
13930
14496
|
this.dispatchConnectionDiagnosticsChange();
|
|
13931
14497
|
if (this.malformedListItems.size > 0) {
|
|
13932
14498
|
this.malformedListItems.clear();
|
|
@@ -14029,6 +14595,7 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
|
|
|
14029
14595
|
onFetchRequest: (entry) => {
|
|
14030
14596
|
this.dispatchFetchRequest({ ...entry, category: "transport" });
|
|
14031
14597
|
this.noteTransportStream(entry);
|
|
14598
|
+
this.rateLimits.record(entry);
|
|
14032
14599
|
},
|
|
14033
14600
|
onFetchResponseBody: (id, body) => {
|
|
14034
14601
|
this.dispatchFetchRequestBodyUpdate(id, body);
|
|
@@ -16368,11 +16935,12 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
|
|
|
16368
16935
|
// for page one.
|
|
16369
16936
|
...cursor !== void 0 ? { cursor } : {}
|
|
16370
16937
|
};
|
|
16938
|
+
const resultSchema = this.isModernEra() ? ModernDirectoryReadResultSchema : DirectoryReadResultSchema;
|
|
16371
16939
|
try {
|
|
16372
16940
|
return await this.invokeMcpClient(
|
|
16373
16941
|
() => this.client.request(
|
|
16374
16942
|
{ method: RESOURCES_DIRECTORY_READ_METHOD, params },
|
|
16375
|
-
|
|
16943
|
+
resultSchema,
|
|
16376
16944
|
this.getRequestOptions(this.progressTokenOf(metadata))
|
|
16377
16945
|
),
|
|
16378
16946
|
{ method: RESOURCES_DIRECTORY_READ_METHOD }
|
|
@@ -16827,6 +17395,7 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
|
|
|
16827
17395
|
this.clearModernReconnectTimer();
|
|
16828
17396
|
this.modernReconnectAttempts = 0;
|
|
16829
17397
|
this.modernNeverAcknowledged = false;
|
|
17398
|
+
this.modernListenRefusal = null;
|
|
16830
17399
|
}
|
|
16831
17400
|
const generation = ++this.modernListenGeneration;
|
|
16832
17401
|
const previous = this.modernSubscription;
|
|
@@ -16845,8 +17414,11 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
|
|
|
16845
17414
|
this.getRequestOptions()
|
|
16846
17415
|
);
|
|
16847
17416
|
} catch (error) {
|
|
16848
|
-
if (generation === this.modernListenGeneration
|
|
16849
|
-
|
|
17417
|
+
if (generation === this.modernListenGeneration) {
|
|
17418
|
+
if (isNeverAcknowledgedSubscriptionClose(error)) {
|
|
17419
|
+
this.modernNeverAcknowledged = true;
|
|
17420
|
+
}
|
|
17421
|
+
this.modernListenRefusal = subscriptionListenRefusal(error) ?? null;
|
|
16850
17422
|
}
|
|
16851
17423
|
throw error;
|
|
16852
17424
|
}
|
|
@@ -16927,6 +17499,10 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
|
|
|
16927
17499
|
* its error either way — the retry is about the subscriptions, not the call.
|
|
16928
17500
|
*/
|
|
16929
17501
|
reconcileModernStreamStateAfterFailedRefresh() {
|
|
17502
|
+
if (this.modernListenRefusal) {
|
|
17503
|
+
this.endModernStreamRefused(this.modernListenRefusal);
|
|
17504
|
+
return;
|
|
17505
|
+
}
|
|
16930
17506
|
if (!this.wantsModernStream()) {
|
|
16931
17507
|
this.setModernStreamState(INACTIVE_SUBSCRIPTION_STREAM_STATE);
|
|
16932
17508
|
return;
|
|
@@ -16959,6 +17535,23 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
|
|
|
16959
17535
|
honoredUris: []
|
|
16960
17536
|
});
|
|
16961
17537
|
}
|
|
17538
|
+
/**
|
|
17539
|
+
* Settle the stream on an HTTP refusal of the listen (#2722): the
|
|
17540
|
+
* `"refused"` status carrying the HTTP answer, no reconnect, and a log line
|
|
17541
|
+
* with the sentence the UI shows. Reported whatever `active` says — see
|
|
17542
|
+
* `ResourceSubscriptionStreamState`.
|
|
17543
|
+
*/
|
|
17544
|
+
endModernStreamRefused(refusal) {
|
|
17545
|
+
this.clearModernReconnectTimer();
|
|
17546
|
+
const message = subscriptionListenRefusalMessage(refusal);
|
|
17547
|
+
this.logger.warn({ message }, "subscriptions/listen refused over HTTP");
|
|
17548
|
+
this.setModernStreamState({
|
|
17549
|
+
active: this.modernStreamActive(),
|
|
17550
|
+
status: "refused",
|
|
17551
|
+
honoredUris: [],
|
|
17552
|
+
refusal
|
|
17553
|
+
});
|
|
17554
|
+
}
|
|
16962
17555
|
/**
|
|
16963
17556
|
* Schedule a reconnect re-listen after the current backoff delay (#1630).
|
|
16964
17557
|
* `modernReconnectAttempts` reflects the number of *consecutive failed*
|
|
@@ -16996,6 +17589,10 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
|
|
|
16996
17589
|
this.endModernStreamNeverAcknowledged();
|
|
16997
17590
|
return;
|
|
16998
17591
|
}
|
|
17592
|
+
if (this.modernListenRefusal && !isTerminalStatus(this.status)) {
|
|
17593
|
+
this.endModernStreamRefused(this.modernListenRefusal);
|
|
17594
|
+
return;
|
|
17595
|
+
}
|
|
16999
17596
|
this.modernReconnectAttempts += 1;
|
|
17000
17597
|
if (this.modernReconnectAttempts > MODERN_RECONNECT_MAX_ATTEMPTS || isTerminalStatus(this.status) || !this.wantsModernStream()) {
|
|
17001
17598
|
this.setModernStreamState({
|
|
@@ -17150,8 +17747,11 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
|
|
|
17150
17747
|
* Re-read OAuth storage and test whether a challenge is already satisfied.
|
|
17151
17748
|
* See {@link OAuthManager.checkAuthChallengeSatisfied}.
|
|
17152
17749
|
*/
|
|
17153
|
-
async checkAuthChallengeSatisfied(challenge) {
|
|
17154
|
-
return this.ensureOAuthManager().checkAuthChallengeSatisfied(
|
|
17750
|
+
async checkAuthChallengeSatisfied(challenge, options) {
|
|
17751
|
+
return this.ensureOAuthManager().checkAuthChallengeSatisfied(
|
|
17752
|
+
challenge,
|
|
17753
|
+
options
|
|
17754
|
+
);
|
|
17155
17755
|
}
|
|
17156
17756
|
/**
|
|
17157
17757
|
* Push recovered OAuth auth state to the remote backend (same MCP session).
|
|
@@ -17315,8 +17915,12 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
|
|
|
17315
17915
|
* Direct transports reconnect after token exchange so the live MCP session
|
|
17316
17916
|
* picks up the new Bearer token (mirrors silent recovery reconnect).
|
|
17317
17917
|
*/
|
|
17318
|
-
async completeOAuthFlow(authorizationCode, iss) {
|
|
17319
|
-
await this.ensureOAuthManager().completeOAuthFlow(
|
|
17918
|
+
async completeOAuthFlow(authorizationCode, iss, state) {
|
|
17919
|
+
await this.ensureOAuthManager().completeOAuthFlow(
|
|
17920
|
+
authorizationCode,
|
|
17921
|
+
iss,
|
|
17922
|
+
state
|
|
17923
|
+
);
|
|
17320
17924
|
if (this.usesDirectAuthRecovery()) {
|
|
17321
17925
|
await this.reconnectAfterAuthRecovery();
|
|
17322
17926
|
}
|
|
@@ -17340,7 +17944,11 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
|
|
|
17340
17944
|
* Finish OAuth after a full-page redirect and reconnect (or reattach) the MCP session.
|
|
17341
17945
|
*/
|
|
17342
17946
|
async resumeAfterOAuth(authorizationCode, options) {
|
|
17343
|
-
await this.completeOAuthFlow(
|
|
17947
|
+
await this.completeOAuthFlow(
|
|
17948
|
+
authorizationCode,
|
|
17949
|
+
options?.iss,
|
|
17950
|
+
options?.state
|
|
17951
|
+
);
|
|
17344
17952
|
const remoteSessionId = options?.remoteSessionId;
|
|
17345
17953
|
const transport = this.baseTransport;
|
|
17346
17954
|
if (remoteSessionId && transport instanceof RemoteClientTransport) {
|
|
@@ -18895,7 +19503,11 @@ async function runRunnerInteractiveOAuth(options) {
|
|
|
18895
19503
|
path: options.callbackListen.pathname,
|
|
18896
19504
|
onCallback: async (params) => {
|
|
18897
19505
|
try {
|
|
18898
|
-
await options.client.completeOAuthFlow(
|
|
19506
|
+
await options.client.completeOAuthFlow(
|
|
19507
|
+
params.code,
|
|
19508
|
+
params.iss,
|
|
19509
|
+
params.state
|
|
19510
|
+
);
|
|
18899
19511
|
flowResolve();
|
|
18900
19512
|
} catch (err) {
|
|
18901
19513
|
flowReject(toRunnerOAuthError(err));
|
|
@@ -18947,7 +19559,10 @@ async function runRunnerInteractiveOAuth(options) {
|
|
|
18947
19559
|
}
|
|
18948
19560
|
if (options.authChallenge) {
|
|
18949
19561
|
const satisfied = await racingSignals(
|
|
18950
|
-
|
|
19562
|
+
// The token exchange just completed, so only scope is in question.
|
|
19563
|
+
options.client.checkAuthChallengeSatisfied(options.authChallenge, {
|
|
19564
|
+
scopeOnly: true
|
|
19565
|
+
})
|
|
18951
19566
|
);
|
|
18952
19567
|
if (!satisfied) {
|
|
18953
19568
|
return {
|
|
@@ -19337,14 +19952,25 @@ import * as path6 from "path";
|
|
|
19337
19952
|
// ../../core/node/openUrl.ts
|
|
19338
19953
|
import open from "open";
|
|
19339
19954
|
var OPEN_URL_TIMEOUT_MS = 5e3;
|
|
19340
|
-
|
|
19341
|
-
|
|
19342
|
-
|
|
19343
|
-
|
|
19955
|
+
var OPENABLE_PROTOCOLS = /* @__PURE__ */ new Set(["http:", "https:"]);
|
|
19956
|
+
function toOpenableHref(href) {
|
|
19957
|
+
let parsed;
|
|
19958
|
+
try {
|
|
19959
|
+
parsed = new URL(href);
|
|
19960
|
+
} catch {
|
|
19961
|
+
throw new Error("refusing to open an invalid URL");
|
|
19962
|
+
}
|
|
19963
|
+
if (!OPENABLE_PROTOCOLS.has(parsed.protocol)) {
|
|
19964
|
+
throw new Error(`refusing to open a non-http(s) URL (${parsed.protocol})`);
|
|
19965
|
+
}
|
|
19966
|
+
return parsed.href;
|
|
19967
|
+
}
|
|
19968
|
+
function absorbLateErrors(child) {
|
|
19969
|
+
child.on("error", () => {
|
|
19344
19970
|
});
|
|
19345
19971
|
}
|
|
19346
19972
|
async function openUrl(url, timeoutMs = OPEN_URL_TIMEOUT_MS) {
|
|
19347
|
-
const href = typeof url === "string" ? url : url.href;
|
|
19973
|
+
const href = toOpenableHref(typeof url === "string" ? url : url.href);
|
|
19348
19974
|
let timer;
|
|
19349
19975
|
const timeout = new Promise((_, reject) => {
|
|
19350
19976
|
timer = setTimeout(
|
|
@@ -19353,8 +19979,7 @@ async function openUrl(url, timeoutMs = OPEN_URL_TIMEOUT_MS) {
|
|
|
19353
19979
|
);
|
|
19354
19980
|
});
|
|
19355
19981
|
try {
|
|
19356
|
-
|
|
19357
|
-
await Promise.race([opened, timeout]);
|
|
19982
|
+
await Promise.race([open(href).then(absorbLateErrors), timeout]);
|
|
19358
19983
|
} finally {
|
|
19359
19984
|
clearTimeout(timer);
|
|
19360
19985
|
}
|