@ai-ecoverse/mcpcat 0.20.2 → 0.20.4

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.
@@ -7312,7 +7312,7 @@ async function consumeSseStream(stream, options) {
7312
7312
  let dataLines = [];
7313
7313
  let dataBytes = 0;
7314
7314
  const maxEventBytes = options.maxEventBytes ?? DEFAULT_MAX_MESSAGE_BYTES;
7315
- const dispatch = () => {
7315
+ const dispatch2 = () => {
7316
7316
  if (dataLines.length === 0) {
7317
7317
  eventName = void 0;
7318
7318
  eventId = void 0;
@@ -7328,7 +7328,7 @@ async function consumeSseStream(stream, options) {
7328
7328
  const processLine = (rawLine) => {
7329
7329
  const line = rawLine.endsWith("\r") ? rawLine.slice(0, -1) : rawLine;
7330
7330
  if (line === "") {
7331
- dispatch();
7331
+ dispatch2();
7332
7332
  return;
7333
7333
  }
7334
7334
  if (line.startsWith(":"))
@@ -7369,7 +7369,7 @@ async function consumeSseStream(stream, options) {
7369
7369
  buffered += decoder3.decode();
7370
7370
  if (buffered)
7371
7371
  processLine(buffered);
7372
- dispatch();
7372
+ dispatch2();
7373
7373
  } finally {
7374
7374
  reader.releaseLock();
7375
7375
  }
@@ -7973,18 +7973,118 @@ var PlatformStdioTransport = class {
7973
7973
  }
7974
7974
  };
7975
7975
 
7976
+ // src/client/modern.ts
7977
+ var MODERN_PROTOCOL_VERSION = "2026-07-28";
7978
+ var META_PROTOCOL_VERSION = "io.modelcontextprotocol/protocolVersion";
7979
+ var META_CLIENT_INFO = "io.modelcontextprotocol/clientInfo";
7980
+ var META_CLIENT_CAPABILITIES = "io.modelcontextprotocol/clientCapabilities";
7981
+ var META_SERVER_INFO = "io.modelcontextprotocol/serverInfo";
7982
+ var HEADER_MISMATCH = -32020;
7983
+ var MISSING_REQUIRED_CLIENT_CAPABILITY = -32021;
7984
+ var UNSUPPORTED_PROTOCOL_VERSION = -32022;
7985
+ var MODERN_ERROR_CODES = /* @__PURE__ */ new Set([
7986
+ HEADER_MISMATCH,
7987
+ MISSING_REQUIRED_CLIENT_CAPABILITY,
7988
+ UNSUPPORTED_PROTOCOL_VERSION
7989
+ ]);
7990
+ function isModernErrorCode(code) {
7991
+ return MODERN_ERROR_CODES.has(code);
7992
+ }
7993
+ function modernRequestMeta(protocolVersion, clientInfo) {
7994
+ return {
7995
+ [META_PROTOCOL_VERSION]: protocolVersion,
7996
+ [META_CLIENT_INFO]: clientInfo,
7997
+ [META_CLIENT_CAPABILITIES]: {}
7998
+ };
7999
+ }
8000
+ function modernRequestParams(params, protocolVersion, clientInfo) {
8001
+ const { _inputResponses, ...rest } = params;
8002
+ const out = { ...rest };
8003
+ if (_inputResponses) out.inputResponses = _inputResponses;
8004
+ out._meta = modernRequestMeta(protocolVersion, clientInfo);
8005
+ return out;
8006
+ }
8007
+ var B64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
8008
+ function base64Utf8(value) {
8009
+ const bytes = new TextEncoder().encode(value);
8010
+ let out = "";
8011
+ for (let i = 0; i < bytes.length; i += 3) {
8012
+ const a = bytes[i];
8013
+ const b = i + 1 < bytes.length ? bytes[i + 1] : 0;
8014
+ const c = i + 2 < bytes.length ? bytes[i + 2] : 0;
8015
+ const n = a << 16 | b << 8 | c;
8016
+ out += B64[n >> 18 & 63] + B64[n >> 12 & 63];
8017
+ out += i + 1 < bytes.length ? B64[n >> 6 & 63] : "=";
8018
+ out += i + 2 < bytes.length ? B64[n & 63] : "=";
8019
+ }
8020
+ return out;
8021
+ }
8022
+ function encodeHeaderValue(value) {
8023
+ const safe = /^[\x20-\x7e\t]*$/.test(value) && value.trim() === value && !(value.startsWith("=?base64?") && value.endsWith("?="));
8024
+ return safe ? value : `=?base64?${base64Utf8(value)}?=`;
8025
+ }
8026
+ function isModernRequest(params) {
8027
+ if (!params || typeof params !== "object") return false;
8028
+ const meta = Object.getOwnPropertyDescriptor(params, "_meta")?.value;
8029
+ if (!meta || typeof meta !== "object") return false;
8030
+ return typeof Object.getOwnPropertyDescriptor(meta, META_PROTOCOL_VERSION)?.value === "string";
8031
+ }
8032
+ function completeModernOutcome(outcome, params) {
8033
+ if (outcome.status !== 200 || !isModernRequest(params)) return outcome;
8034
+ const frame = JSON.parse(outcome.body);
8035
+ const result = frame.result;
8036
+ if (!result || typeof result !== "object" || Array.isArray(result)) return outcome;
8037
+ if (Object.hasOwn(result, "resultType")) return outcome;
8038
+ frame.result = { resultType: "complete", ...result };
8039
+ return { ...outcome, body: JSON.stringify(frame) };
8040
+ }
8041
+ function modernDiscoverResult(supportedVersions, capabilities, serverInfo, instructions) {
8042
+ return {
8043
+ resultType: "complete",
8044
+ supportedVersions,
8045
+ capabilities,
8046
+ serverInfo,
8047
+ ...instructions ? { instructions } : {},
8048
+ _meta: { [META_SERVER_INFO]: serverInfo }
8049
+ };
8050
+ }
8051
+
7976
8052
  // src/client/stateless-connection.ts
7977
- var STATELESS_PROTOCOL_VERSION = "2026-07-28";
8053
+ var STATELESS_PROTOCOL_VERSION = MODERN_PROTOCOL_VERSION;
7978
8054
  var StatelessRpcError = class extends Error {
7979
8055
  code;
7980
8056
  data;
7981
- constructor(code, message, data) {
8057
+ httpStatus;
8058
+ constructor(code, message, data, httpStatus) {
7982
8059
  super(`MCP RPC error ${code}: ${message}`);
7983
8060
  this.name = "StatelessRpcError";
7984
8061
  this.code = code;
7985
8062
  this.data = data;
8063
+ this.httpStatus = httpStatus;
8064
+ }
8065
+ };
8066
+ var UnsupportedModernVersionError = class extends Error {
8067
+ constructor(version, supported) {
8068
+ super(`Server does not support ${version} (advertised: ${supported.join(", ") || "none"})`);
8069
+ this.name = "UnsupportedModernVersionError";
7986
8070
  }
7987
8071
  };
8072
+ function supportedList(data) {
8073
+ const list = data?.supported;
8074
+ return Array.isArray(list) ? list.filter((v) => typeof v === "string") : void 0;
8075
+ }
8076
+ function indicatesLegacyServer(err, http) {
8077
+ if (err instanceof UnsupportedModernVersionError) return true;
8078
+ if (http && err instanceof StatelessHttpError) return err.status >= 400 && err.status < 500;
8079
+ if (!(err instanceof StatelessRpcError)) return false;
8080
+ if (err.code === UNSUPPORTED_PROTOCOL_VERSION) {
8081
+ const supported = supportedList(err.data);
8082
+ return !!supported && !supported.includes(STATELESS_PROTOCOL_VERSION);
8083
+ }
8084
+ if (isModernErrorCode(err.code)) return false;
8085
+ if (!http || err.code === -32601) return true;
8086
+ return err.httpStatus !== void 0 && err.httpStatus >= 400 && err.httpStatus < 500;
8087
+ }
7988
8088
  var StatelessHttpError = class extends Error {
7989
8089
  status;
7990
8090
  constructor(status, statusText, method, snippet) {
@@ -8096,18 +8196,34 @@ function logHeaders(verbose, dir, entries) {
8096
8196
  else verbose.headerIn(k, v);
8097
8197
  }
8098
8198
  }
8199
+ function discoverServerInfo(discoverResult2) {
8200
+ const meta = discoverResult2?._meta;
8201
+ return meta?.[META_SERVER_INFO] ?? discoverResult2?.serverInfo;
8202
+ }
8099
8203
  function buildServerInfo(discoverResult2) {
8204
+ const serverInfo = discoverServerInfo(discoverResult2);
8100
8205
  return {
8101
- name: discoverResult2?.serverInfo?.name,
8102
- version: discoverResult2?.serverInfo?.version,
8103
- title: discoverResult2?.serverInfo?.title,
8206
+ name: serverInfo?.name,
8207
+ version: serverInfo?.version,
8208
+ title: serverInfo?.title,
8104
8209
  protocolVersion: STATELESS_PROTOCOL_VERSION,
8105
8210
  capabilities: discoverResult2?.capabilities ?? {},
8106
8211
  instructions: discoverResult2?.instructions
8107
8212
  };
8108
8213
  }
8214
+ function readHiddenTools(result) {
8215
+ const meta = result?._meta;
8216
+ const hidden = meta?.["io.mcpcat/hidden"];
8217
+ const out = {};
8218
+ if (!hidden || typeof hidden !== "object" || Array.isArray(hidden)) return out;
8219
+ for (const [name, why] of Object.entries(hidden)) {
8220
+ if (typeof why === "string") out[name] = why;
8221
+ }
8222
+ return out;
8223
+ }
8109
8224
  function buildStatelessMethods(requestFn, discoverResult2) {
8110
8225
  let serverInfoCache;
8226
+ let hidden = {};
8111
8227
  return {
8112
8228
  async info() {
8113
8229
  if (serverInfoCache) return serverInfoCache;
@@ -8115,7 +8231,12 @@ function buildStatelessMethods(requestFn, discoverResult2) {
8115
8231
  return serverInfoCache;
8116
8232
  },
8117
8233
  async listTools() {
8118
- return (await requestFn("tools/list", {}))?.tools ?? [];
8234
+ const result = await requestFn("tools/list", {});
8235
+ hidden = readHiddenTools(result);
8236
+ return result?.tools ?? [];
8237
+ },
8238
+ hiddenTool(name) {
8239
+ return Object.hasOwn(hidden, name) ? hidden[name] : void 0;
8119
8240
  },
8120
8241
  async callTool(name, args, _callOptions) {
8121
8242
  const params = {
@@ -8167,17 +8288,17 @@ async function createStatelessConnection(options) {
8167
8288
  redact: redact2 = (text) => text
8168
8289
  } = options;
8169
8290
  let nextId2 = 1;
8170
- let negotiatedVersion;
8291
+ const protocolVersion = STATELESS_PROTOCOL_VERSION;
8171
8292
  function buildHeaders(method, params, token) {
8172
8293
  const h = {
8173
8294
  "Content-Type": "application/json",
8174
8295
  Accept: "application/json, text/event-stream",
8296
+ "MCP-Protocol-Version": protocolVersion,
8175
8297
  "Mcp-Method": method,
8176
8298
  ...extraHeaders
8177
8299
  };
8178
8300
  const n = mcpNameForMethod(method, params);
8179
- if (n) h["Mcp-Name"] = n;
8180
- if (negotiatedVersion) h["Mcp-Protocol-Version"] = negotiatedVersion;
8301
+ if (n) h["Mcp-Name"] = encodeHeaderValue(n);
8181
8302
  if (token) h["Authorization"] = `Bearer ${token}`;
8182
8303
  return h;
8183
8304
  }
@@ -8197,7 +8318,7 @@ async function createStatelessConnection(options) {
8197
8318
  if (p.error && typeof p.error.code === "number") rpc = p.error;
8198
8319
  } catch {
8199
8320
  }
8200
- if (rpc) throw new StatelessRpcError(rpc.code, rpc.message, rpc.data);
8321
+ if (rpc) throw new StatelessRpcError(rpc.code, rpc.message, rpc.data, res.status);
8201
8322
  throw new StatelessHttpError(res.status, res.statusText, method, redact2(text).slice(0, 512));
8202
8323
  }
8203
8324
  async function doFetch(method, params, body) {
@@ -8240,7 +8361,12 @@ async function createStatelessConnection(options) {
8240
8361
  }
8241
8362
  if (verbose?.enabled) verbose.response(JSON.stringify(frame));
8242
8363
  if (frame.error) {
8243
- throw new StatelessRpcError(frame.error.code, frame.error.message, frame.error.data);
8364
+ throw new StatelessRpcError(
8365
+ frame.error.code,
8366
+ frame.error.message,
8367
+ frame.error.data,
8368
+ res.status
8369
+ );
8244
8370
  }
8245
8371
  return frame.result;
8246
8372
  }
@@ -8259,23 +8385,10 @@ async function createStatelessConnection(options) {
8259
8385
  }
8260
8386
  }
8261
8387
  async function request(method, params) {
8262
- const id = nextId2++;
8263
- const meta = {
8264
- protocolVersion: negotiatedVersion ?? STATELESS_PROTOCOL_VERSION,
8265
- clientInfo
8266
- };
8267
- const cleanParams = { ...params };
8268
- if (cleanParams._inputResponses) {
8269
- meta.inputResponses = cleanParams._inputResponses;
8270
- delete cleanParams._inputResponses;
8271
- }
8272
- const body = JSON.stringify({
8273
- jsonrpc: "2.0",
8274
- id,
8275
- method,
8276
- params: { ...cleanParams, _meta: meta }
8277
- });
8388
+ const wireParams = modernRequestParams(params, protocolVersion, clientInfo);
8278
8389
  for (let attempt = 0; attempt < 2; attempt++) {
8390
+ const id = nextId2++;
8391
+ const body = JSON.stringify({ jsonrpc: "2.0", id, method, params: wireParams });
8279
8392
  const { res, token, detach } = await doFetch(method, params, body);
8280
8393
  let retry;
8281
8394
  try {
@@ -8295,16 +8408,11 @@ async function createStatelessConnection(options) {
8295
8408
  }
8296
8409
  throw new McpAuthError(401, url);
8297
8410
  }
8298
- const discoverResult2 = await request("server/discover", {
8299
- protocolVersion: STATELESS_PROTOCOL_VERSION
8300
- });
8411
+ const discoverResult2 = await request("server/discover", {});
8301
8412
  const supported = discoverResult2?.supportedVersions?.filter((v) => typeof v === "string") ?? [];
8302
8413
  if (!supported.includes(STATELESS_PROTOCOL_VERSION)) {
8303
- throw new Error(
8304
- `Server does not support ${STATELESS_PROTOCOL_VERSION} (advertised: ${supported.join(", ") || "none"})`
8305
- );
8414
+ throw new UnsupportedModernVersionError(STATELESS_PROTOCOL_VERSION, supported);
8306
8415
  }
8307
- negotiatedVersion = STATELESS_PROTOCOL_VERSION;
8308
8416
  const extra = buildStatelessMethods(request, discoverResult2);
8309
8417
  return {
8310
8418
  ...extra,
@@ -8451,14 +8559,13 @@ async function createPiConnection(options) {
8451
8559
  }
8452
8560
 
8453
8561
  // src/client/stdio-stateless.ts
8454
- var PROTOCOL_VERSION = "2026-07-28";
8562
+ var PROTOCOL_VERSION = MODERN_PROTOCOL_VERSION;
8455
8563
  function createStdioJsonRpc(proc, platform2, options) {
8456
8564
  const encoder2 = new TextEncoder();
8457
8565
  const decoder3 = new TextDecoder();
8458
8566
  let nextId2 = 1;
8459
8567
  let readBuffer = "";
8460
8568
  const reader = proc.stdout.getReader();
8461
- let negotiatedVersion;
8462
8569
  const { version = "0.0.0", timeoutMs = 6e4, signal: externalSignal, verbose } = options;
8463
8570
  async function readLine2() {
8464
8571
  while (true) {
@@ -8498,20 +8605,11 @@ function createStdioJsonRpc(proc, platform2, options) {
8498
8605
  }
8499
8606
  async function request(method, params) {
8500
8607
  const id = nextId2++;
8501
- const meta = {
8502
- protocolVersion: negotiatedVersion ?? PROTOCOL_VERSION,
8503
- clientInfo: { name: "mcpcat", version }
8504
- };
8505
- const cleanParams = { ...params };
8506
- if (cleanParams._inputResponses) {
8507
- meta.inputResponses = cleanParams._inputResponses;
8508
- delete cleanParams._inputResponses;
8509
- }
8510
8608
  const body = JSON.stringify({
8511
8609
  jsonrpc: "2.0",
8512
8610
  id,
8513
8611
  method,
8514
- params: { ...cleanParams, _meta: meta }
8612
+ params: modernRequestParams(params, PROTOCOL_VERSION, { name: "mcpcat", version })
8515
8613
  });
8516
8614
  if (verbose?.enabled) verbose.request(method, body);
8517
8615
  await send(body);
@@ -8545,22 +8643,29 @@ function createStdioJsonRpc(proc, platform2, options) {
8545
8643
  };
8546
8644
  }
8547
8645
  function buildServerInfo2(discoverResult2) {
8646
+ const serverInfo = discoverServerInfo(discoverResult2);
8548
8647
  return {
8549
- name: discoverResult2?.serverInfo?.name,
8550
- version: discoverResult2?.serverInfo?.version,
8551
- title: discoverResult2?.serverInfo?.title,
8648
+ name: serverInfo?.name,
8649
+ version: serverInfo?.version,
8650
+ title: serverInfo?.title,
8552
8651
  protocolVersion: PROTOCOL_VERSION,
8553
8652
  capabilities: discoverResult2?.capabilities ?? {},
8554
8653
  instructions: discoverResult2?.instructions
8555
8654
  };
8556
8655
  }
8557
8656
  function buildConnectionMethods(rpc, discoverResult2, proc) {
8657
+ let hidden = {};
8558
8658
  return {
8559
8659
  async info() {
8560
8660
  return buildServerInfo2(discoverResult2);
8561
8661
  },
8562
8662
  async listTools() {
8563
- return (await rpc.request("tools/list", {}))?.tools ?? [];
8663
+ const result = await rpc.request("tools/list", {});
8664
+ hidden = readHiddenTools(result);
8665
+ return result?.tools ?? [];
8666
+ },
8667
+ hiddenTool(name) {
8668
+ return Object.hasOwn(hidden, name) ? hidden[name] : void 0;
8564
8669
  },
8565
8670
  async callTool(name, args, callOptions) {
8566
8671
  const params = { name, arguments: args ?? {} };
@@ -8616,25 +8721,27 @@ async function probeStdioStateless(options) {
8616
8721
  });
8617
8722
  let discoverRaw;
8618
8723
  try {
8619
- discoverRaw = await rpc.request("server/discover", {
8620
- protocolVersion: PROTOCOL_VERSION
8621
- });
8724
+ discoverRaw = await rpc.request("server/discover", {});
8622
8725
  } catch (err) {
8623
8726
  const pending = rpc.pendingBuffer();
8624
8727
  rpc.close();
8625
8728
  return { proc, pendingStdout: pending, error: err };
8626
8729
  }
8627
- void readStderr(proc, options.verbose);
8628
8730
  const discoverResult2 = discoverRaw;
8629
- const supported = (discoverResult2?.supportedVersions ?? []).filter(
8731
+ const advertised = discoverResult2?.supportedVersions;
8732
+ const supported = (Array.isArray(advertised) ? advertised : []).filter(
8630
8733
  (v) => typeof v === "string"
8631
8734
  );
8632
8735
  if (!supported.includes(PROTOCOL_VERSION)) {
8633
- await proc.kill("SIGTERM");
8634
- throw new Error(
8635
- `Server does not support ${PROTOCOL_VERSION} (advertised: ${supported.join(", ") || "none"})`
8636
- );
8736
+ const pending = rpc.pendingBuffer();
8737
+ rpc.close();
8738
+ return {
8739
+ proc,
8740
+ pendingStdout: pending,
8741
+ error: new UnsupportedModernVersionError(PROTOCOL_VERSION, supported)
8742
+ };
8637
8743
  }
8744
+ void readStderr(proc, options.verbose);
8638
8745
  return buildConnectionMethods(rpc, discoverResult2, proc);
8639
8746
  }
8640
8747
  async function readStderr(proc, verbose) {
@@ -8678,16 +8785,7 @@ async function connect(platform2, options) {
8678
8785
  verbose: options.verbose
8679
8786
  });
8680
8787
  if (!("proc" in result)) return result;
8681
- const isMethodNotFound = result.error instanceof StatelessRpcError && result.error.code === -32601;
8682
- const isUnsupported = result.error.message.includes("does not support");
8683
- if (options.protocolVersion?.startsWith("2026")) {
8684
- try {
8685
- await result.proc.kill("SIGTERM");
8686
- } catch {
8687
- }
8688
- throw result.error;
8689
- }
8690
- if (!isMethodNotFound && !isUnsupported) {
8788
+ if (options.protocolVersion?.startsWith("2026") || !indicatesLegacyServer(result.error, false)) {
8691
8789
  try {
8692
8790
  await result.proc.kill("SIGTERM");
8693
8791
  } catch {
@@ -8730,12 +8828,11 @@ async function connect(platform2, options) {
8730
8828
  redact: options.redact
8731
8829
  });
8732
8830
  } catch (err) {
8733
- const isMethodNotFound = err instanceof StatelessRpcError && err.code === -32601 || err instanceof StatelessHttpError && err.status >= 400 && err.status < 500;
8734
8831
  if (options.protocolVersion?.startsWith("2026")) throw err;
8735
8832
  if (err instanceof McpAuthError || err instanceof AuthStepUpError) throw err;
8736
8833
  if (err instanceof TransportRefusalError) throw err;
8737
8834
  if (err.name === "McpOAuthAuthorizationRequiredError") throw err;
8738
- if (isMethodNotFound) {
8835
+ if (indicatesLegacyServer(err, true)) {
8739
8836
  return createPiConnection({
8740
8837
  url: options.url,
8741
8838
  headers: options.headers,
@@ -8762,7 +8859,7 @@ function readLine(platform2) {
8762
8859
  return new Promise((resolve2, reject) => {
8763
8860
  const reader = platform2.stdin.getReader();
8764
8861
  const chunks = [];
8765
- function pump() {
8862
+ function pump2() {
8766
8863
  reader.read().then(({ done, value }) => {
8767
8864
  if (done) {
8768
8865
  reader.releaseLock();
@@ -8776,10 +8873,10 @@ function readLine(platform2) {
8776
8873
  resolve2(text.split("\n")[0]);
8777
8874
  return;
8778
8875
  }
8779
- pump();
8876
+ pump2();
8780
8877
  }, reject);
8781
8878
  }
8782
- pump();
8879
+ pump2();
8783
8880
  });
8784
8881
  }
8785
8882
  function concatChunks(chunks) {
@@ -10486,11 +10583,21 @@ async function probeLinkHeaders(fetcher, targetUrl, origin, page, userUrlIsPriva
10486
10583
  }
10487
10584
  return events;
10488
10585
  }
10586
+ function sseResponse(text, id) {
10587
+ for (const block of text.replace(/\r\n?/g, "\n").split("\n\n")) {
10588
+ const data = block.split("\n").filter((l) => l.startsWith("data:")).map((l) => l.slice(5).replace(/^ /, "")).join("\n");
10589
+ const parsed = tryParseJson(data);
10590
+ if (isRecord4(parsed) && parsed.id === id) return parsed;
10591
+ }
10592
+ return void 0;
10593
+ }
10489
10594
  function mcpJsonRpc(method, params, id = 1) {
10490
10595
  return JSON.stringify({ jsonrpc: "2.0", id, method, params });
10491
10596
  }
10492
10597
  function parseServerInfo(result) {
10493
- const si = isRecord4(result.serverInfo) ? result.serverInfo : void 0;
10598
+ const meta = isRecord4(result._meta) ? result._meta : void 0;
10599
+ const fromMeta = meta && isRecord4(meta[META_SERVER_INFO]) ? meta[META_SERVER_INFO] : void 0;
10600
+ const si = fromMeta ?? (isRecord4(result.serverInfo) ? result.serverInfo : void 0);
10494
10601
  if (!si) return void 0;
10495
10602
  return {
10496
10603
  name: typeof si.name === "string" ? si.name : void 0,
@@ -10506,7 +10613,7 @@ function checkAuthResponse(resp) {
10506
10613
  async function tryMcpPost(fetcher, url, body, extraHeaders) {
10507
10614
  const headers = {
10508
10615
  "Content-Type": "application/json",
10509
- Accept: "application/json",
10616
+ Accept: "application/json, text/event-stream",
10510
10617
  ...extraHeaders
10511
10618
  };
10512
10619
  try {
@@ -10521,7 +10628,8 @@ async function tryMcpPost(fetcher, url, body, extraHeaders) {
10521
10628
  if (authSignal) return { url, trust: [authSignal] };
10522
10629
  if (resp.status === 200) {
10523
10630
  const text = await resp.text();
10524
- const parsed = tryParseJson(text);
10631
+ const sse = (resp.headers.get("content-type") ?? "").includes("text/event-stream");
10632
+ const parsed = sse ? sseResponse(text, tryParseJson(body)?.id) : tryParseJson(text);
10525
10633
  if (isRecord4(parsed) && isRecord4(parsed.result)) {
10526
10634
  return { url, serverInfo: parseServerInfo(parsed.result), trust: [] };
10527
10635
  }
@@ -10532,18 +10640,22 @@ async function tryMcpPost(fetcher, url, body, extraHeaders) {
10532
10640
  }
10533
10641
  async function probeMcpEndpoint(fetcher, url) {
10534
10642
  const discoverBody = mcpJsonRpc("server/discover", {
10535
- protocolVersion: "2026-07-28",
10536
- clientInfo: { name: "mcpcat", version: "0.0.0" }
10643
+ _meta: modernRequestMeta(MODERN_PROTOCOL_VERSION, { name: "mcpcat", version: "0.0.0" })
10537
10644
  });
10538
10645
  const hit = await tryMcpPost(fetcher, url, discoverBody, {
10646
+ "MCP-Protocol-Version": MODERN_PROTOCOL_VERSION,
10539
10647
  "Mcp-Method": "server/discover"
10540
10648
  });
10541
10649
  if (hit) return hit;
10542
- const initBody = mcpJsonRpc("initialize", {
10543
- protocolVersion: "2025-11-25",
10544
- clientInfo: { name: "mcpcat", version: "0.0.0" },
10545
- capabilities: {}
10546
- });
10650
+ const initBody = mcpJsonRpc(
10651
+ "initialize",
10652
+ {
10653
+ protocolVersion: "2025-11-25",
10654
+ clientInfo: { name: "mcpcat", version: "0.0.0" },
10655
+ capabilities: {}
10656
+ },
10657
+ 2
10658
+ );
10547
10659
  return tryMcpPost(fetcher, url, initBody);
10548
10660
  }
10549
10661
  async function probeMcp(fetcher, origin, page, targetUrl) {
@@ -11658,7 +11770,7 @@ async function writeResponse(writer, response) {
11658
11770
  var PROTOCOL_VERSIONS = ["2026-07-28", "2025-11-25", "2025-06-18"];
11659
11771
  var OUTPUT_CAP = 1048576;
11660
11772
  var JSON_CT = "application/json; charset=utf-8";
11661
- async function handleJsonRpc(raw, tools, call, serverName, serverVersion) {
11773
+ async function handleJsonRpc(raw, source, call, serverName, serverVersion) {
11662
11774
  let parsed;
11663
11775
  try {
11664
11776
  parsed = JSON.parse(raw);
@@ -11672,19 +11784,35 @@ async function handleJsonRpc(raw, tools, call, serverName, serverVersion) {
11672
11784
  if (!hasOwn(parsed, "id")) return { status: 202, contentType: "text/plain", body: "" };
11673
11785
  const id = readId(parsed);
11674
11786
  const params = Object.getOwnPropertyDescriptor(parsed, "params")?.value;
11787
+ return completeModernOutcome(
11788
+ await dispatch(id, method, params, source, call, serverName, serverVersion),
11789
+ params
11790
+ );
11791
+ }
11792
+ async function dispatch(id, method, params, source, call, serverName, serverVersion) {
11675
11793
  if (method === "initialize")
11676
11794
  return rpcOk(id, initializeResult(params, serverName, serverVersion));
11677
11795
  if (method === "server/discover") return rpcOk(id, discoverResult(serverName, serverVersion));
11678
- if (method === "tools/list") return rpcOk(id, { tools });
11679
11796
  if (method === "ping") return rpcOk(id, {});
11680
- if (method === "tools/call") return toolCall(id, params, tools, call);
11797
+ if (method === "tools/list" || method === "tools/call") {
11798
+ const listing = Array.isArray(source) ? { tools: source } : await source();
11799
+ if (listing.error) return rpcError(id, -32e3, listing.error);
11800
+ if (method === "tools/call") return toolCall(id, params, listing, call);
11801
+ const hidden = listing.hidden && Object.keys(listing.hidden).length > 0 ? listing.hidden : null;
11802
+ return rpcOk(id, {
11803
+ tools: listing.tools,
11804
+ ...hidden ? { _meta: { "io.mcpcat/hidden": hidden } } : {}
11805
+ });
11806
+ }
11681
11807
  return rpcError(id, -32601, `Method not found: ${method}`);
11682
11808
  }
11683
- async function toolCall(id, params, tools, call) {
11809
+ async function toolCall(id, params, { tools, hidden }, call) {
11684
11810
  const name = params && typeof params === "object" ? readString(params, "name") : void 0;
11685
11811
  if (!name) return rpcError(id, -32602, "tools/call requires a name");
11686
- if (!tools.some((tool) => tool.name === name))
11687
- return rpcError(id, -32602, `Unknown tool: ${name}`);
11812
+ if (!tools.some((tool) => tool.name === name)) {
11813
+ const why = hidden && Object.hasOwn(hidden, name) ? hidden[name] : void 0;
11814
+ return rpcError(id, -32602, why ? `Unknown tool: ${name}: ${why}` : `Unknown tool: ${name}`);
11815
+ }
11688
11816
  const args = params && typeof params === "object" ? Object.getOwnPropertyDescriptor(params, "arguments")?.value : void 0;
11689
11817
  try {
11690
11818
  const result = await call(name, args);
@@ -11716,12 +11844,11 @@ function initializeResult(params, serverName, serverVersion) {
11716
11844
  };
11717
11845
  }
11718
11846
  function discoverResult(serverName, serverVersion) {
11719
- return {
11720
- protocolVersion: PROTOCOL_VERSIONS[0],
11721
- supportedVersions: PROTOCOL_VERSIONS,
11722
- capabilities: { tools: { listChanged: false } },
11723
- serverInfo: { name: serverName, version: serverVersion }
11724
- };
11847
+ return modernDiscoverResult(
11848
+ PROTOCOL_VERSIONS,
11849
+ { tools: { listChanged: false } },
11850
+ { name: serverName, version: serverVersion }
11851
+ );
11725
11852
  }
11726
11853
  function cap(value) {
11727
11854
  if (value.length <= OUTPUT_CAP) return value;
@@ -11857,15 +11984,18 @@ function translateCapabilities(info) {
11857
11984
  if (info.capabilities.prompts) caps.prompts = { listChanged: false };
11858
11985
  return caps;
11859
11986
  }
11987
+ function proxyServerInfo(info) {
11988
+ return {
11989
+ name: info.name ?? "mcpcat-proxy",
11990
+ version: info.version ?? "0.0.0",
11991
+ ...info.title ? { title: info.title } : {}
11992
+ };
11993
+ }
11860
11994
  function buildServerResult(info, protocolVersion) {
11861
11995
  return {
11862
11996
  protocolVersion,
11863
11997
  capabilities: translateCapabilities(info),
11864
- serverInfo: {
11865
- name: info.name ?? "mcpcat-proxy",
11866
- version: info.version ?? "0.0.0",
11867
- ...info.title ? { title: info.title } : {}
11868
- },
11998
+ serverInfo: proxyServerInfo(info),
11869
11999
  ...info.instructions ? { instructions: info.instructions } : {}
11870
12000
  };
11871
12001
  }
@@ -11913,10 +12043,16 @@ function createProxyHandler(options) {
11913
12043
  return scrubOk(id, buildServerResult(cachedInfo, pv));
11914
12044
  }
11915
12045
  function handleDiscover(id) {
11916
- return scrubOk(id, {
11917
- ...buildServerResult(cachedInfo, PROTOCOL_VERSIONS2[0]),
11918
- supportedVersions: PROTOCOL_VERSIONS2
11919
- });
12046
+ const info = cachedInfo;
12047
+ return scrubOk(
12048
+ id,
12049
+ modernDiscoverResult(
12050
+ PROTOCOL_VERSIONS2,
12051
+ translateCapabilities(info),
12052
+ proxyServerInfo(info),
12053
+ info.instructions
12054
+ )
12055
+ );
11920
12056
  }
11921
12057
  async function forwardWithRetry(method, doForward, id) {
11922
12058
  try {
@@ -11943,7 +12079,7 @@ function createProxyHandler(options) {
11943
12079
  return scrubErr(id, -32e3, err.message);
11944
12080
  }
11945
12081
  }
11946
- async function dispatch(method, id, params) {
12082
+ async function dispatch2(method, id, params) {
11947
12083
  try {
11948
12084
  await getConnection();
11949
12085
  } catch (err) {
@@ -11980,7 +12116,7 @@ function createProxyHandler(options) {
11980
12116
  if (!hasOwn2(parsed, "id")) return { status: 202, contentType: "text/plain", body: "" };
11981
12117
  const id = readId2(parsed);
11982
12118
  const params = Object.getOwnPropertyDescriptor(parsed, "params")?.value;
11983
- return dispatch(method, id, params);
12119
+ return completeModernOutcome(await dispatch2(method, id, params), params);
11984
12120
  }
11985
12121
  async function shutdown() {
11986
12122
  if (conn) {
@@ -12048,7 +12184,149 @@ ${tail}`,
12048
12184
  }
12049
12185
  };
12050
12186
  }
12051
- async function runProcess(platform2, argv, stdin, timeoutMs, limit) {
12187
+ var DIALOG_POLL_MS = 250;
12188
+ var DIALOG_READY_MS = 1e3;
12189
+ var DIALOG_STALL_MS = 1e3;
12190
+ function watchLoop(watch) {
12191
+ let timer;
12192
+ const result = new Promise((resolve2) => {
12193
+ timer = setInterval(() => {
12194
+ const found = watch();
12195
+ if (found) {
12196
+ clearInterval(timer);
12197
+ resolve2(found);
12198
+ }
12199
+ }, DIALOG_POLL_MS);
12200
+ });
12201
+ return {
12202
+ result,
12203
+ stop() {
12204
+ clearInterval(timer);
12205
+ }
12206
+ };
12207
+ }
12208
+ async function browserSocketUrl(platform2, cdp) {
12209
+ if (/^wss?:\/\//i.test(cdp)) return cdp;
12210
+ if (!/^https?:\/\//i.test(cdp)) return void 0;
12211
+ const res = await platform2.fetch(`${cdp.replace(/\/+$/, "")}/json/version`, {
12212
+ signal: AbortSignal.timeout(DIALOG_READY_MS)
12213
+ });
12214
+ const body = await res.json();
12215
+ return typeof body.webSocketDebuggerUrl === "string" ? body.webSocketDebuggerUrl : void 0;
12216
+ }
12217
+ function parseCdp(data) {
12218
+ try {
12219
+ return JSON.parse(
12220
+ typeof data === "string" ? data : new TextDecoder().decode(data)
12221
+ );
12222
+ } catch {
12223
+ return void 0;
12224
+ }
12225
+ }
12226
+ function applyCdp(state, channel, send, msg, settle) {
12227
+ if (msg.method === "Page.javascriptDialogOpening") {
12228
+ state.open = typeof msg.params?.type === "string" ? msg.params.type : "dialog";
12229
+ } else if (msg.method === "Page.javascriptDialogClosed") {
12230
+ state.open = void 0;
12231
+ } else if (msg.method === "Target.detachedFromTarget") {
12232
+ channel.close();
12233
+ } else if (msg.id === 1) {
12234
+ const sessionId = msg.result?.sessionId;
12235
+ if (typeof sessionId !== "string") {
12236
+ channel.close();
12237
+ return;
12238
+ }
12239
+ state.enableSentAt = Date.now();
12240
+ send({ id: 2, sessionId, method: "Page.enable" });
12241
+ } else if (msg.id === 2) {
12242
+ state.enableSentAt = void 0;
12243
+ settle();
12244
+ }
12245
+ }
12246
+ async function pump(state, channel, send, settle) {
12247
+ const reader = channel.readable.getReader();
12248
+ try {
12249
+ for (; ; ) {
12250
+ const { done, value } = await reader.read();
12251
+ if (done) return;
12252
+ const msg = parseCdp(value);
12253
+ if (msg) applyCdp(state, channel, send, msg, settle);
12254
+ }
12255
+ } catch {
12256
+ return;
12257
+ }
12258
+ }
12259
+ async function attachSocket(platform2, state, url, targetId) {
12260
+ const channel = await platform2.websocket(url).catch(() => void 0);
12261
+ if (!channel) return;
12262
+ state.channel = channel;
12263
+ const writer = channel.writable.getWriter();
12264
+ const send = (message) => {
12265
+ writer.write(JSON.stringify(message)).catch(() => channel.close());
12266
+ };
12267
+ let settle;
12268
+ const settled = new Promise((resolve2) => {
12269
+ settle = resolve2;
12270
+ });
12271
+ void pump(state, channel, send, settle).then(() => {
12272
+ if (state.channel === channel) {
12273
+ state.channel = void 0;
12274
+ state.open = void 0;
12275
+ state.enableSentAt = void 0;
12276
+ }
12277
+ channel.close();
12278
+ settle();
12279
+ });
12280
+ send({ id: 1, method: "Target.attachToTarget", params: { targetId, flatten: true } });
12281
+ await settled;
12282
+ }
12283
+ function watchJsDialogs(platform2, cdp, targetId) {
12284
+ const state = {};
12285
+ let connecting;
12286
+ let closed = false;
12287
+ const connect2 = async () => {
12288
+ if (!platform2.websocket) return;
12289
+ const url = await browserSocketUrl(platform2, cdp).catch(() => void 0);
12290
+ if (!url || closed) return;
12291
+ await attachSocket(platform2, state, url, targetId);
12292
+ };
12293
+ return {
12294
+ async ready() {
12295
+ if (closed || state.channel) return;
12296
+ connecting ??= connect2().finally(() => {
12297
+ connecting = void 0;
12298
+ });
12299
+ let timer;
12300
+ await Promise.race([
12301
+ connecting,
12302
+ new Promise((resolve2) => {
12303
+ timer = setTimeout(resolve2, DIALOG_READY_MS);
12304
+ })
12305
+ ]);
12306
+ clearTimeout(timer);
12307
+ },
12308
+ dialog: () => state.open,
12309
+ stalled: () => state.enableSentAt !== void 0 && Date.now() - state.enableSentAt >= DIALOG_STALL_MS,
12310
+ close() {
12311
+ closed = true;
12312
+ state.channel?.close();
12313
+ }
12314
+ };
12315
+ }
12316
+ function dialogMessage(tab, type) {
12317
+ return `tab ${tab} is blocked by a JavaScript ${type.replace(/[^a-z]/gi, "")} dialog; answer or dismiss it in the browser, then retry`;
12318
+ }
12319
+ function timeoutMessage(verb, timeoutMs, tab, watcher) {
12320
+ const head = `${verb} timed out after ${Math.round(timeoutMs / 1e3)}s`;
12321
+ if (!watcher) {
12322
+ return `${head}; if the page shows a JavaScript dialog (alert, confirm, prompt), answer it, and pass --cdp <url> so mcpcat can detect one`;
12323
+ }
12324
+ if (watcher.stalled()) {
12325
+ return `${head}; tab ${tab} does not answer the DevTools protocol, so a JavaScript dialog opened before mcpcat attached may block it; answer it in the browser, then retry`;
12326
+ }
12327
+ return head;
12328
+ }
12329
+ async function runProcess(platform2, argv, stdin, timeoutMs, limit, watch) {
12052
12330
  let proc;
12053
12331
  try {
12054
12332
  proc = await platform2.spawn({ argv });
@@ -12092,12 +12370,20 @@ async function runProcess(platform2, argv, stdin, timeoutMs, limit) {
12092
12370
  readStream3(proc.stderr, err),
12093
12371
  proc.wait()
12094
12372
  ]);
12095
- const result = await Promise.race([work, timer]);
12373
+ const watcher = watch ? watchLoop(watch) : void 0;
12374
+ const blocked = watcher ? watcher.result.then((type) => ({ blocked: type })) : new Promise(() => {
12375
+ });
12376
+ const result = await Promise.race([work, timer, blocked]);
12096
12377
  clearTimeout(timeoutId);
12378
+ watcher?.stop();
12097
12379
  if (result === "timeout") {
12098
12380
  await proc.kill("SIGTERM");
12099
12381
  return { stdout: "", stderr: "timed out", exitCode: 124, cut: false };
12100
12382
  }
12383
+ if (!Array.isArray(result)) {
12384
+ await proc.kill("SIGTERM");
12385
+ return { stdout: "", stderr: "blocked", exitCode: 124, cut: false, blocked: result.blocked };
12386
+ }
12101
12387
  const [, , exitResult] = result;
12102
12388
  const stdout = out.finish();
12103
12389
  const stderr = err.finish();
@@ -12108,7 +12394,7 @@ async function runProcess(platform2, argv, stdin, timeoutMs, limit) {
12108
12394
  cut: stdout.cut
12109
12395
  };
12110
12396
  }
12111
- async function resolveWebMcpTab(platform2, options) {
12397
+ async function listTabs(platform2, options) {
12112
12398
  const argv = [options.playwrightCli, "tab-list"];
12113
12399
  if (options.cdp) argv.push("--cdp", options.cdp);
12114
12400
  const result = await runProcess(
@@ -12135,6 +12421,13 @@ async function resolveWebMcpTab(platform2, options) {
12135
12421
  if (new Set(rows.map((r) => r.id)).size !== rows.length || active.length > 1) {
12136
12422
  return { error: "could not read playwright-cli tab-list" };
12137
12423
  }
12424
+ return { rows };
12425
+ }
12426
+ async function resolveWebMcpTab(platform2, options) {
12427
+ const listed = await listTabs(platform2, options);
12428
+ if ("error" in listed) return listed;
12429
+ const { rows } = listed;
12430
+ const active = rows.filter((r) => r.active);
12138
12431
  if (active.length === 1) return { tab: active[0].id };
12139
12432
  if (rows.length === 1) return { tab: rows[0].id };
12140
12433
  return {
@@ -12197,20 +12490,57 @@ function ambiguityError(name, peers) {
12197
12490
  `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`
12198
12491
  );
12199
12492
  }
12493
+ async function blockedNow(watcher, tab) {
12494
+ if (!watcher) return void 0;
12495
+ await watcher.ready();
12496
+ const open = watcher.dialog();
12497
+ return open ? dialogMessage(tab, open) : void 0;
12498
+ }
12499
+ async function tabGone(platform2, options) {
12500
+ const { tab } = options;
12501
+ if (!tab) return void 0;
12502
+ const listed = await listTabs(platform2, options);
12503
+ if ("error" in listed || listed.rows.some((r) => r.id === tab)) return void 0;
12504
+ return `tab ${tab} is gone (the browser restarted or the tab closed); restart mcpcat serve --webmcp, or pass --tab <targetId> (see playwright-cli tab-list)`;
12505
+ }
12506
+ function hiddenToolMessage(name) {
12507
+ return `tool ${name} exists on the page but is hidden because it is consequential (consequentialHint); restart with mcpcat serve --webmcp --allow-consequential to expose it`;
12508
+ }
12200
12509
  function createWebMcpHandler(platform2, options) {
12201
12510
  let cache;
12202
12511
  const baseArgs = buildBaseArgs(options);
12203
12512
  const listLimit = Math.max(options.maxOutput, WEBMCP_LIST_FLOOR);
12513
+ const { cdp, tab } = options;
12514
+ const watcher = cdp && tab ? watchJsDialogs(platform2, cdp, tab) : void 0;
12515
+ const watch = watcher ? () => watcher.dialog() : void 0;
12204
12516
  async function listRaw(fresh = false) {
12517
+ const blocked = await blockedNow(watcher, tab);
12518
+ if (blocked) return { tools: [], error: blocked };
12205
12519
  const now = Date.now();
12206
12520
  if (!fresh && cache && now < cache.expiresAt) {
12207
12521
  return { tools: cache.tools };
12208
12522
  }
12209
12523
  const argv = [options.playwrightCli, "webmcp-list", "--json", ...baseArgs];
12210
- const result = await runProcess(platform2, argv, void 0, options.callTimeoutMs, listLimit);
12524
+ const result = await runProcess(
12525
+ platform2,
12526
+ argv,
12527
+ void 0,
12528
+ options.callTimeoutMs,
12529
+ listLimit,
12530
+ watch
12531
+ );
12211
12532
  if (result.exitCode === -1) {
12212
12533
  return { tools: [], error: result.stderr, notFound: true };
12213
12534
  }
12535
+ if (result.blocked) {
12536
+ return { tools: [], error: dialogMessage(tab, result.blocked) };
12537
+ }
12538
+ if (result.exitCode === 124) {
12539
+ return {
12540
+ tools: [],
12541
+ error: timeoutMessage("webmcp-list", options.callTimeoutMs, tab, watcher)
12542
+ };
12543
+ }
12214
12544
  if (result.exitCode !== 0) {
12215
12545
  return { tools: [], error: result.stderr || `webmcp-list exited ${result.exitCode}` };
12216
12546
  }
@@ -12226,6 +12556,10 @@ function createWebMcpHandler(platform2, options) {
12226
12556
  if (!Array.isArray(parsed)) {
12227
12557
  return { tools: [], error: "webmcp-list did not return an array" };
12228
12558
  }
12559
+ if (parsed.length === 0) {
12560
+ const gone2 = await tabGone(platform2, options);
12561
+ if (gone2) return { tools: [], error: gone2 };
12562
+ }
12229
12563
  const tools = parsed;
12230
12564
  cache = { tools, expiresAt: now + options.ttlMs };
12231
12565
  return { tools };
@@ -12233,11 +12567,13 @@ function createWebMcpHandler(platform2, options) {
12233
12567
  async function getRpcTools() {
12234
12568
  const { tools, error, notFound } = await listRaw();
12235
12569
  if (notFound) return null;
12236
- if (error) return [];
12570
+ if (error) return { tools: [], error };
12237
12571
  const rpcTools = [];
12572
+ const hidden = {};
12238
12573
  for (const { name, tool, peers } of exposedTools(tools)) {
12239
12574
  if (peers.length > 1) continue;
12240
12575
  if (tool.annotations?.consequentialHint && !options.allowConsequential) {
12576
+ hidden[name] = hiddenToolMessage(name);
12241
12577
  continue;
12242
12578
  }
12243
12579
  const mapped = mapDescriptionAnnotations(tool.annotations);
@@ -12248,7 +12584,7 @@ function createWebMcpHandler(platform2, options) {
12248
12584
  annotations: mapped
12249
12585
  });
12250
12586
  }
12251
- return rpcTools;
12587
+ return { tools: rpcTools, hidden };
12252
12588
  }
12253
12589
  async function callTool(name, args) {
12254
12590
  const { tools, error } = await listRaw(true);
@@ -12273,11 +12609,18 @@ function createWebMcpHandler(platform2, options) {
12273
12609
  argv,
12274
12610
  stdinData,
12275
12611
  options.callTimeoutMs,
12276
- options.maxOutput
12612
+ options.maxOutput,
12613
+ watch
12277
12614
  );
12278
12615
  if (result.exitCode === -1) {
12279
12616
  throw new Error(result.stderr || "playwright-cli not found");
12280
12617
  }
12618
+ if (result.blocked) {
12619
+ throw new Error(dialogMessage(tab, result.blocked));
12620
+ }
12621
+ if (result.exitCode === 124) {
12622
+ throw new Error(timeoutMessage("webmcp-call", options.callTimeoutMs, tab, watcher));
12623
+ }
12281
12624
  if (result.exitCode === 2) {
12282
12625
  throw new Error(result.stderr || "webmcp-call error");
12283
12626
  }
@@ -12299,7 +12642,10 @@ function createWebMcpHandler(platform2, options) {
12299
12642
  function invalidateCache() {
12300
12643
  cache = void 0;
12301
12644
  }
12302
- return { getRpcTools, callTool, invalidateCache };
12645
+ function close() {
12646
+ watcher?.close();
12647
+ }
12648
+ return { getRpcTools, callTool, invalidateCache, close };
12303
12649
  }
12304
12650
 
12305
12651
  // src/serve/runtime.ts
@@ -13062,23 +13408,42 @@ async function buildProxyConnectOpts(platform2, options, version) {
13062
13408
  }
13063
13409
  return { connectOpts, hiddenValues };
13064
13410
  }
13065
- function resolvePlaywrightCli(platform2, explicit) {
13411
+ async function resolvePlaywrightCli(platform2, explicit) {
13066
13412
  if (explicit) return explicit;
13067
13413
  if (platform2.kind === "kernel") return "playwright-cli";
13068
13414
  const pathEnv = platform2.env.PATH ?? "";
13069
- const sep = pathEnv.includes(";") ? ";" : ":";
13070
- for (const dir of pathEnv.split(sep)) {
13415
+ const windows = pathEnv.includes(";");
13416
+ const names = windows ? ["playwright-cli.exe", "playwright-cli.cmd", "playwright-cli"] : ["playwright-cli"];
13417
+ for (const dir of pathEnv.split(windows ? ";" : ":")) {
13071
13418
  if (!dir) continue;
13072
- return "playwright-cli";
13419
+ for (const name of names) {
13420
+ const candidate = `${dir.replace(/[\\/]+$/, "")}/${name}`;
13421
+ if (await platform2.fs.exists(candidate).catch(() => false)) return "playwright-cli";
13422
+ }
13073
13423
  }
13074
13424
  return void 0;
13075
13425
  }
13426
+ async function listenOrReport(platform2, options, label) {
13427
+ try {
13428
+ return await platform2.listen({ host: options.host, port: options.port });
13429
+ } catch (err) {
13430
+ const { code, message } = err;
13431
+ const reason = code === "EADDRINUSE" || /EADDRINUSE/.test(String(message)) ? "address already in use" : String(message ?? err);
13432
+ const host = options.host.includes(":") ? `[${options.host}]` : options.host;
13433
+ await platform2.write(
13434
+ 2,
13435
+ sanitizeTerminal(`${label}: cannot listen on ${host}:${options.port}: ${reason}
13436
+ `)
13437
+ );
13438
+ return 3;
13439
+ }
13440
+ }
13076
13441
  async function runWebMcpServe(platform2, options, version, resolvedBearer, generatedBearer) {
13077
- const cliPath = resolvePlaywrightCli(platform2, options.webmcpPlaywrightCli);
13442
+ const cliPath = await resolvePlaywrightCli(platform2, options.webmcpPlaywrightCli);
13078
13443
  if (!cliPath) {
13079
13444
  await platform2.write(
13080
13445
  2,
13081
- "mcpcat serve --webmcp: playwright-cli not found. Install it with: curl -fsSL https://get.sliccy.ai/playwright-cli | sh\n"
13446
+ "mcpcat serve --webmcp: playwright-cli not found on PATH. Install it with: curl -fsSL https://get.sliccy.ai/playwright-cli | sh\n"
13082
13447
  );
13083
13448
  return 3;
13084
13449
  }
@@ -13113,6 +13478,20 @@ async function runWebMcpServe(platform2, options, version, resolvedBearer, gener
13113
13478
  callTimeoutMs: options.callTimeout,
13114
13479
  maxOutput: options.maxOutput
13115
13480
  });
13481
+ try {
13482
+ return await serveWebMcpHandler(
13483
+ platform2,
13484
+ options,
13485
+ handler,
13486
+ version,
13487
+ resolvedBearer,
13488
+ generatedBearer
13489
+ );
13490
+ } finally {
13491
+ handler.close();
13492
+ }
13493
+ }
13494
+ async function serveWebMcpHandler(platform2, options, handler, version, resolvedBearer, generatedBearer) {
13116
13495
  const probe = await handler.getRpcTools();
13117
13496
  if (probe === null) {
13118
13497
  await platform2.write(
@@ -13122,7 +13501,7 @@ async function runWebMcpServe(platform2, options, version, resolvedBearer, gener
13122
13501
  return 3;
13123
13502
  }
13124
13503
  const serverName = "mcpcat-webmcp";
13125
- const getTools = async () => await handler.getRpcTools() ?? [];
13504
+ const getTools = async () => await handler.getRpcTools() ?? { tools: [] };
13126
13505
  const callTool = (name, args) => handler.callTool(name, args);
13127
13506
  if (options.transport === "stdio") {
13128
13507
  return runWebMcpStdio(platform2, getTools, callTool, serverName, version);
@@ -13153,8 +13532,7 @@ async function runWebMcpStdio(platform2, getTools, callTool, serverName, version
13153
13532
  const line = buffer.slice(0, nlIdx).trim();
13154
13533
  buffer = buffer.slice(nlIdx + 1);
13155
13534
  if (!line) continue;
13156
- const tools = await getTools();
13157
- const outcome = await handleJsonRpc(line, tools, callTool, serverName, version);
13535
+ const outcome = await handleJsonRpc(line, getTools, callTool, serverName, version);
13158
13536
  if (outcome.status === 202) continue;
13159
13537
  const writer = platform2.stdout.getWriter();
13160
13538
  await writer.write(enc.encode(`${outcome.body}
@@ -13181,7 +13559,8 @@ async function runWebMcpHttp(platform2, options, getTools, callTool, serverName,
13181
13559
  return 2;
13182
13560
  }
13183
13561
  const allowedOrigins = new Set(options.allowOrigins);
13184
- const listener = await platform2.listen({ host: options.host, port: options.port });
13562
+ const listener = await listenOrReport(platform2, options, "mcpcat serve --webmcp");
13563
+ if (typeof listener === "number") return listener;
13185
13564
  const actualPort = listener.port;
13186
13565
  const serverOrigins = buildServerOrigins(options.host, actualPort);
13187
13566
  const allowedHosts = buildAllowedHosts(options.host, actualPort, options.allowHosts);
@@ -13287,8 +13666,7 @@ async function runWebMcpHttp(platform2, options, getTools, callTool, serverName,
13287
13666
  };
13288
13667
  }
13289
13668
  }
13290
- const tools = await getTools();
13291
- const outcome = await handleJsonRpc(request.body, tools, callTool, serverName, version);
13669
+ const outcome = await handleJsonRpc(request.body, getTools, callTool, serverName, version);
13292
13670
  return {
13293
13671
  status: outcome.status,
13294
13672
  reason: outcome.status === 200 ? "OK" : "Accepted",
@@ -13376,10 +13754,11 @@ async function runProxyHttp(platform2, options, proxy, resolvedBearer, generated
13376
13754
  return 2;
13377
13755
  }
13378
13756
  const allowedOrigins = new Set(options.allowOrigins);
13379
- const listener = await platform2.listen({
13380
- host: options.host,
13381
- port: options.port
13382
- });
13757
+ const listener = await listenOrReport(platform2, options, "mcpcat serve --proxy");
13758
+ if (typeof listener === "number") {
13759
+ await proxy.shutdown();
13760
+ return listener;
13761
+ }
13383
13762
  const actualPort = listener.port;
13384
13763
  const serverOrigins = buildServerOrigins(options.host, actualPort);
13385
13764
  const allowedHosts = buildAllowedHosts(options.host, actualPort, options.allowHosts);
@@ -13560,7 +13939,8 @@ async function runHttpServer(platform2, options, tools, call, serverName, versio
13560
13939
  return 2;
13561
13940
  }
13562
13941
  const allowedOrigins = new Set(options.allowOrigins);
13563
- const listener = await platform2.listen({ host: options.host, port: options.port });
13942
+ const listener = await listenOrReport(platform2, options, "mcpcat serve");
13943
+ if (typeof listener === "number") return listener;
13564
13944
  const actualPort = listener.port;
13565
13945
  const serverOrigins = buildServerOrigins(options.host, actualPort);
13566
13946
  const allowedHosts = buildAllowedHosts(options.host, actualPort, options.allowHosts);
@@ -15692,6 +16072,11 @@ async function runCall(platform2, conn, toolName, dataArg, toolArgs, opts) {
15692
16072
  if (toolArgs.length > 0) {
15693
16073
  const tools = await conn.listTools();
15694
16074
  const tool = tools.find((t) => t.name === toolName);
16075
+ const hidden = tool ? void 0 : conn.hiddenTool?.(toolName);
16076
+ if (hidden) {
16077
+ await writeErr(platform2, opts.json ?? false, "usage", hidden);
16078
+ return EXIT_USAGE;
16079
+ }
15695
16080
  const schema = tool?.inputSchema ?? { type: "object", properties: {} };
15696
16081
  const skipRequired = opts.noValidate || !!dataArg;
15697
16082
  const c = coerceArgsBySchema(toolArgs, schema, { noValidate: skipRequired });
@@ -16956,6 +17341,7 @@ function createKernelPlatform(ctx, options) {
16956
17341
  fetch: createKernelFetch(ctx),
16957
17342
  spawn: (options2) => kernelSpawn(ctx, options2),
16958
17343
  listen: (options2) => kernelListen(ctx, options2),
17344
+ ...ctx.websocket ? { websocket: (url) => ctx.websocket(url) } : {},
16959
17345
  fs: kernelFs(ctx),
16960
17346
  env: ctx.env,
16961
17347
  cwd: ctx.cwd,
@@ -16991,7 +17377,7 @@ async function main(ctx) {
16991
17377
  packageDir: "/node_modules/@ai-ecoverse/mcpcat"
16992
17378
  });
16993
17379
  const args = ctx.argv.slice(1);
16994
- return run(platform2, "0.20.2", args);
17380
+ return run(platform2, "0.20.4", args);
16995
17381
  }
16996
17382
  export {
16997
17383
  main as default