@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
|
@@ -86,6 +86,12 @@ var DirectoryReadResultSchema = z.looseObject({
|
|
|
86
86
|
resources: z.array(DirectoryChildSchema),
|
|
87
87
|
nextCursor: z.string().optional()
|
|
88
88
|
});
|
|
89
|
+
var ModernDirectoryReadResultSchema = DirectoryReadResultSchema.extend(
|
|
90
|
+
{
|
|
91
|
+
ttlMs: z.int().min(0),
|
|
92
|
+
cacheScope: z.enum(["public", "private"])
|
|
93
|
+
}
|
|
94
|
+
);
|
|
89
95
|
|
|
90
96
|
// ../../core/mcp/sha256.ts
|
|
91
97
|
var K = new Uint32Array([
|
|
@@ -2129,6 +2135,13 @@ var BaseOAuthClientProvider = class {
|
|
|
2129
2135
|
requestRefreshToken;
|
|
2130
2136
|
/** Server-configured scope, for the #2068 `offline_access` distinction. */
|
|
2131
2137
|
configuredScope;
|
|
2138
|
+
/** Per-flow verifier store (#2726); absent → the shared storage slot. */
|
|
2139
|
+
flowStore;
|
|
2140
|
+
/**
|
|
2141
|
+
* The authId of the flow this provider is driving: set by {@link state} on the
|
|
2142
|
+
* redirect leg and by {@link resumeAuthorizationFlow} on the callback leg.
|
|
2143
|
+
*/
|
|
2144
|
+
flowId;
|
|
2132
2145
|
constructor(serverUrl, oauthConfig) {
|
|
2133
2146
|
this.serverUrl = serverUrl;
|
|
2134
2147
|
this.storage = oauthConfig.storage;
|
|
@@ -2138,6 +2151,7 @@ var BaseOAuthClientProvider = class {
|
|
|
2138
2151
|
this.authorizationParams = oauthConfig.authorizationParams;
|
|
2139
2152
|
this.requestRefreshToken = oauthConfig.requestRefreshToken ?? true;
|
|
2140
2153
|
this.configuredScope = oauthConfig.configuredScope;
|
|
2154
|
+
this.flowStore = oauthConfig.flowStore;
|
|
2141
2155
|
}
|
|
2142
2156
|
/**
|
|
2143
2157
|
* Load persisted scope into {@link cachedScope} before SDK `auth()` (which
|
|
@@ -2208,7 +2222,24 @@ var BaseOAuthClientProvider = class {
|
|
|
2208
2222
|
return metadata;
|
|
2209
2223
|
}
|
|
2210
2224
|
state() {
|
|
2211
|
-
|
|
2225
|
+
const state = generateOAuthState();
|
|
2226
|
+
this.flowId = state;
|
|
2227
|
+
return state;
|
|
2228
|
+
}
|
|
2229
|
+
/**
|
|
2230
|
+
* Bind this provider to the flow a callback's `state` names, so
|
|
2231
|
+
* {@link codeVerifier} reads that flow's verifier and no other (#2726). A
|
|
2232
|
+
* `state` that is not one of ours binds nothing — and, with a flow store, a
|
|
2233
|
+
* flow-less read then fails rather than reaching for the shared slot.
|
|
2234
|
+
*/
|
|
2235
|
+
resumeAuthorizationFlow(state) {
|
|
2236
|
+
this.flowId = parseOAuthState(state)?.authId;
|
|
2237
|
+
}
|
|
2238
|
+
/** Drop the bound flow's verifier once its exchange has completed. */
|
|
2239
|
+
async clearAuthorizationFlow() {
|
|
2240
|
+
if (this.flowStore && this.flowId) {
|
|
2241
|
+
await this.flowStore.clearCodeVerifier(this.flowId);
|
|
2242
|
+
}
|
|
2212
2243
|
}
|
|
2213
2244
|
async clientInformation(ctx) {
|
|
2214
2245
|
const preregistered = await this.storage.getClientInformation(
|
|
@@ -2353,9 +2384,26 @@ var BaseOAuthClientProvider = class {
|
|
|
2353
2384
|
}
|
|
2354
2385
|
}
|
|
2355
2386
|
async saveCodeVerifier(codeVerifier) {
|
|
2387
|
+
if (this.flowStore && this.flowId) {
|
|
2388
|
+
await this.flowStore.saveCodeVerifier(
|
|
2389
|
+
this.flowId,
|
|
2390
|
+
this.serverUrl,
|
|
2391
|
+
codeVerifier
|
|
2392
|
+
);
|
|
2393
|
+
return;
|
|
2394
|
+
}
|
|
2356
2395
|
await this.storage.saveCodeVerifier(this.serverUrl, codeVerifier);
|
|
2357
2396
|
}
|
|
2358
2397
|
async codeVerifier() {
|
|
2398
|
+
if (this.flowStore) {
|
|
2399
|
+
const verifier2 = this.flowId ? await this.flowStore.getCodeVerifier(this.flowId, this.serverUrl) : void 0;
|
|
2400
|
+
if (!verifier2) {
|
|
2401
|
+
throw new Error(
|
|
2402
|
+
"No code verifier saved for this authorization flow. It may have been started in another browser tab or already completed; please re-authenticate."
|
|
2403
|
+
);
|
|
2404
|
+
}
|
|
2405
|
+
return verifier2;
|
|
2406
|
+
}
|
|
2359
2407
|
const verifier = await this.storage.getCodeVerifier(this.serverUrl);
|
|
2360
2408
|
if (!verifier) {
|
|
2361
2409
|
throw new Error("No code verifier saved for session");
|
|
@@ -2402,6 +2450,10 @@ var BaseOAuthClientProvider = class {
|
|
|
2402
2450
|
await this.storage.clearTokens(this.serverUrl);
|
|
2403
2451
|
return;
|
|
2404
2452
|
case "verifier":
|
|
2453
|
+
if (this.flowStore) {
|
|
2454
|
+
await this.clearAuthorizationFlow();
|
|
2455
|
+
return;
|
|
2456
|
+
}
|
|
2405
2457
|
await this.storage.clearCodeVerifier(this.serverUrl);
|
|
2406
2458
|
return;
|
|
2407
2459
|
case "discovery":
|
|
@@ -4885,7 +4937,7 @@ async function resolveConcreteSecretStore(store) {
|
|
|
4885
4937
|
import { resolve as resolve5 } from "path";
|
|
4886
4938
|
|
|
4887
4939
|
// ../../core/auth/node/oauth-secrets.ts
|
|
4888
|
-
import { randomUUID as randomUUID2 } from "crypto";
|
|
4940
|
+
import { createHmac, randomUUID as randomUUID2 } from "crypto";
|
|
4889
4941
|
import { OAuthTokensSchema as OAuthTokensSchema3 } from "@modelcontextprotocol/core";
|
|
4890
4942
|
var PERSIST_TOKENS_ENV = "MCP_INSPECTOR_PERSIST_TOKENS";
|
|
4891
4943
|
var warnedPolicyValues = /* @__PURE__ */ new Set();
|
|
@@ -4918,6 +4970,29 @@ var LEGACY_REGISTRATION_TOKEN_FIELD = "registration-token";
|
|
|
4918
4970
|
var PREREG_CLIENT_SECRET_FIELD = "prereg-client-secret";
|
|
4919
4971
|
var PREREG_REGISTRATION_TOKEN_FIELD = "prereg-registration-token";
|
|
4920
4972
|
var IDP_SESSION_FIELD = "idp-session";
|
|
4973
|
+
var clientCredentialStampField = (field) => `${field}#owner`;
|
|
4974
|
+
var credentialMac = (clientId, value) => createHmac("sha256", value).update(clientId).digest("hex");
|
|
4975
|
+
function clientCredentialStamp(clientId, value) {
|
|
4976
|
+
return JSON.stringify({
|
|
4977
|
+
client_id: clientId,
|
|
4978
|
+
mac: credentialMac(clientId, value)
|
|
4979
|
+
});
|
|
4980
|
+
}
|
|
4981
|
+
function stampedOwner(stamp, value) {
|
|
4982
|
+
if (stamp === void 0) return void 0;
|
|
4983
|
+
let parsed;
|
|
4984
|
+
try {
|
|
4985
|
+
parsed = JSON.parse(stamp);
|
|
4986
|
+
} catch {
|
|
4987
|
+
return void 0;
|
|
4988
|
+
}
|
|
4989
|
+
if (typeof parsed !== "object" || parsed === null) return void 0;
|
|
4990
|
+
const { client_id: clientId, mac } = parsed;
|
|
4991
|
+
if (typeof clientId !== "string" || mac !== credentialMac(clientId, value)) {
|
|
4992
|
+
return void 0;
|
|
4993
|
+
}
|
|
4994
|
+
return clientId;
|
|
4995
|
+
}
|
|
4921
4996
|
function applyTokensPolicy(tokens, policy) {
|
|
4922
4997
|
if (policy === "none") return void 0;
|
|
4923
4998
|
if (policy === "access" && tokens.refresh_token !== void 0) {
|
|
@@ -4946,12 +5021,18 @@ function splitClientInformation(info, fields, secrets) {
|
|
|
4946
5021
|
if (client_secret === void 0 && registration_access_token === void 0) {
|
|
4947
5022
|
return info;
|
|
4948
5023
|
}
|
|
4949
|
-
|
|
4950
|
-
|
|
4951
|
-
|
|
4952
|
-
|
|
4953
|
-
|
|
4954
|
-
|
|
5024
|
+
const put = (field, value) => {
|
|
5025
|
+
if (value === void 0) return;
|
|
5026
|
+
secrets[field] = value;
|
|
5027
|
+
if (fields.stamped && typeof info.client_id === "string") {
|
|
5028
|
+
secrets[clientCredentialStampField(field)] = clientCredentialStamp(
|
|
5029
|
+
info.client_id,
|
|
5030
|
+
value
|
|
5031
|
+
);
|
|
5032
|
+
}
|
|
5033
|
+
};
|
|
5034
|
+
put(fields.clientSecret, client_secret);
|
|
5035
|
+
put(fields.registrationToken, registration_access_token);
|
|
4955
5036
|
return publicInfo;
|
|
4956
5037
|
}
|
|
4957
5038
|
function joinClientInformation(info, fields, secrets) {
|
|
@@ -4970,15 +5051,18 @@ function joinClientInformation(info, fields, secrets) {
|
|
|
4970
5051
|
}
|
|
4971
5052
|
var issuerClientInfoFields = (issuer) => ({
|
|
4972
5053
|
clientSecret: issuerClientSecretField(issuer),
|
|
4973
|
-
registrationToken: issuerRegistrationTokenField(issuer)
|
|
5054
|
+
registrationToken: issuerRegistrationTokenField(issuer),
|
|
5055
|
+
stamped: true
|
|
4974
5056
|
});
|
|
4975
5057
|
var LEGACY_CLIENT_INFO_FIELDS = {
|
|
4976
5058
|
clientSecret: LEGACY_CLIENT_SECRET_FIELD,
|
|
4977
|
-
registrationToken: LEGACY_REGISTRATION_TOKEN_FIELD
|
|
5059
|
+
registrationToken: LEGACY_REGISTRATION_TOKEN_FIELD,
|
|
5060
|
+
stamped: true
|
|
4978
5061
|
};
|
|
4979
5062
|
var PREREG_CLIENT_INFO_FIELDS = {
|
|
4980
5063
|
clientSecret: PREREG_CLIENT_SECRET_FIELD,
|
|
4981
|
-
registrationToken: PREREG_REGISTRATION_TOKEN_FIELD
|
|
5064
|
+
registrationToken: PREREG_REGISTRATION_TOKEN_FIELD,
|
|
5065
|
+
stamped: false
|
|
4982
5066
|
};
|
|
4983
5067
|
function splitServerOAuthState(state, policy) {
|
|
4984
5068
|
const secrets = {};
|
|
@@ -5128,20 +5212,54 @@ function joinIdpSession(residue, secrets) {
|
|
|
5128
5212
|
return stored ? { ...residue, ...stored } : { ...residue };
|
|
5129
5213
|
}
|
|
5130
5214
|
function serverSecretFields(state) {
|
|
5215
|
+
const stamped = (field) => [
|
|
5216
|
+
field,
|
|
5217
|
+
clientCredentialStampField(field)
|
|
5218
|
+
];
|
|
5131
5219
|
const fields = /* @__PURE__ */ new Set([
|
|
5132
5220
|
LEGACY_TOKENS_FIELD,
|
|
5133
|
-
LEGACY_CLIENT_SECRET_FIELD,
|
|
5134
|
-
LEGACY_REGISTRATION_TOKEN_FIELD,
|
|
5221
|
+
...stamped(LEGACY_CLIENT_SECRET_FIELD),
|
|
5222
|
+
...stamped(LEGACY_REGISTRATION_TOKEN_FIELD),
|
|
5135
5223
|
PREREG_CLIENT_SECRET_FIELD,
|
|
5136
5224
|
PREREG_REGISTRATION_TOKEN_FIELD
|
|
5137
5225
|
]);
|
|
5138
5226
|
for (const issuer of Object.keys(state?.byIssuer ?? {})) {
|
|
5139
5227
|
fields.add(issuerTokensField(issuer));
|
|
5140
|
-
|
|
5141
|
-
|
|
5228
|
+
for (const field of stamped(issuerClientSecretField(issuer))) {
|
|
5229
|
+
fields.add(field);
|
|
5230
|
+
}
|
|
5231
|
+
for (const field of stamped(issuerRegistrationTokenField(issuer))) {
|
|
5232
|
+
fields.add(field);
|
|
5233
|
+
}
|
|
5142
5234
|
}
|
|
5143
5235
|
return [...fields];
|
|
5144
5236
|
}
|
|
5237
|
+
function clientCredentialOwners(state) {
|
|
5238
|
+
const owners = /* @__PURE__ */ new Map();
|
|
5239
|
+
const add2 = (info, fields) => {
|
|
5240
|
+
if (typeof info?.client_id !== "string") return;
|
|
5241
|
+
owners.set(fields.clientSecret, info.client_id);
|
|
5242
|
+
owners.set(fields.registrationToken, info.client_id);
|
|
5243
|
+
};
|
|
5244
|
+
add2(state.clientInformation, LEGACY_CLIENT_INFO_FIELDS);
|
|
5245
|
+
for (const [issuer, slot] of Object.entries(state.byIssuer ?? {})) {
|
|
5246
|
+
add2(slot.clientInformation, issuerClientInfoFields(issuer));
|
|
5247
|
+
}
|
|
5248
|
+
return owners;
|
|
5249
|
+
}
|
|
5250
|
+
function restampClientCredentials(state, secrets) {
|
|
5251
|
+
const out = { ...secrets };
|
|
5252
|
+
for (const [field, clientId] of clientCredentialOwners(state)) {
|
|
5253
|
+
const stampField = clientCredentialStampField(field);
|
|
5254
|
+
const value = Object.hasOwn(out, field) ? out[field] : void 0;
|
|
5255
|
+
if (value === void 0) {
|
|
5256
|
+
delete out[stampField];
|
|
5257
|
+
} else if (stampedOwner(out[stampField], value) === void 0) {
|
|
5258
|
+
setOwnEntry(out, stampField, clientCredentialStamp(clientId, value));
|
|
5259
|
+
}
|
|
5260
|
+
}
|
|
5261
|
+
return out;
|
|
5262
|
+
}
|
|
5145
5263
|
function clientInfoHasPlaintext(info) {
|
|
5146
5264
|
return info !== void 0 && (info.client_secret !== void 0 || info.registration_access_token !== void 0);
|
|
5147
5265
|
}
|
|
@@ -5182,6 +5300,44 @@ function coveredLocations(location, byLocation) {
|
|
|
5182
5300
|
([recorded]) => recorded === location || location === KEYRING_LOCATION && recorded.startsWith(FILE_LOCATION_PREFIX)
|
|
5183
5301
|
).map(([recorded, keys]) => ({ location: recorded, keys }));
|
|
5184
5302
|
}
|
|
5303
|
+
var emptyKeys = () => ({
|
|
5304
|
+
servers: /* @__PURE__ */ new Set(),
|
|
5305
|
+
idpSessions: /* @__PURE__ */ new Set(),
|
|
5306
|
+
owners: /* @__PURE__ */ new Map()
|
|
5307
|
+
});
|
|
5308
|
+
function candidateFields(location, byLocation) {
|
|
5309
|
+
const fields = /* @__PURE__ */ new Map();
|
|
5310
|
+
for (const { keys } of coveredLocations(location, byLocation)) {
|
|
5311
|
+
for (const [url, owned] of keys.owners) {
|
|
5312
|
+
const kept = fields.get(url) ?? /* @__PURE__ */ new Set();
|
|
5313
|
+
for (const field of owned.keys()) kept.add(field);
|
|
5314
|
+
fields.set(url, kept);
|
|
5315
|
+
}
|
|
5316
|
+
}
|
|
5317
|
+
return fields;
|
|
5318
|
+
}
|
|
5319
|
+
function addMissingOwners(target, source) {
|
|
5320
|
+
for (const [url, fields] of source) {
|
|
5321
|
+
const kept = target.get(url) ?? /* @__PURE__ */ new Map();
|
|
5322
|
+
for (const [field, clientId] of fields) {
|
|
5323
|
+
if (!kept.has(field)) kept.set(field, clientId);
|
|
5324
|
+
}
|
|
5325
|
+
if (kept.size > 0) target.set(url, kept);
|
|
5326
|
+
}
|
|
5327
|
+
}
|
|
5328
|
+
function mergeOwners(target, source) {
|
|
5329
|
+
let changed = false;
|
|
5330
|
+
for (const [url, fields] of source) {
|
|
5331
|
+
const kept = target.get(url) ?? /* @__PURE__ */ new Map();
|
|
5332
|
+
for (const [field, clientId] of fields) {
|
|
5333
|
+
if (kept.get(field) === clientId) continue;
|
|
5334
|
+
kept.set(field, clientId);
|
|
5335
|
+
changed = true;
|
|
5336
|
+
}
|
|
5337
|
+
if (kept.size > 0) target.set(url, kept);
|
|
5338
|
+
}
|
|
5339
|
+
return changed;
|
|
5340
|
+
}
|
|
5185
5341
|
var warnedLedgerFailures = /* @__PURE__ */ new Set();
|
|
5186
5342
|
function warnLedgerFailure(what, error) {
|
|
5187
5343
|
const reason = error instanceof Error ? error.message : String(error);
|
|
@@ -5195,6 +5351,19 @@ function warnLedgerFailure(what, error) {
|
|
|
5195
5351
|
function isRecord2(value) {
|
|
5196
5352
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
5197
5353
|
}
|
|
5354
|
+
function parseOwners(value) {
|
|
5355
|
+
const owners = /* @__PURE__ */ new Map();
|
|
5356
|
+
if (!isRecord2(value)) return owners;
|
|
5357
|
+
for (const [url, fields] of Object.entries(value)) {
|
|
5358
|
+
if (!isRecord2(fields)) continue;
|
|
5359
|
+
const byField = /* @__PURE__ */ new Map();
|
|
5360
|
+
for (const [field, clientId] of Object.entries(fields)) {
|
|
5361
|
+
if (typeof clientId === "string") byField.set(field, clientId);
|
|
5362
|
+
}
|
|
5363
|
+
if (byField.size > 0) owners.set(url, byField);
|
|
5364
|
+
}
|
|
5365
|
+
return owners;
|
|
5366
|
+
}
|
|
5198
5367
|
function stringsOf(value) {
|
|
5199
5368
|
return Array.isArray(value) ? value.filter((item) => typeof item === "string") : [];
|
|
5200
5369
|
}
|
|
@@ -5216,7 +5385,9 @@ function parseLedger(raw) {
|
|
|
5216
5385
|
if (!isRecord2(keys)) continue;
|
|
5217
5386
|
byLocation.set(location, {
|
|
5218
5387
|
servers: new Set(stringsOf(keys.servers)),
|
|
5219
|
-
idpSessions: new Set(stringsOf(keys.idpSessions))
|
|
5388
|
+
idpSessions: new Set(stringsOf(keys.idpSessions)),
|
|
5389
|
+
owners: parseOwners(keys.owners),
|
|
5390
|
+
...isValidSecretsNamespace(keys.owedTo) && { owedTo: keys.owedTo }
|
|
5220
5391
|
});
|
|
5221
5392
|
}
|
|
5222
5393
|
if (byLocation.size > 0) ledger.set(namespace, byLocation);
|
|
@@ -5230,7 +5401,19 @@ function serializeLedger(ledger) {
|
|
|
5230
5401
|
Object.fromEntries(
|
|
5231
5402
|
[...byLocation].map(([location, keys]) => [
|
|
5232
5403
|
location,
|
|
5233
|
-
{
|
|
5404
|
+
{
|
|
5405
|
+
servers: [...keys.servers],
|
|
5406
|
+
idpSessions: [...keys.idpSessions],
|
|
5407
|
+
...keys.owners.size > 0 && {
|
|
5408
|
+
owners: Object.fromEntries(
|
|
5409
|
+
[...keys.owners].map(([url, fields]) => [
|
|
5410
|
+
url,
|
|
5411
|
+
Object.fromEntries(fields)
|
|
5412
|
+
])
|
|
5413
|
+
)
|
|
5414
|
+
},
|
|
5415
|
+
...keys.owedTo !== void 0 && { owedTo: keys.owedTo }
|
|
5416
|
+
}
|
|
5234
5417
|
])
|
|
5235
5418
|
)
|
|
5236
5419
|
])
|
|
@@ -5242,7 +5425,7 @@ async function writeLedger(stateFilePath, ledger) {
|
|
|
5242
5425
|
if (ledger.size === 0) await deleteStoreFile(path5);
|
|
5243
5426
|
else await writeStoreFile(path5, serializeLedger(ledger));
|
|
5244
5427
|
}
|
|
5245
|
-
async function recordNamespaceKeys(stateFilePath, secretStore, namespace, servers, idpSessions) {
|
|
5428
|
+
async function recordNamespaceKeys(stateFilePath, secretStore, namespace, servers, idpSessions, owners = /* @__PURE__ */ new Map()) {
|
|
5246
5429
|
try {
|
|
5247
5430
|
const ledger = parseLedger(
|
|
5248
5431
|
await readStoreFile(namespaceLedgerPath(stateFilePath))
|
|
@@ -5256,7 +5439,7 @@ async function recordNamespaceKeys(stateFilePath, secretStore, namespace, server
|
|
|
5256
5439
|
let keys = byLocation.get(location);
|
|
5257
5440
|
let changed = false;
|
|
5258
5441
|
if (!keys) {
|
|
5259
|
-
keys =
|
|
5442
|
+
keys = emptyKeys();
|
|
5260
5443
|
byLocation.set(location, keys);
|
|
5261
5444
|
changed = true;
|
|
5262
5445
|
}
|
|
@@ -5270,11 +5453,93 @@ async function recordNamespaceKeys(stateFilePath, secretStore, namespace, server
|
|
|
5270
5453
|
keys.idpSessions.add(issuer);
|
|
5271
5454
|
changed = true;
|
|
5272
5455
|
}
|
|
5456
|
+
if (mergeOwners(keys.owners, owners)) changed = true;
|
|
5273
5457
|
if (changed) await writeLedger(stateFilePath, ledger);
|
|
5274
5458
|
} catch (error) {
|
|
5275
5459
|
warnLedgerFailure("update", error);
|
|
5276
5460
|
}
|
|
5277
5461
|
}
|
|
5462
|
+
async function markUncarriedNamespaces(stateFilePath, secretStore, into) {
|
|
5463
|
+
const ledger = parseLedger(
|
|
5464
|
+
await readStoreFile(namespaceLedgerPath(stateFilePath))
|
|
5465
|
+
);
|
|
5466
|
+
const location = await secretStoreLocation(secretStore);
|
|
5467
|
+
let changed = false;
|
|
5468
|
+
for (const [namespace, byLocation] of ledger) {
|
|
5469
|
+
if (namespace === into) continue;
|
|
5470
|
+
for (const [recorded, keys] of byLocation) {
|
|
5471
|
+
if (recorded === location || keys.owners.size === 0) continue;
|
|
5472
|
+
if (keys.owedTo === into) continue;
|
|
5473
|
+
keys.owedTo = into;
|
|
5474
|
+
changed = true;
|
|
5475
|
+
}
|
|
5476
|
+
}
|
|
5477
|
+
if (changed) await writeLedger(stateFilePath, ledger);
|
|
5478
|
+
}
|
|
5479
|
+
async function pendingCarries(stateFilePath, secretStore, current) {
|
|
5480
|
+
const ledger = parseLedger(
|
|
5481
|
+
await readStoreFile(namespaceLedgerPath(stateFilePath))
|
|
5482
|
+
);
|
|
5483
|
+
const location = await secretStoreLocation(secretStore);
|
|
5484
|
+
const carries = [];
|
|
5485
|
+
for (const [namespace, byLocation] of ledger) {
|
|
5486
|
+
const owed = coveredLocations(location, byLocation).some(
|
|
5487
|
+
({ keys }) => keys.owedTo === current
|
|
5488
|
+
);
|
|
5489
|
+
if (owed) {
|
|
5490
|
+
carries.push({
|
|
5491
|
+
namespace,
|
|
5492
|
+
fields: candidateFields(location, byLocation)
|
|
5493
|
+
});
|
|
5494
|
+
}
|
|
5495
|
+
}
|
|
5496
|
+
return carries.reverse();
|
|
5497
|
+
}
|
|
5498
|
+
async function carryOwed(stateFilePath, ledger, location, secretStore, namespace, current, recorded, now) {
|
|
5499
|
+
let carried = true;
|
|
5500
|
+
for (const [url, fields] of recorded) {
|
|
5501
|
+
for (const field of fields) {
|
|
5502
|
+
const clientId = now.get(url)?.get(field);
|
|
5503
|
+
if (clientId === void 0) continue;
|
|
5504
|
+
try {
|
|
5505
|
+
const from = oauthSecretServerId(url, namespace);
|
|
5506
|
+
const to = oauthSecretServerId(url, current);
|
|
5507
|
+
const stampField = clientCredentialStampField(field);
|
|
5508
|
+
const value = await secretStoreGetStrict(secretStore, from, field);
|
|
5509
|
+
if (value === null) continue;
|
|
5510
|
+
const stamp = await secretStoreGetStrict(secretStore, from, stampField);
|
|
5511
|
+
if (stamp === null || stampedOwner(stamp, value) !== clientId) {
|
|
5512
|
+
continue;
|
|
5513
|
+
}
|
|
5514
|
+
if (await secretStoreGetStrict(secretStore, to, field) !== null) {
|
|
5515
|
+
continue;
|
|
5516
|
+
}
|
|
5517
|
+
let byLocation = ledger.get(current);
|
|
5518
|
+
if (!byLocation) {
|
|
5519
|
+
byLocation = /* @__PURE__ */ new Map();
|
|
5520
|
+
ledger.set(current, byLocation);
|
|
5521
|
+
}
|
|
5522
|
+
const target = byLocation.get(location) ?? emptyKeys();
|
|
5523
|
+
byLocation.set(location, target);
|
|
5524
|
+
const isNew = !target.servers.has(url);
|
|
5525
|
+
target.servers.add(url);
|
|
5526
|
+
const owner = /* @__PURE__ */ new Map([[url, /* @__PURE__ */ new Map([[field, clientId]])]]);
|
|
5527
|
+
if (mergeOwners(target.owners, owner) || isNew) {
|
|
5528
|
+
await writeLedger(stateFilePath, ledger);
|
|
5529
|
+
}
|
|
5530
|
+
await secretStore.set(to, stampField, stamp);
|
|
5531
|
+
await secretStore.set(to, field, value);
|
|
5532
|
+
} catch (error) {
|
|
5533
|
+
carried = false;
|
|
5534
|
+
warnLedgerFailure(
|
|
5535
|
+
"carry over a superseded namespace recorded in",
|
|
5536
|
+
error
|
|
5537
|
+
);
|
|
5538
|
+
}
|
|
5539
|
+
}
|
|
5540
|
+
}
|
|
5541
|
+
return carried;
|
|
5542
|
+
}
|
|
5278
5543
|
async function recordedNamespaces(stateFilePath) {
|
|
5279
5544
|
try {
|
|
5280
5545
|
const ledger = parseLedger(
|
|
@@ -5286,7 +5551,7 @@ async function recordedNamespaces(stateFilePath) {
|
|
|
5286
5551
|
return [];
|
|
5287
5552
|
}
|
|
5288
5553
|
}
|
|
5289
|
-
async function purgeSupersededNamespaces(stateFilePath, secretStore, current) {
|
|
5554
|
+
async function purgeSupersededNamespaces(stateFilePath, secretStore, current, owners = /* @__PURE__ */ new Map()) {
|
|
5290
5555
|
let ledger;
|
|
5291
5556
|
let location;
|
|
5292
5557
|
try {
|
|
@@ -5299,8 +5564,22 @@ async function purgeSupersededNamespaces(stateFilePath, secretStore, current) {
|
|
|
5299
5564
|
return;
|
|
5300
5565
|
}
|
|
5301
5566
|
let changed = false;
|
|
5302
|
-
for (const [namespace, byLocation] of ledger) {
|
|
5567
|
+
for (const [namespace, byLocation] of [...ledger].reverse()) {
|
|
5303
5568
|
if (namespace === current) continue;
|
|
5569
|
+
if (current !== void 0 && coveredLocations(location, byLocation).some(
|
|
5570
|
+
({ keys }) => keys.owedTo === current
|
|
5571
|
+
) && !await carryOwed(
|
|
5572
|
+
stateFilePath,
|
|
5573
|
+
ledger,
|
|
5574
|
+
location,
|
|
5575
|
+
secretStore,
|
|
5576
|
+
namespace,
|
|
5577
|
+
current,
|
|
5578
|
+
candidateFields(location, byLocation),
|
|
5579
|
+
owners
|
|
5580
|
+
)) {
|
|
5581
|
+
break;
|
|
5582
|
+
}
|
|
5304
5583
|
for (const { location: recorded, keys } of coveredLocations(
|
|
5305
5584
|
location,
|
|
5306
5585
|
byLocation
|
|
@@ -5325,12 +5604,11 @@ async function purgeSupersededNamespaces(stateFilePath, secretStore, current) {
|
|
|
5325
5604
|
if (!purged || recorded !== location) continue;
|
|
5326
5605
|
byLocation.delete(recorded);
|
|
5327
5606
|
if (recorded.startsWith(FILE_LOCATION_PREFIX)) {
|
|
5328
|
-
const keyring = byLocation.get(KEYRING_LOCATION) ??
|
|
5329
|
-
|
|
5330
|
-
idpSessions: /* @__PURE__ */ new Set()
|
|
5331
|
-
};
|
|
5607
|
+
const keyring = byLocation.get(KEYRING_LOCATION) ?? emptyKeys();
|
|
5608
|
+
addMissingOwners(keyring.owners, keys.owners);
|
|
5332
5609
|
for (const url of keys.servers) keyring.servers.add(url);
|
|
5333
5610
|
for (const issuer of keys.idpSessions) keyring.idpSessions.add(issuer);
|
|
5611
|
+
keyring.owedTo ??= keys.owedTo;
|
|
5334
5612
|
byLocation.set(KEYRING_LOCATION, keyring);
|
|
5335
5613
|
}
|
|
5336
5614
|
changed = true;
|
|
@@ -5441,7 +5719,12 @@ async function adoptSecretsNamespace(filePath, secretStore, locked) {
|
|
|
5441
5719
|
const existing = parseSecretsNamespace(raw);
|
|
5442
5720
|
if (existing !== void 0) {
|
|
5443
5721
|
if (locked) {
|
|
5444
|
-
await purgeSupersededNamespaces(
|
|
5722
|
+
await purgeSupersededNamespaces(
|
|
5723
|
+
filePath,
|
|
5724
|
+
secretStore,
|
|
5725
|
+
existing,
|
|
5726
|
+
snapshotClientOwners(parseOAuthPersistBlob(raw))
|
|
5727
|
+
);
|
|
5445
5728
|
}
|
|
5446
5729
|
return existing;
|
|
5447
5730
|
}
|
|
@@ -5461,25 +5744,33 @@ async function adoptSecretsNamespace(filePath, secretStore, locked) {
|
|
|
5461
5744
|
`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).`
|
|
5462
5745
|
);
|
|
5463
5746
|
}
|
|
5747
|
+
const values = await readEntrySecrets(
|
|
5748
|
+
snapshot,
|
|
5749
|
+
secretStore,
|
|
5750
|
+
void 0,
|
|
5751
|
+
await recordedNamespaces(filePath)
|
|
5752
|
+
);
|
|
5753
|
+
const owners = snapshotClientOwners(snapshot);
|
|
5464
5754
|
await recordNamespaceKeys(
|
|
5465
5755
|
filePath,
|
|
5466
5756
|
secretStore,
|
|
5467
5757
|
namespace,
|
|
5468
5758
|
Object.keys(snapshot.servers),
|
|
5469
|
-
Object.keys(snapshot.idpSessions)
|
|
5759
|
+
Object.keys(snapshot.idpSessions),
|
|
5760
|
+
writtenClientOwners(snapshot, (url) => values.servers.get(url) ?? {})
|
|
5470
5761
|
);
|
|
5762
|
+
await markUncarriedNamespaces(filePath, secretStore, namespace);
|
|
5471
5763
|
const touched = [];
|
|
5472
5764
|
try {
|
|
5473
|
-
const values = await readEntrySecrets(
|
|
5474
|
-
snapshot,
|
|
5475
|
-
secretStore,
|
|
5476
|
-
void 0,
|
|
5477
|
-
await recordedNamespaces(filePath)
|
|
5478
|
-
);
|
|
5479
5765
|
const copies = [
|
|
5766
|
+
// Restamped: a value read from the legacy ids predates stamps, and a
|
|
5767
|
+
// fallback's stamp may belong to a value read from elsewhere (#2734).
|
|
5480
5768
|
...[...values.servers].map(([url, secrets]) => ({
|
|
5481
5769
|
scopedId: oauthSecretServerId(url, namespace),
|
|
5482
|
-
secrets
|
|
5770
|
+
secrets: restampClientCredentials(
|
|
5771
|
+
getOwnEntry(snapshot.servers, url) ?? {},
|
|
5772
|
+
secrets
|
|
5773
|
+
)
|
|
5483
5774
|
})),
|
|
5484
5775
|
...[...values.idpSessions].map(([issuer, secrets]) => ({
|
|
5485
5776
|
scopedId: oauthIdpSecretServerId(issuer, namespace),
|
|
@@ -5497,7 +5788,7 @@ async function adoptSecretsNamespace(filePath, secretStore, locked) {
|
|
|
5497
5788
|
await restoreSecretFields(secretStore, touched, warnRestoreFailure);
|
|
5498
5789
|
throw error;
|
|
5499
5790
|
}
|
|
5500
|
-
await purgeSupersededNamespaces(filePath, secretStore, namespace);
|
|
5791
|
+
await purgeSupersededNamespaces(filePath, secretStore, namespace, owners);
|
|
5501
5792
|
const legacyIds = [
|
|
5502
5793
|
...Object.keys(snapshot.servers).map((url) => oauthSecretServerId(url)),
|
|
5503
5794
|
...Object.keys(snapshot.idpSessions).map(
|
|
@@ -5704,7 +5995,22 @@ async function writeOAuthSectionsFrom(filePath, resolveSnapshot, sections, secre
|
|
|
5704
5995
|
secretStore,
|
|
5705
5996
|
namespace,
|
|
5706
5997
|
Object.keys(merged.servers),
|
|
5707
|
-
Object.keys(merged.idpSessions)
|
|
5998
|
+
Object.keys(merged.idpSessions),
|
|
5999
|
+
// Only the entries this save writes: another entry's secret in
|
|
6000
|
+
// this store was written for whatever client it was written for,
|
|
6001
|
+
// and relabeling it from the file would misattribute it (#2672).
|
|
6002
|
+
writtenClientOwners(
|
|
6003
|
+
{
|
|
6004
|
+
servers: Object.fromEntries(
|
|
6005
|
+
(effective.servers ?? []).flatMap((url) => {
|
|
6006
|
+
const entry = getOwnEntry(merged.servers, url);
|
|
6007
|
+
return entry === void 0 ? [] : [[url, entry]];
|
|
6008
|
+
})
|
|
6009
|
+
),
|
|
6010
|
+
idpSessions: {}
|
|
6011
|
+
},
|
|
6012
|
+
splitSecretsOf
|
|
6013
|
+
)
|
|
5708
6014
|
);
|
|
5709
6015
|
for (const url of effective.servers ?? []) {
|
|
5710
6016
|
const serverId = oauthSecretServerId(url, namespace);
|
|
@@ -5892,7 +6198,29 @@ function fillMissingSecrets(target, source) {
|
|
|
5892
6198
|
if (!Object.hasOwn(target, field)) setOwnEntry(target, field, value);
|
|
5893
6199
|
}
|
|
5894
6200
|
}
|
|
5895
|
-
|
|
6201
|
+
function writtenClientOwners(snapshot, secretsOf) {
|
|
6202
|
+
const owners = /* @__PURE__ */ new Map();
|
|
6203
|
+
for (const [url, state] of Object.entries(snapshot.servers)) {
|
|
6204
|
+
const secrets = secretsOf(url, state);
|
|
6205
|
+
const fields = new Map(
|
|
6206
|
+
[...clientCredentialOwners(state)].filter(
|
|
6207
|
+
([field]) => Object.hasOwn(secrets, field)
|
|
6208
|
+
)
|
|
6209
|
+
);
|
|
6210
|
+
if (fields.size > 0) owners.set(url, fields);
|
|
6211
|
+
}
|
|
6212
|
+
return owners;
|
|
6213
|
+
}
|
|
6214
|
+
var splitSecretsOf = (_url, state) => splitServerOAuthState(state, "all").secrets;
|
|
6215
|
+
function snapshotClientOwners(snapshot) {
|
|
6216
|
+
const owners = /* @__PURE__ */ new Map();
|
|
6217
|
+
for (const [url, state] of Object.entries(snapshot?.servers ?? {})) {
|
|
6218
|
+
const fields = clientCredentialOwners(state);
|
|
6219
|
+
if (fields.size > 0) owners.set(url, fields);
|
|
6220
|
+
}
|
|
6221
|
+
return owners;
|
|
6222
|
+
}
|
|
6223
|
+
async function readEntrySecrets(snapshot, secretStore, namespace, fallbacks, carries = []) {
|
|
5896
6224
|
const servers = /* @__PURE__ */ new Map();
|
|
5897
6225
|
const idpSessions = /* @__PURE__ */ new Map();
|
|
5898
6226
|
for (const url of Object.keys(snapshot.servers)) servers.set(url, {});
|
|
@@ -5913,14 +6241,49 @@ async function readEntrySecrets(snapshot, secretStore, namespace, fallbacks) {
|
|
|
5913
6241
|
);
|
|
5914
6242
|
}
|
|
5915
6243
|
}
|
|
6244
|
+
const owners = snapshotClientOwners(snapshot);
|
|
6245
|
+
for (const { namespace: from, fields: recorded } of carries) {
|
|
6246
|
+
const requests = [];
|
|
6247
|
+
for (const [url, fields] of recorded) {
|
|
6248
|
+
const target = servers.get(url);
|
|
6249
|
+
if (!target) continue;
|
|
6250
|
+
const wanted = [...fields].filter(
|
|
6251
|
+
(field) => owners.get(url)?.has(field) === true && !Object.hasOwn(target, field)
|
|
6252
|
+
);
|
|
6253
|
+
if (wanted.length > 0) {
|
|
6254
|
+
requests.push({
|
|
6255
|
+
serverId: oauthSecretServerId(url, from),
|
|
6256
|
+
fields: wanted.flatMap((field) => [
|
|
6257
|
+
field,
|
|
6258
|
+
clientCredentialStampField(field)
|
|
6259
|
+
])
|
|
6260
|
+
});
|
|
6261
|
+
}
|
|
6262
|
+
}
|
|
6263
|
+
if (requests.length === 0) continue;
|
|
6264
|
+
const values = await secretStoreGetManyStrict(secretStore, requests);
|
|
6265
|
+
for (const [url, target] of servers) {
|
|
6266
|
+
const found = values[oauthSecretServerId(url, from)] ?? {};
|
|
6267
|
+
for (const [field, clientId] of owners.get(url) ?? []) {
|
|
6268
|
+
const value = Object.hasOwn(found, field) ? found[field] : void 0;
|
|
6269
|
+
const stampField = clientCredentialStampField(field);
|
|
6270
|
+
if (value === void 0 || Object.hasOwn(target, field) || stampedOwner(found[stampField], value) !== clientId) {
|
|
6271
|
+
continue;
|
|
6272
|
+
}
|
|
6273
|
+
setOwnEntry(target, field, value);
|
|
6274
|
+
setOwnEntry(target, stampField, found[stampField]);
|
|
6275
|
+
}
|
|
6276
|
+
}
|
|
6277
|
+
}
|
|
5916
6278
|
return { servers, idpSessions };
|
|
5917
6279
|
}
|
|
5918
|
-
async function joinSnapshot(snapshot, secretStore, namespace, fallbacks) {
|
|
6280
|
+
async function joinSnapshot(snapshot, secretStore, namespace, fallbacks, carries = []) {
|
|
5919
6281
|
const values = await readEntrySecrets(
|
|
5920
6282
|
snapshot,
|
|
5921
6283
|
secretStore,
|
|
5922
6284
|
namespace,
|
|
5923
|
-
fallbacks
|
|
6285
|
+
fallbacks,
|
|
6286
|
+
carries
|
|
5924
6287
|
);
|
|
5925
6288
|
return {
|
|
5926
6289
|
servers: Object.fromEntries(
|
|
@@ -5948,7 +6311,8 @@ async function migratePlaintextSecrets(filePath, secretStore) {
|
|
|
5948
6311
|
secretStore,
|
|
5949
6312
|
namespace,
|
|
5950
6313
|
Object.keys(fresh.servers),
|
|
5951
|
-
Object.keys(fresh.idpSessions)
|
|
6314
|
+
Object.keys(fresh.idpSessions),
|
|
6315
|
+
writtenClientOwners(fresh, splitSecretsOf)
|
|
5952
6316
|
);
|
|
5953
6317
|
}
|
|
5954
6318
|
const migrateEntrySecrets = async (serverId, secrets) => {
|
|
@@ -6001,7 +6365,8 @@ async function readOAuthStore(filePath, secretStore = defaultSecretStore()) {
|
|
|
6001
6365
|
}
|
|
6002
6366
|
const namespace = parseSecretsNamespace(raw);
|
|
6003
6367
|
const fallbacks = namespace === void 0 ? await recordedNamespaces(filePath) : [];
|
|
6004
|
-
|
|
6368
|
+
const carries = namespace === void 0 ? [] : await pendingCarries(filePath, secretStore, namespace);
|
|
6369
|
+
return joinSnapshot(snapshot, secretStore, namespace, fallbacks, carries);
|
|
6005
6370
|
});
|
|
6006
6371
|
}
|
|
6007
6372
|
async function removeOAuthStore(filePath, secretStore = defaultSecretStore()) {
|
|
@@ -6761,8 +7126,226 @@ function createTransportNode(config, options = {}) {
|
|
|
6761
7126
|
}
|
|
6762
7127
|
}
|
|
6763
7128
|
|
|
7129
|
+
// ../../core/mcp/remote/node/cappedBody.ts
|
|
7130
|
+
async function readCappedText(body, maxBytes) {
|
|
7131
|
+
const reader = body.getReader();
|
|
7132
|
+
const decoder = new TextDecoder();
|
|
7133
|
+
let text = "";
|
|
7134
|
+
let bytes = 0;
|
|
7135
|
+
for (; ; ) {
|
|
7136
|
+
const next = await reader.read();
|
|
7137
|
+
if (next.done) return { kind: "complete", text: text + decoder.decode() };
|
|
7138
|
+
bytes += next.value.byteLength;
|
|
7139
|
+
if (bytes > maxBytes) {
|
|
7140
|
+
void reader.cancel().catch(() => {
|
|
7141
|
+
});
|
|
7142
|
+
return { kind: "overflow" };
|
|
7143
|
+
}
|
|
7144
|
+
text += decoder.decode(next.value, { stream: true });
|
|
7145
|
+
}
|
|
7146
|
+
}
|
|
7147
|
+
function declaresOversizeBody(contentLength, maxBytes) {
|
|
7148
|
+
if (contentLength === null || !/^\s*\d+\s*$/.test(contentLength)) {
|
|
7149
|
+
return false;
|
|
7150
|
+
}
|
|
7151
|
+
return Number(contentLength) > maxBytes;
|
|
7152
|
+
}
|
|
7153
|
+
|
|
7154
|
+
// ../../core/mcp/rateLimit.ts
|
|
7155
|
+
import {
|
|
7156
|
+
SdkHttpError
|
|
7157
|
+
} from "@modelcontextprotocol/client";
|
|
7158
|
+
|
|
7159
|
+
// ../../core/mcp/connectionDiagnostics.ts
|
|
7160
|
+
import { SdkError, SdkErrorCode } from "@modelcontextprotocol/client";
|
|
7161
|
+
function isRequestTimeoutData(value) {
|
|
7162
|
+
return typeof value === "object" && value !== null && typeof value.timeout === "number";
|
|
7163
|
+
}
|
|
7164
|
+
function isRequestTimeoutError(err) {
|
|
7165
|
+
return SdkError.isInstance(err) && err.code === SdkErrorCode.RequestTimeout && isRequestTimeoutData(err.data);
|
|
7166
|
+
}
|
|
7167
|
+
function describeSendFailure(err) {
|
|
7168
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
7169
|
+
const status = httpStatusOf(err);
|
|
7170
|
+
if (status === void 0 || message.includes(`HTTP ${status}`)) {
|
|
7171
|
+
return message;
|
|
7172
|
+
}
|
|
7173
|
+
const statusText = httpStatusTextOf(err);
|
|
7174
|
+
return `HTTP ${status}${statusText ? ` ${statusText}` : ""}: ${message}`;
|
|
7175
|
+
}
|
|
7176
|
+
function httpStatusOf(err) {
|
|
7177
|
+
if (typeof err !== "object" || err === null || !("status" in err)) {
|
|
7178
|
+
return void 0;
|
|
7179
|
+
}
|
|
7180
|
+
return typeof err.status === "number" ? err.status : void 0;
|
|
7181
|
+
}
|
|
7182
|
+
function httpStatusTextOf(err) {
|
|
7183
|
+
if (typeof err !== "object" || err === null || !("statusText" in err)) {
|
|
7184
|
+
return void 0;
|
|
7185
|
+
}
|
|
7186
|
+
return typeof err.statusText === "string" ? err.statusText : void 0;
|
|
7187
|
+
}
|
|
7188
|
+
function formatDuration(ms) {
|
|
7189
|
+
const clamped = Math.max(0, ms);
|
|
7190
|
+
if (clamped < 1e3) return `${Math.round(clamped)}ms`;
|
|
7191
|
+
const totalSeconds = Math.round(clamped / 1e3);
|
|
7192
|
+
const hours = Math.floor(totalSeconds / 3600);
|
|
7193
|
+
const minutes = Math.floor(totalSeconds % 3600 / 60);
|
|
7194
|
+
const seconds = totalSeconds % 60;
|
|
7195
|
+
if (hours > 0) return `${hours}h${String(minutes).padStart(2, "0")}m`;
|
|
7196
|
+
if (minutes > 0) return `${minutes}m${String(seconds).padStart(2, "0")}s`;
|
|
7197
|
+
return `${seconds}s`;
|
|
7198
|
+
}
|
|
7199
|
+
var MAX_LISTED_OUTSTANDING_REQUESTS = 5;
|
|
7200
|
+
function describeStreamEndpoint(url) {
|
|
7201
|
+
try {
|
|
7202
|
+
return `GET ${new URL(url).pathname}`;
|
|
7203
|
+
} catch {
|
|
7204
|
+
return `GET ${url}`;
|
|
7205
|
+
}
|
|
7206
|
+
}
|
|
7207
|
+
function plural(count, noun) {
|
|
7208
|
+
return `${count} ${noun}${count === 1 ? "" : "s"}`;
|
|
7209
|
+
}
|
|
7210
|
+
function describeOutstanding(requests, now) {
|
|
7211
|
+
if (requests.length === 0) return "No requests are unanswered.";
|
|
7212
|
+
const listed = requests.slice(0, MAX_LISTED_OUTSTANDING_REQUESTS).map((r) => `${r.method} (sent ${formatDuration(now - r.sentAt)} ago)`);
|
|
7213
|
+
const rest = requests.length - listed.length;
|
|
7214
|
+
const tail = rest > 0 ? `, and ${rest} more` : "";
|
|
7215
|
+
const verb = requests.length === 1 ? "is" : "are";
|
|
7216
|
+
return `${plural(requests.length, "request")} ${verb} unanswered: ${listed.join(", ")}${tail}.`;
|
|
7217
|
+
}
|
|
7218
|
+
function describeLastResponse(lastResponse, now) {
|
|
7219
|
+
if (!lastResponse) return "No response has been received this session.";
|
|
7220
|
+
return `Last response received ${formatDuration(now - lastResponse.receivedAt)} ago (${lastResponse.method}).`;
|
|
7221
|
+
}
|
|
7222
|
+
function describeStream(stream, now) {
|
|
7223
|
+
if (!stream) return void 0;
|
|
7224
|
+
const endpoint = describeStreamEndpoint(stream.url);
|
|
7225
|
+
const events = plural(stream.eventCount, "event");
|
|
7226
|
+
if (stream.closedAt !== void 0) {
|
|
7227
|
+
return `Notification stream (${endpoint}) closed ${formatDuration(now - stream.closedAt)} ago after ${formatDuration(stream.closedAt - stream.openedAt)}, ${events} delivered.`;
|
|
7228
|
+
}
|
|
7229
|
+
return `Notification stream (${endpoint}) open for ${formatDuration(now - stream.openedAt)}, ${events} delivered.`;
|
|
7230
|
+
}
|
|
7231
|
+
function describeConnectionDiagnostics(diagnostics) {
|
|
7232
|
+
const now = diagnostics.capturedAt;
|
|
7233
|
+
const parts = [
|
|
7234
|
+
describeOutstanding(diagnostics.outstandingRequests, now),
|
|
7235
|
+
describeLastResponse(diagnostics.lastResponse, now),
|
|
7236
|
+
describeStream(diagnostics.notificationStream, now)
|
|
7237
|
+
];
|
|
7238
|
+
return parts.filter((p) => p !== void 0).join(" ");
|
|
7239
|
+
}
|
|
7240
|
+
function annotateRequestTimeout(err, method, diagnostics) {
|
|
7241
|
+
if (!isRequestTimeoutError(err)) return err;
|
|
7242
|
+
const data = {
|
|
7243
|
+
...err.data,
|
|
7244
|
+
method,
|
|
7245
|
+
diagnostics
|
|
7246
|
+
};
|
|
7247
|
+
const message = `Request timed out after ${formatDuration(err.data.timeout)} (${method}). ${describeConnectionDiagnostics(diagnostics)}`;
|
|
7248
|
+
const annotated = new SdkError(SdkErrorCode.RequestTimeout, message, data);
|
|
7249
|
+
annotated.cause = err;
|
|
7250
|
+
return annotated;
|
|
7251
|
+
}
|
|
7252
|
+
|
|
7253
|
+
// ../../core/mcp/rateLimit.ts
|
|
7254
|
+
var HTTP_TOO_MANY_REQUESTS = 429;
|
|
7255
|
+
function requestIdsOf(body) {
|
|
7256
|
+
if (body === void 0) return [];
|
|
7257
|
+
let parsed;
|
|
7258
|
+
try {
|
|
7259
|
+
parsed = JSON.parse(body);
|
|
7260
|
+
} catch {
|
|
7261
|
+
return [];
|
|
7262
|
+
}
|
|
7263
|
+
const messages = Array.isArray(parsed) ? parsed : [parsed];
|
|
7264
|
+
const ids = [];
|
|
7265
|
+
for (const message of messages) {
|
|
7266
|
+
if (typeof message !== "object" || message === null) continue;
|
|
7267
|
+
if (!("method" in message) || !("id" in message)) continue;
|
|
7268
|
+
const { id } = message;
|
|
7269
|
+
if (typeof id === "string" || typeof id === "number") ids.push(id);
|
|
7270
|
+
}
|
|
7271
|
+
return ids;
|
|
7272
|
+
}
|
|
7273
|
+
var MAX_UNCLAIMED_RATE_LIMITS = 100;
|
|
7274
|
+
var RateLimitLedger = class {
|
|
7275
|
+
// `undefined` is a real value here: a 429 that carried no header.
|
|
7276
|
+
byRequestId = /* @__PURE__ */ new Map();
|
|
7277
|
+
/** File a tracked fetch, if it was a 429. Anything else is ignored. */
|
|
7278
|
+
record(entry) {
|
|
7279
|
+
if (entry.responseStatus !== HTTP_TOO_MANY_REQUESTS) return;
|
|
7280
|
+
const retryAfter = findHeader(entry.responseHeaders, "retry-after");
|
|
7281
|
+
for (const id of requestIdsOf(entry.requestBody)) {
|
|
7282
|
+
this.byRequestId.delete(id);
|
|
7283
|
+
this.byRequestId.set(id, retryAfter);
|
|
7284
|
+
if (this.byRequestId.size > MAX_UNCLAIMED_RATE_LIMITS) {
|
|
7285
|
+
const [oldest] = this.byRequestId.keys();
|
|
7286
|
+
if (oldest !== void 0) this.byRequestId.delete(oldest);
|
|
7287
|
+
}
|
|
7288
|
+
}
|
|
7289
|
+
}
|
|
7290
|
+
/** The `Retry-After` filed for `id`, removed as it is read. */
|
|
7291
|
+
take(id) {
|
|
7292
|
+
const retryAfter = this.byRequestId.get(id);
|
|
7293
|
+
this.byRequestId.delete(id);
|
|
7294
|
+
return retryAfter;
|
|
7295
|
+
}
|
|
7296
|
+
clear() {
|
|
7297
|
+
this.byRequestId.clear();
|
|
7298
|
+
}
|
|
7299
|
+
};
|
|
7300
|
+
var IMF_FIXDATE = /^[A-Z][a-z]{2}, \d{2} [A-Z][a-z]{2} \d{4} \d{2}:\d{2}:\d{2} GMT$/;
|
|
7301
|
+
function parseRetryAfter(value, now) {
|
|
7302
|
+
if (value === void 0) return void 0;
|
|
7303
|
+
const trimmed = value.trim();
|
|
7304
|
+
if (/^\d+$/.test(trimmed)) return Number(trimmed) * 1e3;
|
|
7305
|
+
if (!IMF_FIXDATE.test(trimmed)) return void 0;
|
|
7306
|
+
const at = Date.parse(trimmed);
|
|
7307
|
+
if (Number.isNaN(at) || new Date(at).toUTCString() !== trimmed) {
|
|
7308
|
+
return void 0;
|
|
7309
|
+
}
|
|
7310
|
+
return Math.max(0, at - now);
|
|
7311
|
+
}
|
|
7312
|
+
function isRateLimitError(err) {
|
|
7313
|
+
return SdkHttpError.isInstance(err) && err.status === HTTP_TOO_MANY_REQUESTS;
|
|
7314
|
+
}
|
|
7315
|
+
function retryAfterOn(err) {
|
|
7316
|
+
const value = err.data.retryAfter;
|
|
7317
|
+
return typeof value === "string" ? value : void 0;
|
|
7318
|
+
}
|
|
7319
|
+
function attachRetryAfter(err, retryAfter) {
|
|
7320
|
+
if (!isRateLimitError(err) || retryAfter === void 0) return;
|
|
7321
|
+
if (retryAfterOn(err) !== void 0) return;
|
|
7322
|
+
err.data.retryAfter = retryAfter;
|
|
7323
|
+
}
|
|
7324
|
+
function describeRetry(retryAfter, retryAfterMs) {
|
|
7325
|
+
if (retryAfterMs !== void 0) {
|
|
7326
|
+
return `The server asked to retry after ${formatDuration(retryAfterMs)}.`;
|
|
7327
|
+
}
|
|
7328
|
+
if (retryAfter !== void 0) {
|
|
7329
|
+
return `The server sent an unparseable Retry-After (${retryAfter}).`;
|
|
7330
|
+
}
|
|
7331
|
+
return "The server sent no Retry-After header.";
|
|
7332
|
+
}
|
|
7333
|
+
function annotateRateLimit(err, method, now = Date.now()) {
|
|
7334
|
+
if (!isRateLimitError(err)) return err;
|
|
7335
|
+
const header2 = retryAfterOn(err);
|
|
7336
|
+
const retryAfterMs = parseRetryAfter(header2, now);
|
|
7337
|
+
const data = {
|
|
7338
|
+
...err.data,
|
|
7339
|
+
method,
|
|
7340
|
+
...header2 !== void 0 && { retryAfter: header2 },
|
|
7341
|
+
...retryAfterMs !== void 0 && { retryAfterMs }
|
|
7342
|
+
};
|
|
7343
|
+
const message = `Rate limited by the server (HTTP 429) on ${method}. ${describeRetry(header2, retryAfterMs)} ${err.message}`;
|
|
7344
|
+
return new SdkHttpError(err.code, message, data, { cause: err });
|
|
7345
|
+
}
|
|
7346
|
+
|
|
6764
7347
|
// ../../core/mcp/remote/node/server.ts
|
|
6765
|
-
import { SdkHttpError } from "@modelcontextprotocol/client";
|
|
7348
|
+
import { SdkHttpError as SdkHttpError2 } from "@modelcontextprotocol/client";
|
|
6766
7349
|
|
|
6767
7350
|
// ../../core/json/xMcpHeader.ts
|
|
6768
7351
|
var X_MCP_HEADER_KEY = "x-mcp-header";
|
|
@@ -8393,6 +8976,8 @@ var RemoteSession = class _RemoteSession {
|
|
|
8393
8976
|
requestWaits = /* @__PURE__ */ new Map();
|
|
8394
8977
|
authProviderHandle = null;
|
|
8395
8978
|
appliedProtocolVersion;
|
|
8979
|
+
/** Each upstream `429`'s `Retry-After`, by the JSON-RPC id that drew it (#2442). */
|
|
8980
|
+
rateLimits = new RateLimitLedger();
|
|
8396
8981
|
/** A protocol version is a dated token (`2025-11-25`, `2026-07-28`, …). */
|
|
8397
8982
|
static PROTOCOL_VERSION_PATTERN = /^[A-Za-z0-9._-]{1,64}$/;
|
|
8398
8983
|
constructor(sessionId) {
|
|
@@ -8604,6 +9189,7 @@ var RemoteSession = class _RemoteSession {
|
|
|
8604
9189
|
this.pushEvent({ type: "message", data: message });
|
|
8605
9190
|
}
|
|
8606
9191
|
onFetchRequest(entry) {
|
|
9192
|
+
this.rateLimits.record(entry);
|
|
8607
9193
|
this.pushEvent({
|
|
8608
9194
|
type: "fetch_request",
|
|
8609
9195
|
data: {
|
|
@@ -8612,6 +9198,14 @@ var RemoteSession = class _RemoteSession {
|
|
|
8612
9198
|
}
|
|
8613
9199
|
});
|
|
8614
9200
|
}
|
|
9201
|
+
/**
|
|
9202
|
+
* The `Retry-After` of the upstream `429` that answered request `id` — read
|
|
9203
|
+
* by the send route, so the header rides back on the send's own error rather
|
|
9204
|
+
* than on a fetch event racing it (#2442).
|
|
9205
|
+
*/
|
|
9206
|
+
takeRetryAfter(id) {
|
|
9207
|
+
return this.rateLimits.take(id);
|
|
9208
|
+
}
|
|
8615
9209
|
onFetchResponseBody(id, responseBody) {
|
|
8616
9210
|
this.pushEvent({
|
|
8617
9211
|
type: "fetch_request_body_update",
|
|
@@ -9064,6 +9658,17 @@ var MAX_APP_DOCUMENT_CHARS = 8 * 1024 * 1024;
|
|
|
9064
9658
|
var MAX_APP_DOCUMENT_BODY_BYTES = 24 * 1024 * 1024;
|
|
9065
9659
|
var MAX_APP_CSP_CHARS = 8 * 1024;
|
|
9066
9660
|
var MAX_STORAGE_BODY_BYTES = 4 * 1024 * 1024;
|
|
9661
|
+
var MAX_PROXIED_RESPONSE_BYTES = 4 * 1024 * 1024;
|
|
9662
|
+
var BODY_FRAMING_HEADERS = /* @__PURE__ */ new Set([
|
|
9663
|
+
"content-length",
|
|
9664
|
+
"transfer-encoding",
|
|
9665
|
+
"content-encoding"
|
|
9666
|
+
]);
|
|
9667
|
+
function withoutBodyFraming(headers) {
|
|
9668
|
+
return Object.fromEntries(
|
|
9669
|
+
Object.entries(headers).filter(([k]) => !BODY_FRAMING_HEADERS.has(k))
|
|
9670
|
+
);
|
|
9671
|
+
}
|
|
9067
9672
|
function isHeaderSafeValue(value) {
|
|
9068
9673
|
return !/[^\t\x20-\x7e]/.test(value);
|
|
9069
9674
|
}
|
|
@@ -9519,8 +10124,10 @@ function createRemoteApp(options) {
|
|
|
9519
10124
|
});
|
|
9520
10125
|
}
|
|
9521
10126
|
const msg = err instanceof Error ? err.message : String(err);
|
|
9522
|
-
if (err instanceof
|
|
10127
|
+
if (err instanceof SdkHttpError2) {
|
|
9523
10128
|
const text = err.data.text;
|
|
10129
|
+
const sentId = "id" in message ? message.id : void 0;
|
|
10130
|
+
const retryAfter = isRateLimitError(err) && (typeof sentId === "string" || typeof sentId === "number") ? session.takeRetryAfter(sentId) : void 0;
|
|
9524
10131
|
return c.json({
|
|
9525
10132
|
ok: false,
|
|
9526
10133
|
kind: "transport_error",
|
|
@@ -9531,7 +10138,8 @@ function createRemoteApp(options) {
|
|
|
9531
10138
|
...err.statusText !== void 0 && {
|
|
9532
10139
|
statusText: err.statusText
|
|
9533
10140
|
},
|
|
9534
|
-
...typeof text === "string" && { text }
|
|
10141
|
+
...typeof text === "string" && { text },
|
|
10142
|
+
...retryAfter !== void 0 && { retryAfter }
|
|
9535
10143
|
}
|
|
9536
10144
|
});
|
|
9537
10145
|
}
|
|
@@ -9664,7 +10272,28 @@ function createRemoteApp(options) {
|
|
|
9664
10272
|
const isStream = contentType?.includes("text/event-stream") || contentType?.includes("application/x-ndjson");
|
|
9665
10273
|
let resBody;
|
|
9666
10274
|
if (!isStream && res.body) {
|
|
9667
|
-
|
|
10275
|
+
const refuseOversize = () => res.ok ? c.json(
|
|
10276
|
+
{
|
|
10277
|
+
error: `response from ${safeUrl} exceeds the ${MAX_PROXIED_RESPONSE_BYTES}-byte limit for a proxied response`
|
|
10278
|
+
},
|
|
10279
|
+
502
|
|
10280
|
+
) : c.json({
|
|
10281
|
+
ok: res.ok,
|
|
10282
|
+
status: res.status,
|
|
10283
|
+
statusText: res.statusText,
|
|
10284
|
+
headers: withoutBodyFraming(resHeaders)
|
|
10285
|
+
});
|
|
10286
|
+
if (declaresOversizeBody(
|
|
10287
|
+
res.headers.get("content-length"),
|
|
10288
|
+
MAX_PROXIED_RESPONSE_BYTES
|
|
10289
|
+
)) {
|
|
10290
|
+
void res.body.cancel().catch(() => {
|
|
10291
|
+
});
|
|
10292
|
+
return refuseOversize();
|
|
10293
|
+
}
|
|
10294
|
+
const read = await readCappedText(res.body, MAX_PROXIED_RESPONSE_BYTES);
|
|
10295
|
+
if (read.kind === "overflow") return refuseOversize();
|
|
10296
|
+
resBody = read.text;
|
|
9668
10297
|
} else if (isStream) {
|
|
9669
10298
|
void res.body?.cancel().catch(() => {
|
|
9670
10299
|
});
|
|
@@ -11792,12 +12421,12 @@ function countFindings(results) {
|
|
|
11792
12421
|
}
|
|
11793
12422
|
return { errors, warnings };
|
|
11794
12423
|
}
|
|
11795
|
-
function
|
|
12424
|
+
function plural2(count, noun) {
|
|
11796
12425
|
return `${count} ${noun}${count === 1 ? "" : "s"}`;
|
|
11797
12426
|
}
|
|
11798
12427
|
function summarizeFindings(results) {
|
|
11799
12428
|
const { errors, warnings } = countFindings(results);
|
|
11800
|
-
return `${
|
|
12429
|
+
return `${plural2(errors, "error")}, ${plural2(warnings, "warning")} across ${plural2(results.length, "tool")}`;
|
|
11801
12430
|
}
|
|
11802
12431
|
function formatSchemaLintReport(results) {
|
|
11803
12432
|
const lines = [];
|
|
@@ -12935,11 +13564,11 @@ import {
|
|
|
12935
13564
|
} from "@modelcontextprotocol/core";
|
|
12936
13565
|
|
|
12937
13566
|
// ../../core/mcp/subscriptionAck.ts
|
|
12938
|
-
import { SdkError, SdkErrorCode } from "@modelcontextprotocol/client";
|
|
13567
|
+
import { SdkError as SdkError2, SdkErrorCode as SdkErrorCode2 } from "@modelcontextprotocol/client";
|
|
12939
13568
|
var NEVER_ACKNOWLEDGED_SDK_MESSAGE = "closed the subscription gracefully before acknowledging";
|
|
12940
13569
|
var NEVER_ACKNOWLEDGED_SUBSCRIPTION_MESSAGE = "The server closed the subscription without acknowledging it. A subscriptions/listen request must first be answered with a notifications/subscriptions/acknowledged notification; a JSON-RPC result for the listen id means graceful closure. Not retrying \u2014 the server would answer the same way again.";
|
|
12941
13570
|
function isNeverAcknowledgedSubscriptionClose(error) {
|
|
12942
|
-
return
|
|
13571
|
+
return SdkError2.isInstance(error) && error.code === SdkErrorCode2.ConnectionClosed && error.message.includes(NEVER_ACKNOWLEDGED_SDK_MESSAGE);
|
|
12943
13572
|
}
|
|
12944
13573
|
function subscriptionFailureMessage(error) {
|
|
12945
13574
|
if (isNeverAcknowledgedSubscriptionClose(error)) {
|
|
@@ -12948,6 +13577,23 @@ function subscriptionFailureMessage(error) {
|
|
|
12948
13577
|
return error instanceof Error ? error.message : String(error);
|
|
12949
13578
|
}
|
|
12950
13579
|
|
|
13580
|
+
// ../../core/mcp/subscriptionRefusal.ts
|
|
13581
|
+
import { SdkErrorCode as SdkErrorCode3, SdkHttpError as SdkHttpError3 } from "@modelcontextprotocol/client";
|
|
13582
|
+
var REFUSAL_STATUSES = /* @__PURE__ */ new Set([400, 403, 404, 405]);
|
|
13583
|
+
function subscriptionListenRefusal(error) {
|
|
13584
|
+
if (!SdkHttpError3.isInstance(error) || error.code !== SdkErrorCode3.ClientHttpNotImplemented || !REFUSAL_STATUSES.has(error.status)) {
|
|
13585
|
+
return void 0;
|
|
13586
|
+
}
|
|
13587
|
+
return {
|
|
13588
|
+
status: error.status,
|
|
13589
|
+
...error.statusText ? { statusText: error.statusText } : {}
|
|
13590
|
+
};
|
|
13591
|
+
}
|
|
13592
|
+
function subscriptionListenRefusalMessage(refusal) {
|
|
13593
|
+
const status = refusal.statusText ? `${refusal.status} ${refusal.statusText}` : String(refusal.status);
|
|
13594
|
+
return `The server refused subscriptions/listen (HTTP ${status}). Not retrying \u2014 the server would answer the same way again.`;
|
|
13595
|
+
}
|
|
13596
|
+
|
|
12951
13597
|
// ../../core/mcp/messageTrackingTransport.ts
|
|
12952
13598
|
var MessageTrackingTransport = class {
|
|
12953
13599
|
baseTransport;
|
|
@@ -13177,7 +13823,7 @@ var TaskProgressRouter = class {
|
|
|
13177
13823
|
};
|
|
13178
13824
|
|
|
13179
13825
|
// ../../core/extension/tasks/rawWireChannel.ts
|
|
13180
|
-
import { SdkError as
|
|
13826
|
+
import { SdkError as SdkError3, SdkErrorCode as SdkErrorCode4 } from "@modelcontextprotocol/client";
|
|
13181
13827
|
import { DispatchError as DispatchError2 } from "@modelcontextprotocol/ext-tasks/client";
|
|
13182
13828
|
var RAW_WIRE_ID_PREFIX = "inspector-ext-";
|
|
13183
13829
|
var RawWireChannel = class {
|
|
@@ -13250,8 +13896,8 @@ var RawWireChannel = class {
|
|
|
13250
13896
|
`Raw MCP request "${message.method}" timed out after ${timeoutMs} ms`,
|
|
13251
13897
|
false,
|
|
13252
13898
|
{
|
|
13253
|
-
cause: this.host.
|
|
13254
|
-
new
|
|
13899
|
+
cause: this.host.annotateError(
|
|
13900
|
+
new SdkError3(SdkErrorCode4.RequestTimeout, "Request timed out", {
|
|
13255
13901
|
timeout: timeoutMs
|
|
13256
13902
|
}),
|
|
13257
13903
|
message.method
|
|
@@ -13291,7 +13937,7 @@ var RawWireChannel = class {
|
|
|
13291
13937
|
if (!pending) return;
|
|
13292
13938
|
pending.cleanup();
|
|
13293
13939
|
reject(
|
|
13294
|
-
this.host.
|
|
13940
|
+
this.host.annotateError(
|
|
13295
13941
|
error instanceof Error ? error : new Error(String(error)),
|
|
13296
13942
|
message.method
|
|
13297
13943
|
)
|
|
@@ -13591,7 +14237,7 @@ function validateToolOutput(provider, tool, result) {
|
|
|
13591
14237
|
|
|
13592
14238
|
// ../../core/mcp/listSalvage.ts
|
|
13593
14239
|
import { z as z4 } from "zod/v4";
|
|
13594
|
-
import { SdkError as
|
|
14240
|
+
import { SdkError as SdkError4, SdkErrorCode as SdkErrorCode5 } from "@modelcontextprotocol/client";
|
|
13595
14241
|
import { ToolSchema } from "@modelcontextprotocol/core";
|
|
13596
14242
|
var LIST_MAX_PAGES = 64;
|
|
13597
14243
|
function listPaginationExceeded(method) {
|
|
@@ -13600,10 +14246,10 @@ function listPaginationExceeded(method) {
|
|
|
13600
14246
|
);
|
|
13601
14247
|
}
|
|
13602
14248
|
function isClientDecodeRejection(err) {
|
|
13603
|
-
return
|
|
14249
|
+
return SdkError4.isInstance(err) && (err.code === SdkErrorCode5.InvalidResult || err.code === SdkErrorCode5.UnsupportedResultType);
|
|
13604
14250
|
}
|
|
13605
14251
|
function isSalvageableRejection(err) {
|
|
13606
|
-
return
|
|
14252
|
+
return SdkError4.isInstance(err) && err.code === SdkErrorCode5.InvalidResult;
|
|
13607
14253
|
}
|
|
13608
14254
|
function lenientListPageSchema(resultSchema, itemsKey) {
|
|
13609
14255
|
return resultSchema.extend({ [itemsKey]: z4.array(z4.unknown()) });
|
|
@@ -14209,79 +14855,6 @@ var ToolCallCancelledError = class extends Error {
|
|
|
14209
14855
|
}
|
|
14210
14856
|
};
|
|
14211
14857
|
|
|
14212
|
-
// ../../core/mcp/connectionDiagnostics.ts
|
|
14213
|
-
import { SdkError as SdkError4, SdkErrorCode as SdkErrorCode4 } from "@modelcontextprotocol/client";
|
|
14214
|
-
function isRequestTimeoutData(value) {
|
|
14215
|
-
return typeof value === "object" && value !== null && typeof value.timeout === "number";
|
|
14216
|
-
}
|
|
14217
|
-
function isRequestTimeoutError(err) {
|
|
14218
|
-
return SdkError4.isInstance(err) && err.code === SdkErrorCode4.RequestTimeout && isRequestTimeoutData(err.data);
|
|
14219
|
-
}
|
|
14220
|
-
function formatDuration(ms) {
|
|
14221
|
-
const clamped = Math.max(0, ms);
|
|
14222
|
-
if (clamped < 1e3) return `${Math.round(clamped)}ms`;
|
|
14223
|
-
const totalSeconds = Math.round(clamped / 1e3);
|
|
14224
|
-
const hours = Math.floor(totalSeconds / 3600);
|
|
14225
|
-
const minutes = Math.floor(totalSeconds % 3600 / 60);
|
|
14226
|
-
const seconds = totalSeconds % 60;
|
|
14227
|
-
if (hours > 0) return `${hours}h${String(minutes).padStart(2, "0")}m`;
|
|
14228
|
-
if (minutes > 0) return `${minutes}m${String(seconds).padStart(2, "0")}s`;
|
|
14229
|
-
return `${seconds}s`;
|
|
14230
|
-
}
|
|
14231
|
-
var MAX_LISTED_OUTSTANDING_REQUESTS = 5;
|
|
14232
|
-
function describeStreamEndpoint(url) {
|
|
14233
|
-
try {
|
|
14234
|
-
return `GET ${new URL(url).pathname}`;
|
|
14235
|
-
} catch {
|
|
14236
|
-
return `GET ${url}`;
|
|
14237
|
-
}
|
|
14238
|
-
}
|
|
14239
|
-
function plural2(count, noun) {
|
|
14240
|
-
return `${count} ${noun}${count === 1 ? "" : "s"}`;
|
|
14241
|
-
}
|
|
14242
|
-
function describeOutstanding(requests, now) {
|
|
14243
|
-
if (requests.length === 0) return "No requests are unanswered.";
|
|
14244
|
-
const listed = requests.slice(0, MAX_LISTED_OUTSTANDING_REQUESTS).map((r) => `${r.method} (sent ${formatDuration(now - r.sentAt)} ago)`);
|
|
14245
|
-
const rest = requests.length - listed.length;
|
|
14246
|
-
const tail = rest > 0 ? `, and ${rest} more` : "";
|
|
14247
|
-
const verb = requests.length === 1 ? "is" : "are";
|
|
14248
|
-
return `${plural2(requests.length, "request")} ${verb} unanswered: ${listed.join(", ")}${tail}.`;
|
|
14249
|
-
}
|
|
14250
|
-
function describeLastResponse(lastResponse, now) {
|
|
14251
|
-
if (!lastResponse) return "No response has been received this session.";
|
|
14252
|
-
return `Last response received ${formatDuration(now - lastResponse.receivedAt)} ago (${lastResponse.method}).`;
|
|
14253
|
-
}
|
|
14254
|
-
function describeStream(stream, now) {
|
|
14255
|
-
if (!stream) return void 0;
|
|
14256
|
-
const endpoint = describeStreamEndpoint(stream.url);
|
|
14257
|
-
const events = plural2(stream.eventCount, "event");
|
|
14258
|
-
if (stream.closedAt !== void 0) {
|
|
14259
|
-
return `Notification stream (${endpoint}) closed ${formatDuration(now - stream.closedAt)} ago after ${formatDuration(stream.closedAt - stream.openedAt)}, ${events} delivered.`;
|
|
14260
|
-
}
|
|
14261
|
-
return `Notification stream (${endpoint}) open for ${formatDuration(now - stream.openedAt)}, ${events} delivered.`;
|
|
14262
|
-
}
|
|
14263
|
-
function describeConnectionDiagnostics(diagnostics) {
|
|
14264
|
-
const now = diagnostics.capturedAt;
|
|
14265
|
-
const parts = [
|
|
14266
|
-
describeOutstanding(diagnostics.outstandingRequests, now),
|
|
14267
|
-
describeLastResponse(diagnostics.lastResponse, now),
|
|
14268
|
-
describeStream(diagnostics.notificationStream, now)
|
|
14269
|
-
];
|
|
14270
|
-
return parts.filter((p) => p !== void 0).join(" ");
|
|
14271
|
-
}
|
|
14272
|
-
function annotateRequestTimeout(err, method, diagnostics) {
|
|
14273
|
-
if (!isRequestTimeoutError(err)) return err;
|
|
14274
|
-
const data = {
|
|
14275
|
-
...err.data,
|
|
14276
|
-
method,
|
|
14277
|
-
diagnostics
|
|
14278
|
-
};
|
|
14279
|
-
const message = `Request timed out after ${formatDuration(err.data.timeout)} (${method}). ${describeConnectionDiagnostics(diagnostics)}`;
|
|
14280
|
-
const annotated = new SdkError4(SdkErrorCode4.RequestTimeout, message, data);
|
|
14281
|
-
annotated.cause = err;
|
|
14282
|
-
return annotated;
|
|
14283
|
-
}
|
|
14284
|
-
|
|
14285
14858
|
// ../../core/auth/ema/clientConfigError.ts
|
|
14286
14859
|
function emaClientNotConfiguredMessage(reason) {
|
|
14287
14860
|
if (reason === "disabled") {
|
|
@@ -14714,11 +15287,6 @@ async function mintEmaResourceTokens(config, resourceContext) {
|
|
|
14714
15287
|
"EMA requires resource authorization server clientId (per-server oauth.clientId)"
|
|
14715
15288
|
);
|
|
14716
15289
|
}
|
|
14717
|
-
if (!config.resourceClientSecret?.trim()) {
|
|
14718
|
-
throw new Error(
|
|
14719
|
-
"EMA requires resource authorization server client secret in server OAuth settings (Test Client secret from xaa.dev resource registration)"
|
|
14720
|
-
);
|
|
14721
|
-
}
|
|
14722
15290
|
const idToken = await getValidIdToken({
|
|
14723
15291
|
idp: config.idp,
|
|
14724
15292
|
storage: config.storage,
|
|
@@ -14984,7 +15552,9 @@ var OAuthManager = class {
|
|
|
14984
15552
|
requestRefreshToken: this.oauthConfig.requestRefreshToken,
|
|
14985
15553
|
// #2068: lets the provider tell an `offline_access` the user asked for
|
|
14986
15554
|
// from one inherited from a previous grant's persisted scope.
|
|
14987
|
-
configuredScope: this.oauthConfig.scope
|
|
15555
|
+
configuredScope: this.oauthConfig.scope,
|
|
15556
|
+
// #2726: in-flight PKCE verifiers keyed by flow, not by server URL.
|
|
15557
|
+
flowStore: this.oauthConfig.flowStore
|
|
14988
15558
|
});
|
|
14989
15559
|
provider.setEventTarget(this.params.getEventTarget());
|
|
14990
15560
|
await provider.prepareForAuth();
|
|
@@ -15148,7 +15718,12 @@ var OAuthManager = class {
|
|
|
15148
15718
|
recordAuthorizationRequestScope(authorizationUrl, requestedScope) {
|
|
15149
15719
|
this.pendingAuthorizationScope = authorizationUrl.searchParams.get("scope")?.trim() || requestedScope;
|
|
15150
15720
|
}
|
|
15151
|
-
|
|
15721
|
+
/**
|
|
15722
|
+
* `state` is the OAuth `state` the callback was redirected back with. It
|
|
15723
|
+
* selects the flow whose PKCE verifier the exchange uses (#2726); without it
|
|
15724
|
+
* a provider with a flow store has no verifier to send.
|
|
15725
|
+
*/
|
|
15726
|
+
async completeOAuthFlow(authorizationCode, iss, state) {
|
|
15152
15727
|
try {
|
|
15153
15728
|
if (this.isEnterpriseManaged()) {
|
|
15154
15729
|
const emaConfig = await this.getEmaFlowConfig();
|
|
@@ -15172,6 +15747,9 @@ var OAuthManager = class {
|
|
|
15172
15747
|
}
|
|
15173
15748
|
const provider = await this.createOAuthProvider();
|
|
15174
15749
|
const serverUrl = this.getServerUrl();
|
|
15750
|
+
if (state !== void 0) {
|
|
15751
|
+
provider.resumeAuthorizationFlow(state);
|
|
15752
|
+
}
|
|
15175
15753
|
const result = await mcpAuth(provider, {
|
|
15176
15754
|
serverUrl,
|
|
15177
15755
|
authorizationCode,
|
|
@@ -15183,6 +15761,7 @@ var OAuthManager = class {
|
|
|
15183
15761
|
`Expected AUTHORIZED after providing authorization code, got: ${result}`
|
|
15184
15762
|
);
|
|
15185
15763
|
}
|
|
15764
|
+
await provider.clearAuthorizationFlow();
|
|
15186
15765
|
const tokens = await provider.tokens();
|
|
15187
15766
|
if (!tokens) {
|
|
15188
15767
|
throw new Error("Failed to retrieve tokens after authorization");
|
|
@@ -15314,15 +15893,21 @@ var OAuthManager = class {
|
|
|
15314
15893
|
* Re-read persisted OAuth state and determine whether `challenge` is already
|
|
15315
15894
|
* satisfied without an authorization-server round-trip.
|
|
15316
15895
|
*
|
|
15317
|
-
*
|
|
15318
|
-
*
|
|
15319
|
-
*
|
|
15320
|
-
*
|
|
15321
|
-
*
|
|
15322
|
-
*
|
|
15323
|
-
*
|
|
15896
|
+
* For `token_expired` and `insufficient_scope`, returns `true` only when
|
|
15897
|
+
* storage holds a *provably* unexpired token — a JWT whose `exp` is still in
|
|
15898
|
+
* the future. An opaque token carries no local expiry evidence, so the
|
|
15899
|
+
* resource server's verdict stands and re-authorization proceeds.
|
|
15900
|
+
* `insufficient_scope` additionally requires stored + token scope to cover
|
|
15901
|
+
* the SEP-2350 union: some resource servers answer an *expired* token with
|
|
15902
|
+
* `insufficient_scope`, and a scope-only check would replay that dead token
|
|
15903
|
+
* instead of refreshing it (#2724, the same gap #2051 closed for
|
|
15904
|
+
* `token_expired`). `invalid_token` and `unauthorized` always return
|
|
15905
|
+
* `false` — the resource server explicitly rejected the credential.
|
|
15906
|
+
*
|
|
15907
|
+
* A caller that has just completed a grant passes `scopeOnly` — see
|
|
15908
|
+
* {@link CheckAuthChallengeSatisfiedOptions}.
|
|
15324
15909
|
*/
|
|
15325
|
-
async checkAuthChallengeSatisfied(challenge) {
|
|
15910
|
+
async checkAuthChallengeSatisfied(challenge, options) {
|
|
15326
15911
|
const storage = this.oauthConfig.storage;
|
|
15327
15912
|
if (!storage) {
|
|
15328
15913
|
return false;
|
|
@@ -15335,6 +15920,14 @@ var OAuthManager = class {
|
|
|
15335
15920
|
if (challenge.reason !== "insufficient_scope") {
|
|
15336
15921
|
return challenge.reason === "token_expired" && isAccessTokenProvablyUnexpired(tokens);
|
|
15337
15922
|
}
|
|
15923
|
+
if (!options?.scopeOnly && !isAccessTokenProvablyUnexpired(tokens)) {
|
|
15924
|
+
return false;
|
|
15925
|
+
}
|
|
15926
|
+
return this.isStepUpScopeCovered(challenge, storage, tokens);
|
|
15927
|
+
}
|
|
15928
|
+
/** Stored + token scope cover the challenge's SEP-2350 union. */
|
|
15929
|
+
async isStepUpScopeCovered(challenge, storage, tokens) {
|
|
15930
|
+
const serverUrl = this.getServerUrl();
|
|
15338
15931
|
const enriched = await this.enrichChallengeWithAuthorizationScopes(
|
|
15339
15932
|
challenge,
|
|
15340
15933
|
tokens.scope
|
|
@@ -15438,14 +16031,14 @@ var OAuthManager = class {
|
|
|
15438
16031
|
}
|
|
15439
16032
|
async handleEnterpriseManagedAuthChallenge(challenge, options) {
|
|
15440
16033
|
const enriched = await this.enrichChallengeWithAuthorizationScopes(challenge);
|
|
15441
|
-
if (enriched.reason === "insufficient_scope" && !options?.confirmedStepUp) {
|
|
16034
|
+
if (enriched.reason === "insufficient_scope" && !options?.confirmedStepUp && !await this.checkAuthChallengeSatisfied(enriched, { scopeOnly: true })) {
|
|
15442
16035
|
return { kind: "step_up_confirm", challenge: enriched };
|
|
15443
16036
|
}
|
|
15444
16037
|
const config = await this.emaFlowConfigForChallenge(enriched);
|
|
15445
16038
|
if (enriched.reason === "insufficient_scope") {
|
|
15446
16039
|
const silent = await trySilentEmaAuth(config);
|
|
15447
16040
|
if (silent.status === "success") {
|
|
15448
|
-
if (await this.checkAuthChallengeSatisfied(enriched)) {
|
|
16041
|
+
if (await this.checkAuthChallengeSatisfied(enriched, { scopeOnly: true })) {
|
|
15449
16042
|
const minted = await this.requireStorage().getTokens(
|
|
15450
16043
|
this.getServerUrl()
|
|
15451
16044
|
);
|
|
@@ -15523,9 +16116,16 @@ var OAuthManager = class {
|
|
|
15523
16116
|
fetchFn: this.params.effectiveAuthFetch,
|
|
15524
16117
|
resourceMetadataUrl,
|
|
15525
16118
|
...enriched.reason === "insufficient_scope" && {
|
|
16119
|
+
// Judge coverage the way checkAuthChallengeSatisfied does: a token
|
|
16120
|
+
// response that omitted `scope` granted what was stored, so a
|
|
16121
|
+
// covered challenge on an expired token refreshes rather than
|
|
16122
|
+
// forcing interactive authorization (#2724).
|
|
15526
16123
|
forceReauthorization: isStrictScopeSuperset(
|
|
15527
16124
|
scopeForAuth,
|
|
15528
|
-
|
|
16125
|
+
resolveEffectiveGrantedScope(
|
|
16126
|
+
await this.oauthConfig.storage?.getScope(serverUrl),
|
|
16127
|
+
tokens?.scope
|
|
16128
|
+
)
|
|
15529
16129
|
)
|
|
15530
16130
|
}
|
|
15531
16131
|
});
|
|
@@ -15534,7 +16134,7 @@ var OAuthManager = class {
|
|
|
15534
16134
|
}
|
|
15535
16135
|
if (result === "AUTHORIZED") {
|
|
15536
16136
|
if (enriched.reason === "insufficient_scope") {
|
|
15537
|
-
if (await this.checkAuthChallengeSatisfied(enriched)) {
|
|
16137
|
+
if (await this.checkAuthChallengeSatisfied(enriched, { scopeOnly: true })) {
|
|
15538
16138
|
const freshTokens = await provider.tokens();
|
|
15539
16139
|
const scopeToPersist = resolvePersistedScopeAfterGrant(
|
|
15540
16140
|
freshTokens?.scope,
|
|
@@ -15636,7 +16236,7 @@ var OAuthManager = class {
|
|
|
15636
16236
|
if (result !== "AUTHORIZED") {
|
|
15637
16237
|
return null;
|
|
15638
16238
|
}
|
|
15639
|
-
if (await this.checkAuthChallengeSatisfied(enriched)) {
|
|
16239
|
+
if (await this.checkAuthChallengeSatisfied(enriched, { scopeOnly: true })) {
|
|
15640
16240
|
const freshTokens = await provider.tokens();
|
|
15641
16241
|
const scopeToPersist = resolvePersistedScopeAfterGrant(
|
|
15642
16242
|
freshTokens?.scope,
|
|
@@ -15668,8 +16268,8 @@ var OAuthManager = class {
|
|
|
15668
16268
|
// ../../core/mcp/remote/remoteClientTransport.ts
|
|
15669
16269
|
import {
|
|
15670
16270
|
SdkError as SdkError5,
|
|
15671
|
-
SdkErrorCode as
|
|
15672
|
-
SdkHttpError as
|
|
16271
|
+
SdkErrorCode as SdkErrorCode6,
|
|
16272
|
+
SdkHttpError as SdkHttpError4
|
|
15673
16273
|
} from "@modelcontextprotocol/client";
|
|
15674
16274
|
|
|
15675
16275
|
// ../../core/mcp/remote/types.ts
|
|
@@ -15704,14 +16304,15 @@ function isConnectTransportError(json) {
|
|
|
15704
16304
|
return typeof json === "object" && json !== null && "ok" in json && json.ok === false && "kind" in json && json.kind === "transport_error";
|
|
15705
16305
|
}
|
|
15706
16306
|
var SDK_ERROR_CODES = new Set(
|
|
15707
|
-
Object.values(
|
|
16307
|
+
Object.values(SdkErrorCode6)
|
|
15708
16308
|
);
|
|
15709
16309
|
function toSdkHttpError(message, http) {
|
|
15710
|
-
const code = SDK_ERROR_CODES.has(http.code) ? http.code :
|
|
15711
|
-
return new
|
|
16310
|
+
const code = SDK_ERROR_CODES.has(http.code) ? http.code : SdkErrorCode6.ClientHttpNotImplemented;
|
|
16311
|
+
return new SdkHttpError4(code, message, {
|
|
15712
16312
|
status: http.status,
|
|
15713
16313
|
...http.statusText !== void 0 && { statusText: http.statusText },
|
|
15714
|
-
...http.text !== void 0 && { text: http.text }
|
|
16314
|
+
...http.text !== void 0 && { text: http.text },
|
|
16315
|
+
...http.retryAfter !== void 0 && { retryAfter: http.retryAfter }
|
|
15715
16316
|
});
|
|
15716
16317
|
}
|
|
15717
16318
|
function legacySessionId(json) {
|
|
@@ -16145,7 +16746,7 @@ var RemoteClientTransport = class {
|
|
|
16145
16746
|
timer = setTimeout(() => {
|
|
16146
16747
|
this.sseResponseWaits.delete(requestId);
|
|
16147
16748
|
reject(
|
|
16148
|
-
new SdkError5(
|
|
16749
|
+
new SdkError5(SdkErrorCode6.RequestTimeout, "Request timed out", {
|
|
16149
16750
|
timeout: this.sseResponseTimeoutMs
|
|
16150
16751
|
})
|
|
16151
16752
|
);
|
|
@@ -16357,7 +16958,7 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
|
|
|
16357
16958
|
transport: () => this.transport,
|
|
16358
16959
|
defaultTimeoutMs: () => this.requestTimeout ?? DEFAULT_REQUEST_TIMEOUT_MSEC,
|
|
16359
16960
|
resetTimeoutOnProgress: () => this.resetTimeoutOnProgress,
|
|
16360
|
-
|
|
16961
|
+
annotateError: (error, method) => this.annotateRequestError(error, method)
|
|
16361
16962
|
});
|
|
16362
16963
|
dispatchTaskRequest = (request, options) => this.rawWire.dispatch(request, options);
|
|
16363
16964
|
// Every outbound request still awaiting a response, keyed by JSON-RPC id.
|
|
@@ -16377,6 +16978,10 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
|
|
|
16377
16978
|
// `GET` on Streamable HTTP). Both per-session, reset with the maps above.
|
|
16378
16979
|
lastResponse;
|
|
16379
16980
|
notificationStream;
|
|
16981
|
+
// Each tracked `429`'s `Retry-After`, by the JSON-RPC id that drew it, until
|
|
16982
|
+
// that id's failed send claims it (#2442). Per-session, reset with the
|
|
16983
|
+
// diagnostics above.
|
|
16984
|
+
rateLimits = new RateLimitLedger();
|
|
16380
16985
|
/** True when the cached transport was built with an OAuth authProvider attached. */
|
|
16381
16986
|
transportHasAuthProvider = false;
|
|
16382
16987
|
/** Dedupes concurrent ambient auth challenges (reason + scopes). */
|
|
@@ -16521,6 +17126,10 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
|
|
|
16521
17126
|
// site to the two failure handlers. Cleared by any user-initiated refresh (a
|
|
16522
17127
|
// subscribe/unsubscribe is a fresh attempt, and the server may have changed).
|
|
16523
17128
|
modernNeverAcknowledged = false;
|
|
17129
|
+
// Set when the last `listen()` was refused at the HTTP layer with a status a
|
|
17130
|
+
// retry cannot change (#2722) — the sibling of `modernNeverAcknowledged`,
|
|
17131
|
+
// carried and cleared the same way, holding the HTTP answer the UI reports.
|
|
17132
|
+
modernListenRefusal = null;
|
|
16524
17133
|
/** Which in-flight task calls own each progress token (#2316). */
|
|
16525
17134
|
taskProgress = new TaskProgressRouter();
|
|
16526
17135
|
// Abort controller for the in-flight ordinary (non-task) tool call. Aborting
|
|
@@ -16778,7 +17387,7 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
|
|
|
16778
17387
|
this.clientInfo,
|
|
16779
17388
|
Object.keys(clientOptions).length > 0 ? clientOptions : void 0
|
|
16780
17389
|
);
|
|
16781
|
-
this.
|
|
17390
|
+
this.annotateSdkRequestErrors(this.client);
|
|
16782
17391
|
if (this.tasksCapabilityAdvertised) {
|
|
16783
17392
|
this.bindReceiverTasks();
|
|
16784
17393
|
}
|
|
@@ -16786,7 +17395,8 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
|
|
|
16786
17395
|
/**
|
|
16787
17396
|
* Decorate the SDK Client's `request` so a per-request timeout rejects with
|
|
16788
17397
|
* the connection's state at that moment instead of a bare `Request timed
|
|
16789
|
-
* out` (#2318)
|
|
17398
|
+
* out` (#2318), and a `429` rejects as rate limiting with the server's
|
|
17399
|
+
* `Retry-After` instead of a bare `Error POSTing to endpoint` (#2442).
|
|
16790
17400
|
*
|
|
16791
17401
|
* `Protocol.request` is the one funnel every SDK verb goes through —
|
|
16792
17402
|
* `listTools`, `callTool`, `readResource`, the handshake's `initialize`,
|
|
@@ -16804,21 +17414,30 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
|
|
|
16804
17414
|
* implementation signature serves both at runtime; the single cast on the
|
|
16805
17415
|
* way back is what the overloads cost.
|
|
16806
17416
|
*/
|
|
16807
|
-
|
|
17417
|
+
annotateSdkRequestErrors(client) {
|
|
16808
17418
|
const sdkRequest = client.request.bind(client);
|
|
16809
17419
|
const decorated = async (...args) => {
|
|
16810
17420
|
try {
|
|
16811
17421
|
return await sdkRequest(...args);
|
|
16812
17422
|
} catch (err) {
|
|
16813
|
-
throw
|
|
16814
|
-
err,
|
|
16815
|
-
args[0].method,
|
|
16816
|
-
this.getConnectionDiagnostics()
|
|
16817
|
-
);
|
|
17423
|
+
throw this.annotateRequestError(err, args[0].method);
|
|
16818
17424
|
}
|
|
16819
17425
|
};
|
|
16820
17426
|
client.request = decorated;
|
|
16821
17427
|
}
|
|
17428
|
+
/**
|
|
17429
|
+
* The one place a failed request's error is decorated, shared by the SDK
|
|
17430
|
+
* funnel above and the raw-wire channel that bypasses it (modern-era tool
|
|
17431
|
+
* calls and task operations), so a caller sees the same error whichever
|
|
17432
|
+
* path its request took: a timeout with the connection's state (#2318), a
|
|
17433
|
+
* `429` named as rate limiting (#2442), anything else untouched.
|
|
17434
|
+
*/
|
|
17435
|
+
annotateRequestError(err, method) {
|
|
17436
|
+
return annotateRateLimit(
|
|
17437
|
+
annotateRequestTimeout(err, method, this.getConnectionDiagnostics()),
|
|
17438
|
+
method
|
|
17439
|
+
);
|
|
17440
|
+
}
|
|
16822
17441
|
/**
|
|
16823
17442
|
* @param base - the composed OAuth-path fetch, deadline innermost (#2319).
|
|
16824
17443
|
*
|
|
@@ -16862,9 +17481,14 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
|
|
|
16862
17481
|
},
|
|
16863
17482
|
trackSendFailure: (message, error) => {
|
|
16864
17483
|
if (isRequestTimeoutError(error)) return;
|
|
17484
|
+
attachRetryAfter(error, this.rateLimits.take(message.id));
|
|
16865
17485
|
if (this.outstandingRequests.delete(message.id)) {
|
|
16866
17486
|
this.dispatchConnectionDiagnosticsChange();
|
|
16867
17487
|
}
|
|
17488
|
+
this.dispatchTypedEvent("requestFailed", {
|
|
17489
|
+
id: message.id,
|
|
17490
|
+
reason: describeSendFailure(error)
|
|
17491
|
+
});
|
|
16868
17492
|
},
|
|
16869
17493
|
trackResponse: (message, origin) => {
|
|
16870
17494
|
const responseId = message.id;
|
|
@@ -16930,6 +17554,7 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
|
|
|
16930
17554
|
};
|
|
16931
17555
|
baseTransport.onerror = (error) => {
|
|
16932
17556
|
if (this.status === "connecting") return;
|
|
17557
|
+
if (subscriptionListenRefusal(error) !== void 0) return;
|
|
16933
17558
|
this.status = "error";
|
|
16934
17559
|
this.dispatchTypedEvent("statusChange", this.status);
|
|
16935
17560
|
this.dispatchTypedEvent("error", error);
|
|
@@ -17175,6 +17800,7 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
|
|
|
17175
17800
|
this.clearModernReconnectTimer();
|
|
17176
17801
|
this.modernReconnectAttempts = 0;
|
|
17177
17802
|
this.modernNeverAcknowledged = false;
|
|
17803
|
+
this.modernListenRefusal = null;
|
|
17178
17804
|
this.modernSubscription = null;
|
|
17179
17805
|
this.setModernStreamState(INACTIVE_SUBSCRIPTION_STREAM_STATE);
|
|
17180
17806
|
this.dispatchSubscriptionsChange();
|
|
@@ -17213,6 +17839,7 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
|
|
|
17213
17839
|
this.taskProgress.clear();
|
|
17214
17840
|
this.lastResponse = void 0;
|
|
17215
17841
|
this.notificationStream = void 0;
|
|
17842
|
+
this.rateLimits.clear();
|
|
17216
17843
|
this.dispatchConnectionDiagnosticsChange();
|
|
17217
17844
|
if (this.malformedListItems.size > 0) {
|
|
17218
17845
|
this.malformedListItems.clear();
|
|
@@ -17315,6 +17942,7 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
|
|
|
17315
17942
|
onFetchRequest: (entry) => {
|
|
17316
17943
|
this.dispatchFetchRequest({ ...entry, category: "transport" });
|
|
17317
17944
|
this.noteTransportStream(entry);
|
|
17945
|
+
this.rateLimits.record(entry);
|
|
17318
17946
|
},
|
|
17319
17947
|
onFetchResponseBody: (id, body) => {
|
|
17320
17948
|
this.dispatchFetchRequestBodyUpdate(id, body);
|
|
@@ -18031,7 +18659,7 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
|
|
|
18031
18659
|
}
|
|
18032
18660
|
const wrapped = withInputRequired(resultSchema);
|
|
18033
18661
|
const signal = requestOptions.signal;
|
|
18034
|
-
let
|
|
18662
|
+
let round2 = 0;
|
|
18035
18663
|
let nextParams = params;
|
|
18036
18664
|
while (true) {
|
|
18037
18665
|
const outcome = await client.request(
|
|
@@ -18045,8 +18673,8 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
|
|
|
18045
18673
|
if (!isInputRequiredResult(outcome)) {
|
|
18046
18674
|
return outcome;
|
|
18047
18675
|
}
|
|
18048
|
-
|
|
18049
|
-
if (
|
|
18676
|
+
round2 += 1;
|
|
18677
|
+
if (round2 > _InspectorClient.MRTR_MAX_ROUNDS) {
|
|
18050
18678
|
throw new Error(
|
|
18051
18679
|
`Multi-round-trip "${method}" exceeded ${_InspectorClient.MRTR_MAX_ROUNDS} input_required rounds without completing.`
|
|
18052
18680
|
);
|
|
@@ -19654,11 +20282,12 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
|
|
|
19654
20282
|
// for page one.
|
|
19655
20283
|
...cursor2 !== void 0 ? { cursor: cursor2 } : {}
|
|
19656
20284
|
};
|
|
20285
|
+
const resultSchema = this.isModernEra() ? ModernDirectoryReadResultSchema : DirectoryReadResultSchema;
|
|
19657
20286
|
try {
|
|
19658
20287
|
return await this.invokeMcpClient(
|
|
19659
20288
|
() => this.client.request(
|
|
19660
20289
|
{ method: RESOURCES_DIRECTORY_READ_METHOD, params },
|
|
19661
|
-
|
|
20290
|
+
resultSchema,
|
|
19662
20291
|
this.getRequestOptions(this.progressTokenOf(metadata))
|
|
19663
20292
|
),
|
|
19664
20293
|
{ method: RESOURCES_DIRECTORY_READ_METHOD }
|
|
@@ -20113,6 +20742,7 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
|
|
|
20113
20742
|
this.clearModernReconnectTimer();
|
|
20114
20743
|
this.modernReconnectAttempts = 0;
|
|
20115
20744
|
this.modernNeverAcknowledged = false;
|
|
20745
|
+
this.modernListenRefusal = null;
|
|
20116
20746
|
}
|
|
20117
20747
|
const generation = ++this.modernListenGeneration;
|
|
20118
20748
|
const previous = this.modernSubscription;
|
|
@@ -20131,8 +20761,11 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
|
|
|
20131
20761
|
this.getRequestOptions()
|
|
20132
20762
|
);
|
|
20133
20763
|
} catch (error) {
|
|
20134
|
-
if (generation === this.modernListenGeneration
|
|
20135
|
-
|
|
20764
|
+
if (generation === this.modernListenGeneration) {
|
|
20765
|
+
if (isNeverAcknowledgedSubscriptionClose(error)) {
|
|
20766
|
+
this.modernNeverAcknowledged = true;
|
|
20767
|
+
}
|
|
20768
|
+
this.modernListenRefusal = subscriptionListenRefusal(error) ?? null;
|
|
20136
20769
|
}
|
|
20137
20770
|
throw error;
|
|
20138
20771
|
}
|
|
@@ -20213,6 +20846,10 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
|
|
|
20213
20846
|
* its error either way — the retry is about the subscriptions, not the call.
|
|
20214
20847
|
*/
|
|
20215
20848
|
reconcileModernStreamStateAfterFailedRefresh() {
|
|
20849
|
+
if (this.modernListenRefusal) {
|
|
20850
|
+
this.endModernStreamRefused(this.modernListenRefusal);
|
|
20851
|
+
return;
|
|
20852
|
+
}
|
|
20216
20853
|
if (!this.wantsModernStream()) {
|
|
20217
20854
|
this.setModernStreamState(INACTIVE_SUBSCRIPTION_STREAM_STATE);
|
|
20218
20855
|
return;
|
|
@@ -20245,6 +20882,23 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
|
|
|
20245
20882
|
honoredUris: []
|
|
20246
20883
|
});
|
|
20247
20884
|
}
|
|
20885
|
+
/**
|
|
20886
|
+
* Settle the stream on an HTTP refusal of the listen (#2722): the
|
|
20887
|
+
* `"refused"` status carrying the HTTP answer, no reconnect, and a log line
|
|
20888
|
+
* with the sentence the UI shows. Reported whatever `active` says — see
|
|
20889
|
+
* `ResourceSubscriptionStreamState`.
|
|
20890
|
+
*/
|
|
20891
|
+
endModernStreamRefused(refusal) {
|
|
20892
|
+
this.clearModernReconnectTimer();
|
|
20893
|
+
const message = subscriptionListenRefusalMessage(refusal);
|
|
20894
|
+
this.logger.warn({ message }, "subscriptions/listen refused over HTTP");
|
|
20895
|
+
this.setModernStreamState({
|
|
20896
|
+
active: this.modernStreamActive(),
|
|
20897
|
+
status: "refused",
|
|
20898
|
+
honoredUris: [],
|
|
20899
|
+
refusal
|
|
20900
|
+
});
|
|
20901
|
+
}
|
|
20248
20902
|
/**
|
|
20249
20903
|
* Schedule a reconnect re-listen after the current backoff delay (#1630).
|
|
20250
20904
|
* `modernReconnectAttempts` reflects the number of *consecutive failed*
|
|
@@ -20282,6 +20936,10 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
|
|
|
20282
20936
|
this.endModernStreamNeverAcknowledged();
|
|
20283
20937
|
return;
|
|
20284
20938
|
}
|
|
20939
|
+
if (this.modernListenRefusal && !isTerminalStatus(this.status)) {
|
|
20940
|
+
this.endModernStreamRefused(this.modernListenRefusal);
|
|
20941
|
+
return;
|
|
20942
|
+
}
|
|
20285
20943
|
this.modernReconnectAttempts += 1;
|
|
20286
20944
|
if (this.modernReconnectAttempts > MODERN_RECONNECT_MAX_ATTEMPTS || isTerminalStatus(this.status) || !this.wantsModernStream()) {
|
|
20287
20945
|
this.setModernStreamState({
|
|
@@ -20436,8 +21094,11 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
|
|
|
20436
21094
|
* Re-read OAuth storage and test whether a challenge is already satisfied.
|
|
20437
21095
|
* See {@link OAuthManager.checkAuthChallengeSatisfied}.
|
|
20438
21096
|
*/
|
|
20439
|
-
async checkAuthChallengeSatisfied(challenge) {
|
|
20440
|
-
return this.ensureOAuthManager().checkAuthChallengeSatisfied(
|
|
21097
|
+
async checkAuthChallengeSatisfied(challenge, options) {
|
|
21098
|
+
return this.ensureOAuthManager().checkAuthChallengeSatisfied(
|
|
21099
|
+
challenge,
|
|
21100
|
+
options
|
|
21101
|
+
);
|
|
20441
21102
|
}
|
|
20442
21103
|
/**
|
|
20443
21104
|
* Push recovered OAuth auth state to the remote backend (same MCP session).
|
|
@@ -20601,8 +21262,12 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
|
|
|
20601
21262
|
* Direct transports reconnect after token exchange so the live MCP session
|
|
20602
21263
|
* picks up the new Bearer token (mirrors silent recovery reconnect).
|
|
20603
21264
|
*/
|
|
20604
|
-
async completeOAuthFlow(authorizationCode, iss) {
|
|
20605
|
-
await this.ensureOAuthManager().completeOAuthFlow(
|
|
21265
|
+
async completeOAuthFlow(authorizationCode, iss, state) {
|
|
21266
|
+
await this.ensureOAuthManager().completeOAuthFlow(
|
|
21267
|
+
authorizationCode,
|
|
21268
|
+
iss,
|
|
21269
|
+
state
|
|
21270
|
+
);
|
|
20606
21271
|
if (this.usesDirectAuthRecovery()) {
|
|
20607
21272
|
await this.reconnectAfterAuthRecovery();
|
|
20608
21273
|
}
|
|
@@ -20626,7 +21291,11 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
|
|
|
20626
21291
|
* Finish OAuth after a full-page redirect and reconnect (or reattach) the MCP session.
|
|
20627
21292
|
*/
|
|
20628
21293
|
async resumeAfterOAuth(authorizationCode, options) {
|
|
20629
|
-
await this.completeOAuthFlow(
|
|
21294
|
+
await this.completeOAuthFlow(
|
|
21295
|
+
authorizationCode,
|
|
21296
|
+
options?.iss,
|
|
21297
|
+
options?.state
|
|
21298
|
+
);
|
|
20630
21299
|
const remoteSessionId = options?.remoteSessionId;
|
|
20631
21300
|
const transport = this.baseTransport;
|
|
20632
21301
|
if (remoteSessionId && transport instanceof RemoteClientTransport) {
|
|
@@ -21064,6 +21733,16 @@ var MessageLogState = class extends TypedEventTarget {
|
|
|
21064
21733
|
return;
|
|
21065
21734
|
}
|
|
21066
21735
|
};
|
|
21736
|
+
const onRequestFailed = (event) => {
|
|
21737
|
+
const { id, reason } = event.detail;
|
|
21738
|
+
const requestEntry = this.pendingRequestEntries.get(id);
|
|
21739
|
+
if (!requestEntry) return;
|
|
21740
|
+
this.pendingRequestEntries.delete(id);
|
|
21741
|
+
requestEntry.clientError = reason;
|
|
21742
|
+
requestEntry.duration = Date.now() - requestEntry.timestamp.getTime();
|
|
21743
|
+
this.dispatchTypedEvent("message", requestEntry);
|
|
21744
|
+
this.dispatchTypedEvent("messagesChange", this.getMessages());
|
|
21745
|
+
};
|
|
21067
21746
|
const onStatusChange = () => {
|
|
21068
21747
|
if (isTerminalStatus(this.client?.getStatus())) {
|
|
21069
21748
|
this.messages = [];
|
|
@@ -21073,11 +21752,13 @@ var MessageLogState = class extends TypedEventTarget {
|
|
|
21073
21752
|
};
|
|
21074
21753
|
this.client.addEventListener("message", onMessage);
|
|
21075
21754
|
this.client.addEventListener("responseRejected", onResponseRejected);
|
|
21755
|
+
this.client.addEventListener("requestFailed", onRequestFailed);
|
|
21076
21756
|
this.client.addEventListener("statusChange", onStatusChange);
|
|
21077
21757
|
this.unsubscribe = () => {
|
|
21078
21758
|
if (this.client) {
|
|
21079
21759
|
this.client.removeEventListener("message", onMessage);
|
|
21080
21760
|
this.client.removeEventListener("responseRejected", onResponseRejected);
|
|
21761
|
+
this.client.removeEventListener("requestFailed", onRequestFailed);
|
|
21081
21762
|
this.client.removeEventListener("statusChange", onStatusChange);
|
|
21082
21763
|
}
|
|
21083
21764
|
this.client = null;
|
|
@@ -21526,6 +22207,192 @@ var ManagedSkillsState = class extends TypedEventTarget {
|
|
|
21526
22207
|
}
|
|
21527
22208
|
};
|
|
21528
22209
|
|
|
22210
|
+
// ../../core/cli/handlers/load-test.ts
|
|
22211
|
+
var DEFAULT_LOAD_CONCURRENCY = 10;
|
|
22212
|
+
var DEFAULT_LOAD_DURATION_MS = 1e4;
|
|
22213
|
+
function positiveIntegerParser(flag) {
|
|
22214
|
+
return (value) => {
|
|
22215
|
+
const trimmed = value.trim();
|
|
22216
|
+
const n = /^\d+$/.test(trimmed) ? Number(trimmed) : NaN;
|
|
22217
|
+
if (!Number.isSafeInteger(n) || n <= 0) {
|
|
22218
|
+
throw new Error(`${flag} must be a positive integer.`);
|
|
22219
|
+
}
|
|
22220
|
+
return n;
|
|
22221
|
+
};
|
|
22222
|
+
}
|
|
22223
|
+
function positiveNumberParser(flag) {
|
|
22224
|
+
return (value) => {
|
|
22225
|
+
const n = value.trim() === "" ? NaN : Number(value);
|
|
22226
|
+
if (!Number.isFinite(n) || n <= 0) {
|
|
22227
|
+
throw new Error(`${flag} must be a positive number.`);
|
|
22228
|
+
}
|
|
22229
|
+
return n;
|
|
22230
|
+
};
|
|
22231
|
+
}
|
|
22232
|
+
function resolveLoadOptions(flags) {
|
|
22233
|
+
const durationMs = flags.durationSec !== void 0 ? flags.durationSec * 1e3 : flags.requests === void 0 ? DEFAULT_LOAD_DURATION_MS : void 0;
|
|
22234
|
+
return {
|
|
22235
|
+
concurrency: flags.concurrency ?? DEFAULT_LOAD_CONCURRENCY,
|
|
22236
|
+
...durationMs !== void 0 && { durationMs },
|
|
22237
|
+
...flags.requests !== void 0 && { requests: flags.requests },
|
|
22238
|
+
...flags.rate !== void 0 && { rate: flags.rate }
|
|
22239
|
+
};
|
|
22240
|
+
}
|
|
22241
|
+
var MAX_ERROR_KINDS = 10;
|
|
22242
|
+
var OTHER_ERRORS = "(other)";
|
|
22243
|
+
var realClock = {
|
|
22244
|
+
now: () => performance.now(),
|
|
22245
|
+
sleep: (ms) => new Promise((resolve9) => setTimeout(resolve9, ms))
|
|
22246
|
+
};
|
|
22247
|
+
function percentile(sorted, p) {
|
|
22248
|
+
const rank = Math.ceil(p / 100 * sorted.length);
|
|
22249
|
+
return sorted[Math.min(Math.max(rank, 1), sorted.length) - 1];
|
|
22250
|
+
}
|
|
22251
|
+
function round(n, places = 2) {
|
|
22252
|
+
const scale = 10 ** places;
|
|
22253
|
+
return Math.round(n * scale) / scale;
|
|
22254
|
+
}
|
|
22255
|
+
function summarizeLatency(latencies) {
|
|
22256
|
+
if (latencies.length === 0) return null;
|
|
22257
|
+
const sorted = [...latencies].sort((a, b) => a - b);
|
|
22258
|
+
const total = sorted.reduce((sum, n) => sum + n, 0);
|
|
22259
|
+
return {
|
|
22260
|
+
min: round(sorted[0]),
|
|
22261
|
+
mean: round(total / sorted.length),
|
|
22262
|
+
p50: round(percentile(sorted, 50)),
|
|
22263
|
+
p90: round(percentile(sorted, 90)),
|
|
22264
|
+
p99: round(percentile(sorted, 99)),
|
|
22265
|
+
max: round(sorted[sorted.length - 1])
|
|
22266
|
+
};
|
|
22267
|
+
}
|
|
22268
|
+
var ErrorTally = class {
|
|
22269
|
+
counts = /* @__PURE__ */ new Map();
|
|
22270
|
+
other = 0;
|
|
22271
|
+
add(message) {
|
|
22272
|
+
const count = this.counts.get(message);
|
|
22273
|
+
if (count !== void 0) this.counts.set(message, count + 1);
|
|
22274
|
+
else if (this.counts.size < MAX_ERROR_KINDS) this.counts.set(message, 1);
|
|
22275
|
+
else this.other++;
|
|
22276
|
+
}
|
|
22277
|
+
entries() {
|
|
22278
|
+
const list = [...this.counts].map(([message, count]) => ({
|
|
22279
|
+
message,
|
|
22280
|
+
count
|
|
22281
|
+
}));
|
|
22282
|
+
list.sort((a, b) => b.count - a.count);
|
|
22283
|
+
if (this.other > 0) list.push({ message: OTHER_ERRORS, count: this.other });
|
|
22284
|
+
return list;
|
|
22285
|
+
}
|
|
22286
|
+
};
|
|
22287
|
+
async function runLoad(call, options, clock = realClock) {
|
|
22288
|
+
const { concurrency, durationMs, requests, rate } = options;
|
|
22289
|
+
const latencies = [];
|
|
22290
|
+
const tally = new ErrorTally();
|
|
22291
|
+
let ok = 0;
|
|
22292
|
+
let toolErrors = 0;
|
|
22293
|
+
let errors = 0;
|
|
22294
|
+
let started = 0;
|
|
22295
|
+
const start = clock.now();
|
|
22296
|
+
const deadline = durationMs === void 0 ? Infinity : start + durationMs;
|
|
22297
|
+
const interval = rate === void 0 ? 0 : 1e3 / rate;
|
|
22298
|
+
let nextStart = start;
|
|
22299
|
+
let turnstile = Promise.resolve();
|
|
22300
|
+
const claimSlot = async () => {
|
|
22301
|
+
const ahead = turnstile;
|
|
22302
|
+
let release;
|
|
22303
|
+
turnstile = new Promise((resolve9) => release = resolve9);
|
|
22304
|
+
await ahead;
|
|
22305
|
+
try {
|
|
22306
|
+
for (; ; ) {
|
|
22307
|
+
if (requests !== void 0 && started >= requests) return false;
|
|
22308
|
+
const now = clock.now();
|
|
22309
|
+
if (now >= deadline) return false;
|
|
22310
|
+
const scheduled = Math.max(nextStart, now);
|
|
22311
|
+
if (scheduled >= deadline) return false;
|
|
22312
|
+
if (scheduled <= now) {
|
|
22313
|
+
started++;
|
|
22314
|
+
nextStart = now - nextStart < interval ? nextStart + interval : now + interval;
|
|
22315
|
+
return true;
|
|
22316
|
+
}
|
|
22317
|
+
await clock.sleep(scheduled - now);
|
|
22318
|
+
}
|
|
22319
|
+
} finally {
|
|
22320
|
+
release();
|
|
22321
|
+
}
|
|
22322
|
+
};
|
|
22323
|
+
const worker = async () => {
|
|
22324
|
+
while (await claimSlot()) {
|
|
22325
|
+
const t0 = clock.now();
|
|
22326
|
+
try {
|
|
22327
|
+
const outcome = await call();
|
|
22328
|
+
if (outcome === "ok") ok++;
|
|
22329
|
+
else toolErrors++;
|
|
22330
|
+
} catch (err) {
|
|
22331
|
+
errors++;
|
|
22332
|
+
tally.add(err instanceof Error ? err.message : String(err));
|
|
22333
|
+
}
|
|
22334
|
+
latencies.push(clock.now() - t0);
|
|
22335
|
+
}
|
|
22336
|
+
};
|
|
22337
|
+
await Promise.all(Array.from({ length: concurrency }, worker));
|
|
22338
|
+
const elapsedMs = clock.now() - start;
|
|
22339
|
+
const completed = ok + toolErrors + errors;
|
|
22340
|
+
return {
|
|
22341
|
+
concurrency,
|
|
22342
|
+
...durationMs !== void 0 && { durationMs },
|
|
22343
|
+
...requests !== void 0 && { requestLimit: requests },
|
|
22344
|
+
...rate !== void 0 && { rate },
|
|
22345
|
+
requests: completed,
|
|
22346
|
+
ok,
|
|
22347
|
+
toolErrors,
|
|
22348
|
+
errors,
|
|
22349
|
+
// Four places, so a rare failure under heavy load (23 in 41k) still shows.
|
|
22350
|
+
errorRate: completed === 0 ? 0 : round((toolErrors + errors) / completed, 4),
|
|
22351
|
+
elapsedMs: round(elapsedMs),
|
|
22352
|
+
throughput: elapsedMs > 0 ? round(completed * 1e3 / elapsedMs) : 0,
|
|
22353
|
+
latencyMs: summarizeLatency(latencies),
|
|
22354
|
+
errorMessages: tally.entries()
|
|
22355
|
+
};
|
|
22356
|
+
}
|
|
22357
|
+
async function runToolLoad(inspectorClient, args, options, clock) {
|
|
22358
|
+
if (!args.toolName) {
|
|
22359
|
+
throw new Error("--load requires --tool-name.");
|
|
22360
|
+
}
|
|
22361
|
+
const toolsState = new ManagedToolsState(inspectorClient);
|
|
22362
|
+
let found;
|
|
22363
|
+
try {
|
|
22364
|
+
toolsState.setMetadata(args.metadata);
|
|
22365
|
+
await toolsState.refresh();
|
|
22366
|
+
found = toolsState.getTools().find((t) => t.name === args.toolName);
|
|
22367
|
+
} finally {
|
|
22368
|
+
toolsState.destroy();
|
|
22369
|
+
}
|
|
22370
|
+
if (!found) {
|
|
22371
|
+
throw new CliExitCodeError(
|
|
22372
|
+
EXIT_CODES.TOOL_ERROR,
|
|
22373
|
+
`Tool '${args.toolName}' not found on server.`,
|
|
22374
|
+
{ code: "tool_not_found" }
|
|
22375
|
+
);
|
|
22376
|
+
}
|
|
22377
|
+
const tool = found;
|
|
22378
|
+
const useTask = tool.execution?.taskSupport === "required";
|
|
22379
|
+
const call = async () => {
|
|
22380
|
+
const params = [
|
|
22381
|
+
tool,
|
|
22382
|
+
args.toolArg ?? {},
|
|
22383
|
+
args.metadata,
|
|
22384
|
+
args.toolMeta
|
|
22385
|
+
];
|
|
22386
|
+
const invocation = useTask ? await inspectorClient.callToolStream(...params) : await inspectorClient.callTool(...params);
|
|
22387
|
+
if (invocation.result === null) {
|
|
22388
|
+
throw new Error(invocation.error ?? "Tool call failed");
|
|
22389
|
+
}
|
|
22390
|
+
return invocation.result.isError === true ? "tool_error" : "ok";
|
|
22391
|
+
};
|
|
22392
|
+
const summary = await runLoad(call, options, clock);
|
|
22393
|
+
return { tool: tool.name, ...summary };
|
|
22394
|
+
}
|
|
22395
|
+
|
|
21529
22396
|
// ../../core/mcp/apps.ts
|
|
21530
22397
|
import { getToolUiResourceUri } from "@modelcontextprotocol/ext-apps/app-bridge";
|
|
21531
22398
|
function getAppResourceUri(tool) {
|
|
@@ -22512,7 +23379,11 @@ async function runRunnerInteractiveOAuth(options) {
|
|
|
22512
23379
|
path: options.callbackListen.pathname,
|
|
22513
23380
|
onCallback: async (params) => {
|
|
22514
23381
|
try {
|
|
22515
|
-
await options.client.completeOAuthFlow(
|
|
23382
|
+
await options.client.completeOAuthFlow(
|
|
23383
|
+
params.code,
|
|
23384
|
+
params.iss,
|
|
23385
|
+
params.state
|
|
23386
|
+
);
|
|
22516
23387
|
flowResolve();
|
|
22517
23388
|
} catch (err) {
|
|
22518
23389
|
flowReject(toRunnerOAuthError(err));
|
|
@@ -22564,7 +23435,10 @@ async function runRunnerInteractiveOAuth(options) {
|
|
|
22564
23435
|
}
|
|
22565
23436
|
if (options.authChallenge) {
|
|
22566
23437
|
const satisfied = await racingSignals(
|
|
22567
|
-
|
|
23438
|
+
// The token exchange just completed, so only scope is in question.
|
|
23439
|
+
options.client.checkAuthChallengeSatisfied(options.authChallenge, {
|
|
23440
|
+
scopeOnly: true
|
|
23441
|
+
})
|
|
22568
23442
|
);
|
|
22569
23443
|
if (!satisfied) {
|
|
22570
23444
|
return {
|
|
@@ -22590,14 +23464,25 @@ async function runRunnerInteractiveOAuth(options) {
|
|
|
22590
23464
|
// ../../core/node/openUrl.ts
|
|
22591
23465
|
import open from "open";
|
|
22592
23466
|
var OPEN_URL_TIMEOUT_MS = 5e3;
|
|
22593
|
-
|
|
22594
|
-
|
|
22595
|
-
|
|
22596
|
-
|
|
23467
|
+
var OPENABLE_PROTOCOLS = /* @__PURE__ */ new Set(["http:", "https:"]);
|
|
23468
|
+
function toOpenableHref(href) {
|
|
23469
|
+
let parsed;
|
|
23470
|
+
try {
|
|
23471
|
+
parsed = new URL(href);
|
|
23472
|
+
} catch {
|
|
23473
|
+
throw new Error("refusing to open an invalid URL");
|
|
23474
|
+
}
|
|
23475
|
+
if (!OPENABLE_PROTOCOLS.has(parsed.protocol)) {
|
|
23476
|
+
throw new Error(`refusing to open a non-http(s) URL (${parsed.protocol})`);
|
|
23477
|
+
}
|
|
23478
|
+
return parsed.href;
|
|
23479
|
+
}
|
|
23480
|
+
function absorbLateErrors(child) {
|
|
23481
|
+
child.on("error", () => {
|
|
22597
23482
|
});
|
|
22598
23483
|
}
|
|
22599
23484
|
async function openUrl(url, timeoutMs = OPEN_URL_TIMEOUT_MS) {
|
|
22600
|
-
const href = typeof url === "string" ? url : url.href;
|
|
23485
|
+
const href = toOpenableHref(typeof url === "string" ? url : url.href);
|
|
22601
23486
|
let timer;
|
|
22602
23487
|
const timeout = new Promise((_, reject) => {
|
|
22603
23488
|
timer = setTimeout(
|
|
@@ -22606,8 +23491,7 @@ async function openUrl(url, timeoutMs = OPEN_URL_TIMEOUT_MS) {
|
|
|
22606
23491
|
);
|
|
22607
23492
|
});
|
|
22608
23493
|
try {
|
|
22609
|
-
|
|
22610
|
-
await Promise.race([opened, timeout]);
|
|
23494
|
+
await Promise.race([open(href).then(absorbLateErrors), timeout]);
|
|
22611
23495
|
} finally {
|
|
22612
23496
|
clearTimeout(timer);
|
|
22613
23497
|
}
|
|
@@ -23061,7 +23945,7 @@ var validLogLevels = Object.values(
|
|
|
23061
23945
|
LoggingLevelSchema2.enum
|
|
23062
23946
|
);
|
|
23063
23947
|
var CLI_CLIENT_NAME = "inspector-cli";
|
|
23064
|
-
async function callMethod(serverConfig, serverSettings, args, clientConfig, cliAuthOverrides, callbackUrlConfig, storedAuthOnly, relogin, revoke) {
|
|
23948
|
+
async function callMethod(serverConfig, serverSettings, args, clientConfig, cliAuthOverrides, callbackUrlConfig, storedAuthOnly, relogin, revoke, load) {
|
|
23065
23949
|
if (relogin) {
|
|
23066
23950
|
if (!("url" in serverConfig && serverConfig.url)) {
|
|
23067
23951
|
throw new Error(
|
|
@@ -23167,6 +24051,22 @@ async function callMethod(serverConfig, serverSettings, args, clientConfig, cliA
|
|
|
23167
24051
|
serverSettings,
|
|
23168
24052
|
{ storedAuthOnly, autoOpenControl, quiet: args.quiet }
|
|
23169
24053
|
);
|
|
24054
|
+
if (load) {
|
|
24055
|
+
const summary = await withCliAuthRecoveryRetry(
|
|
24056
|
+
inspectorClient,
|
|
24057
|
+
serverConfig,
|
|
24058
|
+
redirectUrlProvider,
|
|
24059
|
+
callbackUrlConfig,
|
|
24060
|
+
serverSettings,
|
|
24061
|
+
() => runToolLoad(inspectorClient, args, load),
|
|
24062
|
+
{ storedAuthOnly, autoOpenControl, quiet: args.quiet }
|
|
24063
|
+
);
|
|
24064
|
+
await writeFormattedResult(
|
|
24065
|
+
summary,
|
|
24066
|
+
args.format === "json" ? "json" : "text"
|
|
24067
|
+
);
|
|
24068
|
+
return;
|
|
24069
|
+
}
|
|
23170
24070
|
const outcome = await withCliAuthRecoveryRetry(
|
|
23171
24071
|
inspectorClient,
|
|
23172
24072
|
serverConfig,
|
|
@@ -23357,6 +24257,7 @@ function buildHandoff(serverUrl, statePath, transport) {
|
|
|
23357
24257
|
const host = process.env.HOST || "127.0.0.1";
|
|
23358
24258
|
const linkHost = isAllInterfacesHost(host) ? "localhost" : canonicalUrlHost(host);
|
|
23359
24259
|
const clientPort = process.env.CLIENT_PORT || "6274";
|
|
24260
|
+
const scheme = process.env.INSPECTOR_TLS_KEY_PATH?.trim() && process.env.INSPECTOR_TLS_CERT_PATH?.trim() ? "https" : "http";
|
|
23360
24261
|
const sandboxPort = process.env.MCP_SANDBOX_PORT || "6275";
|
|
23361
24262
|
const appOriginPort = process.env.MCP_APP_ORIGIN_PORT || "6278";
|
|
23362
24263
|
const apiToken = process.env.MCP_INSPECTOR_API_TOKEN || void 0;
|
|
@@ -23368,7 +24269,7 @@ function buildHandoff(serverUrl, statePath, transport) {
|
|
|
23368
24269
|
if (apiToken) params.set("autoConnect", apiToken);
|
|
23369
24270
|
return {
|
|
23370
24271
|
serverUrl: normalizedUrl,
|
|
23371
|
-
deepLink:
|
|
24272
|
+
deepLink: `${scheme}://${linkHost}:${clientPort}/?${params.toString()}`,
|
|
23372
24273
|
portForwardCmd: `coder port-forward <workspace> --tcp ${clientPort}:${clientPort} --tcp ${sandboxPort}:${sandboxPort} --tcp ${appOriginPort}:${appOriginPort}`,
|
|
23373
24274
|
oauthStatePath: statePath,
|
|
23374
24275
|
apiToken: apiToken ?? null,
|
|
@@ -23528,6 +24429,25 @@ async function parseArgs(argv) {
|
|
|
23528
24429
|
).option(
|
|
23529
24430
|
"--require-digests",
|
|
23530
24431
|
'With --verify: exit 9 when a skill advertises no digests (resources: "dynamic"), instead of reporting it as unverifiable and exiting 0.'
|
|
24432
|
+
).option(
|
|
24433
|
+
"--load",
|
|
24434
|
+
"Load-test one tool: call it repeatedly and concurrently, then print a latency and error-rate summary instead of a result. Use with --method tools/call --tool-name <name>. Bounded by --load-duration (default 10s) and/or --load-requests."
|
|
24435
|
+
).option(
|
|
24436
|
+
"--load-concurrency <n>",
|
|
24437
|
+
"With --load: most calls in flight at once (positive integer; default 10).",
|
|
24438
|
+
positiveIntegerParser("--load-concurrency")
|
|
24439
|
+
).option(
|
|
24440
|
+
"--load-duration <sec>",
|
|
24441
|
+
"With --load: stop starting calls after this many seconds; in-flight calls still finish (default 10 when --load-requests is not set).",
|
|
24442
|
+
positiveNumberParser("--load-duration")
|
|
24443
|
+
).option(
|
|
24444
|
+
"--load-requests <n>",
|
|
24445
|
+
"With --load: stop after starting this many calls (positive integer).",
|
|
24446
|
+
positiveIntegerParser("--load-requests")
|
|
24447
|
+
).option(
|
|
24448
|
+
"--load-rate <n>",
|
|
24449
|
+
"With --load: start at most this many calls per second, spread evenly (positive number; default unlimited).",
|
|
24450
|
+
positiveNumberParser("--load-rate")
|
|
23531
24451
|
).option(
|
|
23532
24452
|
"--connect-timeout <ms>",
|
|
23533
24453
|
`Connection timeout in ms (default ${DEFAULT_CONNECT_TIMEOUT_MS} for ad-hoc --server-url / target invocations; 0 = no timeout).`,
|
|
@@ -23680,6 +24600,35 @@ async function parseArgs(argv) {
|
|
|
23680
24600
|
if (options.skillCatalogMaxBytes !== void 0 && !options.verify) {
|
|
23681
24601
|
throw new Error("--skill-catalog-max-bytes requires --verify.");
|
|
23682
24602
|
}
|
|
24603
|
+
const loadBounds = [
|
|
24604
|
+
["--load-concurrency", options.loadConcurrency],
|
|
24605
|
+
["--load-duration", options.loadDuration],
|
|
24606
|
+
["--load-requests", options.loadRequests],
|
|
24607
|
+
["--load-rate", options.loadRate]
|
|
24608
|
+
];
|
|
24609
|
+
for (const [flag, value] of loadBounds) {
|
|
24610
|
+
if (value !== void 0 && !options.load) {
|
|
24611
|
+
throw new Error(`${flag} requires --load.`);
|
|
24612
|
+
}
|
|
24613
|
+
}
|
|
24614
|
+
if (options.load) {
|
|
24615
|
+
if (options.method !== "tools/call") {
|
|
24616
|
+
throw new Error("--load requires --method tools/call.");
|
|
24617
|
+
}
|
|
24618
|
+
if (options.appInfo) {
|
|
24619
|
+
throw new Error("--load cannot be combined with --app-info.");
|
|
24620
|
+
}
|
|
24621
|
+
if (options.listStoredAuth || options.printHandoff) {
|
|
24622
|
+
throw new Error(
|
|
24623
|
+
"--load cannot be combined with --list-stored-auth or --print-handoff."
|
|
24624
|
+
);
|
|
24625
|
+
}
|
|
24626
|
+
if (options.output !== void 0 || options.outputFormat !== void 0) {
|
|
24627
|
+
throw new Error(
|
|
24628
|
+
"--load cannot be combined with --output or --output-format; redirect stdout to keep the summary."
|
|
24629
|
+
);
|
|
24630
|
+
}
|
|
24631
|
+
}
|
|
23683
24632
|
if (options.advertiseApps && (options.listStoredAuth || options.printHandoff || options.method === "servers/list" || options.method === "servers/show" || isCatalogWriteMethod(options.method))) {
|
|
23684
24633
|
throw new Error(
|
|
23685
24634
|
"--advertise-apps requires a command that connects to a server; it has no effect with --list-stored-auth, --print-handoff, or a --method servers/* catalog command."
|
|
@@ -23902,7 +24851,15 @@ async function parseArgs(argv) {
|
|
|
23902
24851
|
relogin: options.relogin === true,
|
|
23903
24852
|
// Commander's `--no-revoke` defaults this to true; only an explicit
|
|
23904
24853
|
// `--no-revoke` makes it false.
|
|
23905
|
-
revoke: options.revoke !== false
|
|
24854
|
+
revoke: options.revoke !== false,
|
|
24855
|
+
...options.load && {
|
|
24856
|
+
load: resolveLoadOptions({
|
|
24857
|
+
concurrency: options.loadConcurrency,
|
|
24858
|
+
durationSec: options.loadDuration,
|
|
24859
|
+
requests: options.loadRequests,
|
|
24860
|
+
rate: options.loadRate
|
|
24861
|
+
})
|
|
24862
|
+
}
|
|
23906
24863
|
};
|
|
23907
24864
|
}
|
|
23908
24865
|
function discardWarning() {
|
|
@@ -23929,7 +24886,8 @@ async function runParsedCli(argv) {
|
|
|
23929
24886
|
callbackUrl,
|
|
23930
24887
|
storedAuthOnly,
|
|
23931
24888
|
relogin,
|
|
23932
|
-
revoke
|
|
24889
|
+
revoke,
|
|
24890
|
+
load
|
|
23933
24891
|
} = parsed;
|
|
23934
24892
|
const clientConfig = await loadRunnerClientConfig({ clientConfigPath });
|
|
23935
24893
|
let callbackUrlConfig;
|
|
@@ -23954,7 +24912,8 @@ async function runParsedCli(argv) {
|
|
|
23954
24912
|
callbackUrlConfig,
|
|
23955
24913
|
storedAuthOnly === true,
|
|
23956
24914
|
relogin === true,
|
|
23957
|
-
revoke !== false
|
|
24915
|
+
revoke !== false,
|
|
24916
|
+
load
|
|
23958
24917
|
);
|
|
23959
24918
|
}
|
|
23960
24919
|
|