@ai-ecoverse/mcpcat 0.15.5 → 0.15.7

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.
@@ -1854,6 +1854,16 @@ var init_version = __esm({
1854
1854
  });
1855
1855
 
1856
1856
  // node_modules/quickjs-wasi/dist/index.js
1857
+ var dist_exports = {};
1858
+ __export(dist_exports, {
1859
+ CompileFlags: () => CompileFlags,
1860
+ EvalFlags: () => EvalFlags,
1861
+ Intrinsics: () => Intrinsics,
1862
+ JSException: () => JSException,
1863
+ JSValueHandle: () => JSValueHandle,
1864
+ MAX_STACK_SIZE: () => MAX_STACK_SIZE,
1865
+ QuickJS: () => QuickJS
1866
+ });
1857
1867
  function stringKeyNeedsValuePath(key) {
1858
1868
  return key.includes("\0") || LONE_SURROGATE_RE.test(key);
1859
1869
  }
@@ -5329,8 +5339,8 @@ function parseCodemodeSource(input) {
5329
5339
  throw new CodemodeSourceError(`The ${CODEMODE_OPTIONS_PREFIX} line must be the first line of the input; found one on line ${misplaced + 1}`);
5330
5340
  }
5331
5341
  const newline = input.indexOf("\n");
5332
- const firstLine = lines[0].replace(/\r$/, "");
5333
- const trimmed = firstLine.trimStart();
5342
+ const firstLine2 = lines[0].replace(/\r$/, "");
5343
+ const trimmed = firstLine2.trimStart();
5334
5344
  if (!trimmed.startsWith(CODEMODE_OPTIONS_PREFIX))
5335
5345
  return { code: input, options: {} };
5336
5346
  const code2 = newline === -1 ? "" : input.slice(newline);
@@ -5365,8 +5375,8 @@ SOURCE: /[\s\S]+/
5365
5375
  });
5366
5376
 
5367
5377
  // node_modules/@earendil-works/pi-codemode/dist/index.js
5368
- var dist_exports = {};
5369
- __export(dist_exports, {
5378
+ var dist_exports2 = {};
5379
+ __export(dist_exports2, {
5370
5380
  CODEMODE_OPTIONS_PREFIX: () => CODEMODE_OPTIONS_PREFIX,
5371
5381
  CODEMODE_SOURCE_GRAMMAR: () => CODEMODE_SOURCE_GRAMMAR,
5372
5382
  CodemodeSandbox: () => CodemodeSandbox,
@@ -8369,8 +8379,9 @@ var McpTimeoutError2 = class extends Error {
8369
8379
  var McpAuthError = class extends Error {
8370
8380
  status;
8371
8381
  target;
8372
- constructor(status, target) {
8373
- super(`HTTP ${status}: authentication required. Run: mcpcat login ${target}`);
8382
+ constructor(status, target, reason) {
8383
+ const why = reason ? ` (${reason})` : "";
8384
+ super(`HTTP ${status}: authentication required${why}. Run: mcpcat login ${target}`);
8374
8385
  this.name = "McpAuthError";
8375
8386
  this.status = status;
8376
8387
  this.target = target;
@@ -8388,12 +8399,23 @@ var TransportRefusalError = class extends Error {
8388
8399
  };
8389
8400
  var AuthRetryableError = class extends Error {
8390
8401
  response;
8391
- constructor(response) {
8402
+ reason;
8403
+ constructor(response, reason) {
8392
8404
  super(`HTTP ${response.status}: auth retry`);
8393
8405
  this.name = "AuthRetryableError";
8394
8406
  this.response = response;
8407
+ this.reason = reason;
8395
8408
  }
8396
8409
  };
8410
+ var KERNEL_REFUSAL = "nodeTransport refuses ";
8411
+ function firstLine(text) {
8412
+ return text.trim().split("\n")[0].trim().slice(0, 200);
8413
+ }
8414
+ async function authReason(res) {
8415
+ const type = res.headers.get("content-type") ?? "";
8416
+ if (!type.toLowerCase().startsWith("text/plain")) return void 0;
8417
+ return firstLine(await res.text()) || void 0;
8418
+ }
8397
8419
  function selectSseFrame(body, targetId) {
8398
8420
  const text = new TextDecoder().decode(body);
8399
8421
  const blocks = text.replace(/\r\n/g, "\n").split(/\n\n/);
@@ -8510,7 +8532,8 @@ async function createStatelessConnection(options) {
8510
8532
  clientInfo = { name: "mcpcat", version: "0.0.0" },
8511
8533
  signal: externalSignal,
8512
8534
  verbose,
8513
- authProvider
8535
+ authProvider,
8536
+ transportRefusals
8514
8537
  } = options;
8515
8538
  let nextId2 = 1;
8516
8539
  let negotiatedVersion;
@@ -8528,18 +8551,14 @@ async function createStatelessConnection(options) {
8528
8551
  return h;
8529
8552
  }
8530
8553
  async function handleErrorStatus(res, method) {
8531
- if (res.status === 403) {
8532
- const wwwAuth = res.headers.get("www-authenticate") ?? "";
8533
- if (!wwwAuth) {
8534
- const body = await res.text();
8535
- if (body && !body.includes("insufficient_scope")) {
8536
- throw new TransportRefusalError(403, body.split("\n")[0].slice(0, 200));
8537
- }
8554
+ if (res.status === 401 || res.status === 403) {
8555
+ const reason = await authReason(res);
8556
+ if (transportRefusals && res.status === 403 && !res.headers.has("www-authenticate") && reason?.startsWith(KERNEL_REFUSAL)) {
8557
+ throw new TransportRefusalError(403, reason);
8538
8558
  }
8559
+ if (authProvider) throw new AuthRetryableError(res, reason);
8560
+ throw new McpAuthError(res.status, url, reason);
8539
8561
  }
8540
- if ((res.status === 401 || res.status === 403) && authProvider)
8541
- throw new AuthRetryableError(res);
8542
- if (res.status === 401 || res.status === 403) throw new McpAuthError(res.status, url);
8543
8562
  const text = await res.text();
8544
8563
  let rpc;
8545
8564
  try {
@@ -8594,7 +8613,7 @@ async function createStatelessConnection(options) {
8594
8613
  return frame.result;
8595
8614
  }
8596
8615
  async function handleAuthError(err, token) {
8597
- if (!authProvider?.onUnauthorized) throw new McpAuthError(err.response.status, url);
8616
+ if (!authProvider?.onUnauthorized) throw new McpAuthError(err.response.status, url, err.reason);
8598
8617
  try {
8599
8618
  await authProvider.onUnauthorized({
8600
8619
  response: err.response,
@@ -8604,7 +8623,7 @@ async function createStatelessConnection(options) {
8604
8623
  });
8605
8624
  } catch (authErr) {
8606
8625
  if (authErr instanceof AuthStepUpError || authErr instanceof JerkTimeoutError) throw authErr;
8607
- throw new McpAuthError(err.response.status, url);
8626
+ throw new McpAuthError(err.response.status, url, err.reason);
8608
8627
  }
8609
8628
  }
8610
8629
  async function request(method, params) {
@@ -9065,7 +9084,8 @@ async function connect(platform2, options) {
9065
9084
  timeoutMs: options.timeoutMs,
9066
9085
  signal: options.signal,
9067
9086
  verbose: options.verbose,
9068
- authProvider: options.authProvider
9087
+ authProvider: options.authProvider,
9088
+ transportRefusals: platform2.kind === "kernel"
9069
9089
  });
9070
9090
  } catch (err) {
9071
9091
  const isMethodNotFound = err instanceof StatelessRpcError && err.code === -32601 || err instanceof StatelessHttpError && err.status >= 400 && err.status < 500;
@@ -9357,7 +9377,7 @@ function toolDeclaration(tool) {
9357
9377
  };
9358
9378
  }
9359
9379
  async function runDescribeTs(platform2, targets, budget) {
9360
- const { renderDeclarations: renderDeclarations2 } = await Promise.resolve().then(() => (init_dist2(), dist_exports));
9380
+ const { renderDeclarations: renderDeclarations2 } = await Promise.resolve().then(() => (init_dist2(), dist_exports2));
9361
9381
  const allTools = [];
9362
9382
  for (const target of targets) {
9363
9383
  const tools = await target.conn.listTools();
@@ -9428,6 +9448,39 @@ async function runGrep(platform2, pattern, targets, options) {
9428
9448
  // src/codemode/run.ts
9429
9449
  init_dist2();
9430
9450
  init_source();
9451
+
9452
+ // src/codemode/deadline.ts
9453
+ var expiries = /* @__PURE__ */ new WeakMap();
9454
+ var hooked = /* @__PURE__ */ new WeakSet();
9455
+ function hookDeadlines(quickjs) {
9456
+ if (hooked.has(quickjs)) return;
9457
+ hooked.add(quickjs);
9458
+ const create = quickjs.create.bind(quickjs);
9459
+ quickjs.create = (options) => {
9460
+ const expired = options.wasm ? expiries.get(options.wasm) : void 0;
9461
+ const inner = options.interruptHandler;
9462
+ if (!expired || !inner) return create(options);
9463
+ return create({ ...options, interruptHandler: () => expired() || inner() });
9464
+ };
9465
+ }
9466
+ function bindDeadline(wasm, timeoutMs, now = Date.now) {
9467
+ let endsAt = Number.POSITIVE_INFINITY;
9468
+ let tripped = false;
9469
+ expiries.set(wasm, () => {
9470
+ if (!tripped && now() >= endsAt) tripped = true;
9471
+ return tripped;
9472
+ });
9473
+ return {
9474
+ start() {
9475
+ endsAt = now() + timeoutMs;
9476
+ },
9477
+ get tripped() {
9478
+ return tripped;
9479
+ }
9480
+ };
9481
+ }
9482
+
9483
+ // src/codemode/run.ts
9431
9484
  async function loadStore(platform2, stateDir2) {
9432
9485
  const storePath = `${stateDir2}/codemode-store.json`;
9433
9486
  try {
@@ -9613,9 +9666,23 @@ async function runCode(targets, source, options) {
9613
9666
  ...buildDiscoveryGlobals(allDeclaredTools, namespaces, searchDocs),
9614
9667
  ...options.allowShell ? [buildShellGlobal(platform2)] : []
9615
9668
  ];
9616
- const sandbox = await createSandbox(platform2, toolImpls, globals, timeoutMs, memoryLimitBytes);
9669
+ const { sandbox, deadline } = await createSandbox(
9670
+ platform2,
9671
+ toolImpls,
9672
+ globals,
9673
+ timeoutMs,
9674
+ memoryLimitBytes
9675
+ );
9617
9676
  try {
9618
- const result = await sandbox.execute(code2, { store });
9677
+ deadline?.start();
9678
+ const executed = await sandbox.execute(code2, { store });
9679
+ const result = deadline?.tripped && !executed.ok ? {
9680
+ ...executed,
9681
+ error: {
9682
+ kind: "timeout",
9683
+ message: `Execution timed out after ${timeoutMs} ms`
9684
+ }
9685
+ } : executed;
9619
9686
  if (result.ok) {
9620
9687
  const writes = result.storeWrites;
9621
9688
  if (Object.keys(writes.set).length > 0 || writes.delete.length > 0) {
@@ -9661,12 +9728,23 @@ async function createSandbox(platform2, tools, globals, timeoutMs, memoryLimitBy
9661
9728
  const isKernel2 = platform2.packageDir !== void 0;
9662
9729
  if (isKernel2) {
9663
9730
  const { InlineCodemodeSandbox: InlineCodemodeSandbox2 } = await Promise.resolve().then(() => (init_portable(), portable_exports));
9731
+ const { QuickJS: QuickJS2 } = await Promise.resolve().then(() => (init_dist(), dist_exports));
9732
+ hookDeadlines(QuickJS2);
9664
9733
  const wasm = await loadWasmKernel(platform2);
9665
- return new InlineCodemodeSandbox2({ tools, globals, timeoutMs, wasm, memoryLimitBytes });
9734
+ const deadline = bindDeadline(wasm, timeoutMs);
9735
+ const sandbox2 = new InlineCodemodeSandbox2({
9736
+ tools,
9737
+ globals,
9738
+ timeoutMs,
9739
+ wasm,
9740
+ memoryLimitBytes,
9741
+ interruptBudget: Number.POSITIVE_INFINITY
9742
+ });
9743
+ return { sandbox: sandbox2, deadline };
9666
9744
  }
9667
- const { CodemodeSandbox: CodemodeSandbox2, loadQuickJSWasm: loadQuickJSWasm2 } = await Promise.resolve().then(() => (init_dist2(), dist_exports));
9745
+ const { CodemodeSandbox: CodemodeSandbox2, loadQuickJSWasm: loadQuickJSWasm2 } = await Promise.resolve().then(() => (init_dist2(), dist_exports2));
9668
9746
  const wasmPath = await resolveWasmPath(platform2);
9669
- return new CodemodeSandbox2({
9747
+ const sandbox = new CodemodeSandbox2({
9670
9748
  tools,
9671
9749
  globals,
9672
9750
  timeoutMs,
@@ -9674,6 +9752,7 @@ async function createSandbox(platform2, tools, globals, timeoutMs, memoryLimitBy
9674
9752
  wasm: loadQuickJSWasm2(wasmPath),
9675
9753
  workerUrl: new URL("./codemode-worker.mjs", import.meta.url)
9676
9754
  });
9755
+ return { sandbox };
9677
9756
  }
9678
9757
  async function resolveWasmPath(platform2) {
9679
9758
  const candidates = [
@@ -12183,16 +12262,45 @@ function wrapUntrusted(content) {
12183
12262
  ${neutralised}
12184
12263
  ${closeTag}`;
12185
12264
  }
12186
- async function runProcess(platform2, argv, stdin, timeoutMs) {
12265
+ var WEBMCP_LIST_FLOOR = 1048576;
12266
+ function capCollector(limit) {
12267
+ const dec = new TextDecoder();
12268
+ const half = Math.floor(limit / 2);
12269
+ let head = "";
12270
+ let tail = "";
12271
+ let total = 0;
12272
+ const take = (piece) => {
12273
+ if (!piece) return;
12274
+ total += piece.length;
12275
+ if (head.length < limit) head += piece.slice(0, limit - head.length);
12276
+ tail = half > 0 ? (tail + piece).slice(-half) : "";
12277
+ };
12278
+ return {
12279
+ push(chunk) {
12280
+ take(dec.decode(chunk, { stream: true }));
12281
+ },
12282
+ finish() {
12283
+ take(dec.decode());
12284
+ if (total <= limit) return { text: head, cut: false };
12285
+ return {
12286
+ text: `${head.slice(0, half)}
12287
+ \u2026 ${total - limit} characters cut \u2026
12288
+ ${tail}`,
12289
+ cut: true
12290
+ };
12291
+ }
12292
+ };
12293
+ }
12294
+ async function runProcess(platform2, argv, stdin, timeoutMs, limit) {
12187
12295
  let proc;
12188
12296
  try {
12189
12297
  proc = await platform2.spawn({ argv });
12190
- } catch (err) {
12191
- const code2 = err.code;
12298
+ } catch (err2) {
12299
+ const code2 = err2.code;
12192
12300
  if (code2 === "ENOENT" || code2 === "EACCES") {
12193
- return { stdout: "", stderr: `command not found: ${argv[0]}`, exitCode: -1 };
12301
+ return { stdout: "", stderr: `command not found: ${argv[0]}`, exitCode: -1, cut: false };
12194
12302
  }
12195
- throw err;
12303
+ throw err2;
12196
12304
  }
12197
12305
  if (stdin !== void 0) {
12198
12306
  const writer = proc.stdin.getWriter();
@@ -12204,8 +12312,8 @@ async function runProcess(platform2, argv, stdin, timeoutMs) {
12204
12312
  } catch {
12205
12313
  }
12206
12314
  }
12207
- const stdoutChunks = [];
12208
- const stderrChunks = [];
12315
+ const out = capCollector(limit);
12316
+ const err = capCollector(limit);
12209
12317
  const readStream3 = async (stream, into) => {
12210
12318
  const r = stream.getReader();
12211
12319
  try {
@@ -12218,53 +12326,136 @@ async function runProcess(platform2, argv, stdin, timeoutMs) {
12218
12326
  r.releaseLock();
12219
12327
  }
12220
12328
  };
12221
- const timer = new Promise(
12222
- (resolve) => setTimeout(() => resolve("timeout"), timeoutMs ?? 3e4)
12223
- );
12329
+ let timeoutId;
12330
+ const timer = new Promise((resolve) => {
12331
+ timeoutId = setTimeout(() => resolve("timeout"), timeoutMs);
12332
+ });
12224
12333
  const work = Promise.all([
12225
- readStream3(proc.stdout, stdoutChunks),
12226
- readStream3(proc.stderr, stderrChunks),
12334
+ readStream3(proc.stdout, out),
12335
+ readStream3(proc.stderr, err),
12227
12336
  proc.wait()
12228
12337
  ]);
12229
12338
  const result = await Promise.race([work, timer]);
12339
+ clearTimeout(timeoutId);
12230
12340
  if (result === "timeout") {
12231
12341
  await proc.kill("SIGTERM");
12232
- return { stdout: "", stderr: "timed out", exitCode: 124 };
12342
+ return { stdout: "", stderr: "timed out", exitCode: 124, cut: false };
12233
12343
  }
12234
12344
  const [, , exitResult] = result;
12235
- const dec = new TextDecoder();
12236
- const concat2 = (chunks) => {
12237
- const total = chunks.reduce((s, c) => s + c.length, 0);
12238
- const out = new Uint8Array(total);
12239
- let offset = 0;
12240
- for (const c of chunks) {
12241
- out.set(c, offset);
12242
- offset += c.length;
12243
- }
12244
- return out;
12345
+ const stdout = out.finish();
12346
+ const stderr = err.finish();
12347
+ return {
12348
+ stdout: stdout.text,
12349
+ stderr: stderr.text,
12350
+ exitCode: exitResult.status,
12351
+ cut: stdout.cut
12245
12352
  };
12353
+ }
12354
+ async function resolveWebMcpTab(platform2, options) {
12355
+ const argv = [options.playwrightCli, "tab-list"];
12356
+ if (options.cdp) argv.push("--cdp", options.cdp);
12357
+ const result = await runProcess(
12358
+ platform2,
12359
+ argv,
12360
+ void 0,
12361
+ options.callTimeoutMs,
12362
+ WEBMCP_LIST_FLOOR
12363
+ );
12364
+ if (result.exitCode === -1) return { error: result.stderr, notFound: true };
12365
+ if (result.exitCode !== 0) {
12366
+ return { error: result.stderr.trim() || `tab-list exited ${result.exitCode}` };
12367
+ }
12368
+ const rows = [];
12369
+ for (const line of result.stdout.split("\n")) {
12370
+ const match = /^(\d+): \[([^\]\s]+)\] /.exec(line);
12371
+ if (!match) continue;
12372
+ if (Number(match[1]) !== rows.length + 1) {
12373
+ return { error: "could not read playwright-cli tab-list" };
12374
+ }
12375
+ rows.push({ id: match[2], active: line.endsWith('" (active)') });
12376
+ }
12377
+ const active = rows.filter((r) => r.active);
12378
+ if (new Set(rows.map((r) => r.id)).size !== rows.length || active.length > 1) {
12379
+ return { error: "could not read playwright-cli tab-list" };
12380
+ }
12381
+ if (active.length === 1) return { tab: active[0].id };
12382
+ if (rows.length === 1) return { tab: rows[0].id };
12246
12383
  return {
12247
- stdout: dec.decode(concat2(stdoutChunks)),
12248
- stderr: dec.decode(concat2(stderrChunks)),
12249
- exitCode: exitResult.status
12384
+ error: rows.length === 0 ? "no browser tab is open; open one or pass --tab <targetId>" : "no current playwright-cli tab; pass --tab <targetId> (see playwright-cli tab-list)"
12250
12385
  };
12251
12386
  }
12387
+ function mapDescriptionAnnotations(ann) {
12388
+ const mapped = { openWorldHint: true };
12389
+ if (!ann) return mapped;
12390
+ if (ann.readOnlyHint !== void 0) mapped.readOnlyHint = ann.readOnlyHint;
12391
+ if (ann.consequentialHint) mapped.destructiveHint = true;
12392
+ return mapped;
12393
+ }
12394
+ function originPrefix(tools) {
12395
+ const origins = new Set(tools.map((t) => t.origin));
12396
+ return origins.size > 1;
12397
+ }
12398
+ function hostFromOrigin(origin) {
12399
+ try {
12400
+ return new URL(origin).host;
12401
+ } catch {
12402
+ return origin;
12403
+ }
12404
+ }
12405
+ function buildDescription(tool) {
12406
+ const parts = [];
12407
+ if (tool.description) parts.push(tool.description);
12408
+ parts.push(`[origin: ${tool.origin}]`);
12409
+ return parts.join("\n");
12410
+ }
12411
+ function buildToolName(tool, prefix) {
12412
+ const base = tool.name;
12413
+ if (!prefix) return base;
12414
+ const host = hostFromOrigin(tool.origin).replace(/[^a-zA-Z0-9_]/g, "_");
12415
+ return `${host}__${base}`;
12416
+ }
12417
+ function exposedTools(tools) {
12418
+ const needPrefix = originPrefix(tools);
12419
+ const byBase = /* @__PURE__ */ new Map();
12420
+ const byName = /* @__PURE__ */ new Map();
12421
+ for (const tool of tools) {
12422
+ const name = buildToolName(tool, needPrefix);
12423
+ byBase.set(tool.name, [...byBase.get(tool.name) ?? [], tool]);
12424
+ byName.set(name, [...byName.get(name) ?? [], tool]);
12425
+ }
12426
+ return tools.map((tool) => {
12427
+ const name = buildToolName(tool, needPrefix);
12428
+ const sameBase = byBase.get(tool.name);
12429
+ const sameName = byName.get(name);
12430
+ return { name, tool, peers: sameBase.length > 1 ? sameBase : sameName };
12431
+ });
12432
+ }
12433
+ function ambiguityError(name, peers) {
12434
+ const origins = [...new Set(peers.map((t) => String(t.origin)))].join(", ");
12435
+ return new Error(
12436
+ `tool "${name}" is ambiguous: ${peers.length} tools on the page match it (${origins}), and playwright-cli runs the first tool by name and cannot select one by origin, so mcpcat refuses the call`
12437
+ );
12438
+ }
12252
12439
  function createWebMcpHandler(platform2, options) {
12253
12440
  let cache;
12254
12441
  const baseArgs = buildBaseArgs(options);
12255
- async function listRaw() {
12442
+ const listLimit = Math.max(options.maxOutput, WEBMCP_LIST_FLOOR);
12443
+ async function listRaw(fresh = false) {
12256
12444
  const now = Date.now();
12257
- if (cache && now < cache.expiresAt) {
12445
+ if (!fresh && cache && now < cache.expiresAt) {
12258
12446
  return { tools: cache.tools };
12259
12447
  }
12260
12448
  const argv = [options.playwrightCli, "webmcp-list", "--json", ...baseArgs];
12261
- const result = await runProcess(platform2, argv, void 0, options.callTimeoutMs);
12449
+ const result = await runProcess(platform2, argv, void 0, options.callTimeoutMs, listLimit);
12262
12450
  if (result.exitCode === -1) {
12263
12451
  return { tools: [], error: result.stderr, notFound: true };
12264
12452
  }
12265
12453
  if (result.exitCode !== 0) {
12266
12454
  return { tools: [], error: result.stderr || `webmcp-list exited ${result.exitCode}` };
12267
12455
  }
12456
+ if (result.cut) {
12457
+ return { tools: [], error: `webmcp-list output exceeds ${listLimit} characters` };
12458
+ }
12268
12459
  let parsed;
12269
12460
  try {
12270
12461
  parsed = JSON.parse(result.stdout);
@@ -12278,49 +12469,19 @@ function createWebMcpHandler(platform2, options) {
12278
12469
  cache = { tools, expiresAt: now + options.ttlMs };
12279
12470
  return { tools };
12280
12471
  }
12281
- function mapDescriptionAnnotations(ann) {
12282
- const mapped = { openWorldHint: true };
12283
- if (!ann) return mapped;
12284
- if (ann.readOnlyHint !== void 0) mapped.readOnlyHint = ann.readOnlyHint;
12285
- if (ann.consequentialHint) mapped.destructiveHint = true;
12286
- return mapped;
12287
- }
12288
- function originPrefix(tools) {
12289
- const origins = new Set(tools.map((t) => t.origin));
12290
- return origins.size > 1;
12291
- }
12292
- function hostFromOrigin(origin) {
12293
- try {
12294
- return new URL(origin).host;
12295
- } catch {
12296
- return origin;
12297
- }
12298
- }
12299
- function buildDescription(tool) {
12300
- const parts = [];
12301
- if (tool.description) parts.push(tool.description);
12302
- parts.push(`[origin: ${tool.origin}]`);
12303
- return parts.join("\n");
12304
- }
12305
- function buildToolName(tool, prefix) {
12306
- const base = tool.name;
12307
- if (!prefix) return base;
12308
- const host = hostFromOrigin(tool.origin).replace(/[^a-zA-Z0-9_]/g, "_");
12309
- return `${host}__${base}`;
12310
- }
12311
12472
  async function getRpcTools() {
12312
12473
  const { tools, error, notFound } = await listRaw();
12313
12474
  if (notFound) return null;
12314
12475
  if (error) return [];
12315
- const needPrefix = originPrefix(tools);
12316
12476
  const rpcTools = [];
12317
- for (const tool of tools) {
12477
+ for (const { name, tool, peers } of exposedTools(tools)) {
12478
+ if (peers.length > 1) continue;
12318
12479
  if (tool.annotations?.consequentialHint && !options.allowConsequential) {
12319
12480
  continue;
12320
12481
  }
12321
12482
  const mapped = mapDescriptionAnnotations(tool.annotations);
12322
12483
  rpcTools.push({
12323
- name: buildToolName(tool, needPrefix),
12484
+ name,
12324
12485
  description: buildDescription(tool),
12325
12486
  inputSchema: tool.inputSchema,
12326
12487
  annotations: mapped
@@ -12329,21 +12490,30 @@ function createWebMcpHandler(platform2, options) {
12329
12490
  return rpcTools;
12330
12491
  }
12331
12492
  async function callTool(name, args2) {
12332
- const { tools, error } = await listRaw();
12493
+ const { tools, error } = await listRaw(true);
12333
12494
  if (error) {
12334
12495
  return { stdout: "", stderr: error, exitCode: 2 };
12335
12496
  }
12336
- const needPrefix = originPrefix(tools);
12337
- const tool = tools.find((t) => buildToolName(t, needPrefix) === name);
12338
- if (!tool) {
12497
+ const match = exposedTools(tools).find((t) => t.name === name);
12498
+ if (!match) {
12339
12499
  return { stdout: "", stderr: `unknown tool: ${name}`, exitCode: 2 };
12340
12500
  }
12501
+ if (match.peers.length > 1) {
12502
+ throw ambiguityError(name, match.peers);
12503
+ }
12504
+ const { tool } = match;
12341
12505
  const argv = [options.playwrightCli, "webmcp-call", tool.name, "-", ...baseArgs];
12342
12506
  if (options.allowConsequential) {
12343
12507
  argv.push("--allow-consequential");
12344
12508
  }
12345
12509
  const stdinData = JSON.stringify(args2 ?? {});
12346
- const result = await runProcess(platform2, argv, stdinData, options.callTimeoutMs);
12510
+ const result = await runProcess(
12511
+ platform2,
12512
+ argv,
12513
+ stdinData,
12514
+ options.callTimeoutMs,
12515
+ options.maxOutput
12516
+ );
12347
12517
  if (result.exitCode === -1) {
12348
12518
  throw new Error(result.stderr || "playwright-cli not found");
12349
12519
  }
@@ -12869,6 +13039,7 @@ async function discoverWithSignal(platform2, commands, childEnv, cwd) {
12869
13039
  };
12870
13040
  platform2.onSignal?.("SIGTERM", onShutdown);
12871
13041
  platform2.onSignal?.("SIGINT", onShutdown);
13042
+ platform2.onSignal?.("SIGHUP", onShutdown);
12872
13043
  for (const cmd of commands) {
12873
13044
  try {
12874
13045
  const cli = await discoverCli(platform2, cmd.path, cmd.name, childEnv, cwd, earlyAbort.signal);
@@ -13094,9 +13265,30 @@ async function runWebMcpServe(platform2, options, version, resolvedBearer, gener
13094
13265
  return 3;
13095
13266
  }
13096
13267
  const cdp = options.webmcpCdp ?? platform2.env.SLICC_CDP_URL;
13268
+ let tab = options.webmcpTab;
13269
+ if (!tab) {
13270
+ const resolved = await resolveWebMcpTab(platform2, {
13271
+ playwrightCli: cliPath,
13272
+ cdp,
13273
+ callTimeoutMs: options.callTimeout
13274
+ });
13275
+ if (resolved.notFound) {
13276
+ await platform2.write(
13277
+ 2,
13278
+ "mcpcat serve --webmcp: playwright-cli not found. Install it with: curl -fsSL https://get.sliccy.ai/playwright-cli | sh\n"
13279
+ );
13280
+ return 3;
13281
+ }
13282
+ if (!resolved.tab) {
13283
+ await platform2.write(2, `mcpcat serve --webmcp: ${resolved.error}
13284
+ `);
13285
+ return 3;
13286
+ }
13287
+ tab = resolved.tab;
13288
+ }
13097
13289
  const handler = createWebMcpHandler(platform2, {
13098
13290
  playwrightCli: cliPath,
13099
- tab: options.webmcpTab,
13291
+ tab,
13100
13292
  cdp,
13101
13293
  allowConsequential: options.webmcpAllowConsequential,
13102
13294
  ttlMs: options.webmcpTtl,
@@ -13178,6 +13370,21 @@ async function runWebMcpHttp(platform2, options, getTools, callTool, serverName,
13178
13370
  const address = serveAddress(platform2, options.host, actualPort);
13179
13371
  const listenUrl = address.url;
13180
13372
  let tokenFilePath;
13373
+ let shutdownRequested = false;
13374
+ const shutdown = () => {
13375
+ if (shutdownRequested) return;
13376
+ shutdownRequested = true;
13377
+ listener.close();
13378
+ if (tokenFilePath) {
13379
+ platform2.fs.unlink?.(tokenFilePath).catch(() => {
13380
+ });
13381
+ }
13382
+ };
13383
+ if (platform2.onSignal) {
13384
+ platform2.onSignal("SIGINT", shutdown);
13385
+ platform2.onSignal("SIGTERM", shutdown);
13386
+ platform2.onSignal("SIGHUP", shutdown);
13387
+ }
13181
13388
  if (generatedBearer && resolvedBearer) {
13182
13389
  tokenFilePath = await publishBearer(
13183
13390
  platform2,
@@ -13186,31 +13393,22 @@ async function runWebMcpHttp(platform2, options, getTools, callTool, serverName,
13186
13393
  address,
13187
13394
  options.json
13188
13395
  );
13396
+ if (shutdownRequested && tokenFilePath) {
13397
+ await platform2.fs.unlink?.(tokenFilePath).catch(() => {
13398
+ });
13399
+ }
13189
13400
  } else {
13190
13401
  if (options.json) {
13191
13402
  await platform2.write(1, `${JSON.stringify(address)}
13192
13403
  `);
13193
13404
  }
13194
13405
  }
13195
- await platform2.write(2, `mcpcat serve --webmcp: listening on ${listenUrl}
13406
+ if (!shutdownRequested) {
13407
+ await platform2.write(2, `mcpcat serve --webmcp: listening on ${listenUrl}
13196
13408
  `);
13197
- if (options.onListening) {
13198
- await options.onListening(actualPort);
13199
- }
13200
- let shutdownRequested = false;
13201
- const shutdown = () => {
13202
- if (shutdownRequested) return;
13203
- shutdownRequested = true;
13204
- listener.close();
13205
- if (tokenFilePath) {
13206
- platform2.fs.unlink?.(tokenFilePath).catch(() => {
13207
- });
13409
+ if (options.onListening) {
13410
+ await options.onListening(actualPort);
13208
13411
  }
13209
- };
13210
- if (platform2.onSignal) {
13211
- platform2.onSignal("SIGINT", shutdown);
13212
- platform2.onSignal("SIGTERM", shutdown);
13213
- platform2.onSignal("SIGHUP", shutdown);
13214
13412
  }
13215
13413
  const handler = async (request) => {
13216
13414
  if (request.target !== "/mcp") {
@@ -13374,6 +13572,23 @@ async function runProxyHttp(platform2, options, proxy, resolvedBearer, generated
13374
13572
  const address = serveAddress(platform2, options.host, actualPort);
13375
13573
  const listenUrl = address.url;
13376
13574
  let tokenFilePath;
13575
+ let shutdownRequested = false;
13576
+ let proxyDone;
13577
+ const shutdown = () => {
13578
+ if (shutdownRequested) return;
13579
+ shutdownRequested = true;
13580
+ listener.close();
13581
+ proxyDone = proxy.shutdown();
13582
+ if (tokenFilePath) {
13583
+ platform2.fs.unlink?.(tokenFilePath).catch(() => {
13584
+ });
13585
+ }
13586
+ };
13587
+ if (platform2.onSignal) {
13588
+ platform2.onSignal("SIGINT", shutdown);
13589
+ platform2.onSignal("SIGTERM", shutdown);
13590
+ platform2.onSignal("SIGHUP", shutdown);
13591
+ }
13377
13592
  if (generatedBearer && resolvedBearer) {
13378
13593
  tokenFilePath = await publishBearer(
13379
13594
  platform2,
@@ -13382,32 +13597,22 @@ async function runProxyHttp(platform2, options, proxy, resolvedBearer, generated
13382
13597
  address,
13383
13598
  options.json
13384
13599
  );
13600
+ if (shutdownRequested && tokenFilePath) {
13601
+ await platform2.fs.unlink?.(tokenFilePath).catch(() => {
13602
+ });
13603
+ }
13385
13604
  } else {
13386
13605
  if (options.json) {
13387
13606
  await platform2.write(1, `${JSON.stringify(address)}
13388
13607
  `);
13389
13608
  }
13390
13609
  }
13391
- await platform2.write(2, `mcpcat serve --proxy: listening on ${listenUrl}
13610
+ if (!shutdownRequested) {
13611
+ await platform2.write(2, `mcpcat serve --proxy: listening on ${listenUrl}
13392
13612
  `);
13393
- if (options.onListening) {
13394
- await options.onListening(actualPort);
13395
- }
13396
- let shutdownRequested = false;
13397
- const shutdown = () => {
13398
- if (shutdownRequested) return;
13399
- shutdownRequested = true;
13400
- listener.close();
13401
- void proxy.shutdown();
13402
- if (tokenFilePath) {
13403
- platform2.fs.unlink?.(tokenFilePath).catch(() => {
13404
- });
13613
+ if (options.onListening) {
13614
+ await options.onListening(actualPort);
13405
13615
  }
13406
- };
13407
- if (platform2.onSignal) {
13408
- platform2.onSignal("SIGINT", shutdown);
13409
- platform2.onSignal("SIGTERM", shutdown);
13410
- platform2.onSignal("SIGHUP", shutdown);
13411
13616
  }
13412
13617
  const handler = (request) => handleProxyMcpRequest(
13413
13618
  request,
@@ -13432,6 +13637,7 @@ async function runProxyHttp(platform2, options, proxy, resolvedBearer, generated
13432
13637
  } catch {
13433
13638
  if (!shutdownRequested) throw new Error("listener closed unexpectedly");
13434
13639
  }
13640
+ await proxyDone;
13435
13641
  return 0;
13436
13642
  }
13437
13643
  async function handleProxyMcpRequest(request, proxy, serverOrigins, allowedOrigins, allowedHosts, resolvedBearer) {
@@ -13551,22 +13757,6 @@ async function runHttpServer(platform2, options, tools, call, serverName, versio
13551
13757
  const address = serveAddress(platform2, options.host, actualPort);
13552
13758
  const listenUrl = address.url;
13553
13759
  let tokenFilePath;
13554
- if (generatedBearer && resolvedBearer) {
13555
- tokenFilePath = await publishBearer(
13556
- platform2,
13557
- resolvedBearer,
13558
- actualPort,
13559
- address,
13560
- options.json
13561
- );
13562
- } else {
13563
- if (options.json) {
13564
- await platform2.write(1, `${JSON.stringify(address)}
13565
- `);
13566
- }
13567
- }
13568
- await platform2.write(2, `mcpcat serve: listening on ${listenUrl}
13569
- `);
13570
13760
  let shutdownRequested = false;
13571
13761
  let inflightDone;
13572
13762
  const shutdown = () => {
@@ -13584,6 +13774,28 @@ async function runHttpServer(platform2, options, tools, call, serverName, versio
13584
13774
  platform2.onSignal("SIGTERM", shutdown);
13585
13775
  platform2.onSignal("SIGHUP", shutdown);
13586
13776
  }
13777
+ if (generatedBearer && resolvedBearer) {
13778
+ tokenFilePath = await publishBearer(
13779
+ platform2,
13780
+ resolvedBearer,
13781
+ actualPort,
13782
+ address,
13783
+ options.json
13784
+ );
13785
+ if (shutdownRequested && tokenFilePath) {
13786
+ await platform2.fs.unlink?.(tokenFilePath).catch(() => {
13787
+ });
13788
+ }
13789
+ } else {
13790
+ if (options.json) {
13791
+ await platform2.write(1, `${JSON.stringify(address)}
13792
+ `);
13793
+ }
13794
+ }
13795
+ if (!shutdownRequested) {
13796
+ await platform2.write(2, `mcpcat serve: listening on ${listenUrl}
13797
+ `);
13798
+ }
13587
13799
  const handler = (request) => handleMcpRequest(
13588
13800
  request,
13589
13801
  tools,
@@ -16734,5 +16946,5 @@ try {
16734
16946
  });
16735
16947
  } catch {
16736
16948
  }
16737
- var code = await run(platform, "0.15.5", args);
16949
+ var code = await run(platform, "0.15.7", args);
16738
16950
  process.exit(code);