@modelcontextprotocol/inspector 2.13.0 → 2.14.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.
Files changed (32) hide show
  1. package/README.md +1 -1
  2. package/clients/cli/build/index.js +1013 -80
  3. package/clients/mcpdo/build/mcp-bin.js +50 -22
  4. package/clients/mcpdo/build/mcpdod.js +1 -1
  5. package/clients/mcpdo/build/{server-BHuKGkMj.js → server-Dv339Ejx.js} +886 -48
  6. package/clients/tui/build/index.js +781 -46
  7. package/clients/web/build/index.js +223 -124
  8. package/clients/web/dist/assets/fredoka-normal-hebrew-CjvktpUN.woff2 +0 -0
  9. package/clients/web/dist/assets/fredoka-normal-latin-DOQG_Jwn.woff2 +0 -0
  10. package/clients/web/dist/assets/fredoka-normal-latin-ext-tYNUQHO6.woff2 +0 -0
  11. package/clients/web/dist/assets/index-BCud2uMC.js +1594 -0
  12. package/clients/web/dist/assets/{index-dqp8Sd8W.css → index-Dval2a7I.css} +1 -1
  13. package/clients/web/dist/assets/{prism-light-BIhJtJkv.js → prism-light-CoVRM9R2.js} +1 -1
  14. package/clients/web/dist/assets/roboto-mono-italic-cyrillic-C1E63de8.woff2 +0 -0
  15. package/clients/web/dist/assets/roboto-mono-italic-cyrillic-ext-DR9ovEqT.woff2 +0 -0
  16. package/clients/web/dist/assets/roboto-mono-italic-greek-Cs2u2nVh.woff2 +0 -0
  17. package/clients/web/dist/assets/roboto-mono-italic-latin-D_P-kX1t.woff2 +0 -0
  18. package/clients/web/dist/assets/roboto-mono-italic-latin-ext-CErTcBzw.woff2 +0 -0
  19. package/clients/web/dist/assets/roboto-mono-italic-vietnamese-D4UvzJMM.woff2 +0 -0
  20. package/clients/web/dist/assets/roboto-mono-normal-cyrillic-DfzWIUPd.woff2 +0 -0
  21. package/clients/web/dist/assets/roboto-mono-normal-cyrillic-ext-D8QBEkUe.woff2 +0 -0
  22. package/clients/web/dist/assets/roboto-mono-normal-greek-DlC4X1lQ.woff2 +0 -0
  23. package/clients/web/dist/assets/roboto-mono-normal-latin-BAKpfWyj.woff2 +0 -0
  24. package/clients/web/dist/assets/roboto-mono-normal-latin-ext-CSSVNFqV.woff2 +0 -0
  25. package/clients/web/dist/assets/roboto-mono-normal-vietnamese-DFkHeOHB.woff2 +0 -0
  26. package/clients/web/dist/fonts/Fredoka-OFL.txt +93 -0
  27. package/clients/web/dist/fonts/RobotoMono-OFL.txt +93 -0
  28. package/clients/web/dist/index.html +2 -5
  29. package/clients/web/static/sandbox_proxy.html +177 -6
  30. package/package.json +5 -4
  31. package/skills/mcpdo/SKILL.md +14 -0
  32. package/clients/web/dist/assets/index-BqEbhCDX.js +0 -1587
@@ -18,7 +18,7 @@ import nodeFs, { existsSync, readFileSync, rmdirSync, statSync } from "node:fs";
18
18
  import properLockfile from "proper-lockfile";
19
19
  import * as crypto$1 from "node:crypto";
20
20
  import { createHmac, randomBytes, randomUUID, timingSafeEqual } from "node:crypto";
21
- import { homedir } from "node:os";
21
+ import { homedir, tmpdir } from "node:os";
22
22
  import { Hono } from "hono";
23
23
  import { streamSSE } from "hono/streaming";
24
24
  import { bodyLimit } from "hono/body-limit";
@@ -32,7 +32,10 @@ import { bindTaskReceiver } from "@modelcontextprotocol/ext-tasks/receiver";
32
32
  import { runtimeCodecFromStandardSchema, taskId, toJsonValue } from "@modelcontextprotocol/ext-tasks/core";
33
33
  import { TASKS_EXTENSION_ID_V2 as TASKS_EXTENSION_KEY } from "@modelcontextprotocol/ext-tasks/core/v2";
34
34
  import { RESOURCE_MIME_TYPE, getToolUiResourceUri } from "@modelcontextprotocol/ext-apps/app-bridge";
35
- import { AjvJsonSchemaValidator } from "@modelcontextprotocol/client/validators/ajv";
35
+ import Ajv from "ajv";
36
+ import { Ajv2019 } from "ajv/dist/2019.js";
37
+ import { Ajv2020 } from "ajv/dist/2020.js";
38
+ import { addFormats } from "@modelcontextprotocol/client/validators/ajv";
36
39
  import { createServer } from "node:http";
37
40
  import open from "open";
38
41
  import { createInterface } from "node:readline/promises";
@@ -5114,7 +5117,8 @@ const EXIT_CODES = {
5114
5117
  SKILL_INCOMPLETE: 8,
5115
5118
  SKILL_UNVERIFIABLE: 9,
5116
5119
  LOAD_THRESHOLD_EXCEEDED: 10,
5117
- RATE_LIMITED: 11
5120
+ RATE_LIMITED: 11,
5121
+ INTERRUPTED: 130
5118
5122
  };
5119
5123
  function rateLimitFieldsOf(error) {
5120
5124
  const data = error?.data;
@@ -7048,26 +7052,37 @@ function expectedSecretFields(stored) {
7048
7052
  if (isStdioStored(stored) && stored.env) for (const k of Object.keys(stored.env)) fields.push(envSecretField(k));
7049
7053
  return fields;
7050
7054
  }
7051
- const DEFAULT_SEED_CONFIG = { mcpServers: {
7052
- "filesystem-server-default": {
7053
- type: "stdio",
7054
- command: "npx",
7055
- args: [
7056
- "-y",
7057
- "@modelcontextprotocol/server-filesystem",
7058
- "/tmp"
7059
- ]
7060
- },
7061
- "everything-server-default": {
7062
- type: "stdio",
7063
- command: "npx",
7064
- args: ["-y", "@modelcontextprotocol/server-everything"]
7065
- },
7066
- "example-server-default": {
7067
- type: "streamable-http",
7068
- url: "https://example-server.modelcontextprotocol.io/mcp"
7055
+ const EXAMPLE_SERVER_URL = "https://example-server.modelcontextprotocol.io/mcp";
7056
+ function adHocServerName(config) {
7057
+ if (config.type === "sse" || config.type === "streamable-http") try {
7058
+ return new URL(config.url).host || "server";
7059
+ } catch {
7060
+ return "server";
7069
7061
  }
7070
- } };
7062
+ return config.command?.split(/[\\/]/).pop() || "server";
7063
+ }
7064
+ function defaultSeedConfig(tempDir) {
7065
+ return { mcpServers: {
7066
+ "filesystem-server-default": {
7067
+ type: "stdio",
7068
+ command: "npx",
7069
+ args: [
7070
+ "-y",
7071
+ "@modelcontextprotocol/server-filesystem",
7072
+ tempDir
7073
+ ]
7074
+ },
7075
+ "everything-server-default": {
7076
+ type: "stdio",
7077
+ command: "npx",
7078
+ args: ["-y", "@modelcontextprotocol/server-everything"]
7079
+ },
7080
+ "example-server-default": {
7081
+ type: "streamable-http",
7082
+ url: EXAMPLE_SERVER_URL
7083
+ }
7084
+ } };
7085
+ }
7071
7086
  //#endregion
7072
7087
  //#region ../../core/mcp/import/clientConfig.ts
7073
7088
  function parseJsonObject(raw) {
@@ -7202,9 +7217,13 @@ function waitForProgressToken(waits, token) {
7202
7217
  //#endregion
7203
7218
  //#region ../../core/mcp/remote/node/remote-session.ts
7204
7219
  var RemoteSession = class RemoteSession {
7220
+ static DEFAULT_DETACH_GRACE_MS = 6e4;
7221
+ static MAX_QUEUED_EVENTS = 1e3;
7205
7222
  sessionId;
7206
7223
  transport;
7207
7224
  eventQueue = [];
7225
+ droppedEventCount = 0;
7226
+ detachTimer;
7208
7227
  eventConsumer = null;
7209
7228
  transportDead = false;
7210
7229
  transportError = null;
@@ -7235,13 +7254,47 @@ var RemoteSession = class RemoteSession {
7235
7254
  this.transport.setProtocolVersion?.(version);
7236
7255
  }
7237
7256
  setEventConsumer(consumer) {
7257
+ this.cancelDetachGrace();
7238
7258
  this.eventConsumer = consumer;
7259
+ if (this.droppedEventCount > 0) {
7260
+ const dropped = this.droppedEventCount;
7261
+ this.droppedEventCount = 0;
7262
+ consumer({
7263
+ type: "stdio_log",
7264
+ data: {
7265
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
7266
+ message: `[inspector] ${dropped} event(s) were dropped while no client was attached (the queue holds at most ${RemoteSession.MAX_QUEUED_EVENTS})`
7267
+ }
7268
+ });
7269
+ }
7239
7270
  while (this.eventQueue.length > 0) consumer(this.eventQueue.shift());
7240
7271
  }
7241
7272
  clearEventConsumer() {
7242
7273
  this.eventConsumer = null;
7243
7274
  return this.transportDead;
7244
7275
  }
7276
+ detachEventConsumer(consumer) {
7277
+ if (this.eventConsumer !== consumer) return false;
7278
+ this.eventConsumer = null;
7279
+ return true;
7280
+ }
7281
+ armDetachGrace(graceMs, onExpire) {
7282
+ this.cancelDetachGrace();
7283
+ this.detachTimer = setTimeout(() => {
7284
+ this.detachTimer = void 0;
7285
+ onExpire();
7286
+ }, graceMs);
7287
+ this.detachTimer.unref();
7288
+ }
7289
+ cancelDetachGrace() {
7290
+ if (this.detachTimer !== void 0) {
7291
+ clearTimeout(this.detachTimer);
7292
+ this.detachTimer = void 0;
7293
+ }
7294
+ }
7295
+ hasPendingDetachGrace() {
7296
+ return this.detachTimer !== void 0;
7297
+ }
7245
7298
  markTransportDead(error) {
7246
7299
  this.transportDead = true;
7247
7300
  this.transportError = error;
@@ -7352,7 +7405,13 @@ var RemoteSession = class RemoteSession {
7352
7405
  }
7353
7406
  pushEvent(event) {
7354
7407
  if (this.eventConsumer) this.eventConsumer(event);
7355
- else this.eventQueue.push(event);
7408
+ else {
7409
+ if (this.eventQueue.length >= RemoteSession.MAX_QUEUED_EVENTS) {
7410
+ this.eventQueue.shift();
7411
+ this.droppedEventCount++;
7412
+ }
7413
+ this.eventQueue.push(event);
7414
+ }
7356
7415
  }
7357
7416
  onMessage(message) {
7358
7417
  if ("id" in message && message.id !== null && message.id !== void 0 && ("result" in message || "error" in message)) this.settleRequestWait(message.id);
@@ -7832,6 +7891,14 @@ function createRemoteApp(options) {
7832
7891
  const authToken = dangerouslyOmitAuth ? "" : options.authToken || process.env[API_SERVER_ENV_VARS.AUTH_TOKEN] || randomBytes(32).toString("hex");
7833
7892
  const app = new Hono();
7834
7893
  const sessions = /* @__PURE__ */ new Map();
7894
+ const sessionDetachGraceMs = options.sessionDetachGraceMs ?? RemoteSession.DEFAULT_DETACH_GRACE_MS;
7895
+ const armSessionReap = (session) => {
7896
+ session.armDetachGrace(sessionDetachGraceMs, () => {
7897
+ sessions.delete(session.sessionId);
7898
+ session.rejectActiveRequestWaits(/* @__PURE__ */ new Error("Session closed: no client attached"));
7899
+ session.transport.close().catch(() => {});
7900
+ });
7901
+ };
7835
7902
  const { logger: fileLogger, allowedOrigins } = options;
7836
7903
  const storageDir = options.storageDir ?? getDefaultStorageDir();
7837
7904
  const mcpConfigPath = options.mcpConfigPath ?? getDefaultMcpConfigPath();
@@ -8018,6 +8085,7 @@ function createRemoteApp(options) {
8018
8085
  return c.json({ error: `Failed to start transport: ${msg}` }, is401 ? 401 : 500);
8019
8086
  }
8020
8087
  sessions.set(sessionId, session);
8088
+ armSessionReap(session);
8021
8089
  return c.json({ sessionId });
8022
8090
  });
8023
8091
  app.post("/api/mcp/send", async (c) => {
@@ -8108,13 +8176,14 @@ function createRemoteApp(options) {
8108
8176
  const session = sessions.get(sessionId);
8109
8177
  if (!session) return c.json({ error: "Session not found" }, 404);
8110
8178
  return streamSSE(c, async (stream) => {
8111
- session.setEventConsumer((event) => {
8179
+ const consumer = (event) => {
8112
8180
  const data = JSON.stringify(event);
8113
8181
  stream.writeSSE({
8114
8182
  event: event.type,
8115
8183
  data
8116
8184
  });
8117
- });
8185
+ };
8186
+ session.setEventConsumer(consumer);
8118
8187
  if (session.isTransportDead()) {
8119
8188
  await new Promise((resolve) => setTimeout(resolve, 0));
8120
8189
  session.clearEventConsumer();
@@ -8122,7 +8191,12 @@ function createRemoteApp(options) {
8122
8191
  return;
8123
8192
  }
8124
8193
  await primeAndHoldSseStream(stream, () => {
8125
- if (session.clearEventConsumer() || session.isTransportDead()) sessions.delete(sessionId);
8194
+ if (!session.detachEventConsumer(consumer)) return;
8195
+ if (session.isTransportDead()) {
8196
+ sessions.delete(sessionId);
8197
+ return;
8198
+ }
8199
+ armSessionReap(session);
8126
8200
  });
8127
8201
  });
8128
8202
  });
@@ -8137,6 +8211,7 @@ function createRemoteApp(options) {
8137
8211
  if (!sessionId) return c.json({ error: "Missing sessionId" }, 400);
8138
8212
  const session = sessions.get(sessionId);
8139
8213
  if (session) {
8214
+ session.cancelDetachGrace();
8140
8215
  session.clearEventConsumer();
8141
8216
  await session.transport.close();
8142
8217
  sessions.delete(sessionId);
@@ -8800,8 +8875,9 @@ function createRemoteApp(options) {
8800
8875
  const settled = await withWriteLock(async () => {
8801
8876
  const rawInside = await readStoreFile(mcpConfigPath);
8802
8877
  if (rawInside === null) {
8803
- await writeMcpAndTrackMtime(serializeStore(DEFAULT_SEED_CONFIG));
8804
- return DEFAULT_SEED_CONFIG;
8878
+ const seed = defaultSeedConfig(tmpdir());
8879
+ await writeMcpAndTrackMtime(serializeStore(seed));
8880
+ return seed;
8805
8881
  }
8806
8882
  const parsedInside = parseStore(rawInside);
8807
8883
  const inside = { mcpServers: normalizeMcpServers(parsedInside?.mcpServers) };
@@ -9356,8 +9432,9 @@ async function loadServerEntries(serverOptions) {
9356
9432
  }
9357
9433
  const configs = resolveServerConfigs(serverOptions, "multi");
9358
9434
  if (configs.length === 0) throw new Error("At least one server is required. Use --catalog/--config <path> or an ad-hoc target (command/URL).");
9359
- return { default: {
9360
- config: configs[0],
9435
+ const config = configs[0];
9436
+ return { [adHocServerName(config)]: {
9437
+ config,
9361
9438
  settings: mergeSettings(void 0, serverOptions)
9362
9439
  } };
9363
9440
  }
@@ -11177,6 +11254,627 @@ function validateToolOutput(provider, tool, result) {
11177
11254
  return;
11178
11255
  }
11179
11256
  }
11257
+ //#endregion
11258
+ //#region ../../core/json/linearRegExp.ts
11259
+ var CharMatcher = class {
11260
+ re;
11261
+ ascii = /* @__PURE__ */ new Uint8Array(128);
11262
+ constructor(source, unicode) {
11263
+ this.re = new RegExp(`^(?:${source})$`, unicode ? "u" : "");
11264
+ }
11265
+ matches(cp, unicode) {
11266
+ if (cp < 128) {
11267
+ const memo = this.ascii[cp];
11268
+ if (memo !== 0) return memo === 1;
11269
+ const result = this.re.test(String.fromCharCode(cp));
11270
+ this.ascii[cp] = result ? 1 : 2;
11271
+ return result;
11272
+ }
11273
+ return this.re.test(unicode ? String.fromCodePoint(cp) : String.fromCharCode(cp));
11274
+ }
11275
+ };
11276
+ var Unsupported = class extends Error {};
11277
+ const MAX_GROUP_DEPTH = 64;
11278
+ const MAX_PROGRAM_SIZE = 1e4;
11279
+ const MAX_COMPILE_STEPS = 10 * MAX_PROGRAM_SIZE;
11280
+ const MAX_PATTERN_LENGTH = 8192;
11281
+ const COMPILE_BUDGET_EXHAUSTED = "the patterns together exceed the compilation budget";
11282
+ function isCompileBudgetExhausted(budget) {
11283
+ return budget.steps < 0 || budget.size < 0;
11284
+ }
11285
+ const QUANTIFIER_BRACES = /\{(\d+)(?:(,)(\d*))?\}/y;
11286
+ const HEX4 = /^[0-9a-fA-F]{4}$/;
11287
+ const HEX2 = /^[0-9a-fA-F]{2}$/;
11288
+ var Parser = class {
11289
+ pos = 0;
11290
+ src;
11291
+ unicode;
11292
+ constructor(src, unicode) {
11293
+ this.src = src;
11294
+ this.unicode = unicode;
11295
+ }
11296
+ parse() {
11297
+ const node = this.parseAlternation(0);
11298
+ /* v8 ignore next -- the native RegExp constructor rejects an unbalanced `)` first */
11299
+ if (this.pos !== this.src.length) throw new Unsupported("unbalanced group");
11300
+ return node;
11301
+ }
11302
+ parseAlternation(depth) {
11303
+ const options = [this.parseSequence(depth)];
11304
+ while (this.src[this.pos] === "|") {
11305
+ this.pos++;
11306
+ options.push(this.parseSequence(depth));
11307
+ }
11308
+ return options.length === 1 ? options[0] : {
11309
+ kind: "alt",
11310
+ options
11311
+ };
11312
+ }
11313
+ parseSequence(depth) {
11314
+ const items = [];
11315
+ while (this.pos < this.src.length && this.src[this.pos] !== "|" && this.src[this.pos] !== ")") items.push(this.parseTerm(depth));
11316
+ return items.length === 1 ? items[0] : {
11317
+ kind: "seq",
11318
+ items
11319
+ };
11320
+ }
11321
+ parseTerm(depth) {
11322
+ const atom = this.parseAtom(depth);
11323
+ const quantifier = this.readQuantifier();
11324
+ if (!quantifier) return atom;
11325
+ if (this.src[this.pos] === "?") this.pos++;
11326
+ return {
11327
+ kind: "repeat",
11328
+ body: atom,
11329
+ ...quantifier
11330
+ };
11331
+ }
11332
+ readQuantifier() {
11333
+ const c = this.src[this.pos];
11334
+ if (c === "*") {
11335
+ this.pos++;
11336
+ return {
11337
+ min: 0,
11338
+ max: Infinity
11339
+ };
11340
+ }
11341
+ if (c === "+") {
11342
+ this.pos++;
11343
+ return {
11344
+ min: 1,
11345
+ max: Infinity
11346
+ };
11347
+ }
11348
+ if (c === "?") {
11349
+ this.pos++;
11350
+ return {
11351
+ min: 0,
11352
+ max: 1
11353
+ };
11354
+ }
11355
+ if (c === "{") {
11356
+ QUANTIFIER_BRACES.lastIndex = this.pos;
11357
+ const m = QUANTIFIER_BRACES.exec(this.src);
11358
+ if (!m) return void 0;
11359
+ this.pos += m[0].length;
11360
+ const min = Number(m[1]);
11361
+ return {
11362
+ min,
11363
+ max: m[2] === void 0 ? min : m[3] ? Number(m[3]) : Infinity
11364
+ };
11365
+ }
11366
+ }
11367
+ parseAtom(depth) {
11368
+ switch (this.src[this.pos]) {
11369
+ case "(": return this.parseGroup(depth);
11370
+ case "[": return this.parseClass();
11371
+ case "\\": return this.parseEscape();
11372
+ case ".":
11373
+ this.pos++;
11374
+ return {
11375
+ kind: "char",
11376
+ source: "."
11377
+ };
11378
+ case "^":
11379
+ this.pos++;
11380
+ return {
11381
+ kind: "assert",
11382
+ assertion: "start"
11383
+ };
11384
+ case "$":
11385
+ this.pos++;
11386
+ return {
11387
+ kind: "assert",
11388
+ assertion: "end"
11389
+ };
11390
+ case "*":
11391
+ case "+":
11392
+ case "?":
11393
+ /* v8 ignore next -- "nothing to repeat" is a native syntax error */
11394
+ throw new Unsupported("nothing to repeat");
11395
+ case "{":
11396
+ QUANTIFIER_BRACES.lastIndex = this.pos;
11397
+ if (QUANTIFIER_BRACES.test(this.src))
11398
+ /* v8 ignore next -- rejected by the native RegExp constructor first */
11399
+ throw new Unsupported("nothing to repeat");
11400
+ this.pos++;
11401
+ return {
11402
+ kind: "char",
11403
+ source: "\\{"
11404
+ };
11405
+ default: {
11406
+ const width = this.codePointWidth(this.pos);
11407
+ const source = this.src.slice(this.pos, this.pos + width);
11408
+ this.pos += width;
11409
+ return {
11410
+ kind: "char",
11411
+ source
11412
+ };
11413
+ }
11414
+ }
11415
+ }
11416
+ codePointWidth(at) {
11417
+ if (!this.unicode) return 1;
11418
+ return this.src.codePointAt(at) > 65535 ? 2 : 1;
11419
+ }
11420
+ parseGroup(depth) {
11421
+ if (depth >= MAX_GROUP_DEPTH) throw new Unsupported("groups nested too deeply");
11422
+ this.pos++;
11423
+ if (this.src.startsWith("?:", this.pos)) this.pos += 2;
11424
+ else if (this.src.startsWith("?<", this.pos) && this.src[this.pos + 2] !== "=" && this.src[this.pos + 2] !== "!") {
11425
+ const close = this.src.indexOf(">", this.pos);
11426
+ /* v8 ignore next -- an unterminated group name is a native syntax error */
11427
+ if (close < 0) throw new Unsupported("unterminated group name");
11428
+ this.pos = close + 1;
11429
+ } else if (this.src[this.pos] === "?") throw new Unsupported("lookaround or group modifier");
11430
+ const body = this.parseAlternation(depth + 1);
11431
+ /* v8 ignore next -- an unterminated group is a native syntax error */
11432
+ if (this.src[this.pos] !== ")") throw new Unsupported("unterminated group");
11433
+ this.pos++;
11434
+ return body;
11435
+ }
11436
+ parseClass() {
11437
+ const start = this.pos;
11438
+ this.pos++;
11439
+ while (this.pos < this.src.length && this.src[this.pos] !== "]") this.pos += this.src[this.pos] === "\\" ? 2 : 1;
11440
+ /* v8 ignore next -- an unterminated class is a native syntax error */
11441
+ if (this.pos >= this.src.length) throw new Unsupported("unterminated class");
11442
+ this.pos++;
11443
+ return {
11444
+ kind: "char",
11445
+ source: this.src.slice(start, this.pos)
11446
+ };
11447
+ }
11448
+ parseEscape() {
11449
+ const next = this.src[this.pos + 1];
11450
+ /* v8 ignore next -- a trailing backslash is a native syntax error */
11451
+ if (next === void 0) throw new Unsupported("trailing backslash");
11452
+ switch (next) {
11453
+ case "b":
11454
+ this.pos += 2;
11455
+ return {
11456
+ kind: "assert",
11457
+ assertion: "wordBoundary"
11458
+ };
11459
+ case "B":
11460
+ this.pos += 2;
11461
+ return {
11462
+ kind: "assert",
11463
+ assertion: "notWordBoundary"
11464
+ };
11465
+ case "1":
11466
+ case "2":
11467
+ case "3":
11468
+ case "4":
11469
+ case "5":
11470
+ case "6":
11471
+ case "7":
11472
+ case "8":
11473
+ case "9":
11474
+ case "k": throw new Unsupported("backreference");
11475
+ case "0":
11476
+ if (/[0-9]/.test(this.src[this.pos + 2] ?? "")) throw new Unsupported("octal escape");
11477
+ return this.takeChar(2);
11478
+ case "c":
11479
+ if (!/[a-zA-Z]/.test(this.src[this.pos + 2] ?? "")) throw new Unsupported("incomplete control escape");
11480
+ return this.takeChar(3);
11481
+ case "p":
11482
+ case "P":
11483
+ if (this.unicode && this.src[this.pos + 2] === "{") return this.takeThrough("}");
11484
+ return this.takeChar(2);
11485
+ case "x": return this.takeChar(HEX2.test(this.src.slice(this.pos + 2, this.pos + 4)) ? 4 : 2);
11486
+ case "u": return this.parseUnicodeEscape();
11487
+ default: return this.takeChar(1 + this.codePointWidth(this.pos + 1));
11488
+ }
11489
+ }
11490
+ parseUnicodeEscape() {
11491
+ if (this.unicode && this.src[this.pos + 2] === "{") return this.takeThrough("}");
11492
+ const hex = this.src.slice(this.pos + 2, this.pos + 6);
11493
+ if (!HEX4.test(hex)) return this.takeChar(2);
11494
+ const unit = parseInt(hex, 16);
11495
+ if (this.unicode && unit >= 55296 && unit <= 56319 && this.src.startsWith("\\u", this.pos + 6)) {
11496
+ const low = this.src.slice(this.pos + 8, this.pos + 12);
11497
+ if (HEX4.test(low)) {
11498
+ const lowUnit = parseInt(low, 16);
11499
+ if (lowUnit >= 56320 && lowUnit <= 57343) return this.takeChar(12);
11500
+ }
11501
+ }
11502
+ return this.takeChar(6);
11503
+ }
11504
+ takeChar(length) {
11505
+ const source = this.src.slice(this.pos, this.pos + length);
11506
+ this.pos += length;
11507
+ return {
11508
+ kind: "char",
11509
+ source
11510
+ };
11511
+ }
11512
+ takeThrough(terminator) {
11513
+ const end = this.src.indexOf(terminator, this.pos);
11514
+ /* v8 ignore next -- an unterminated `\p{` / `\u{` is a native syntax error */
11515
+ if (end < 0) throw new Unsupported("unterminated escape");
11516
+ return this.takeChar(end + 1 - this.pos);
11517
+ }
11518
+ };
11519
+ var Compiler = class {
11520
+ prog = [];
11521
+ matchers = /* @__PURE__ */ new Map();
11522
+ unicode;
11523
+ budget;
11524
+ steps = 0;
11525
+ constructor(unicode, budget) {
11526
+ this.unicode = unicode;
11527
+ this.budget = budget;
11528
+ }
11529
+ emit(inst) {
11530
+ if (this.prog.length >= 1e4) throw new Unsupported("pattern too complex");
11531
+ if (this.budget && --this.budget.size < 0) throw new Unsupported(COMPILE_BUDGET_EXHAUSTED);
11532
+ this.prog.push(inst);
11533
+ return this.prog.length - 1;
11534
+ }
11535
+ matcher(source) {
11536
+ let matcher = this.matchers.get(source);
11537
+ if (!matcher) {
11538
+ try {
11539
+ matcher = new CharMatcher(source, this.unicode);
11540
+ } catch {
11541
+ /* v8 ignore start -- defensive: each atom is a slice the native constructor already accepted in context */
11542
+ throw new Unsupported("unrecognized character syntax");
11543
+ }
11544
+ this.matchers.set(source, matcher);
11545
+ }
11546
+ return matcher;
11547
+ }
11548
+ compile(node) {
11549
+ if (++this.steps > 1e5) throw new Unsupported("pattern too complex");
11550
+ if (this.budget && --this.budget.steps < 0) throw new Unsupported(COMPILE_BUDGET_EXHAUSTED);
11551
+ switch (node.kind) {
11552
+ case "char":
11553
+ this.emit({
11554
+ op: "char",
11555
+ matcher: this.matcher(node.source)
11556
+ });
11557
+ return;
11558
+ case "assert":
11559
+ this.emit({
11560
+ op: "assert",
11561
+ assertion: node.assertion
11562
+ });
11563
+ return;
11564
+ case "seq":
11565
+ for (const item of node.items) this.compile(item);
11566
+ return;
11567
+ case "alt":
11568
+ this.compileAlternation(node.options);
11569
+ return;
11570
+ case "repeat":
11571
+ this.compileRepeat(node.body, node.min, node.max);
11572
+ return;
11573
+ }
11574
+ }
11575
+ compileAlternation(options) {
11576
+ const jumps = [];
11577
+ for (let i = 0; i < options.length - 1; i++) {
11578
+ const split = this.emit({
11579
+ op: "split",
11580
+ a: 0,
11581
+ b: 0
11582
+ });
11583
+ this.patchSplit(split, split + 1, -1);
11584
+ this.compile(options[i]);
11585
+ jumps.push(this.emit({
11586
+ op: "jmp",
11587
+ to: 0
11588
+ }));
11589
+ this.patchSplit(split, -1, this.prog.length);
11590
+ }
11591
+ this.compile(options[options.length - 1]);
11592
+ for (const jump of jumps) this.patchJump(jump, this.prog.length);
11593
+ }
11594
+ compileRepeat(body, min, max) {
11595
+ for (let i = 0; i < min; i++) {
11596
+ const before = this.prog.length;
11597
+ this.compile(body);
11598
+ if (this.prog.length === before) return;
11599
+ }
11600
+ if (max === Infinity) {
11601
+ const split = this.emit({
11602
+ op: "split",
11603
+ a: 0,
11604
+ b: 0
11605
+ });
11606
+ this.compile(body);
11607
+ this.emit({
11608
+ op: "jmp",
11609
+ to: split
11610
+ });
11611
+ this.patchSplit(split, split + 1, this.prog.length);
11612
+ return;
11613
+ }
11614
+ const splits = [];
11615
+ for (let i = min; i < max; i++) {
11616
+ const split = this.emit({
11617
+ op: "split",
11618
+ a: 0,
11619
+ b: 0
11620
+ });
11621
+ this.patchSplit(split, split + 1, -1);
11622
+ splits.push(split);
11623
+ this.compile(body);
11624
+ }
11625
+ for (const split of splits) this.patchSplit(split, -1, this.prog.length);
11626
+ }
11627
+ patchSplit(at, a, b) {
11628
+ const inst = this.prog[at];
11629
+ if (a >= 0) inst.a = a;
11630
+ if (b >= 0) inst.b = b;
11631
+ }
11632
+ patchJump(at, to) {
11633
+ this.prog[at].to = to;
11634
+ }
11635
+ };
11636
+ function isWordUnit(unit) {
11637
+ return unit >= 48 && unit <= 57 || unit >= 65 && unit <= 90 || unit >= 97 && unit <= 122 || unit === 95;
11638
+ }
11639
+ var LinearRegExp = class {
11640
+ anchoredStart;
11641
+ prog;
11642
+ unicode;
11643
+ constructor(prog, unicode) {
11644
+ this.prog = prog;
11645
+ this.unicode = unicode;
11646
+ const first = prog[0];
11647
+ this.anchoredStart = first !== void 0 && first.op === "assert" && first.assertion === "start";
11648
+ }
11649
+ get size() {
11650
+ return this.prog.length;
11651
+ }
11652
+ cost(input) {
11653
+ return (input.length + 1) * this.prog.length;
11654
+ }
11655
+ search(input, budget) {
11656
+ if (this.cost(input) > budget) return void 0;
11657
+ const prog = this.prog;
11658
+ const size = prog.length;
11659
+ const mark = new Int32Array(size).fill(-1);
11660
+ const stack = [];
11661
+ let clist = new Int32Array(size);
11662
+ let nlist = new Int32Array(size);
11663
+ const add = (list, count, start, pos, generation) => {
11664
+ stack.push(start);
11665
+ while (stack.length > 0) {
11666
+ const pc = stack.pop();
11667
+ if (mark[pc] === generation) continue;
11668
+ mark[pc] = generation;
11669
+ const inst = prog[pc];
11670
+ switch (inst.op) {
11671
+ case "match":
11672
+ stack.length = 0;
11673
+ return -1;
11674
+ case "jmp":
11675
+ stack.push(inst.to);
11676
+ break;
11677
+ case "split":
11678
+ stack.push(inst.b, inst.a);
11679
+ break;
11680
+ case "assert":
11681
+ if (this.holds(inst.assertion, input, pos)) stack.push(pc + 1);
11682
+ break;
11683
+ case "char": list[count++] = pc;
11684
+ }
11685
+ }
11686
+ return count;
11687
+ };
11688
+ let pos = 0;
11689
+ let generation = 0;
11690
+ let clen = add(clist, 0, 0, pos, generation);
11691
+ if (clen < 0) return true;
11692
+ while (pos < input.length) {
11693
+ if (this.anchoredStart && clen === 0) return false;
11694
+ const cp = this.unicode ? input.codePointAt(pos) : input.charCodeAt(pos);
11695
+ const next = pos + (this.unicode && cp > 65535 ? 2 : 1);
11696
+ generation++;
11697
+ let nlen = 0;
11698
+ for (let i = 0; i < clen; i++) {
11699
+ const pc = clist[i];
11700
+ if (prog[pc].matcher.matches(cp, this.unicode)) {
11701
+ nlen = add(nlist, nlen, pc + 1, next, generation);
11702
+ if (nlen < 0) return true;
11703
+ }
11704
+ }
11705
+ nlen = add(nlist, nlen, 0, next, generation);
11706
+ if (nlen < 0) return true;
11707
+ [clist, nlist] = [nlist, clist];
11708
+ clen = nlen;
11709
+ pos = next;
11710
+ }
11711
+ return false;
11712
+ }
11713
+ holds(assertion, input, pos) {
11714
+ switch (assertion) {
11715
+ case "start": return pos === 0;
11716
+ case "end": return pos === input.length;
11717
+ case "wordBoundary":
11718
+ case "notWordBoundary": return (pos > 0 && isWordUnit(input.charCodeAt(pos - 1))) !== (pos < input.length && isWordUnit(input.charCodeAt(pos))) === (assertion === "wordBoundary");
11719
+ }
11720
+ }
11721
+ };
11722
+ function compileLinearRegExp(pattern, unicode, budget) {
11723
+ if (pattern.length > 8192) return {
11724
+ ok: false,
11725
+ reason: `pattern longer than ${MAX_PATTERN_LENGTH} characters`
11726
+ };
11727
+ if (budget) {
11728
+ budget.steps -= pattern.length;
11729
+ if (isCompileBudgetExhausted(budget)) return {
11730
+ ok: false,
11731
+ reason: COMPILE_BUDGET_EXHAUSTED
11732
+ };
11733
+ }
11734
+ new RegExp(pattern, unicode ? "u" : "");
11735
+ try {
11736
+ const ast = new Parser(pattern, unicode).parse();
11737
+ const compiler = new Compiler(unicode, budget);
11738
+ compiler.compile(ast);
11739
+ compiler.prog.push({ op: "match" });
11740
+ return {
11741
+ ok: true,
11742
+ regExp: new LinearRegExp(compiler.prog, unicode)
11743
+ };
11744
+ } catch (error) {
11745
+ /* v8 ignore next -- every throw inside the parser/compiler is an Unsupported */
11746
+ if (!(error instanceof Unsupported)) throw error;
11747
+ return {
11748
+ ok: false,
11749
+ reason: error.message
11750
+ };
11751
+ }
11752
+ }
11753
+ //#endregion
11754
+ //#region ../../core/mcp/linearPatternValidator.ts
11755
+ const PATTERN_EVALUATION_BUDGET = 2e7;
11756
+ const SCHEMA_COMPILE_BUDGET = {
11757
+ steps: 10 * MAX_COMPILE_STEPS,
11758
+ size: 10 * MAX_PROGRAM_SIZE
11759
+ };
11760
+ const MAX_QUOTED_PATTERN = 120;
11761
+ const DRAFT_2020_12 = /* @__PURE__ */ new Set(["https://json-schema.org/draft/2020-12/schema", "http://json-schema.org/draft/2020-12/schema"]);
11762
+ const DRAFT_2019_09 = /* @__PURE__ */ new Set(["https://json-schema.org/draft/2019-09/schema", "http://json-schema.org/draft/2019-09/schema"]);
11763
+ const DRAFT_07_OR_06 = /* @__PURE__ */ new Set([
11764
+ "https://json-schema.org/draft-07/schema",
11765
+ "http://json-schema.org/draft-07/schema",
11766
+ "https://json-schema.org/draft-06/schema",
11767
+ "http://json-schema.org/draft-06/schema"
11768
+ ]);
11769
+ function declaredDialect(schema) {
11770
+ const declared = schema.$schema;
11771
+ if (typeof declared !== "string") return "2020-12";
11772
+ const uri = declared.replace(/#$/, "");
11773
+ if (DRAFT_2020_12.has(uri)) return "2020-12";
11774
+ if (DRAFT_2019_09.has(uri)) return "2019-09";
11775
+ if (DRAFT_07_OR_06.has(uri)) return "draft-7";
11776
+ throw new Error(`JSON Schema declares an unsupported dialect ("$schema": "${declared.slice(0, 200)}"). Supported: JSON Schema 2020-12, 2019-09, draft-07, and draft-06.`);
11777
+ }
11778
+ function quote(pattern) {
11779
+ return pattern.length > MAX_QUOTED_PATTERN ? `${pattern.slice(0, MAX_QUOTED_PATTERN)}…` : pattern;
11780
+ }
11781
+ var PatternLedger = class {
11782
+ remaining = PATTERN_EVALUATION_BUDGET;
11783
+ unevaluated = /* @__PURE__ */ new Set();
11784
+ reset() {
11785
+ this.remaining = PATTERN_EVALUATION_BUDGET;
11786
+ this.unevaluated.clear();
11787
+ }
11788
+ };
11789
+ var LinearPattern = class {
11790
+ source;
11791
+ flags;
11792
+ compiled;
11793
+ ledger;
11794
+ constructor(source, flags, compiled, ledger) {
11795
+ this.source = source;
11796
+ this.flags = flags;
11797
+ this.compiled = compiled;
11798
+ this.ledger = ledger;
11799
+ }
11800
+ test(input) {
11801
+ if (!this.compiled.ok) return this.skip(this.compiled.reason);
11802
+ const { regExp } = this.compiled;
11803
+ const cost = regExp.cost(input);
11804
+ if (cost > this.ledger.remaining) return this.skip(`checking a ${input.length}-character value would exceed the evaluation budget`);
11805
+ this.ledger.remaining -= cost;
11806
+ return regExp.search(input, cost) === true;
11807
+ }
11808
+ skip(reason) {
11809
+ this.ledger.unevaluated.add(`pattern "${quote(this.source)}" was not evaluated (${reason})`);
11810
+ return true;
11811
+ }
11812
+ toString() {
11813
+ return `/${this.source}/${this.flags}`;
11814
+ }
11815
+ };
11816
+ function createRegExpEngine(ledger, budget) {
11817
+ const engine = (pattern, flags) => new LinearPattern(pattern, flags, compileLinearRegExp(pattern, flags.includes("u"), budget()), ledger);
11818
+ return Object.assign(engine, { code: "linearRegExp" });
11819
+ }
11820
+ var LinearPatternJsonSchemaValidator = class {
11821
+ ledger = new PatternLedger();
11822
+ engines = /* @__PURE__ */ new Map();
11823
+ compileBudget = { ...SCHEMA_COMPILE_BUDGET };
11824
+ overBudget = /* @__PURE__ */ new WeakSet();
11825
+ engineFor(dialect) {
11826
+ let engine = this.engines.get(dialect);
11827
+ if (!engine) {
11828
+ const options = {
11829
+ strict: false,
11830
+ validateFormats: true,
11831
+ validateSchema: false,
11832
+ allErrors: true,
11833
+ code: { regExp: createRegExpEngine(this.ledger, () => this.compileBudget) }
11834
+ };
11835
+ engine = dialect === "2020-12" ? new Ajv2020(options) : dialect === "2019-09" ? new Ajv2019(options) : new Ajv(options);
11836
+ addFormats(engine);
11837
+ this.engines.set(dialect, engine);
11838
+ }
11839
+ return engine;
11840
+ }
11841
+ getValidator(schema) {
11842
+ const engine = this.engineFor(declaredDialect(schema));
11843
+ const id = schema.$id;
11844
+ const validate = this.tracked(() => (typeof id === "string" ? engine.getSchema(id) : void 0) ?? engine.compile(schema));
11845
+ const overBudget = this.overBudget.has(validate);
11846
+ return (input) => {
11847
+ this.ledger.reset();
11848
+ const valid = validate(input);
11849
+ const unevaluated = [...this.ledger.unevaluated];
11850
+ if (overBudget) unevaluated.unshift("the schema's patterns exceed the compilation budget");
11851
+ if (valid && unevaluated.length === 0) return {
11852
+ valid: true,
11853
+ data: input,
11854
+ errorMessage: void 0
11855
+ };
11856
+ const messages = valid ? [] : [engine.errorsText(validate.errors)];
11857
+ if (unevaluated.length > 0) messages.push(`${unevaluated.join("; ")}, so the value could not be verified against the schema`);
11858
+ return {
11859
+ valid: false,
11860
+ data: void 0,
11861
+ errorMessage: messages.join("; ")
11862
+ };
11863
+ };
11864
+ }
11865
+ tracked(run) {
11866
+ const outer = this.compileBudget;
11867
+ const budget = { ...SCHEMA_COMPILE_BUDGET };
11868
+ this.compileBudget = budget;
11869
+ try {
11870
+ const validate = run();
11871
+ if (isCompileBudgetExhausted(budget)) this.overBudget.add(validate);
11872
+ return validate;
11873
+ } finally {
11874
+ this.compileBudget = outer;
11875
+ }
11876
+ }
11877
+ };
11180
11878
  function listPaginationExceeded(method) {
11181
11879
  return /* @__PURE__ */ new Error(`Salvage walk for ${method} exceeded 64 pages without the server's pagination converging`);
11182
11880
  }
@@ -12923,6 +13621,16 @@ var RemoteClientTransport = class {
12923
13621
  }
12924
13622
  return await res.json();
12925
13623
  }
13624
+ releaseOnPageHide() {
13625
+ if (!this._sessionId) return false;
13626
+ this.fetchFn(`${this.baseUrl}/api/mcp/disconnect`, {
13627
+ method: "POST",
13628
+ headers: this.headers,
13629
+ body: JSON.stringify({ sessionId: this._sessionId }),
13630
+ keepalive: true
13631
+ }).catch(() => {});
13632
+ return true;
13633
+ }
12926
13634
  async close() {
12927
13635
  if (this.closed) return;
12928
13636
  this.closed = true;
@@ -13860,7 +14568,7 @@ var InspectorClient = class InspectorClient extends InspectorClientEventTarget {
13860
14568
  params: request.params,
13861
14569
  signal: controller.signal
13862
14570
  });
13863
- this.outputValidator ??= new AjvJsonSchemaValidator();
14571
+ this.outputValidator ??= new LinearPatternJsonSchemaValidator();
13864
14572
  const invalid = validateAppElicitResult(this.outputValidator, request.params, result);
13865
14573
  if (invalid) {
13866
14574
  this.logger.warn({
@@ -14459,7 +15167,7 @@ var InspectorClient = class InspectorClient extends InspectorClientEventTarget {
14459
15167
  });
14460
15168
  }
14461
15169
  validateToolOutput(tool, result) {
14462
- this.outputValidator ??= new AjvJsonSchemaValidator();
15170
+ this.outputValidator ??= new LinearPatternJsonSchemaValidator();
14463
15171
  return validateToolOutput(this.outputValidator, tool, result);
14464
15172
  }
14465
15173
  cancelPendingTaskInput(taskId) {
@@ -15306,6 +16014,10 @@ var InspectorClient = class InspectorClient extends InspectorClientEventTarget {
15306
16014
  getRemoteBackendSessionId() {
15307
16015
  if (this.baseTransport instanceof RemoteClientTransport) return this.baseTransport.getRemoteBackendSessionId();
15308
16016
  }
16017
+ releaseRemoteSessionOnPageHide() {
16018
+ if (this.baseTransport instanceof RemoteClientTransport) return this.baseTransport.releaseOnPageHide();
16019
+ return false;
16020
+ }
15309
16021
  async resumeAfterOAuth(authorizationCode, options) {
15310
16022
  await this.completeOAuthFlow(authorizationCode, options?.iss, options?.state);
15311
16023
  const remoteSessionId = options?.remoteSessionId;
@@ -16032,6 +16744,7 @@ function resolveLoadOptions(flags) {
16032
16744
  };
16033
16745
  }
16034
16746
  const OTHER_ERRORS = "(other)";
16747
+ const NO_TOOL_ERROR_TEXT = "(no text content)";
16035
16748
  const yieldToEventLoop = () => new Promise((resolve) => setImmediate(resolve));
16036
16749
  const realClock = {
16037
16750
  now: () => performance.now(),
@@ -16101,10 +16814,45 @@ var ErrorTally = class {
16101
16814
  return list;
16102
16815
  }
16103
16816
  };
16817
+ function isLoadStop(value) {
16818
+ if (typeof value !== "object" || value === null) return false;
16819
+ const { reason, detail } = value;
16820
+ return (reason === "interrupted" || reason === "connection_lost" || reason === "consecutive_errors") && typeof detail === "string";
16821
+ }
16822
+ function toolErrorText(content) {
16823
+ const blocks = Array.isArray(content) ? content : [];
16824
+ for (const block of blocks) {
16825
+ if (typeof block !== "object" || block === null) continue;
16826
+ const { type, text } = block;
16827
+ if (type !== "text" || typeof text !== "string") continue;
16828
+ const trimmed = text.trim();
16829
+ if (trimmed === "") continue;
16830
+ return trimmed.length > 200 ? `${trimmed.slice(0, 200)}…` : trimmed;
16831
+ }
16832
+ return NO_TOOL_ERROR_TEXT;
16833
+ }
16834
+ const LOAD_FLAG_CONFLICT = "load_flag_conflict";
16835
+ function loadSummaryWarnings(summary) {
16836
+ const warnings = [];
16837
+ const stop = summary.stoppedEarly;
16838
+ if (stop !== void 0 && stop.reason !== "interrupted") warnings.push(`Warning: the load run stopped early (${stop.reason}): ${stop.detail}.`);
16839
+ const shortfall = loadRateShortfallWarning(summary);
16840
+ if (shortfall !== void 0) warnings.push(shortfall);
16841
+ return warnings;
16842
+ }
16843
+ function loadRateShortfallWarning(summary) {
16844
+ const { rate, achievedRate, plannedStarts, droppedSlots } = summary;
16845
+ if (rate === void 0 || plannedStarts === void 0 || droppedSlots === void 0 || plannedStarts === 0) return;
16846
+ const started = plannedStarts - droppedSlots;
16847
+ if (started >= plannedStarts * .9) return void 0;
16848
+ return `Warning: --load-rate ${rate}/s was not reached: ${started} of ${plannedStarts} planned calls started (${achievedRate}/s); ${droppedSlots} slots were dropped because every worker was busy. Raise --load-concurrency or lower --load-rate.`;
16849
+ }
16104
16850
  async function runLoad(call, options, clock = realClock, signal) {
16105
16851
  const { concurrency, durationMs, requests, rate } = options;
16106
- const latencies = [];
16852
+ const okLatencies = [];
16853
+ const errorLatencies = [];
16107
16854
  const tally = new ErrorTally();
16855
+ const toolTally = new ErrorTally();
16108
16856
  let ok = 0;
16109
16857
  let toolErrors = 0;
16110
16858
  let errors = 0;
@@ -16113,6 +16861,37 @@ async function runLoad(call, options, clock = realClock, signal) {
16113
16861
  const deadline = durationMs === void 0 ? Infinity : start + durationMs;
16114
16862
  const interval = rate === void 0 ? 0 : 1e3 / rate;
16115
16863
  let nextStart = start;
16864
+ const halt = new AbortController();
16865
+ let stoppedEarly;
16866
+ let stoppedAt = 0;
16867
+ const stopRun = (stop) => {
16868
+ if (stoppedEarly) {
16869
+ if (stop.reason === "interrupted") stoppedEarly = stop;
16870
+ return;
16871
+ }
16872
+ stoppedEarly = stop;
16873
+ stoppedAt = clock.now();
16874
+ halt.abort();
16875
+ };
16876
+ const onAbort = () => {
16877
+ const reason = signal?.reason;
16878
+ stopRun(isLoadStop(reason) ? reason : {
16879
+ reason: "interrupted",
16880
+ detail: "the run was interrupted"
16881
+ });
16882
+ };
16883
+ if (signal?.aborted) onAbort();
16884
+ else signal?.addEventListener("abort", onAbort, { once: true });
16885
+ let errorStreak = 0;
16886
+ let streakSince = 0;
16887
+ const noteThrown = (now) => {
16888
+ if (errorStreak++ === 0) streakSince = now;
16889
+ const window = now - streakSince;
16890
+ if ((requests === void 0 || started < requests) && now < deadline && nextStart < deadline && (errorStreak >= 50 || errorStreak >= 5 && window >= 1e3)) stopRun({
16891
+ reason: "consecutive_errors",
16892
+ detail: `${errorStreak} calls in a row threw over ${round(window)}ms; the server looks unavailable`
16893
+ });
16894
+ };
16116
16895
  let turnstile = Promise.resolve();
16117
16896
  const claimSlot = async () => {
16118
16897
  const ahead = turnstile;
@@ -16122,7 +16901,7 @@ async function runLoad(call, options, clock = realClock, signal) {
16122
16901
  try {
16123
16902
  if (started > 0 && started % 64 === 0) await yieldToEventLoop();
16124
16903
  for (;;) {
16125
- if (signal?.aborted) return false;
16904
+ if (halt.signal.aborted) return false;
16126
16905
  if (requests !== void 0 && started >= requests) return false;
16127
16906
  const now = clock.now();
16128
16907
  if (now >= deadline) return false;
@@ -16133,7 +16912,7 @@ async function runLoad(call, options, clock = realClock, signal) {
16133
16912
  nextStart = now - nextStart < interval ? nextStart + interval : now + interval;
16134
16913
  return true;
16135
16914
  }
16136
- await sleepUnlessAborted(clock, scheduled - now, signal);
16915
+ await sleepUnlessAborted(clock, scheduled - now, halt.signal);
16137
16916
  }
16138
16917
  } finally {
16139
16918
  release();
@@ -16141,26 +16920,57 @@ async function runLoad(call, options, clock = realClock, signal) {
16141
16920
  };
16142
16921
  const worker = async (index) => {
16143
16922
  while (await claimSlot()) {
16144
- if (signal?.aborted) return;
16923
+ if (halt.signal.aborted) {
16924
+ started--;
16925
+ return;
16926
+ }
16145
16927
  const t0 = clock.now();
16146
16928
  try {
16147
- if (await call(index) === "ok") ok++;
16148
- else toolErrors++;
16929
+ const outcome = await call(index);
16930
+ const latency = clock.now() - t0;
16931
+ errorStreak = 0;
16932
+ if (outcome === "ok") {
16933
+ ok++;
16934
+ okLatencies.push(latency);
16935
+ } else {
16936
+ toolErrors++;
16937
+ errorLatencies.push(latency);
16938
+ toolTally.add(typeof outcome === "object" ? outcome.message : NO_TOOL_ERROR_TEXT);
16939
+ }
16149
16940
  } catch (err) {
16941
+ const now = clock.now();
16150
16942
  errors++;
16943
+ errorLatencies.push(now - t0);
16151
16944
  tally.add(err instanceof Error ? err.message : String(err));
16945
+ noteThrown(now);
16152
16946
  }
16153
- latencies.push(clock.now() - t0);
16154
16947
  }
16155
16948
  };
16156
- await Promise.all(Array.from({ length: concurrency }, (_, i) => worker(i)));
16157
- const elapsedMs = clock.now() - start;
16949
+ try {
16950
+ await Promise.all(Array.from({ length: concurrency }, (_, i) => worker(i)));
16951
+ } finally {
16952
+ signal?.removeEventListener("abort", onAbort);
16953
+ }
16954
+ const end = clock.now();
16955
+ const requestCapped = requests !== void 0 && started >= requests;
16956
+ const elapsedMs = durationMs !== void 0 && stoppedEarly === void 0 && !requestCapped ? Math.max(end - start, durationMs) : end - start;
16158
16957
  const completed = ok + toolErrors + errors;
16958
+ const startWindowEnd = () => {
16959
+ let windowEnd = deadline;
16960
+ if (requestCapped) windowEnd = Math.min(windowEnd, nextStart);
16961
+ if (stoppedEarly !== void 0) windowEnd = Math.min(windowEnd, stoppedAt);
16962
+ /* v8 ignore next -- defensive: a run with no duration, no request cap
16963
+ and no stop never ends, so one of the three always applies. */
16964
+ return Number.isFinite(windowEnd) ? windowEnd : end;
16965
+ };
16159
16966
  return {
16160
16967
  concurrency,
16161
16968
  ...durationMs !== void 0 && { durationMs },
16162
16969
  ...requests !== void 0 && { requestLimit: requests },
16163
- ...rate !== void 0 && { rate },
16970
+ ...rate !== void 0 && rateFigures(rate, {
16971
+ started,
16972
+ windowMs: startWindowEnd() - start
16973
+ }),
16164
16974
  requests: completed,
16165
16975
  ok,
16166
16976
  toolErrors,
@@ -16168,8 +16978,23 @@ async function runLoad(call, options, clock = realClock, signal) {
16168
16978
  errorRate: completed === 0 ? 0 : round((toolErrors + errors) / completed, 4),
16169
16979
  elapsedMs: round(elapsedMs),
16170
16980
  throughput: elapsedMs > 0 ? round(completed * 1e3 / elapsedMs) : 0,
16171
- latencyMs: summarizeLatency(latencies),
16172
- errorMessages: tally.entries()
16981
+ okThroughput: elapsedMs > 0 ? round(ok * 1e3 / elapsedMs) : 0,
16982
+ latencyMs: summarizeLatency(okLatencies),
16983
+ errorLatencyMs: summarizeLatency(errorLatencies),
16984
+ errorMessages: tally.entries(),
16985
+ toolErrorMessages: toolTally.entries(),
16986
+ ...stoppedEarly !== void 0 && { stoppedEarly }
16987
+ };
16988
+ }
16989
+ function rateFigures(rate, run) {
16990
+ const { started, windowMs } = run;
16991
+ const slots = Math.ceil(windowMs * rate / 1e3 - 1e-9);
16992
+ const plannedStarts = Math.max(started, slots);
16993
+ return {
16994
+ rate,
16995
+ plannedStarts,
16996
+ droppedSlots: plannedStarts - started,
16997
+ achievedRate: windowMs > 0 ? round(started * 1e3 / windowMs) : 0
16173
16998
  };
16174
16999
  }
16175
17000
  async function runLoadAcrossConnections(primary, openConnection, callOn, options, clock = realClock, signal) {
@@ -16231,7 +17056,11 @@ async function runToolLoad(inspectorClient, args, options, extra = {}) {
16231
17056
  /* v8 ignore next 3 -- defensive: `ToolCallInvocation.result` is nullable,
16232
17057
  but both call paths throw on failure rather than return a null result. */
16233
17058
  if (invocation.result === null) throw new Error(invocation.error ?? "Tool call failed");
16234
- return invocation.result.isError === true ? "tool_error" : "ok";
17059
+ if (invocation.result.isError !== true) return "ok";
17060
+ return {
17061
+ outcome: "tool_error",
17062
+ message: toolErrorText(invocation.result.content)
17063
+ };
16235
17064
  };
16236
17065
  const summary = await runLoadAcrossConnections(inspectorClient, extra.openConnection, call, options, extra.clock, extra.signal);
16237
17066
  return {
@@ -16239,6 +17068,41 @@ async function runToolLoad(inspectorClient, args, options, extra = {}) {
16239
17068
  ...summary
16240
17069
  };
16241
17070
  }
17071
+ async function withLoadInterrupt(run, options = {}) {
17072
+ const controller = new AbortController();
17073
+ const exit = options.exit ?? ((code) => process.exit(code));
17074
+ const now = options.now ?? (() => performance.now());
17075
+ let first;
17076
+ const onSignal = (signal) => {
17077
+ if (first !== void 0) {
17078
+ if (signal === first.signal && now() - first.at < 500) return;
17079
+ exit(EXIT_CODES.INTERRUPTED);
17080
+ return;
17081
+ }
17082
+ first = {
17083
+ signal,
17084
+ at: now()
17085
+ };
17086
+ const stop = {
17087
+ reason: "interrupted",
17088
+ detail: `received ${signal}`
17089
+ };
17090
+ controller.abort(stop);
17091
+ if (!options.quiet) process.stderr.write(`Interrupted (${signal}): no further calls will start; waiting for in-flight calls to finish. Press Ctrl+C again to exit now.\n`);
17092
+ };
17093
+ process.on("SIGINT", onSignal);
17094
+ process.on("SIGTERM", onSignal);
17095
+ try {
17096
+ return await run(controller.signal);
17097
+ } finally {
17098
+ process.off("SIGINT", onSignal);
17099
+ process.off("SIGTERM", onSignal);
17100
+ }
17101
+ }
17102
+ function assertNotInterrupted(summary) {
17103
+ if (summary.stoppedEarly?.reason !== "interrupted") return;
17104
+ throw new CliExitCodeError(EXIT_CODES.INTERRUPTED, `Load run interrupted (${summary.stoppedEarly.detail}); the summary covers the ${summary.requests} calls that completed.`, { code: "interrupted" });
17105
+ }
16242
17106
  //#endregion
16243
17107
  //#region ../../core/cli/handlers/load-thresholds.ts
16244
17108
  const LOAD_ERROR_RATE_EXCEEDED = "load_error_rate_exceeded";
@@ -16274,7 +17138,7 @@ function p99Breach(summary, max) {
16274
17138
  const p99 = summary.latencyMs?.p99;
16275
17139
  if (p99 === void 0) return {
16276
17140
  code: LOAD_P99_EXCEEDED,
16277
- message: `no call completed, so there is no p99 latency to check against --load-max-p99 ${max}`
17141
+ message: `no call succeeded, so there is no p99 latency to check against --load-max-p99 ${max}`
16278
17142
  };
16279
17143
  if (p99 <= max) return void 0;
16280
17144
  return {
@@ -16843,21 +17707,38 @@ async function runMethod(inspectorClient, args, options = {}) {
16843
17707
  //#region ../../core/auth/node/oauth-callback-server.ts
16844
17708
  const DEFAULT_HOSTNAME = "127.0.0.1";
16845
17709
  const DEFAULT_CALLBACK_PATH = "/oauth/callback";
16846
- const SUCCESS_HTML = `<!DOCTYPE html>
16847
- <html>
16848
- <head><meta charset="utf-8"><title>OAuth complete</title></head>
16849
- <body><p>OAuth complete. You can close this window.</p></body>
16850
- </html>`;
16851
- function errorHtml(message) {
17710
+ const SUCCESS_HTML = page("OAuth complete", "OAuth complete. You can close this window.");
17711
+ const NOT_CALLBACK_HTML = page("Not the OAuth callback", "This address is not the OAuth callback the Inspector is waiting for. Sign-in has not finished; return to the Inspector to continue.");
17712
+ const STATE_MISMATCH_HTML = page("OAuth callback ignored", "This callback does not belong to the sign-in in progress and was ignored. Sign-in has not finished; the Inspector is still waiting for the authorization server.");
17713
+ const ALREADY_HANDLED_HTML = page("OAuth callback already received", "This sign-in has already received its callback. You can close this window.");
17714
+ const STOPPED_HTML = page("OAuth callback not expected", "The Inspector is no longer waiting for this sign-in. Start sign-in again from the Inspector.");
17715
+ function errorHtml(serverText) {
17716
+ return page("OAuth error", `OAuth failed. The authorization server said: ${serverText}`);
17717
+ }
17718
+ function exchangeErrorHtml(message) {
17719
+ return page("OAuth error", `OAuth failed: ${message}`);
17720
+ }
17721
+ function page(title, text) {
16852
17722
  return `<!DOCTYPE html>
16853
17723
  <html>
16854
- <head><meta charset="utf-8"><title>OAuth error</title></head>
16855
- <body><p>OAuth failed: ${escapeHtml(message)}</p></body>
17724
+ <head><meta charset="utf-8"><title>${escapeHtml(title)}</title></head>
17725
+ <body><p>${escapeHtml(text)}</p></body>
16856
17726
  </html>`;
16857
17727
  }
16858
17728
  function escapeHtml(s) {
16859
17729
  return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
16860
17730
  }
17731
+ const ANY_STATE = Symbol("any-state");
17732
+ const STOPPED = Symbol("stopped");
17733
+ function createStopSignal() {
17734
+ let fire;
17735
+ return {
17736
+ stopped: new Promise((resolve) => {
17737
+ fire = () => resolve(STOPPED);
17738
+ }),
17739
+ fire
17740
+ };
17741
+ }
16861
17742
  var OAuthCallbackServer = class {
16862
17743
  server = null;
16863
17744
  port = 0;
@@ -16865,6 +17746,8 @@ var OAuthCallbackServer = class {
16865
17746
  handled = false;
16866
17747
  onCallback;
16867
17748
  onError;
17749
+ expectedState = Promise.resolve(ANY_STATE);
17750
+ stopSignal = createStopSignal();
16868
17751
  async start(options = {}) {
16869
17752
  const { port = 0, hostname = DEFAULT_HOSTNAME, path = DEFAULT_CALLBACK_PATH, onCallback, onError } = options;
16870
17753
  if (!path.startsWith("/")) return Promise.reject(/* @__PURE__ */ new Error("Callback path must start with '/' (absolute path)"));
@@ -16872,6 +17755,8 @@ var OAuthCallbackServer = class {
16872
17755
  this.onError = onError;
16873
17756
  this.handled = false;
16874
17757
  this.callbackPath = path;
17758
+ this.stopSignal = createStopSignal();
17759
+ this.expectedState = "expectedState" in options ? Promise.race([Promise.resolve(options.expectedState).catch(() => STOPPED), this.stopSignal.stopped]) : Promise.resolve(ANY_STATE);
16875
17760
  return new Promise((resolve, reject) => {
16876
17761
  this.server = createServer((req, res) => this.handleRequest(req, res));
16877
17762
  this.server.on("error", reject);
@@ -16895,6 +17780,7 @@ var OAuthCallbackServer = class {
16895
17780
  }
16896
17781
  async stop() {
16897
17782
  if (!this.server) return;
17783
+ this.stopSignal.fire();
16898
17784
  await new Promise((resolve) => {
16899
17785
  this.server.close(() => resolve());
16900
17786
  });
@@ -16903,11 +17789,14 @@ var OAuthCallbackServer = class {
16903
17789
  handleRequest(req, res) {
16904
17790
  const needJson = req.headers["accept"]?.includes("application/json");
16905
17791
  const send = (status, body, contentType = "text/html; charset=utf-8") => {
16906
- res.writeHead(status, { "Content-Type": contentType });
17792
+ res.writeHead(status, {
17793
+ "Content-Type": contentType,
17794
+ Connection: "close"
17795
+ });
16907
17796
  res.end(body);
16908
17797
  };
16909
17798
  if (req.method !== "GET") {
16910
- send(405, needJson ? "{\"error\":\"Method Not Allowed\"}" : SUCCESS_HTML);
17799
+ send(405, needJson ? "{\"error\":\"Method Not Allowed\"}" : NOT_CALLBACK_HTML);
16911
17800
  return;
16912
17801
  }
16913
17802
  let pathname;
@@ -16924,15 +17813,26 @@ var OAuthCallbackServer = class {
16924
17813
  search = u.search;
16925
17814
  state = u.searchParams.get("state") ?? void 0;
16926
17815
  } catch {
16927
- send(400, needJson ? "{\"error\":\"Bad Request\"}" : SUCCESS_HTML);
17816
+ send(400, needJson ? "{\"error\":\"Bad Request\"}" : NOT_CALLBACK_HTML);
16928
17817
  return;
16929
17818
  }
16930
17819
  if (pathname !== this.callbackPath) {
16931
- send(404, needJson ? "{\"error\":\"Not Found\"}" : SUCCESS_HTML);
17820
+ send(404, needJson ? "{\"error\":\"Not Found\"}" : NOT_CALLBACK_HTML);
17821
+ return;
17822
+ }
17823
+ this.expectedState.then((expected) => this.handleCallback(expected, state, search, needJson, send));
17824
+ }
17825
+ handleCallback(expected, state, search, needJson, send) {
17826
+ if (expected === STOPPED) {
17827
+ send(503, needJson ? "{\"error\":\"Not waiting for a callback\"}" : STOPPED_HTML);
17828
+ return;
17829
+ }
17830
+ if (expected !== ANY_STATE && state !== expected) {
17831
+ send(400, needJson ? "{\"error\":\"State mismatch\"}" : STATE_MISMATCH_HTML);
16932
17832
  return;
16933
17833
  }
16934
17834
  if (this.handled) {
16935
- send(409, needJson ? "{\"error\":\"Callback already handled\"}" : SUCCESS_HTML);
17835
+ send(409, needJson ? "{\"error\":\"Callback already handled\"}" : ALREADY_HANDLED_HTML);
16936
17836
  return;
16937
17837
  }
16938
17838
  const params = parseOAuthCallbackParams(search);
@@ -16950,9 +17850,10 @@ var OAuthCallbackServer = class {
16950
17850
  const msg = err instanceof Error ? err.message : String(err);
16951
17851
  this.onError?.({
16952
17852
  error: "callback_error",
16953
- error_description: msg
17853
+ error_description: msg,
17854
+ source: "callback"
16954
17855
  });
16955
- send(500, errorHtml(msg));
17856
+ send(500, exchangeErrorHtml(msg));
16956
17857
  this.stop();
16957
17858
  });
16958
17859
  else {
@@ -16965,7 +17866,8 @@ var OAuthCallbackServer = class {
16965
17866
  const msg = generateOAuthErrorDescription(params);
16966
17867
  this.onError?.({
16967
17868
  error: params.error,
16968
- error_description: params.error_description ?? void 0
17869
+ error_description: params.error_description ?? void 0,
17870
+ source: "redirect"
16969
17871
  });
16970
17872
  send(400, errorHtml(msg));
16971
17873
  }
@@ -17029,6 +17931,14 @@ function toRunnerOAuthError(err) {
17029
17931
  }
17030
17932
  return err instanceof Error ? err : new Error(String(err));
17031
17933
  }
17934
+ function stateOf(authorizationUrl) {
17935
+ return authorizationUrl.searchParams.get("state") ?? void 0;
17936
+ }
17937
+ function callbackErrorMessage(params) {
17938
+ if (params.source === "callback") return params.error_description ?? "OAuth callback failed";
17939
+ if (!params.error && !params.error_description) return "OAuth error";
17940
+ return `OAuth authorization failed. The authorization server said: ${params.error_description ? `${params.error_description} (${params.error})` : params.error}`;
17941
+ }
17032
17942
  async function runRunnerInteractiveOAuth(options) {
17033
17943
  const server = (options.createCallbackServer ?? createOAuthCallbackServer)();
17034
17944
  let flowResolve;
@@ -17058,11 +17968,16 @@ async function runRunnerInteractiveOAuth(options) {
17058
17968
  process.on("SIGTERM", onSignal);
17059
17969
  }
17060
17970
  let timeoutId;
17971
+ let resolveExpectedState;
17972
+ const expectedState = new Promise((resolve) => {
17973
+ resolveExpectedState = resolve;
17974
+ });
17061
17975
  try {
17062
17976
  const { redirectUrl } = await racingSignals(server.start({
17063
17977
  hostname: options.callbackListen.hostname,
17064
17978
  port: options.callbackListen.port,
17065
17979
  path: options.callbackListen.pathname,
17980
+ expectedState,
17066
17981
  onCallback: async (params) => {
17067
17982
  try {
17068
17983
  await options.client.completeOAuthFlow(params.code, params.iss, params.state);
@@ -17072,10 +17987,7 @@ async function runRunnerInteractiveOAuth(options) {
17072
17987
  }
17073
17988
  },
17074
17989
  onError: (params) => {
17075
- flowReject(new Error(
17076
- /* v8 ignore next -- params.error is a required non-null string, so the "OAuth error" fallback is unreachable */
17077
- params.error_description ?? params.error ?? "OAuth error"
17078
- ));
17990
+ flowReject(new Error(callbackErrorMessage(params)));
17079
17991
  }
17080
17992
  }));
17081
17993
  options.onCallbackServer?.(server);
@@ -17091,10 +18003,16 @@ async function runRunnerInteractiveOAuth(options) {
17091
18003
  })]);
17092
18004
  waitForCallback.catch(() => {});
17093
18005
  if (options.authorizationUrl) {
18006
+ resolveExpectedState(stateOf(options.authorizationUrl));
17094
18007
  await racingSignals(options.client.beginInteractiveAuthorization(options.authorizationUrl));
17095
18008
  await waitForCallback;
17096
- } else if (await racingSignals(options.client.authenticate()) !== void 0) await waitForCallback;
17097
- else return { kind: "already_authorized" };
18009
+ } else {
18010
+ const authUrl = await racingSignals(options.client.authenticate());
18011
+ if (authUrl !== void 0) {
18012
+ resolveExpectedState(stateOf(authUrl));
18013
+ await waitForCallback;
18014
+ } else return { kind: "already_authorized" };
18015
+ }
17098
18016
  if (options.authChallenge) {
17099
18017
  if (!await racingSignals(options.client.checkAuthChallengeSatisfied(options.authChallenge, { scopeOnly: true }))) return {
17100
18018
  kind: "insufficient_scope",
@@ -17460,7 +18378,7 @@ function readInspectorVersion(callerUrl) {
17460
18378
  const validLogLevels = Object.values(LoggingLevelSchema.enum);
17461
18379
  const CLI_CLIENT_NAME = "inspector-cli";
17462
18380
  async function callMethod(serverConfig, serverSettings, args, clientConfig, cliAuthOverrides, callbackUrlConfig, storedAuthOnly, relogin, revoke, load, loadThresholds) {
17463
- if (load?.connections !== void 0 && load.connections > 1 && !isOAuthCapableServerConfig(serverConfig)) throw new CliExitCodeError(EXIT_CODES.USAGE, "--load-connections above 1 requires an HTTP or SSE server; over stdio each connection would spawn its own server process.");
18381
+ if (load?.connections !== void 0 && load.connections > 1 && !isOAuthCapableServerConfig(serverConfig)) throw new CliExitCodeError(EXIT_CODES.USAGE, "--load-connections above 1 requires an HTTP or SSE server; over stdio each connection would spawn its own server process.", { code: LOAD_FLAG_CONFLICT });
17464
18382
  if (relogin) {
17465
18383
  if (!("url" in serverConfig && serverConfig.url)) throw new Error("--relogin requires an HTTP/SSE server URL (no OAuth store entry for stdio)");
17466
18384
  const revocation = await clearStoredAuthForRelogin(serverConfig.url, { revoke: revoke && serverSettings?.oauthRevokeOnClear !== false });
@@ -17527,12 +18445,17 @@ async function callMethod(serverConfig, serverSettings, args, clientConfig, cliA
17527
18445
  quiet: args.quiet
17528
18446
  });
17529
18447
  if (load) {
17530
- const summary = await withCliAuthRecoveryRetry(inspectorClient, serverConfig, redirectUrlProvider, callbackUrlConfig, serverSettings, () => runToolLoad(inspectorClient, args, load, { openConnection: openLoadConnection }), {
18448
+ const summary = await withLoadInterrupt((signal) => withCliAuthRecoveryRetry(inspectorClient, serverConfig, redirectUrlProvider, callbackUrlConfig, serverSettings, () => runToolLoad(inspectorClient, args, load, {
18449
+ openConnection: openLoadConnection,
18450
+ signal
18451
+ }), {
17531
18452
  storedAuthOnly,
17532
18453
  autoOpenControl,
17533
18454
  quiet: args.quiet
17534
- });
18455
+ }), { quiet: args.quiet });
17535
18456
  await writeFormattedResult(summary, args.format === "json" ? "json" : "text");
18457
+ if (!args.quiet) for (const warning of loadSummaryWarnings(summary)) await awaitableError(`${warning}\n`);
18458
+ assertNotInterrupted(summary);
17536
18459
  checkLoadThresholds(summary, loadThresholds);
17537
18460
  return;
17538
18461
  }
@@ -17798,7 +18721,7 @@ async function parseArgs(argv) {
17798
18721
  ];
17799
18722
  if (!validTransports.includes(value)) throw new Error(`Invalid transport type: ${value}. Valid types are: ${validTransports.join(", ")}`);
17800
18723
  return value;
17801
- }).option("--server-url <url>", "Server URL for SSE/HTTP transport").option("--header <headers...>", "HTTP headers as \"HeaderName: Value\" pairs (for HTTP/SSE transports)", parseHeaderPair, {}).option("--metadata <pairs...>", "General metadata as key=value pairs (applied to all methods)", parseKeyValuePair, {}).option("--tool-metadata <pairs...>", "Tool-specific metadata as key=value pairs (for tools/call method only)", parseKeyValuePair, {}).option("--app-info", "Probe the tool's MCP App UI metadata (resourceUri, csp, permissions, domain) and emit it as one JSON line; exit 2 when the tool has no app. Use with --method tools/call --tool-name <name> (the tool itself is not invoked) or --method tools/list (one NDJSON line per tool).").option("--advertise-apps", "Advertise the MCP Apps UI extension (io.modelcontextprotocol/ui) at initialize. Off by default because the CLI cannot render an App; set it when a server only exposes its App tools to a client that claims App support, e.g. for an --app-info probe.").option("--strict", "Report tool-schema portability problems in full (path, issue, suggested fix) on stderr, and exit 6 if any is error-severity. Use with --method tools/list. Without it, a one-line count is printed instead.").option("--verify", "Run the SEP-2640 conformance and digest checks over the skills returned, emit one JSON report per skill on stdout, and exit 7 if any fails or 8 if any could not be fully checked within the read bounds. Use with --method skills/list or --method skills/get.").option("--require-digests", "With --verify: exit 9 when a skill advertises no digests (resources: \"dynamic\"), instead of reporting it as unverifiable and exiting 0.").option("--load", "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.").option("--load-concurrency <n>", "With --load: most calls in flight at once (positive integer; default 10).", positiveIntegerParser("--load-concurrency")).option("--load-connections <n>", "With --load: spread the calls over this many independent connections (sessions) to an HTTP or SSE server, opened before timing starts (positive integer, at most --load-concurrency; default 1).", positiveIntegerParser("--load-connections")).option("--load-duration <sec>", "With --load: stop starting calls after this many seconds; in-flight calls still finish (default 10 when --load-requests is not set).", positiveNumberParser("--load-duration")).option("--load-requests <n>", "With --load: stop after starting this many calls (positive integer).", positiveIntegerParser("--load-requests")).option("--load-rate <n>", "With --load: start at most this many calls per second, spread evenly (positive number; default unlimited).", positiveNumberParser("--load-rate")).option("--load-max-error-rate <fraction>", "With --load: exit 10 (load_error_rate_exceeded) when the share of failed calls is above this value, after printing the summary (0 to 1; 0 fails on any failed call).", fractionParser("--load-max-error-rate")).option("--load-max-p99 <ms>", "With --load: exit 10 (load_p99_exceeded) when the p99 latency is above this many milliseconds, or when no call completed, after printing the summary (positive number).", maxP99Parser).option("--connect-timeout <ms>", `Connection timeout in ms (default ${DEFAULT_CONNECT_TIMEOUT_MS} for ad-hoc --server-url / target invocations; 0 = no timeout).`, (v) => {
18724
+ }).option("--server-url <url>", "Server URL for SSE/HTTP transport").option("--header <headers...>", "HTTP headers as \"HeaderName: Value\" pairs (for HTTP/SSE transports)", parseHeaderPair, {}).option("--metadata <pairs...>", "General metadata as key=value pairs (applied to all methods)", parseKeyValuePair, {}).option("--tool-metadata <pairs...>", "Tool-specific metadata as key=value pairs (for tools/call method only)", parseKeyValuePair, {}).option("--app-info", "Probe the tool's MCP App UI metadata (resourceUri, csp, permissions, domain) and emit it as one JSON line; exit 2 when the tool has no app. Use with --method tools/call --tool-name <name> (the tool itself is not invoked) or --method tools/list (one NDJSON line per tool).").option("--advertise-apps", "Advertise the MCP Apps UI extension (io.modelcontextprotocol/ui) at initialize. Off by default because the CLI cannot render an App; set it when a server only exposes its App tools to a client that claims App support, e.g. for an --app-info probe.").option("--strict", "Report tool-schema portability problems in full (path, issue, suggested fix) on stderr, and exit 6 if any is error-severity. Use with --method tools/list. Without it, a one-line count is printed instead.").option("--verify", "Run the SEP-2640 conformance and digest checks over the skills returned, emit one JSON report per skill on stdout, and exit 7 if any fails or 8 if any could not be fully checked within the read bounds. Use with --method skills/list or --method skills/get.").option("--require-digests", "With --verify: exit 9 when a skill advertises no digests (resources: \"dynamic\"), instead of reporting it as unverifiable and exiting 0.").option("--load", "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.").option("--load-concurrency <n>", "With --load: most calls in flight at once (positive integer; default 10).", positiveIntegerParser("--load-concurrency")).option("--load-connections <n>", "With --load: spread the calls over this many independent connections (sessions) to an HTTP or SSE server, opened before timing starts (positive integer, at most --load-concurrency; default 1).", positiveIntegerParser("--load-connections")).option("--load-duration <sec>", "With --load: stop starting calls after this many seconds; in-flight calls still finish (default 10 when --load-requests is not set).", positiveNumberParser("--load-duration")).option("--load-requests <n>", "With --load: stop after starting this many calls (positive integer).", positiveIntegerParser("--load-requests")).option("--load-rate <n>", "With --load: start at most this many calls per second, spread evenly (positive number; default unlimited).", positiveNumberParser("--load-rate")).option("--load-max-error-rate <fraction>", "With --load: exit 10 (load_error_rate_exceeded) when the share of failed calls is above this value, after printing the summary (0 to 1; 0 fails on any failed call).", fractionParser("--load-max-error-rate")).option("--load-max-p99 <ms>", "With --load: exit 10 (load_p99_exceeded) when the p99 latency of the successful calls is above this many milliseconds, or when no call succeeded, after printing the summary (positive number).", maxP99Parser).option("--connect-timeout <ms>", `Connection timeout in ms (default ${DEFAULT_CONNECT_TIMEOUT_MS} for ad-hoc --server-url / target invocations; 0 = no timeout).`, (v) => {
17802
18725
  const n = Number(v);
17803
18726
  if (!Number.isFinite(n) || n < 0) throw new Error(`--connect-timeout must be a non-negative number.`);
17804
18727
  return n;
@@ -17849,12 +18772,12 @@ async function parseArgs(argv) {
17849
18772
  ["--load-max-error-rate", options.loadMaxErrorRate],
17850
18773
  ["--load-max-p99", options.loadMaxP99]
17851
18774
  ];
17852
- for (const [flag, value] of loadBounds) if (value !== void 0 && !options.load) throw new Error(`${flag} requires --load.`);
18775
+ for (const [flag, value] of loadBounds) if (value !== void 0 && !options.load) throw loadFlagConflict(`${flag} requires --load.`);
17853
18776
  if (options.load) {
17854
- if (options.method !== "tools/call") throw new Error("--load requires --method tools/call.");
17855
- if (options.appInfo) throw new Error("--load cannot be combined with --app-info.");
17856
- if (options.listStoredAuth || options.printHandoff) throw new Error("--load cannot be combined with --list-stored-auth or --print-handoff.");
17857
- if (options.output !== void 0 || options.outputFormat !== void 0) throw new Error("--load cannot be combined with --output or --output-format; redirect stdout to keep the summary.");
18777
+ if (options.method !== "tools/call") throw loadFlagConflict("--load requires --method tools/call.");
18778
+ if (options.appInfo) throw loadFlagConflict("--load cannot be combined with --app-info.");
18779
+ if (options.listStoredAuth || options.printHandoff) throw loadFlagConflict("--load cannot be combined with --list-stored-auth or --print-handoff.");
18780
+ if (options.output !== void 0 || options.outputFormat !== void 0) throw loadFlagConflict("--load cannot be combined with --output or --output-format; redirect stdout to keep the summary.");
17858
18781
  }
17859
18782
  if (options.advertiseApps && (options.listStoredAuth || options.printHandoff || options.method === "servers/list" || options.method === "servers/show" || isCatalogWriteMethod(options.method))) throw new Error("--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.");
17860
18783
  const oauthStatePath = getStateFilePath();
@@ -18002,14 +18925,24 @@ async function parseArgs(argv) {
18002
18925
  storedAuthOnly: options.storedAuthOnly === true,
18003
18926
  relogin: options.relogin === true,
18004
18927
  revoke: options.revoke !== false,
18005
- ...options.load && { load: resolveLoadOptions({
18928
+ ...options.load && { load: resolveCliLoadOptions(options) }
18929
+ };
18930
+ }
18931
+ function loadFlagConflict(message) {
18932
+ return new CliExitCodeError(EXIT_CODES.USAGE, message, { code: LOAD_FLAG_CONFLICT });
18933
+ }
18934
+ function resolveCliLoadOptions(options) {
18935
+ try {
18936
+ return resolveLoadOptions({
18006
18937
  concurrency: options.loadConcurrency,
18007
18938
  connections: options.loadConnections,
18008
18939
  durationSec: options.loadDuration,
18009
18940
  requests: options.loadRequests,
18010
18941
  rate: options.loadRate
18011
- }) }
18012
- };
18942
+ });
18943
+ } catch (err) {
18944
+ throw loadFlagConflict(err.message);
18945
+ }
18013
18946
  }
18014
18947
  function discardWarning() {}
18015
18948
  async function runCli(argv) {