@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.
@@ -1475,6 +1475,16 @@ var init_version = __esm({
1475
1475
  });
1476
1476
 
1477
1477
  // node_modules/quickjs-wasi/dist/index.js
1478
+ var dist_exports = {};
1479
+ __export(dist_exports, {
1480
+ CompileFlags: () => CompileFlags,
1481
+ EvalFlags: () => EvalFlags,
1482
+ Intrinsics: () => Intrinsics,
1483
+ JSException: () => JSException,
1484
+ JSValueHandle: () => JSValueHandle,
1485
+ MAX_STACK_SIZE: () => MAX_STACK_SIZE,
1486
+ QuickJS: () => QuickJS
1487
+ });
1478
1488
  function stringKeyNeedsValuePath(key) {
1479
1489
  return key.includes("\0") || LONE_SURROGATE_RE.test(key);
1480
1490
  }
@@ -4950,8 +4960,8 @@ function parseCodemodeSource(input) {
4950
4960
  throw new CodemodeSourceError(`The ${CODEMODE_OPTIONS_PREFIX} line must be the first line of the input; found one on line ${misplaced + 1}`);
4951
4961
  }
4952
4962
  const newline = input.indexOf("\n");
4953
- const firstLine = lines[0].replace(/\r$/, "");
4954
- const trimmed = firstLine.trimStart();
4963
+ const firstLine2 = lines[0].replace(/\r$/, "");
4964
+ const trimmed = firstLine2.trimStart();
4955
4965
  if (!trimmed.startsWith(CODEMODE_OPTIONS_PREFIX))
4956
4966
  return { code: input, options: {} };
4957
4967
  const code = newline === -1 ? "" : input.slice(newline);
@@ -4986,8 +4996,8 @@ SOURCE: /[\s\S]+/
4986
4996
  });
4987
4997
 
4988
4998
  // node_modules/@earendil-works/pi-codemode/dist/index.js
4989
- var dist_exports = {};
4990
- __export(dist_exports, {
4999
+ var dist_exports2 = {};
5000
+ __export(dist_exports2, {
4991
5001
  CODEMODE_OPTIONS_PREFIX: () => CODEMODE_OPTIONS_PREFIX,
4992
5002
  CODEMODE_SOURCE_GRAMMAR: () => CODEMODE_SOURCE_GRAMMAR,
4993
5003
  CodemodeSandbox: () => CodemodeSandbox,
@@ -7990,8 +8000,9 @@ var McpTimeoutError2 = class extends Error {
7990
8000
  var McpAuthError = class extends Error {
7991
8001
  status;
7992
8002
  target;
7993
- constructor(status, target) {
7994
- super(`HTTP ${status}: authentication required. Run: mcpcat login ${target}`);
8003
+ constructor(status, target, reason) {
8004
+ const why = reason ? ` (${reason})` : "";
8005
+ super(`HTTP ${status}: authentication required${why}. Run: mcpcat login ${target}`);
7995
8006
  this.name = "McpAuthError";
7996
8007
  this.status = status;
7997
8008
  this.target = target;
@@ -8009,12 +8020,23 @@ var TransportRefusalError = class extends Error {
8009
8020
  };
8010
8021
  var AuthRetryableError = class extends Error {
8011
8022
  response;
8012
- constructor(response) {
8023
+ reason;
8024
+ constructor(response, reason) {
8013
8025
  super(`HTTP ${response.status}: auth retry`);
8014
8026
  this.name = "AuthRetryableError";
8015
8027
  this.response = response;
8028
+ this.reason = reason;
8016
8029
  }
8017
8030
  };
8031
+ var KERNEL_REFUSAL = "nodeTransport refuses ";
8032
+ function firstLine(text) {
8033
+ return text.trim().split("\n")[0].trim().slice(0, 200);
8034
+ }
8035
+ async function authReason(res) {
8036
+ const type = res.headers.get("content-type") ?? "";
8037
+ if (!type.toLowerCase().startsWith("text/plain")) return void 0;
8038
+ return firstLine(await res.text()) || void 0;
8039
+ }
8018
8040
  function selectSseFrame(body, targetId) {
8019
8041
  const text = new TextDecoder().decode(body);
8020
8042
  const blocks = text.replace(/\r\n/g, "\n").split(/\n\n/);
@@ -8131,7 +8153,8 @@ async function createStatelessConnection(options) {
8131
8153
  clientInfo = { name: "mcpcat", version: "0.0.0" },
8132
8154
  signal: externalSignal,
8133
8155
  verbose,
8134
- authProvider
8156
+ authProvider,
8157
+ transportRefusals
8135
8158
  } = options;
8136
8159
  let nextId2 = 1;
8137
8160
  let negotiatedVersion;
@@ -8149,18 +8172,14 @@ async function createStatelessConnection(options) {
8149
8172
  return h;
8150
8173
  }
8151
8174
  async function handleErrorStatus(res, method) {
8152
- if (res.status === 403) {
8153
- const wwwAuth = res.headers.get("www-authenticate") ?? "";
8154
- if (!wwwAuth) {
8155
- const body = await res.text();
8156
- if (body && !body.includes("insufficient_scope")) {
8157
- throw new TransportRefusalError(403, body.split("\n")[0].slice(0, 200));
8158
- }
8175
+ if (res.status === 401 || res.status === 403) {
8176
+ const reason = await authReason(res);
8177
+ if (transportRefusals && res.status === 403 && !res.headers.has("www-authenticate") && reason?.startsWith(KERNEL_REFUSAL)) {
8178
+ throw new TransportRefusalError(403, reason);
8159
8179
  }
8180
+ if (authProvider) throw new AuthRetryableError(res, reason);
8181
+ throw new McpAuthError(res.status, url, reason);
8160
8182
  }
8161
- if ((res.status === 401 || res.status === 403) && authProvider)
8162
- throw new AuthRetryableError(res);
8163
- if (res.status === 401 || res.status === 403) throw new McpAuthError(res.status, url);
8164
8183
  const text = await res.text();
8165
8184
  let rpc;
8166
8185
  try {
@@ -8215,7 +8234,7 @@ async function createStatelessConnection(options) {
8215
8234
  return frame.result;
8216
8235
  }
8217
8236
  async function handleAuthError(err, token) {
8218
- if (!authProvider?.onUnauthorized) throw new McpAuthError(err.response.status, url);
8237
+ if (!authProvider?.onUnauthorized) throw new McpAuthError(err.response.status, url, err.reason);
8219
8238
  try {
8220
8239
  await authProvider.onUnauthorized({
8221
8240
  response: err.response,
@@ -8225,7 +8244,7 @@ async function createStatelessConnection(options) {
8225
8244
  });
8226
8245
  } catch (authErr) {
8227
8246
  if (authErr instanceof AuthStepUpError || authErr instanceof JerkTimeoutError) throw authErr;
8228
- throw new McpAuthError(err.response.status, url);
8247
+ throw new McpAuthError(err.response.status, url, err.reason);
8229
8248
  }
8230
8249
  }
8231
8250
  async function request(method, params) {
@@ -8686,7 +8705,8 @@ async function connect(platform2, options) {
8686
8705
  timeoutMs: options.timeoutMs,
8687
8706
  signal: options.signal,
8688
8707
  verbose: options.verbose,
8689
- authProvider: options.authProvider
8708
+ authProvider: options.authProvider,
8709
+ transportRefusals: platform2.kind === "kernel"
8690
8710
  });
8691
8711
  } catch (err) {
8692
8712
  const isMethodNotFound = err instanceof StatelessRpcError && err.code === -32601 || err instanceof StatelessHttpError && err.status >= 400 && err.status < 500;
@@ -8978,7 +8998,7 @@ function toolDeclaration(tool) {
8978
8998
  };
8979
8999
  }
8980
9000
  async function runDescribeTs(platform2, targets, budget) {
8981
- const { renderDeclarations: renderDeclarations2 } = await Promise.resolve().then(() => (init_dist2(), dist_exports));
9001
+ const { renderDeclarations: renderDeclarations2 } = await Promise.resolve().then(() => (init_dist2(), dist_exports2));
8982
9002
  const allTools = [];
8983
9003
  for (const target of targets) {
8984
9004
  const tools = await target.conn.listTools();
@@ -9049,6 +9069,39 @@ async function runGrep(platform2, pattern, targets, options) {
9049
9069
  // src/codemode/run.ts
9050
9070
  init_dist2();
9051
9071
  init_source();
9072
+
9073
+ // src/codemode/deadline.ts
9074
+ var expiries = /* @__PURE__ */ new WeakMap();
9075
+ var hooked = /* @__PURE__ */ new WeakSet();
9076
+ function hookDeadlines(quickjs) {
9077
+ if (hooked.has(quickjs)) return;
9078
+ hooked.add(quickjs);
9079
+ const create = quickjs.create.bind(quickjs);
9080
+ quickjs.create = (options) => {
9081
+ const expired = options.wasm ? expiries.get(options.wasm) : void 0;
9082
+ const inner = options.interruptHandler;
9083
+ if (!expired || !inner) return create(options);
9084
+ return create({ ...options, interruptHandler: () => expired() || inner() });
9085
+ };
9086
+ }
9087
+ function bindDeadline(wasm, timeoutMs, now = Date.now) {
9088
+ let endsAt = Number.POSITIVE_INFINITY;
9089
+ let tripped = false;
9090
+ expiries.set(wasm, () => {
9091
+ if (!tripped && now() >= endsAt) tripped = true;
9092
+ return tripped;
9093
+ });
9094
+ return {
9095
+ start() {
9096
+ endsAt = now() + timeoutMs;
9097
+ },
9098
+ get tripped() {
9099
+ return tripped;
9100
+ }
9101
+ };
9102
+ }
9103
+
9104
+ // src/codemode/run.ts
9052
9105
  async function loadStore(platform2, stateDir2) {
9053
9106
  const storePath = `${stateDir2}/codemode-store.json`;
9054
9107
  try {
@@ -9234,9 +9287,23 @@ async function runCode(targets, source, options) {
9234
9287
  ...buildDiscoveryGlobals(allDeclaredTools, namespaces, searchDocs),
9235
9288
  ...options.allowShell ? [buildShellGlobal(platform2)] : []
9236
9289
  ];
9237
- const sandbox = await createSandbox(platform2, toolImpls, globals, timeoutMs, memoryLimitBytes);
9290
+ const { sandbox, deadline } = await createSandbox(
9291
+ platform2,
9292
+ toolImpls,
9293
+ globals,
9294
+ timeoutMs,
9295
+ memoryLimitBytes
9296
+ );
9238
9297
  try {
9239
- const result = await sandbox.execute(code, { store });
9298
+ deadline?.start();
9299
+ const executed = await sandbox.execute(code, { store });
9300
+ const result = deadline?.tripped && !executed.ok ? {
9301
+ ...executed,
9302
+ error: {
9303
+ kind: "timeout",
9304
+ message: `Execution timed out after ${timeoutMs} ms`
9305
+ }
9306
+ } : executed;
9240
9307
  if (result.ok) {
9241
9308
  const writes = result.storeWrites;
9242
9309
  if (Object.keys(writes.set).length > 0 || writes.delete.length > 0) {
@@ -9282,12 +9349,23 @@ async function createSandbox(platform2, tools, globals, timeoutMs, memoryLimitBy
9282
9349
  const isKernel2 = platform2.packageDir !== void 0;
9283
9350
  if (isKernel2) {
9284
9351
  const { InlineCodemodeSandbox: InlineCodemodeSandbox2 } = await Promise.resolve().then(() => (init_portable(), portable_exports));
9352
+ const { QuickJS: QuickJS2 } = await Promise.resolve().then(() => (init_dist(), dist_exports));
9353
+ hookDeadlines(QuickJS2);
9285
9354
  const wasm = await loadWasmKernel(platform2);
9286
- return new InlineCodemodeSandbox2({ tools, globals, timeoutMs, wasm, memoryLimitBytes });
9355
+ const deadline = bindDeadline(wasm, timeoutMs);
9356
+ const sandbox2 = new InlineCodemodeSandbox2({
9357
+ tools,
9358
+ globals,
9359
+ timeoutMs,
9360
+ wasm,
9361
+ memoryLimitBytes,
9362
+ interruptBudget: Number.POSITIVE_INFINITY
9363
+ });
9364
+ return { sandbox: sandbox2, deadline };
9287
9365
  }
9288
- const { CodemodeSandbox: CodemodeSandbox2, loadQuickJSWasm: loadQuickJSWasm2 } = await Promise.resolve().then(() => (init_dist2(), dist_exports));
9366
+ const { CodemodeSandbox: CodemodeSandbox2, loadQuickJSWasm: loadQuickJSWasm2 } = await Promise.resolve().then(() => (init_dist2(), dist_exports2));
9289
9367
  const wasmPath = await resolveWasmPath(platform2);
9290
- return new CodemodeSandbox2({
9368
+ const sandbox = new CodemodeSandbox2({
9291
9369
  tools,
9292
9370
  globals,
9293
9371
  timeoutMs,
@@ -9295,6 +9373,7 @@ async function createSandbox(platform2, tools, globals, timeoutMs, memoryLimitBy
9295
9373
  wasm: loadQuickJSWasm2(wasmPath),
9296
9374
  workerUrl: new URL("./codemode-worker.mjs", import.meta.url)
9297
9375
  });
9376
+ return { sandbox };
9298
9377
  }
9299
9378
  async function resolveWasmPath(platform2) {
9300
9379
  const candidates = [
@@ -11804,16 +11883,45 @@ function wrapUntrusted(content) {
11804
11883
  ${neutralised}
11805
11884
  ${closeTag}`;
11806
11885
  }
11807
- async function runProcess(platform2, argv, stdin, timeoutMs) {
11886
+ var WEBMCP_LIST_FLOOR = 1048576;
11887
+ function capCollector(limit) {
11888
+ const dec = new TextDecoder();
11889
+ const half = Math.floor(limit / 2);
11890
+ let head = "";
11891
+ let tail = "";
11892
+ let total = 0;
11893
+ const take = (piece) => {
11894
+ if (!piece) return;
11895
+ total += piece.length;
11896
+ if (head.length < limit) head += piece.slice(0, limit - head.length);
11897
+ tail = half > 0 ? (tail + piece).slice(-half) : "";
11898
+ };
11899
+ return {
11900
+ push(chunk) {
11901
+ take(dec.decode(chunk, { stream: true }));
11902
+ },
11903
+ finish() {
11904
+ take(dec.decode());
11905
+ if (total <= limit) return { text: head, cut: false };
11906
+ return {
11907
+ text: `${head.slice(0, half)}
11908
+ \u2026 ${total - limit} characters cut \u2026
11909
+ ${tail}`,
11910
+ cut: true
11911
+ };
11912
+ }
11913
+ };
11914
+ }
11915
+ async function runProcess(platform2, argv, stdin, timeoutMs, limit) {
11808
11916
  let proc;
11809
11917
  try {
11810
11918
  proc = await platform2.spawn({ argv });
11811
- } catch (err) {
11812
- const code = err.code;
11919
+ } catch (err2) {
11920
+ const code = err2.code;
11813
11921
  if (code === "ENOENT" || code === "EACCES") {
11814
- return { stdout: "", stderr: `command not found: ${argv[0]}`, exitCode: -1 };
11922
+ return { stdout: "", stderr: `command not found: ${argv[0]}`, exitCode: -1, cut: false };
11815
11923
  }
11816
- throw err;
11924
+ throw err2;
11817
11925
  }
11818
11926
  if (stdin !== void 0) {
11819
11927
  const writer = proc.stdin.getWriter();
@@ -11825,8 +11933,8 @@ async function runProcess(platform2, argv, stdin, timeoutMs) {
11825
11933
  } catch {
11826
11934
  }
11827
11935
  }
11828
- const stdoutChunks = [];
11829
- const stderrChunks = [];
11936
+ const out = capCollector(limit);
11937
+ const err = capCollector(limit);
11830
11938
  const readStream3 = async (stream, into) => {
11831
11939
  const r = stream.getReader();
11832
11940
  try {
@@ -11839,53 +11947,136 @@ async function runProcess(platform2, argv, stdin, timeoutMs) {
11839
11947
  r.releaseLock();
11840
11948
  }
11841
11949
  };
11842
- const timer = new Promise(
11843
- (resolve2) => setTimeout(() => resolve2("timeout"), timeoutMs ?? 3e4)
11844
- );
11950
+ let timeoutId;
11951
+ const timer = new Promise((resolve2) => {
11952
+ timeoutId = setTimeout(() => resolve2("timeout"), timeoutMs);
11953
+ });
11845
11954
  const work = Promise.all([
11846
- readStream3(proc.stdout, stdoutChunks),
11847
- readStream3(proc.stderr, stderrChunks),
11955
+ readStream3(proc.stdout, out),
11956
+ readStream3(proc.stderr, err),
11848
11957
  proc.wait()
11849
11958
  ]);
11850
11959
  const result = await Promise.race([work, timer]);
11960
+ clearTimeout(timeoutId);
11851
11961
  if (result === "timeout") {
11852
11962
  await proc.kill("SIGTERM");
11853
- return { stdout: "", stderr: "timed out", exitCode: 124 };
11963
+ return { stdout: "", stderr: "timed out", exitCode: 124, cut: false };
11854
11964
  }
11855
11965
  const [, , exitResult] = result;
11856
- const dec = new TextDecoder();
11857
- const concat2 = (chunks) => {
11858
- const total = chunks.reduce((s, c) => s + c.length, 0);
11859
- const out = new Uint8Array(total);
11860
- let offset = 0;
11861
- for (const c of chunks) {
11862
- out.set(c, offset);
11863
- offset += c.length;
11864
- }
11865
- return out;
11966
+ const stdout = out.finish();
11967
+ const stderr = err.finish();
11968
+ return {
11969
+ stdout: stdout.text,
11970
+ stderr: stderr.text,
11971
+ exitCode: exitResult.status,
11972
+ cut: stdout.cut
11866
11973
  };
11974
+ }
11975
+ async function resolveWebMcpTab(platform2, options) {
11976
+ const argv = [options.playwrightCli, "tab-list"];
11977
+ if (options.cdp) argv.push("--cdp", options.cdp);
11978
+ const result = await runProcess(
11979
+ platform2,
11980
+ argv,
11981
+ void 0,
11982
+ options.callTimeoutMs,
11983
+ WEBMCP_LIST_FLOOR
11984
+ );
11985
+ if (result.exitCode === -1) return { error: result.stderr, notFound: true };
11986
+ if (result.exitCode !== 0) {
11987
+ return { error: result.stderr.trim() || `tab-list exited ${result.exitCode}` };
11988
+ }
11989
+ const rows = [];
11990
+ for (const line of result.stdout.split("\n")) {
11991
+ const match = /^(\d+): \[([^\]\s]+)\] /.exec(line);
11992
+ if (!match) continue;
11993
+ if (Number(match[1]) !== rows.length + 1) {
11994
+ return { error: "could not read playwright-cli tab-list" };
11995
+ }
11996
+ rows.push({ id: match[2], active: line.endsWith('" (active)') });
11997
+ }
11998
+ const active = rows.filter((r) => r.active);
11999
+ if (new Set(rows.map((r) => r.id)).size !== rows.length || active.length > 1) {
12000
+ return { error: "could not read playwright-cli tab-list" };
12001
+ }
12002
+ if (active.length === 1) return { tab: active[0].id };
12003
+ if (rows.length === 1) return { tab: rows[0].id };
11867
12004
  return {
11868
- stdout: dec.decode(concat2(stdoutChunks)),
11869
- stderr: dec.decode(concat2(stderrChunks)),
11870
- exitCode: exitResult.status
12005
+ 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)"
11871
12006
  };
11872
12007
  }
12008
+ function mapDescriptionAnnotations(ann) {
12009
+ const mapped = { openWorldHint: true };
12010
+ if (!ann) return mapped;
12011
+ if (ann.readOnlyHint !== void 0) mapped.readOnlyHint = ann.readOnlyHint;
12012
+ if (ann.consequentialHint) mapped.destructiveHint = true;
12013
+ return mapped;
12014
+ }
12015
+ function originPrefix(tools) {
12016
+ const origins = new Set(tools.map((t) => t.origin));
12017
+ return origins.size > 1;
12018
+ }
12019
+ function hostFromOrigin(origin) {
12020
+ try {
12021
+ return new URL(origin).host;
12022
+ } catch {
12023
+ return origin;
12024
+ }
12025
+ }
12026
+ function buildDescription(tool) {
12027
+ const parts = [];
12028
+ if (tool.description) parts.push(tool.description);
12029
+ parts.push(`[origin: ${tool.origin}]`);
12030
+ return parts.join("\n");
12031
+ }
12032
+ function buildToolName(tool, prefix) {
12033
+ const base = tool.name;
12034
+ if (!prefix) return base;
12035
+ const host = hostFromOrigin(tool.origin).replace(/[^a-zA-Z0-9_]/g, "_");
12036
+ return `${host}__${base}`;
12037
+ }
12038
+ function exposedTools(tools) {
12039
+ const needPrefix = originPrefix(tools);
12040
+ const byBase = /* @__PURE__ */ new Map();
12041
+ const byName = /* @__PURE__ */ new Map();
12042
+ for (const tool of tools) {
12043
+ const name = buildToolName(tool, needPrefix);
12044
+ byBase.set(tool.name, [...byBase.get(tool.name) ?? [], tool]);
12045
+ byName.set(name, [...byName.get(name) ?? [], tool]);
12046
+ }
12047
+ return tools.map((tool) => {
12048
+ const name = buildToolName(tool, needPrefix);
12049
+ const sameBase = byBase.get(tool.name);
12050
+ const sameName = byName.get(name);
12051
+ return { name, tool, peers: sameBase.length > 1 ? sameBase : sameName };
12052
+ });
12053
+ }
12054
+ function ambiguityError(name, peers) {
12055
+ const origins = [...new Set(peers.map((t) => String(t.origin)))].join(", ");
12056
+ return new Error(
12057
+ `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`
12058
+ );
12059
+ }
11873
12060
  function createWebMcpHandler(platform2, options) {
11874
12061
  let cache;
11875
12062
  const baseArgs = buildBaseArgs(options);
11876
- async function listRaw() {
12063
+ const listLimit = Math.max(options.maxOutput, WEBMCP_LIST_FLOOR);
12064
+ async function listRaw(fresh = false) {
11877
12065
  const now = Date.now();
11878
- if (cache && now < cache.expiresAt) {
12066
+ if (!fresh && cache && now < cache.expiresAt) {
11879
12067
  return { tools: cache.tools };
11880
12068
  }
11881
12069
  const argv = [options.playwrightCli, "webmcp-list", "--json", ...baseArgs];
11882
- const result = await runProcess(platform2, argv, void 0, options.callTimeoutMs);
12070
+ const result = await runProcess(platform2, argv, void 0, options.callTimeoutMs, listLimit);
11883
12071
  if (result.exitCode === -1) {
11884
12072
  return { tools: [], error: result.stderr, notFound: true };
11885
12073
  }
11886
12074
  if (result.exitCode !== 0) {
11887
12075
  return { tools: [], error: result.stderr || `webmcp-list exited ${result.exitCode}` };
11888
12076
  }
12077
+ if (result.cut) {
12078
+ return { tools: [], error: `webmcp-list output exceeds ${listLimit} characters` };
12079
+ }
11889
12080
  let parsed;
11890
12081
  try {
11891
12082
  parsed = JSON.parse(result.stdout);
@@ -11899,49 +12090,19 @@ function createWebMcpHandler(platform2, options) {
11899
12090
  cache = { tools, expiresAt: now + options.ttlMs };
11900
12091
  return { tools };
11901
12092
  }
11902
- function mapDescriptionAnnotations(ann) {
11903
- const mapped = { openWorldHint: true };
11904
- if (!ann) return mapped;
11905
- if (ann.readOnlyHint !== void 0) mapped.readOnlyHint = ann.readOnlyHint;
11906
- if (ann.consequentialHint) mapped.destructiveHint = true;
11907
- return mapped;
11908
- }
11909
- function originPrefix(tools) {
11910
- const origins = new Set(tools.map((t) => t.origin));
11911
- return origins.size > 1;
11912
- }
11913
- function hostFromOrigin(origin) {
11914
- try {
11915
- return new URL(origin).host;
11916
- } catch {
11917
- return origin;
11918
- }
11919
- }
11920
- function buildDescription(tool) {
11921
- const parts = [];
11922
- if (tool.description) parts.push(tool.description);
11923
- parts.push(`[origin: ${tool.origin}]`);
11924
- return parts.join("\n");
11925
- }
11926
- function buildToolName(tool, prefix) {
11927
- const base = tool.name;
11928
- if (!prefix) return base;
11929
- const host = hostFromOrigin(tool.origin).replace(/[^a-zA-Z0-9_]/g, "_");
11930
- return `${host}__${base}`;
11931
- }
11932
12093
  async function getRpcTools() {
11933
12094
  const { tools, error, notFound } = await listRaw();
11934
12095
  if (notFound) return null;
11935
12096
  if (error) return [];
11936
- const needPrefix = originPrefix(tools);
11937
12097
  const rpcTools = [];
11938
- for (const tool of tools) {
12098
+ for (const { name, tool, peers } of exposedTools(tools)) {
12099
+ if (peers.length > 1) continue;
11939
12100
  if (tool.annotations?.consequentialHint && !options.allowConsequential) {
11940
12101
  continue;
11941
12102
  }
11942
12103
  const mapped = mapDescriptionAnnotations(tool.annotations);
11943
12104
  rpcTools.push({
11944
- name: buildToolName(tool, needPrefix),
12105
+ name,
11945
12106
  description: buildDescription(tool),
11946
12107
  inputSchema: tool.inputSchema,
11947
12108
  annotations: mapped
@@ -11950,21 +12111,30 @@ function createWebMcpHandler(platform2, options) {
11950
12111
  return rpcTools;
11951
12112
  }
11952
12113
  async function callTool(name, args) {
11953
- const { tools, error } = await listRaw();
12114
+ const { tools, error } = await listRaw(true);
11954
12115
  if (error) {
11955
12116
  return { stdout: "", stderr: error, exitCode: 2 };
11956
12117
  }
11957
- const needPrefix = originPrefix(tools);
11958
- const tool = tools.find((t) => buildToolName(t, needPrefix) === name);
11959
- if (!tool) {
12118
+ const match = exposedTools(tools).find((t) => t.name === name);
12119
+ if (!match) {
11960
12120
  return { stdout: "", stderr: `unknown tool: ${name}`, exitCode: 2 };
11961
12121
  }
12122
+ if (match.peers.length > 1) {
12123
+ throw ambiguityError(name, match.peers);
12124
+ }
12125
+ const { tool } = match;
11962
12126
  const argv = [options.playwrightCli, "webmcp-call", tool.name, "-", ...baseArgs];
11963
12127
  if (options.allowConsequential) {
11964
12128
  argv.push("--allow-consequential");
11965
12129
  }
11966
12130
  const stdinData = JSON.stringify(args ?? {});
11967
- const result = await runProcess(platform2, argv, stdinData, options.callTimeoutMs);
12131
+ const result = await runProcess(
12132
+ platform2,
12133
+ argv,
12134
+ stdinData,
12135
+ options.callTimeoutMs,
12136
+ options.maxOutput
12137
+ );
11968
12138
  if (result.exitCode === -1) {
11969
12139
  throw new Error(result.stderr || "playwright-cli not found");
11970
12140
  }
@@ -12490,6 +12660,7 @@ async function discoverWithSignal(platform2, commands, childEnv, cwd) {
12490
12660
  };
12491
12661
  platform2.onSignal?.("SIGTERM", onShutdown);
12492
12662
  platform2.onSignal?.("SIGINT", onShutdown);
12663
+ platform2.onSignal?.("SIGHUP", onShutdown);
12493
12664
  for (const cmd of commands) {
12494
12665
  try {
12495
12666
  const cli = await discoverCli(platform2, cmd.path, cmd.name, childEnv, cwd, earlyAbort.signal);
@@ -12715,9 +12886,30 @@ async function runWebMcpServe(platform2, options, version, resolvedBearer, gener
12715
12886
  return 3;
12716
12887
  }
12717
12888
  const cdp = options.webmcpCdp ?? platform2.env.SLICC_CDP_URL;
12889
+ let tab = options.webmcpTab;
12890
+ if (!tab) {
12891
+ const resolved = await resolveWebMcpTab(platform2, {
12892
+ playwrightCli: cliPath,
12893
+ cdp,
12894
+ callTimeoutMs: options.callTimeout
12895
+ });
12896
+ if (resolved.notFound) {
12897
+ await platform2.write(
12898
+ 2,
12899
+ "mcpcat serve --webmcp: playwright-cli not found. Install it with: curl -fsSL https://get.sliccy.ai/playwright-cli | sh\n"
12900
+ );
12901
+ return 3;
12902
+ }
12903
+ if (!resolved.tab) {
12904
+ await platform2.write(2, `mcpcat serve --webmcp: ${resolved.error}
12905
+ `);
12906
+ return 3;
12907
+ }
12908
+ tab = resolved.tab;
12909
+ }
12718
12910
  const handler = createWebMcpHandler(platform2, {
12719
12911
  playwrightCli: cliPath,
12720
- tab: options.webmcpTab,
12912
+ tab,
12721
12913
  cdp,
12722
12914
  allowConsequential: options.webmcpAllowConsequential,
12723
12915
  ttlMs: options.webmcpTtl,
@@ -12799,6 +12991,21 @@ async function runWebMcpHttp(platform2, options, getTools, callTool, serverName,
12799
12991
  const address = serveAddress(platform2, options.host, actualPort);
12800
12992
  const listenUrl = address.url;
12801
12993
  let tokenFilePath;
12994
+ let shutdownRequested = false;
12995
+ const shutdown = () => {
12996
+ if (shutdownRequested) return;
12997
+ shutdownRequested = true;
12998
+ listener.close();
12999
+ if (tokenFilePath) {
13000
+ platform2.fs.unlink?.(tokenFilePath).catch(() => {
13001
+ });
13002
+ }
13003
+ };
13004
+ if (platform2.onSignal) {
13005
+ platform2.onSignal("SIGINT", shutdown);
13006
+ platform2.onSignal("SIGTERM", shutdown);
13007
+ platform2.onSignal("SIGHUP", shutdown);
13008
+ }
12802
13009
  if (generatedBearer && resolvedBearer) {
12803
13010
  tokenFilePath = await publishBearer(
12804
13011
  platform2,
@@ -12807,31 +13014,22 @@ async function runWebMcpHttp(platform2, options, getTools, callTool, serverName,
12807
13014
  address,
12808
13015
  options.json
12809
13016
  );
13017
+ if (shutdownRequested && tokenFilePath) {
13018
+ await platform2.fs.unlink?.(tokenFilePath).catch(() => {
13019
+ });
13020
+ }
12810
13021
  } else {
12811
13022
  if (options.json) {
12812
13023
  await platform2.write(1, `${JSON.stringify(address)}
12813
13024
  `);
12814
13025
  }
12815
13026
  }
12816
- await platform2.write(2, `mcpcat serve --webmcp: listening on ${listenUrl}
13027
+ if (!shutdownRequested) {
13028
+ await platform2.write(2, `mcpcat serve --webmcp: listening on ${listenUrl}
12817
13029
  `);
12818
- if (options.onListening) {
12819
- await options.onListening(actualPort);
12820
- }
12821
- let shutdownRequested = false;
12822
- const shutdown = () => {
12823
- if (shutdownRequested) return;
12824
- shutdownRequested = true;
12825
- listener.close();
12826
- if (tokenFilePath) {
12827
- platform2.fs.unlink?.(tokenFilePath).catch(() => {
12828
- });
13030
+ if (options.onListening) {
13031
+ await options.onListening(actualPort);
12829
13032
  }
12830
- };
12831
- if (platform2.onSignal) {
12832
- platform2.onSignal("SIGINT", shutdown);
12833
- platform2.onSignal("SIGTERM", shutdown);
12834
- platform2.onSignal("SIGHUP", shutdown);
12835
13033
  }
12836
13034
  const handler = async (request) => {
12837
13035
  if (request.target !== "/mcp") {
@@ -12995,6 +13193,23 @@ async function runProxyHttp(platform2, options, proxy, resolvedBearer, generated
12995
13193
  const address = serveAddress(platform2, options.host, actualPort);
12996
13194
  const listenUrl = address.url;
12997
13195
  let tokenFilePath;
13196
+ let shutdownRequested = false;
13197
+ let proxyDone;
13198
+ const shutdown = () => {
13199
+ if (shutdownRequested) return;
13200
+ shutdownRequested = true;
13201
+ listener.close();
13202
+ proxyDone = proxy.shutdown();
13203
+ if (tokenFilePath) {
13204
+ platform2.fs.unlink?.(tokenFilePath).catch(() => {
13205
+ });
13206
+ }
13207
+ };
13208
+ if (platform2.onSignal) {
13209
+ platform2.onSignal("SIGINT", shutdown);
13210
+ platform2.onSignal("SIGTERM", shutdown);
13211
+ platform2.onSignal("SIGHUP", shutdown);
13212
+ }
12998
13213
  if (generatedBearer && resolvedBearer) {
12999
13214
  tokenFilePath = await publishBearer(
13000
13215
  platform2,
@@ -13003,32 +13218,22 @@ async function runProxyHttp(platform2, options, proxy, resolvedBearer, generated
13003
13218
  address,
13004
13219
  options.json
13005
13220
  );
13221
+ if (shutdownRequested && tokenFilePath) {
13222
+ await platform2.fs.unlink?.(tokenFilePath).catch(() => {
13223
+ });
13224
+ }
13006
13225
  } else {
13007
13226
  if (options.json) {
13008
13227
  await platform2.write(1, `${JSON.stringify(address)}
13009
13228
  `);
13010
13229
  }
13011
13230
  }
13012
- await platform2.write(2, `mcpcat serve --proxy: listening on ${listenUrl}
13231
+ if (!shutdownRequested) {
13232
+ await platform2.write(2, `mcpcat serve --proxy: listening on ${listenUrl}
13013
13233
  `);
13014
- if (options.onListening) {
13015
- await options.onListening(actualPort);
13016
- }
13017
- let shutdownRequested = false;
13018
- const shutdown = () => {
13019
- if (shutdownRequested) return;
13020
- shutdownRequested = true;
13021
- listener.close();
13022
- void proxy.shutdown();
13023
- if (tokenFilePath) {
13024
- platform2.fs.unlink?.(tokenFilePath).catch(() => {
13025
- });
13234
+ if (options.onListening) {
13235
+ await options.onListening(actualPort);
13026
13236
  }
13027
- };
13028
- if (platform2.onSignal) {
13029
- platform2.onSignal("SIGINT", shutdown);
13030
- platform2.onSignal("SIGTERM", shutdown);
13031
- platform2.onSignal("SIGHUP", shutdown);
13032
13237
  }
13033
13238
  const handler = (request) => handleProxyMcpRequest(
13034
13239
  request,
@@ -13053,6 +13258,7 @@ async function runProxyHttp(platform2, options, proxy, resolvedBearer, generated
13053
13258
  } catch {
13054
13259
  if (!shutdownRequested) throw new Error("listener closed unexpectedly");
13055
13260
  }
13261
+ await proxyDone;
13056
13262
  return 0;
13057
13263
  }
13058
13264
  async function handleProxyMcpRequest(request, proxy, serverOrigins, allowedOrigins, allowedHosts, resolvedBearer) {
@@ -13172,22 +13378,6 @@ async function runHttpServer(platform2, options, tools, call, serverName, versio
13172
13378
  const address = serveAddress(platform2, options.host, actualPort);
13173
13379
  const listenUrl = address.url;
13174
13380
  let tokenFilePath;
13175
- if (generatedBearer && resolvedBearer) {
13176
- tokenFilePath = await publishBearer(
13177
- platform2,
13178
- resolvedBearer,
13179
- actualPort,
13180
- address,
13181
- options.json
13182
- );
13183
- } else {
13184
- if (options.json) {
13185
- await platform2.write(1, `${JSON.stringify(address)}
13186
- `);
13187
- }
13188
- }
13189
- await platform2.write(2, `mcpcat serve: listening on ${listenUrl}
13190
- `);
13191
13381
  let shutdownRequested = false;
13192
13382
  let inflightDone;
13193
13383
  const shutdown = () => {
@@ -13205,6 +13395,28 @@ async function runHttpServer(platform2, options, tools, call, serverName, versio
13205
13395
  platform2.onSignal("SIGTERM", shutdown);
13206
13396
  platform2.onSignal("SIGHUP", shutdown);
13207
13397
  }
13398
+ if (generatedBearer && resolvedBearer) {
13399
+ tokenFilePath = await publishBearer(
13400
+ platform2,
13401
+ resolvedBearer,
13402
+ actualPort,
13403
+ address,
13404
+ options.json
13405
+ );
13406
+ if (shutdownRequested && tokenFilePath) {
13407
+ await platform2.fs.unlink?.(tokenFilePath).catch(() => {
13408
+ });
13409
+ }
13410
+ } else {
13411
+ if (options.json) {
13412
+ await platform2.write(1, `${JSON.stringify(address)}
13413
+ `);
13414
+ }
13415
+ }
13416
+ if (!shutdownRequested) {
13417
+ await platform2.write(2, `mcpcat serve: listening on ${listenUrl}
13418
+ `);
13419
+ }
13208
13420
  const handler = (request) => handleMcpRequest(
13209
13421
  request,
13210
13422
  tools,
@@ -16274,6 +16486,15 @@ function collectHeaders(init) {
16274
16486
  }
16275
16487
  return entries;
16276
16488
  }
16489
+ async function encodeBody(body, headerEntries) {
16490
+ if (body == null) return new Uint8Array(0);
16491
+ const encoded = new Response(body);
16492
+ const type = encoded.headers.get("content-type");
16493
+ if (type && !headerEntries.some(([k]) => k.toLowerCase() === "content-type")) {
16494
+ headerEntries.push(["Content-Type", type]);
16495
+ }
16496
+ return new Uint8Array(await encoded.arrayBuffer());
16497
+ }
16277
16498
  function buildHttpRequest(method, url, headerEntries, bodyBytes) {
16278
16499
  const enc = new TextEncoder();
16279
16500
  const has = (n) => headerEntries.some(([k]) => k.toLowerCase() === n.toLowerCase());
@@ -16316,15 +16537,7 @@ function parseHttpResponse(buf, headerPos, bufLen) {
16316
16537
  }
16317
16538
  async function kernelLoopbackFetch(ctx, url, port, init, onRefused) {
16318
16539
  const method = (init?.method ?? "GET").toUpperCase();
16319
- const enc = new TextEncoder();
16320
16540
  const dec = new TextDecoder();
16321
- let bodyStr = "";
16322
- if (init?.body != null) {
16323
- bodyStr = typeof init.body === "string" ? init.body : dec.decode(init.body);
16324
- }
16325
- const bodyBytes = enc.encode(bodyStr);
16326
- const headerEntries = collectHeaders(init);
16327
- const reqBytes = buildHttpRequest(method, url, headerEntries, bodyBytes);
16328
16541
  let conn;
16329
16542
  try {
16330
16543
  conn = await ctx.net.connect({ host: "127.0.0.1", port });
@@ -16332,8 +16545,10 @@ async function kernelLoopbackFetch(ctx, url, port, init, onRefused) {
16332
16545
  if (onRefused && isConnectionRefused(err)) return onRefused();
16333
16546
  throw err;
16334
16547
  }
16548
+ const headerEntries = collectHeaders(init);
16549
+ const bodyBytes = await encodeBody(init?.body, headerEntries);
16335
16550
  const writer = conn.writable.getWriter();
16336
- await writer.write(reqBytes);
16551
+ await writer.write(buildHttpRequest(method, url, headerEntries, bodyBytes));
16337
16552
  const reader = conn.readable.getReader();
16338
16553
  let buf = new Uint8Array(4096);
16339
16554
  let bufLen = 0;
@@ -16452,7 +16667,7 @@ async function main(ctx) {
16452
16667
  packageDir: "/node_modules/@ai-ecoverse/mcpcat"
16453
16668
  });
16454
16669
  const args = ctx.argv.slice(1);
16455
- return run(platform2, "0.15.5", args);
16670
+ return run(platform2, "0.15.7", args);
16456
16671
  }
16457
16672
  export {
16458
16673
  main as default