@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.
@@ -7691,7 +7691,7 @@ async function consumeSseStream(stream, options) {
7691
7691
  let dataLines = [];
7692
7692
  let dataBytes = 0;
7693
7693
  const maxEventBytes = options.maxEventBytes ?? DEFAULT_MAX_MESSAGE_BYTES;
7694
- const dispatch = () => {
7694
+ const dispatch2 = () => {
7695
7695
  if (dataLines.length === 0) {
7696
7696
  eventName = void 0;
7697
7697
  eventId = void 0;
@@ -7707,7 +7707,7 @@ async function consumeSseStream(stream, options) {
7707
7707
  const processLine = (rawLine) => {
7708
7708
  const line = rawLine.endsWith("\r") ? rawLine.slice(0, -1) : rawLine;
7709
7709
  if (line === "") {
7710
- dispatch();
7710
+ dispatch2();
7711
7711
  return;
7712
7712
  }
7713
7713
  if (line.startsWith(":"))
@@ -7748,7 +7748,7 @@ async function consumeSseStream(stream, options) {
7748
7748
  buffered += decoder3.decode();
7749
7749
  if (buffered)
7750
7750
  processLine(buffered);
7751
- dispatch();
7751
+ dispatch2();
7752
7752
  } finally {
7753
7753
  reader.releaseLock();
7754
7754
  }
@@ -8352,18 +8352,118 @@ var PlatformStdioTransport = class {
8352
8352
  }
8353
8353
  };
8354
8354
 
8355
+ // src/client/modern.ts
8356
+ var MODERN_PROTOCOL_VERSION = "2026-07-28";
8357
+ var META_PROTOCOL_VERSION = "io.modelcontextprotocol/protocolVersion";
8358
+ var META_CLIENT_INFO = "io.modelcontextprotocol/clientInfo";
8359
+ var META_CLIENT_CAPABILITIES = "io.modelcontextprotocol/clientCapabilities";
8360
+ var META_SERVER_INFO = "io.modelcontextprotocol/serverInfo";
8361
+ var HEADER_MISMATCH = -32020;
8362
+ var MISSING_REQUIRED_CLIENT_CAPABILITY = -32021;
8363
+ var UNSUPPORTED_PROTOCOL_VERSION = -32022;
8364
+ var MODERN_ERROR_CODES = /* @__PURE__ */ new Set([
8365
+ HEADER_MISMATCH,
8366
+ MISSING_REQUIRED_CLIENT_CAPABILITY,
8367
+ UNSUPPORTED_PROTOCOL_VERSION
8368
+ ]);
8369
+ function isModernErrorCode(code2) {
8370
+ return MODERN_ERROR_CODES.has(code2);
8371
+ }
8372
+ function modernRequestMeta(protocolVersion, clientInfo) {
8373
+ return {
8374
+ [META_PROTOCOL_VERSION]: protocolVersion,
8375
+ [META_CLIENT_INFO]: clientInfo,
8376
+ [META_CLIENT_CAPABILITIES]: {}
8377
+ };
8378
+ }
8379
+ function modernRequestParams(params, protocolVersion, clientInfo) {
8380
+ const { _inputResponses, ...rest } = params;
8381
+ const out = { ...rest };
8382
+ if (_inputResponses) out.inputResponses = _inputResponses;
8383
+ out._meta = modernRequestMeta(protocolVersion, clientInfo);
8384
+ return out;
8385
+ }
8386
+ var B64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
8387
+ function base64Utf8(value) {
8388
+ const bytes = new TextEncoder().encode(value);
8389
+ let out = "";
8390
+ for (let i = 0; i < bytes.length; i += 3) {
8391
+ const a = bytes[i];
8392
+ const b = i + 1 < bytes.length ? bytes[i + 1] : 0;
8393
+ const c = i + 2 < bytes.length ? bytes[i + 2] : 0;
8394
+ const n = a << 16 | b << 8 | c;
8395
+ out += B64[n >> 18 & 63] + B64[n >> 12 & 63];
8396
+ out += i + 1 < bytes.length ? B64[n >> 6 & 63] : "=";
8397
+ out += i + 2 < bytes.length ? B64[n & 63] : "=";
8398
+ }
8399
+ return out;
8400
+ }
8401
+ function encodeHeaderValue(value) {
8402
+ const safe = /^[\x20-\x7e\t]*$/.test(value) && value.trim() === value && !(value.startsWith("=?base64?") && value.endsWith("?="));
8403
+ return safe ? value : `=?base64?${base64Utf8(value)}?=`;
8404
+ }
8405
+ function isModernRequest(params) {
8406
+ if (!params || typeof params !== "object") return false;
8407
+ const meta = Object.getOwnPropertyDescriptor(params, "_meta")?.value;
8408
+ if (!meta || typeof meta !== "object") return false;
8409
+ return typeof Object.getOwnPropertyDescriptor(meta, META_PROTOCOL_VERSION)?.value === "string";
8410
+ }
8411
+ function completeModernOutcome(outcome, params) {
8412
+ if (outcome.status !== 200 || !isModernRequest(params)) return outcome;
8413
+ const frame = JSON.parse(outcome.body);
8414
+ const result = frame.result;
8415
+ if (!result || typeof result !== "object" || Array.isArray(result)) return outcome;
8416
+ if (Object.hasOwn(result, "resultType")) return outcome;
8417
+ frame.result = { resultType: "complete", ...result };
8418
+ return { ...outcome, body: JSON.stringify(frame) };
8419
+ }
8420
+ function modernDiscoverResult(supportedVersions, capabilities, serverInfo, instructions) {
8421
+ return {
8422
+ resultType: "complete",
8423
+ supportedVersions,
8424
+ capabilities,
8425
+ serverInfo,
8426
+ ...instructions ? { instructions } : {},
8427
+ _meta: { [META_SERVER_INFO]: serverInfo }
8428
+ };
8429
+ }
8430
+
8355
8431
  // src/client/stateless-connection.ts
8356
- var STATELESS_PROTOCOL_VERSION = "2026-07-28";
8432
+ var STATELESS_PROTOCOL_VERSION = MODERN_PROTOCOL_VERSION;
8357
8433
  var StatelessRpcError = class extends Error {
8358
8434
  code;
8359
8435
  data;
8360
- constructor(code2, message, data) {
8436
+ httpStatus;
8437
+ constructor(code2, message, data, httpStatus) {
8361
8438
  super(`MCP RPC error ${code2}: ${message}`);
8362
8439
  this.name = "StatelessRpcError";
8363
8440
  this.code = code2;
8364
8441
  this.data = data;
8442
+ this.httpStatus = httpStatus;
8443
+ }
8444
+ };
8445
+ var UnsupportedModernVersionError = class extends Error {
8446
+ constructor(version, supported) {
8447
+ super(`Server does not support ${version} (advertised: ${supported.join(", ") || "none"})`);
8448
+ this.name = "UnsupportedModernVersionError";
8365
8449
  }
8366
8450
  };
8451
+ function supportedList(data) {
8452
+ const list = data?.supported;
8453
+ return Array.isArray(list) ? list.filter((v) => typeof v === "string") : void 0;
8454
+ }
8455
+ function indicatesLegacyServer(err, http) {
8456
+ if (err instanceof UnsupportedModernVersionError) return true;
8457
+ if (http && err instanceof StatelessHttpError) return err.status >= 400 && err.status < 500;
8458
+ if (!(err instanceof StatelessRpcError)) return false;
8459
+ if (err.code === UNSUPPORTED_PROTOCOL_VERSION) {
8460
+ const supported = supportedList(err.data);
8461
+ return !!supported && !supported.includes(STATELESS_PROTOCOL_VERSION);
8462
+ }
8463
+ if (isModernErrorCode(err.code)) return false;
8464
+ if (!http || err.code === -32601) return true;
8465
+ return err.httpStatus !== void 0 && err.httpStatus >= 400 && err.httpStatus < 500;
8466
+ }
8367
8467
  var StatelessHttpError = class extends Error {
8368
8468
  status;
8369
8469
  constructor(status, statusText, method, snippet) {
@@ -8475,18 +8575,34 @@ function logHeaders(verbose, dir, entries) {
8475
8575
  else verbose.headerIn(k, v);
8476
8576
  }
8477
8577
  }
8578
+ function discoverServerInfo(discoverResult2) {
8579
+ const meta = discoverResult2?._meta;
8580
+ return meta?.[META_SERVER_INFO] ?? discoverResult2?.serverInfo;
8581
+ }
8478
8582
  function buildServerInfo(discoverResult2) {
8583
+ const serverInfo = discoverServerInfo(discoverResult2);
8479
8584
  return {
8480
- name: discoverResult2?.serverInfo?.name,
8481
- version: discoverResult2?.serverInfo?.version,
8482
- title: discoverResult2?.serverInfo?.title,
8585
+ name: serverInfo?.name,
8586
+ version: serverInfo?.version,
8587
+ title: serverInfo?.title,
8483
8588
  protocolVersion: STATELESS_PROTOCOL_VERSION,
8484
8589
  capabilities: discoverResult2?.capabilities ?? {},
8485
8590
  instructions: discoverResult2?.instructions
8486
8591
  };
8487
8592
  }
8593
+ function readHiddenTools(result) {
8594
+ const meta = result?._meta;
8595
+ const hidden = meta?.["io.mcpcat/hidden"];
8596
+ const out = {};
8597
+ if (!hidden || typeof hidden !== "object" || Array.isArray(hidden)) return out;
8598
+ for (const [name, why] of Object.entries(hidden)) {
8599
+ if (typeof why === "string") out[name] = why;
8600
+ }
8601
+ return out;
8602
+ }
8488
8603
  function buildStatelessMethods(requestFn, discoverResult2) {
8489
8604
  let serverInfoCache;
8605
+ let hidden = {};
8490
8606
  return {
8491
8607
  async info() {
8492
8608
  if (serverInfoCache) return serverInfoCache;
@@ -8494,7 +8610,12 @@ function buildStatelessMethods(requestFn, discoverResult2) {
8494
8610
  return serverInfoCache;
8495
8611
  },
8496
8612
  async listTools() {
8497
- return (await requestFn("tools/list", {}))?.tools ?? [];
8613
+ const result = await requestFn("tools/list", {});
8614
+ hidden = readHiddenTools(result);
8615
+ return result?.tools ?? [];
8616
+ },
8617
+ hiddenTool(name) {
8618
+ return Object.hasOwn(hidden, name) ? hidden[name] : void 0;
8498
8619
  },
8499
8620
  async callTool(name, args2, _callOptions) {
8500
8621
  const params = {
@@ -8546,17 +8667,17 @@ async function createStatelessConnection(options) {
8546
8667
  redact: redact2 = (text) => text
8547
8668
  } = options;
8548
8669
  let nextId2 = 1;
8549
- let negotiatedVersion;
8670
+ const protocolVersion = STATELESS_PROTOCOL_VERSION;
8550
8671
  function buildHeaders(method, params, token) {
8551
8672
  const h = {
8552
8673
  "Content-Type": "application/json",
8553
8674
  Accept: "application/json, text/event-stream",
8675
+ "MCP-Protocol-Version": protocolVersion,
8554
8676
  "Mcp-Method": method,
8555
8677
  ...extraHeaders
8556
8678
  };
8557
8679
  const n = mcpNameForMethod(method, params);
8558
- if (n) h["Mcp-Name"] = n;
8559
- if (negotiatedVersion) h["Mcp-Protocol-Version"] = negotiatedVersion;
8680
+ if (n) h["Mcp-Name"] = encodeHeaderValue(n);
8560
8681
  if (token) h["Authorization"] = `Bearer ${token}`;
8561
8682
  return h;
8562
8683
  }
@@ -8576,7 +8697,7 @@ async function createStatelessConnection(options) {
8576
8697
  if (p.error && typeof p.error.code === "number") rpc = p.error;
8577
8698
  } catch {
8578
8699
  }
8579
- if (rpc) throw new StatelessRpcError(rpc.code, rpc.message, rpc.data);
8700
+ if (rpc) throw new StatelessRpcError(rpc.code, rpc.message, rpc.data, res.status);
8580
8701
  throw new StatelessHttpError(res.status, res.statusText, method, redact2(text).slice(0, 512));
8581
8702
  }
8582
8703
  async function doFetch(method, params, body) {
@@ -8619,7 +8740,12 @@ async function createStatelessConnection(options) {
8619
8740
  }
8620
8741
  if (verbose?.enabled) verbose.response(JSON.stringify(frame));
8621
8742
  if (frame.error) {
8622
- throw new StatelessRpcError(frame.error.code, frame.error.message, frame.error.data);
8743
+ throw new StatelessRpcError(
8744
+ frame.error.code,
8745
+ frame.error.message,
8746
+ frame.error.data,
8747
+ res.status
8748
+ );
8623
8749
  }
8624
8750
  return frame.result;
8625
8751
  }
@@ -8638,23 +8764,10 @@ async function createStatelessConnection(options) {
8638
8764
  }
8639
8765
  }
8640
8766
  async function request(method, params) {
8641
- const id = nextId2++;
8642
- const meta = {
8643
- protocolVersion: negotiatedVersion ?? STATELESS_PROTOCOL_VERSION,
8644
- clientInfo
8645
- };
8646
- const cleanParams = { ...params };
8647
- if (cleanParams._inputResponses) {
8648
- meta.inputResponses = cleanParams._inputResponses;
8649
- delete cleanParams._inputResponses;
8650
- }
8651
- const body = JSON.stringify({
8652
- jsonrpc: "2.0",
8653
- id,
8654
- method,
8655
- params: { ...cleanParams, _meta: meta }
8656
- });
8767
+ const wireParams = modernRequestParams(params, protocolVersion, clientInfo);
8657
8768
  for (let attempt = 0; attempt < 2; attempt++) {
8769
+ const id = nextId2++;
8770
+ const body = JSON.stringify({ jsonrpc: "2.0", id, method, params: wireParams });
8658
8771
  const { res, token, detach } = await doFetch(method, params, body);
8659
8772
  let retry;
8660
8773
  try {
@@ -8674,16 +8787,11 @@ async function createStatelessConnection(options) {
8674
8787
  }
8675
8788
  throw new McpAuthError(401, url);
8676
8789
  }
8677
- const discoverResult2 = await request("server/discover", {
8678
- protocolVersion: STATELESS_PROTOCOL_VERSION
8679
- });
8790
+ const discoverResult2 = await request("server/discover", {});
8680
8791
  const supported = discoverResult2?.supportedVersions?.filter((v) => typeof v === "string") ?? [];
8681
8792
  if (!supported.includes(STATELESS_PROTOCOL_VERSION)) {
8682
- throw new Error(
8683
- `Server does not support ${STATELESS_PROTOCOL_VERSION} (advertised: ${supported.join(", ") || "none"})`
8684
- );
8793
+ throw new UnsupportedModernVersionError(STATELESS_PROTOCOL_VERSION, supported);
8685
8794
  }
8686
- negotiatedVersion = STATELESS_PROTOCOL_VERSION;
8687
8795
  const extra = buildStatelessMethods(request, discoverResult2);
8688
8796
  return {
8689
8797
  ...extra,
@@ -8830,14 +8938,13 @@ async function createPiConnection(options) {
8830
8938
  }
8831
8939
 
8832
8940
  // src/client/stdio-stateless.ts
8833
- var PROTOCOL_VERSION = "2026-07-28";
8941
+ var PROTOCOL_VERSION = MODERN_PROTOCOL_VERSION;
8834
8942
  function createStdioJsonRpc(proc, platform2, options) {
8835
8943
  const encoder2 = new TextEncoder();
8836
8944
  const decoder3 = new TextDecoder();
8837
8945
  let nextId2 = 1;
8838
8946
  let readBuffer = "";
8839
8947
  const reader = proc.stdout.getReader();
8840
- let negotiatedVersion;
8841
8948
  const { version = "0.0.0", timeoutMs = 6e4, signal: externalSignal, verbose } = options;
8842
8949
  async function readLine2() {
8843
8950
  while (true) {
@@ -8877,20 +8984,11 @@ function createStdioJsonRpc(proc, platform2, options) {
8877
8984
  }
8878
8985
  async function request(method, params) {
8879
8986
  const id = nextId2++;
8880
- const meta = {
8881
- protocolVersion: negotiatedVersion ?? PROTOCOL_VERSION,
8882
- clientInfo: { name: "mcpcat", version }
8883
- };
8884
- const cleanParams = { ...params };
8885
- if (cleanParams._inputResponses) {
8886
- meta.inputResponses = cleanParams._inputResponses;
8887
- delete cleanParams._inputResponses;
8888
- }
8889
8987
  const body = JSON.stringify({
8890
8988
  jsonrpc: "2.0",
8891
8989
  id,
8892
8990
  method,
8893
- params: { ...cleanParams, _meta: meta }
8991
+ params: modernRequestParams(params, PROTOCOL_VERSION, { name: "mcpcat", version })
8894
8992
  });
8895
8993
  if (verbose?.enabled) verbose.request(method, body);
8896
8994
  await send(body);
@@ -8924,22 +9022,29 @@ function createStdioJsonRpc(proc, platform2, options) {
8924
9022
  };
8925
9023
  }
8926
9024
  function buildServerInfo2(discoverResult2) {
9025
+ const serverInfo = discoverServerInfo(discoverResult2);
8927
9026
  return {
8928
- name: discoverResult2?.serverInfo?.name,
8929
- version: discoverResult2?.serverInfo?.version,
8930
- title: discoverResult2?.serverInfo?.title,
9027
+ name: serverInfo?.name,
9028
+ version: serverInfo?.version,
9029
+ title: serverInfo?.title,
8931
9030
  protocolVersion: PROTOCOL_VERSION,
8932
9031
  capabilities: discoverResult2?.capabilities ?? {},
8933
9032
  instructions: discoverResult2?.instructions
8934
9033
  };
8935
9034
  }
8936
9035
  function buildConnectionMethods(rpc, discoverResult2, proc) {
9036
+ let hidden = {};
8937
9037
  return {
8938
9038
  async info() {
8939
9039
  return buildServerInfo2(discoverResult2);
8940
9040
  },
8941
9041
  async listTools() {
8942
- return (await rpc.request("tools/list", {}))?.tools ?? [];
9042
+ const result = await rpc.request("tools/list", {});
9043
+ hidden = readHiddenTools(result);
9044
+ return result?.tools ?? [];
9045
+ },
9046
+ hiddenTool(name) {
9047
+ return Object.hasOwn(hidden, name) ? hidden[name] : void 0;
8943
9048
  },
8944
9049
  async callTool(name, args2, callOptions) {
8945
9050
  const params = { name, arguments: args2 ?? {} };
@@ -8995,25 +9100,27 @@ async function probeStdioStateless(options) {
8995
9100
  });
8996
9101
  let discoverRaw;
8997
9102
  try {
8998
- discoverRaw = await rpc.request("server/discover", {
8999
- protocolVersion: PROTOCOL_VERSION
9000
- });
9103
+ discoverRaw = await rpc.request("server/discover", {});
9001
9104
  } catch (err) {
9002
9105
  const pending = rpc.pendingBuffer();
9003
9106
  rpc.close();
9004
9107
  return { proc, pendingStdout: pending, error: err };
9005
9108
  }
9006
- void readStderr(proc, options.verbose);
9007
9109
  const discoverResult2 = discoverRaw;
9008
- const supported = (discoverResult2?.supportedVersions ?? []).filter(
9110
+ const advertised = discoverResult2?.supportedVersions;
9111
+ const supported = (Array.isArray(advertised) ? advertised : []).filter(
9009
9112
  (v) => typeof v === "string"
9010
9113
  );
9011
9114
  if (!supported.includes(PROTOCOL_VERSION)) {
9012
- await proc.kill("SIGTERM");
9013
- throw new Error(
9014
- `Server does not support ${PROTOCOL_VERSION} (advertised: ${supported.join(", ") || "none"})`
9015
- );
9115
+ const pending = rpc.pendingBuffer();
9116
+ rpc.close();
9117
+ return {
9118
+ proc,
9119
+ pendingStdout: pending,
9120
+ error: new UnsupportedModernVersionError(PROTOCOL_VERSION, supported)
9121
+ };
9016
9122
  }
9123
+ void readStderr(proc, options.verbose);
9017
9124
  return buildConnectionMethods(rpc, discoverResult2, proc);
9018
9125
  }
9019
9126
  async function readStderr(proc, verbose) {
@@ -9057,16 +9164,7 @@ async function connect(platform2, options) {
9057
9164
  verbose: options.verbose
9058
9165
  });
9059
9166
  if (!("proc" in result)) return result;
9060
- const isMethodNotFound = result.error instanceof StatelessRpcError && result.error.code === -32601;
9061
- const isUnsupported = result.error.message.includes("does not support");
9062
- if (options.protocolVersion?.startsWith("2026")) {
9063
- try {
9064
- await result.proc.kill("SIGTERM");
9065
- } catch {
9066
- }
9067
- throw result.error;
9068
- }
9069
- if (!isMethodNotFound && !isUnsupported) {
9167
+ if (options.protocolVersion?.startsWith("2026") || !indicatesLegacyServer(result.error, false)) {
9070
9168
  try {
9071
9169
  await result.proc.kill("SIGTERM");
9072
9170
  } catch {
@@ -9109,12 +9207,11 @@ async function connect(platform2, options) {
9109
9207
  redact: options.redact
9110
9208
  });
9111
9209
  } catch (err) {
9112
- const isMethodNotFound = err instanceof StatelessRpcError && err.code === -32601 || err instanceof StatelessHttpError && err.status >= 400 && err.status < 500;
9113
9210
  if (options.protocolVersion?.startsWith("2026")) throw err;
9114
9211
  if (err instanceof McpAuthError || err instanceof AuthStepUpError) throw err;
9115
9212
  if (err instanceof TransportRefusalError) throw err;
9116
9213
  if (err.name === "McpOAuthAuthorizationRequiredError") throw err;
9117
- if (isMethodNotFound) {
9214
+ if (indicatesLegacyServer(err, true)) {
9118
9215
  return createPiConnection({
9119
9216
  url: options.url,
9120
9217
  headers: options.headers,
@@ -9141,7 +9238,7 @@ function readLine(platform2) {
9141
9238
  return new Promise((resolve, reject) => {
9142
9239
  const reader = platform2.stdin.getReader();
9143
9240
  const chunks = [];
9144
- function pump() {
9241
+ function pump2() {
9145
9242
  reader.read().then(({ done, value }) => {
9146
9243
  if (done) {
9147
9244
  reader.releaseLock();
@@ -9155,10 +9252,10 @@ function readLine(platform2) {
9155
9252
  resolve(text.split("\n")[0]);
9156
9253
  return;
9157
9254
  }
9158
- pump();
9255
+ pump2();
9159
9256
  }, reject);
9160
9257
  }
9161
- pump();
9258
+ pump2();
9162
9259
  });
9163
9260
  }
9164
9261
  function concatChunks(chunks) {
@@ -10865,11 +10962,21 @@ async function probeLinkHeaders(fetcher, targetUrl, origin, page, userUrlIsPriva
10865
10962
  }
10866
10963
  return events;
10867
10964
  }
10965
+ function sseResponse(text, id) {
10966
+ for (const block of text.replace(/\r\n?/g, "\n").split("\n\n")) {
10967
+ const data = block.split("\n").filter((l) => l.startsWith("data:")).map((l) => l.slice(5).replace(/^ /, "")).join("\n");
10968
+ const parsed = tryParseJson(data);
10969
+ if (isRecord4(parsed) && parsed.id === id) return parsed;
10970
+ }
10971
+ return void 0;
10972
+ }
10868
10973
  function mcpJsonRpc(method, params, id = 1) {
10869
10974
  return JSON.stringify({ jsonrpc: "2.0", id, method, params });
10870
10975
  }
10871
10976
  function parseServerInfo(result) {
10872
- const si = isRecord4(result.serverInfo) ? result.serverInfo : void 0;
10977
+ const meta = isRecord4(result._meta) ? result._meta : void 0;
10978
+ const fromMeta = meta && isRecord4(meta[META_SERVER_INFO]) ? meta[META_SERVER_INFO] : void 0;
10979
+ const si = fromMeta ?? (isRecord4(result.serverInfo) ? result.serverInfo : void 0);
10873
10980
  if (!si) return void 0;
10874
10981
  return {
10875
10982
  name: typeof si.name === "string" ? si.name : void 0,
@@ -10885,7 +10992,7 @@ function checkAuthResponse(resp) {
10885
10992
  async function tryMcpPost(fetcher, url, body, extraHeaders) {
10886
10993
  const headers = {
10887
10994
  "Content-Type": "application/json",
10888
- Accept: "application/json",
10995
+ Accept: "application/json, text/event-stream",
10889
10996
  ...extraHeaders
10890
10997
  };
10891
10998
  try {
@@ -10900,7 +11007,8 @@ async function tryMcpPost(fetcher, url, body, extraHeaders) {
10900
11007
  if (authSignal) return { url, trust: [authSignal] };
10901
11008
  if (resp.status === 200) {
10902
11009
  const text = await resp.text();
10903
- const parsed = tryParseJson(text);
11010
+ const sse = (resp.headers.get("content-type") ?? "").includes("text/event-stream");
11011
+ const parsed = sse ? sseResponse(text, tryParseJson(body)?.id) : tryParseJson(text);
10904
11012
  if (isRecord4(parsed) && isRecord4(parsed.result)) {
10905
11013
  return { url, serverInfo: parseServerInfo(parsed.result), trust: [] };
10906
11014
  }
@@ -10911,18 +11019,22 @@ async function tryMcpPost(fetcher, url, body, extraHeaders) {
10911
11019
  }
10912
11020
  async function probeMcpEndpoint(fetcher, url) {
10913
11021
  const discoverBody = mcpJsonRpc("server/discover", {
10914
- protocolVersion: "2026-07-28",
10915
- clientInfo: { name: "mcpcat", version: "0.0.0" }
11022
+ _meta: modernRequestMeta(MODERN_PROTOCOL_VERSION, { name: "mcpcat", version: "0.0.0" })
10916
11023
  });
10917
11024
  const hit = await tryMcpPost(fetcher, url, discoverBody, {
11025
+ "MCP-Protocol-Version": MODERN_PROTOCOL_VERSION,
10918
11026
  "Mcp-Method": "server/discover"
10919
11027
  });
10920
11028
  if (hit) return hit;
10921
- const initBody = mcpJsonRpc("initialize", {
10922
- protocolVersion: "2025-11-25",
10923
- clientInfo: { name: "mcpcat", version: "0.0.0" },
10924
- capabilities: {}
10925
- });
11029
+ const initBody = mcpJsonRpc(
11030
+ "initialize",
11031
+ {
11032
+ protocolVersion: "2025-11-25",
11033
+ clientInfo: { name: "mcpcat", version: "0.0.0" },
11034
+ capabilities: {}
11035
+ },
11036
+ 2
11037
+ );
10926
11038
  return tryMcpPost(fetcher, url, initBody);
10927
11039
  }
10928
11040
  async function probeMcp(fetcher, origin, page, targetUrl) {
@@ -12037,7 +12149,7 @@ async function writeResponse(writer, response) {
12037
12149
  var PROTOCOL_VERSIONS = ["2026-07-28", "2025-11-25", "2025-06-18"];
12038
12150
  var OUTPUT_CAP = 1048576;
12039
12151
  var JSON_CT = "application/json; charset=utf-8";
12040
- async function handleJsonRpc(raw, tools, call, serverName, serverVersion) {
12152
+ async function handleJsonRpc(raw, source, call, serverName, serverVersion) {
12041
12153
  let parsed;
12042
12154
  try {
12043
12155
  parsed = JSON.parse(raw);
@@ -12051,19 +12163,35 @@ async function handleJsonRpc(raw, tools, call, serverName, serverVersion) {
12051
12163
  if (!hasOwn(parsed, "id")) return { status: 202, contentType: "text/plain", body: "" };
12052
12164
  const id = readId(parsed);
12053
12165
  const params = Object.getOwnPropertyDescriptor(parsed, "params")?.value;
12166
+ return completeModernOutcome(
12167
+ await dispatch(id, method, params, source, call, serverName, serverVersion),
12168
+ params
12169
+ );
12170
+ }
12171
+ async function dispatch(id, method, params, source, call, serverName, serverVersion) {
12054
12172
  if (method === "initialize")
12055
12173
  return rpcOk(id, initializeResult(params, serverName, serverVersion));
12056
12174
  if (method === "server/discover") return rpcOk(id, discoverResult(serverName, serverVersion));
12057
- if (method === "tools/list") return rpcOk(id, { tools });
12058
12175
  if (method === "ping") return rpcOk(id, {});
12059
- if (method === "tools/call") return toolCall(id, params, tools, call);
12176
+ if (method === "tools/list" || method === "tools/call") {
12177
+ const listing = Array.isArray(source) ? { tools: source } : await source();
12178
+ if (listing.error) return rpcError(id, -32e3, listing.error);
12179
+ if (method === "tools/call") return toolCall(id, params, listing, call);
12180
+ const hidden = listing.hidden && Object.keys(listing.hidden).length > 0 ? listing.hidden : null;
12181
+ return rpcOk(id, {
12182
+ tools: listing.tools,
12183
+ ...hidden ? { _meta: { "io.mcpcat/hidden": hidden } } : {}
12184
+ });
12185
+ }
12060
12186
  return rpcError(id, -32601, `Method not found: ${method}`);
12061
12187
  }
12062
- async function toolCall(id, params, tools, call) {
12188
+ async function toolCall(id, params, { tools, hidden }, call) {
12063
12189
  const name = params && typeof params === "object" ? readString(params, "name") : void 0;
12064
12190
  if (!name) return rpcError(id, -32602, "tools/call requires a name");
12065
- if (!tools.some((tool) => tool.name === name))
12066
- return rpcError(id, -32602, `Unknown tool: ${name}`);
12191
+ if (!tools.some((tool) => tool.name === name)) {
12192
+ const why = hidden && Object.hasOwn(hidden, name) ? hidden[name] : void 0;
12193
+ return rpcError(id, -32602, why ? `Unknown tool: ${name}: ${why}` : `Unknown tool: ${name}`);
12194
+ }
12067
12195
  const args2 = params && typeof params === "object" ? Object.getOwnPropertyDescriptor(params, "arguments")?.value : void 0;
12068
12196
  try {
12069
12197
  const result = await call(name, args2);
@@ -12095,12 +12223,11 @@ function initializeResult(params, serverName, serverVersion) {
12095
12223
  };
12096
12224
  }
12097
12225
  function discoverResult(serverName, serverVersion) {
12098
- return {
12099
- protocolVersion: PROTOCOL_VERSIONS[0],
12100
- supportedVersions: PROTOCOL_VERSIONS,
12101
- capabilities: { tools: { listChanged: false } },
12102
- serverInfo: { name: serverName, version: serverVersion }
12103
- };
12226
+ return modernDiscoverResult(
12227
+ PROTOCOL_VERSIONS,
12228
+ { tools: { listChanged: false } },
12229
+ { name: serverName, version: serverVersion }
12230
+ );
12104
12231
  }
12105
12232
  function cap(value) {
12106
12233
  if (value.length <= OUTPUT_CAP) return value;
@@ -12236,15 +12363,18 @@ function translateCapabilities(info) {
12236
12363
  if (info.capabilities.prompts) caps.prompts = { listChanged: false };
12237
12364
  return caps;
12238
12365
  }
12366
+ function proxyServerInfo(info) {
12367
+ return {
12368
+ name: info.name ?? "mcpcat-proxy",
12369
+ version: info.version ?? "0.0.0",
12370
+ ...info.title ? { title: info.title } : {}
12371
+ };
12372
+ }
12239
12373
  function buildServerResult(info, protocolVersion) {
12240
12374
  return {
12241
12375
  protocolVersion,
12242
12376
  capabilities: translateCapabilities(info),
12243
- serverInfo: {
12244
- name: info.name ?? "mcpcat-proxy",
12245
- version: info.version ?? "0.0.0",
12246
- ...info.title ? { title: info.title } : {}
12247
- },
12377
+ serverInfo: proxyServerInfo(info),
12248
12378
  ...info.instructions ? { instructions: info.instructions } : {}
12249
12379
  };
12250
12380
  }
@@ -12292,10 +12422,16 @@ function createProxyHandler(options) {
12292
12422
  return scrubOk(id, buildServerResult(cachedInfo, pv));
12293
12423
  }
12294
12424
  function handleDiscover(id) {
12295
- return scrubOk(id, {
12296
- ...buildServerResult(cachedInfo, PROTOCOL_VERSIONS2[0]),
12297
- supportedVersions: PROTOCOL_VERSIONS2
12298
- });
12425
+ const info = cachedInfo;
12426
+ return scrubOk(
12427
+ id,
12428
+ modernDiscoverResult(
12429
+ PROTOCOL_VERSIONS2,
12430
+ translateCapabilities(info),
12431
+ proxyServerInfo(info),
12432
+ info.instructions
12433
+ )
12434
+ );
12299
12435
  }
12300
12436
  async function forwardWithRetry(method, doForward, id) {
12301
12437
  try {
@@ -12322,7 +12458,7 @@ function createProxyHandler(options) {
12322
12458
  return scrubErr(id, -32e3, err.message);
12323
12459
  }
12324
12460
  }
12325
- async function dispatch(method, id, params) {
12461
+ async function dispatch2(method, id, params) {
12326
12462
  try {
12327
12463
  await getConnection();
12328
12464
  } catch (err) {
@@ -12359,7 +12495,7 @@ function createProxyHandler(options) {
12359
12495
  if (!hasOwn2(parsed, "id")) return { status: 202, contentType: "text/plain", body: "" };
12360
12496
  const id = readId2(parsed);
12361
12497
  const params = Object.getOwnPropertyDescriptor(parsed, "params")?.value;
12362
- return dispatch(method, id, params);
12498
+ return completeModernOutcome(await dispatch2(method, id, params), params);
12363
12499
  }
12364
12500
  async function shutdown() {
12365
12501
  if (conn) {
@@ -12427,7 +12563,149 @@ ${tail}`,
12427
12563
  }
12428
12564
  };
12429
12565
  }
12430
- async function runProcess(platform2, argv, stdin, timeoutMs, limit) {
12566
+ var DIALOG_POLL_MS = 250;
12567
+ var DIALOG_READY_MS = 1e3;
12568
+ var DIALOG_STALL_MS = 1e3;
12569
+ function watchLoop(watch) {
12570
+ let timer;
12571
+ const result = new Promise((resolve) => {
12572
+ timer = setInterval(() => {
12573
+ const found = watch();
12574
+ if (found) {
12575
+ clearInterval(timer);
12576
+ resolve(found);
12577
+ }
12578
+ }, DIALOG_POLL_MS);
12579
+ });
12580
+ return {
12581
+ result,
12582
+ stop() {
12583
+ clearInterval(timer);
12584
+ }
12585
+ };
12586
+ }
12587
+ async function browserSocketUrl(platform2, cdp) {
12588
+ if (/^wss?:\/\//i.test(cdp)) return cdp;
12589
+ if (!/^https?:\/\//i.test(cdp)) return void 0;
12590
+ const res = await platform2.fetch(`${cdp.replace(/\/+$/, "")}/json/version`, {
12591
+ signal: AbortSignal.timeout(DIALOG_READY_MS)
12592
+ });
12593
+ const body = await res.json();
12594
+ return typeof body.webSocketDebuggerUrl === "string" ? body.webSocketDebuggerUrl : void 0;
12595
+ }
12596
+ function parseCdp(data) {
12597
+ try {
12598
+ return JSON.parse(
12599
+ typeof data === "string" ? data : new TextDecoder().decode(data)
12600
+ );
12601
+ } catch {
12602
+ return void 0;
12603
+ }
12604
+ }
12605
+ function applyCdp(state, channel, send, msg, settle) {
12606
+ if (msg.method === "Page.javascriptDialogOpening") {
12607
+ state.open = typeof msg.params?.type === "string" ? msg.params.type : "dialog";
12608
+ } else if (msg.method === "Page.javascriptDialogClosed") {
12609
+ state.open = void 0;
12610
+ } else if (msg.method === "Target.detachedFromTarget") {
12611
+ channel.close();
12612
+ } else if (msg.id === 1) {
12613
+ const sessionId = msg.result?.sessionId;
12614
+ if (typeof sessionId !== "string") {
12615
+ channel.close();
12616
+ return;
12617
+ }
12618
+ state.enableSentAt = Date.now();
12619
+ send({ id: 2, sessionId, method: "Page.enable" });
12620
+ } else if (msg.id === 2) {
12621
+ state.enableSentAt = void 0;
12622
+ settle();
12623
+ }
12624
+ }
12625
+ async function pump(state, channel, send, settle) {
12626
+ const reader = channel.readable.getReader();
12627
+ try {
12628
+ for (; ; ) {
12629
+ const { done, value } = await reader.read();
12630
+ if (done) return;
12631
+ const msg = parseCdp(value);
12632
+ if (msg) applyCdp(state, channel, send, msg, settle);
12633
+ }
12634
+ } catch {
12635
+ return;
12636
+ }
12637
+ }
12638
+ async function attachSocket(platform2, state, url, targetId) {
12639
+ const channel = await platform2.websocket(url).catch(() => void 0);
12640
+ if (!channel) return;
12641
+ state.channel = channel;
12642
+ const writer = channel.writable.getWriter();
12643
+ const send = (message) => {
12644
+ writer.write(JSON.stringify(message)).catch(() => channel.close());
12645
+ };
12646
+ let settle;
12647
+ const settled = new Promise((resolve) => {
12648
+ settle = resolve;
12649
+ });
12650
+ void pump(state, channel, send, settle).then(() => {
12651
+ if (state.channel === channel) {
12652
+ state.channel = void 0;
12653
+ state.open = void 0;
12654
+ state.enableSentAt = void 0;
12655
+ }
12656
+ channel.close();
12657
+ settle();
12658
+ });
12659
+ send({ id: 1, method: "Target.attachToTarget", params: { targetId, flatten: true } });
12660
+ await settled;
12661
+ }
12662
+ function watchJsDialogs(platform2, cdp, targetId) {
12663
+ const state = {};
12664
+ let connecting;
12665
+ let closed = false;
12666
+ const connect2 = async () => {
12667
+ if (!platform2.websocket) return;
12668
+ const url = await browserSocketUrl(platform2, cdp).catch(() => void 0);
12669
+ if (!url || closed) return;
12670
+ await attachSocket(platform2, state, url, targetId);
12671
+ };
12672
+ return {
12673
+ async ready() {
12674
+ if (closed || state.channel) return;
12675
+ connecting ??= connect2().finally(() => {
12676
+ connecting = void 0;
12677
+ });
12678
+ let timer;
12679
+ await Promise.race([
12680
+ connecting,
12681
+ new Promise((resolve) => {
12682
+ timer = setTimeout(resolve, DIALOG_READY_MS);
12683
+ })
12684
+ ]);
12685
+ clearTimeout(timer);
12686
+ },
12687
+ dialog: () => state.open,
12688
+ stalled: () => state.enableSentAt !== void 0 && Date.now() - state.enableSentAt >= DIALOG_STALL_MS,
12689
+ close() {
12690
+ closed = true;
12691
+ state.channel?.close();
12692
+ }
12693
+ };
12694
+ }
12695
+ function dialogMessage(tab, type) {
12696
+ return `tab ${tab} is blocked by a JavaScript ${type.replace(/[^a-z]/gi, "")} dialog; answer or dismiss it in the browser, then retry`;
12697
+ }
12698
+ function timeoutMessage(verb, timeoutMs, tab, watcher) {
12699
+ const head = `${verb} timed out after ${Math.round(timeoutMs / 1e3)}s`;
12700
+ if (!watcher) {
12701
+ return `${head}; if the page shows a JavaScript dialog (alert, confirm, prompt), answer it, and pass --cdp <url> so mcpcat can detect one`;
12702
+ }
12703
+ if (watcher.stalled()) {
12704
+ 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`;
12705
+ }
12706
+ return head;
12707
+ }
12708
+ async function runProcess(platform2, argv, stdin, timeoutMs, limit, watch) {
12431
12709
  let proc;
12432
12710
  try {
12433
12711
  proc = await platform2.spawn({ argv });
@@ -12471,12 +12749,20 @@ async function runProcess(platform2, argv, stdin, timeoutMs, limit) {
12471
12749
  readStream3(proc.stderr, err),
12472
12750
  proc.wait()
12473
12751
  ]);
12474
- const result = await Promise.race([work, timer]);
12752
+ const watcher = watch ? watchLoop(watch) : void 0;
12753
+ const blocked = watcher ? watcher.result.then((type) => ({ blocked: type })) : new Promise(() => {
12754
+ });
12755
+ const result = await Promise.race([work, timer, blocked]);
12475
12756
  clearTimeout(timeoutId);
12757
+ watcher?.stop();
12476
12758
  if (result === "timeout") {
12477
12759
  await proc.kill("SIGTERM");
12478
12760
  return { stdout: "", stderr: "timed out", exitCode: 124, cut: false };
12479
12761
  }
12762
+ if (!Array.isArray(result)) {
12763
+ await proc.kill("SIGTERM");
12764
+ return { stdout: "", stderr: "blocked", exitCode: 124, cut: false, blocked: result.blocked };
12765
+ }
12480
12766
  const [, , exitResult] = result;
12481
12767
  const stdout = out.finish();
12482
12768
  const stderr = err.finish();
@@ -12487,7 +12773,7 @@ async function runProcess(platform2, argv, stdin, timeoutMs, limit) {
12487
12773
  cut: stdout.cut
12488
12774
  };
12489
12775
  }
12490
- async function resolveWebMcpTab(platform2, options) {
12776
+ async function listTabs(platform2, options) {
12491
12777
  const argv = [options.playwrightCli, "tab-list"];
12492
12778
  if (options.cdp) argv.push("--cdp", options.cdp);
12493
12779
  const result = await runProcess(
@@ -12514,6 +12800,13 @@ async function resolveWebMcpTab(platform2, options) {
12514
12800
  if (new Set(rows.map((r) => r.id)).size !== rows.length || active.length > 1) {
12515
12801
  return { error: "could not read playwright-cli tab-list" };
12516
12802
  }
12803
+ return { rows };
12804
+ }
12805
+ async function resolveWebMcpTab(platform2, options) {
12806
+ const listed = await listTabs(platform2, options);
12807
+ if ("error" in listed) return listed;
12808
+ const { rows } = listed;
12809
+ const active = rows.filter((r) => r.active);
12517
12810
  if (active.length === 1) return { tab: active[0].id };
12518
12811
  if (rows.length === 1) return { tab: rows[0].id };
12519
12812
  return {
@@ -12576,20 +12869,57 @@ function ambiguityError(name, peers) {
12576
12869
  `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`
12577
12870
  );
12578
12871
  }
12872
+ async function blockedNow(watcher, tab) {
12873
+ if (!watcher) return void 0;
12874
+ await watcher.ready();
12875
+ const open2 = watcher.dialog();
12876
+ return open2 ? dialogMessage(tab, open2) : void 0;
12877
+ }
12878
+ async function tabGone(platform2, options) {
12879
+ const { tab } = options;
12880
+ if (!tab) return void 0;
12881
+ const listed = await listTabs(platform2, options);
12882
+ if ("error" in listed || listed.rows.some((r) => r.id === tab)) return void 0;
12883
+ 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)`;
12884
+ }
12885
+ function hiddenToolMessage(name) {
12886
+ 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`;
12887
+ }
12579
12888
  function createWebMcpHandler(platform2, options) {
12580
12889
  let cache;
12581
12890
  const baseArgs = buildBaseArgs(options);
12582
12891
  const listLimit = Math.max(options.maxOutput, WEBMCP_LIST_FLOOR);
12892
+ const { cdp, tab } = options;
12893
+ const watcher = cdp && tab ? watchJsDialogs(platform2, cdp, tab) : void 0;
12894
+ const watch = watcher ? () => watcher.dialog() : void 0;
12583
12895
  async function listRaw(fresh = false) {
12896
+ const blocked = await blockedNow(watcher, tab);
12897
+ if (blocked) return { tools: [], error: blocked };
12584
12898
  const now = Date.now();
12585
12899
  if (!fresh && cache && now < cache.expiresAt) {
12586
12900
  return { tools: cache.tools };
12587
12901
  }
12588
12902
  const argv = [options.playwrightCli, "webmcp-list", "--json", ...baseArgs];
12589
- const result = await runProcess(platform2, argv, void 0, options.callTimeoutMs, listLimit);
12903
+ const result = await runProcess(
12904
+ platform2,
12905
+ argv,
12906
+ void 0,
12907
+ options.callTimeoutMs,
12908
+ listLimit,
12909
+ watch
12910
+ );
12590
12911
  if (result.exitCode === -1) {
12591
12912
  return { tools: [], error: result.stderr, notFound: true };
12592
12913
  }
12914
+ if (result.blocked) {
12915
+ return { tools: [], error: dialogMessage(tab, result.blocked) };
12916
+ }
12917
+ if (result.exitCode === 124) {
12918
+ return {
12919
+ tools: [],
12920
+ error: timeoutMessage("webmcp-list", options.callTimeoutMs, tab, watcher)
12921
+ };
12922
+ }
12593
12923
  if (result.exitCode !== 0) {
12594
12924
  return { tools: [], error: result.stderr || `webmcp-list exited ${result.exitCode}` };
12595
12925
  }
@@ -12605,6 +12935,10 @@ function createWebMcpHandler(platform2, options) {
12605
12935
  if (!Array.isArray(parsed)) {
12606
12936
  return { tools: [], error: "webmcp-list did not return an array" };
12607
12937
  }
12938
+ if (parsed.length === 0) {
12939
+ const gone2 = await tabGone(platform2, options);
12940
+ if (gone2) return { tools: [], error: gone2 };
12941
+ }
12608
12942
  const tools = parsed;
12609
12943
  cache = { tools, expiresAt: now + options.ttlMs };
12610
12944
  return { tools };
@@ -12612,11 +12946,13 @@ function createWebMcpHandler(platform2, options) {
12612
12946
  async function getRpcTools() {
12613
12947
  const { tools, error, notFound } = await listRaw();
12614
12948
  if (notFound) return null;
12615
- if (error) return [];
12949
+ if (error) return { tools: [], error };
12616
12950
  const rpcTools = [];
12951
+ const hidden = {};
12617
12952
  for (const { name, tool, peers } of exposedTools(tools)) {
12618
12953
  if (peers.length > 1) continue;
12619
12954
  if (tool.annotations?.consequentialHint && !options.allowConsequential) {
12955
+ hidden[name] = hiddenToolMessage(name);
12620
12956
  continue;
12621
12957
  }
12622
12958
  const mapped = mapDescriptionAnnotations(tool.annotations);
@@ -12627,7 +12963,7 @@ function createWebMcpHandler(platform2, options) {
12627
12963
  annotations: mapped
12628
12964
  });
12629
12965
  }
12630
- return rpcTools;
12966
+ return { tools: rpcTools, hidden };
12631
12967
  }
12632
12968
  async function callTool(name, args2) {
12633
12969
  const { tools, error } = await listRaw(true);
@@ -12652,11 +12988,18 @@ function createWebMcpHandler(platform2, options) {
12652
12988
  argv,
12653
12989
  stdinData,
12654
12990
  options.callTimeoutMs,
12655
- options.maxOutput
12991
+ options.maxOutput,
12992
+ watch
12656
12993
  );
12657
12994
  if (result.exitCode === -1) {
12658
12995
  throw new Error(result.stderr || "playwright-cli not found");
12659
12996
  }
12997
+ if (result.blocked) {
12998
+ throw new Error(dialogMessage(tab, result.blocked));
12999
+ }
13000
+ if (result.exitCode === 124) {
13001
+ throw new Error(timeoutMessage("webmcp-call", options.callTimeoutMs, tab, watcher));
13002
+ }
12660
13003
  if (result.exitCode === 2) {
12661
13004
  throw new Error(result.stderr || "webmcp-call error");
12662
13005
  }
@@ -12678,7 +13021,10 @@ function createWebMcpHandler(platform2, options) {
12678
13021
  function invalidateCache() {
12679
13022
  cache = void 0;
12680
13023
  }
12681
- return { getRpcTools, callTool, invalidateCache };
13024
+ function close() {
13025
+ watcher?.close();
13026
+ }
13027
+ return { getRpcTools, callTool, invalidateCache, close };
12682
13028
  }
12683
13029
 
12684
13030
  // src/serve/runtime.ts
@@ -13441,23 +13787,42 @@ async function buildProxyConnectOpts(platform2, options, version) {
13441
13787
  }
13442
13788
  return { connectOpts, hiddenValues };
13443
13789
  }
13444
- function resolvePlaywrightCli(platform2, explicit) {
13790
+ async function resolvePlaywrightCli(platform2, explicit) {
13445
13791
  if (explicit) return explicit;
13446
13792
  if (platform2.kind === "kernel") return "playwright-cli";
13447
13793
  const pathEnv = platform2.env.PATH ?? "";
13448
- const sep = pathEnv.includes(";") ? ";" : ":";
13449
- for (const dir of pathEnv.split(sep)) {
13794
+ const windows = pathEnv.includes(";");
13795
+ const names = windows ? ["playwright-cli.exe", "playwright-cli.cmd", "playwright-cli"] : ["playwright-cli"];
13796
+ for (const dir of pathEnv.split(windows ? ";" : ":")) {
13450
13797
  if (!dir) continue;
13451
- return "playwright-cli";
13798
+ for (const name of names) {
13799
+ const candidate = `${dir.replace(/[\\/]+$/, "")}/${name}`;
13800
+ if (await platform2.fs.exists(candidate).catch(() => false)) return "playwright-cli";
13801
+ }
13452
13802
  }
13453
13803
  return void 0;
13454
13804
  }
13805
+ async function listenOrReport(platform2, options, label) {
13806
+ try {
13807
+ return await platform2.listen({ host: options.host, port: options.port });
13808
+ } catch (err) {
13809
+ const { code: code2, message } = err;
13810
+ const reason = code2 === "EADDRINUSE" || /EADDRINUSE/.test(String(message)) ? "address already in use" : String(message ?? err);
13811
+ const host = options.host.includes(":") ? `[${options.host}]` : options.host;
13812
+ await platform2.write(
13813
+ 2,
13814
+ sanitizeTerminal(`${label}: cannot listen on ${host}:${options.port}: ${reason}
13815
+ `)
13816
+ );
13817
+ return 3;
13818
+ }
13819
+ }
13455
13820
  async function runWebMcpServe(platform2, options, version, resolvedBearer, generatedBearer) {
13456
- const cliPath = resolvePlaywrightCli(platform2, options.webmcpPlaywrightCli);
13821
+ const cliPath = await resolvePlaywrightCli(platform2, options.webmcpPlaywrightCli);
13457
13822
  if (!cliPath) {
13458
13823
  await platform2.write(
13459
13824
  2,
13460
- "mcpcat serve --webmcp: playwright-cli not found. Install it with: curl -fsSL https://get.sliccy.ai/playwright-cli | sh\n"
13825
+ "mcpcat serve --webmcp: playwright-cli not found on PATH. Install it with: curl -fsSL https://get.sliccy.ai/playwright-cli | sh\n"
13461
13826
  );
13462
13827
  return 3;
13463
13828
  }
@@ -13492,6 +13857,20 @@ async function runWebMcpServe(platform2, options, version, resolvedBearer, gener
13492
13857
  callTimeoutMs: options.callTimeout,
13493
13858
  maxOutput: options.maxOutput
13494
13859
  });
13860
+ try {
13861
+ return await serveWebMcpHandler(
13862
+ platform2,
13863
+ options,
13864
+ handler,
13865
+ version,
13866
+ resolvedBearer,
13867
+ generatedBearer
13868
+ );
13869
+ } finally {
13870
+ handler.close();
13871
+ }
13872
+ }
13873
+ async function serveWebMcpHandler(platform2, options, handler, version, resolvedBearer, generatedBearer) {
13495
13874
  const probe = await handler.getRpcTools();
13496
13875
  if (probe === null) {
13497
13876
  await platform2.write(
@@ -13501,7 +13880,7 @@ async function runWebMcpServe(platform2, options, version, resolvedBearer, gener
13501
13880
  return 3;
13502
13881
  }
13503
13882
  const serverName = "mcpcat-webmcp";
13504
- const getTools = async () => await handler.getRpcTools() ?? [];
13883
+ const getTools = async () => await handler.getRpcTools() ?? { tools: [] };
13505
13884
  const callTool = (name, args2) => handler.callTool(name, args2);
13506
13885
  if (options.transport === "stdio") {
13507
13886
  return runWebMcpStdio(platform2, getTools, callTool, serverName, version);
@@ -13532,8 +13911,7 @@ async function runWebMcpStdio(platform2, getTools, callTool, serverName, version
13532
13911
  const line = buffer.slice(0, nlIdx).trim();
13533
13912
  buffer = buffer.slice(nlIdx + 1);
13534
13913
  if (!line) continue;
13535
- const tools = await getTools();
13536
- const outcome = await handleJsonRpc(line, tools, callTool, serverName, version);
13914
+ const outcome = await handleJsonRpc(line, getTools, callTool, serverName, version);
13537
13915
  if (outcome.status === 202) continue;
13538
13916
  const writer = platform2.stdout.getWriter();
13539
13917
  await writer.write(enc.encode(`${outcome.body}
@@ -13560,7 +13938,8 @@ async function runWebMcpHttp(platform2, options, getTools, callTool, serverName,
13560
13938
  return 2;
13561
13939
  }
13562
13940
  const allowedOrigins = new Set(options.allowOrigins);
13563
- const listener = await platform2.listen({ host: options.host, port: options.port });
13941
+ const listener = await listenOrReport(platform2, options, "mcpcat serve --webmcp");
13942
+ if (typeof listener === "number") return listener;
13564
13943
  const actualPort = listener.port;
13565
13944
  const serverOrigins = buildServerOrigins(options.host, actualPort);
13566
13945
  const allowedHosts = buildAllowedHosts(options.host, actualPort, options.allowHosts);
@@ -13666,8 +14045,7 @@ async function runWebMcpHttp(platform2, options, getTools, callTool, serverName,
13666
14045
  };
13667
14046
  }
13668
14047
  }
13669
- const tools = await getTools();
13670
- const outcome = await handleJsonRpc(request.body, tools, callTool, serverName, version);
14048
+ const outcome = await handleJsonRpc(request.body, getTools, callTool, serverName, version);
13671
14049
  return {
13672
14050
  status: outcome.status,
13673
14051
  reason: outcome.status === 200 ? "OK" : "Accepted",
@@ -13755,10 +14133,11 @@ async function runProxyHttp(platform2, options, proxy, resolvedBearer, generated
13755
14133
  return 2;
13756
14134
  }
13757
14135
  const allowedOrigins = new Set(options.allowOrigins);
13758
- const listener = await platform2.listen({
13759
- host: options.host,
13760
- port: options.port
13761
- });
14136
+ const listener = await listenOrReport(platform2, options, "mcpcat serve --proxy");
14137
+ if (typeof listener === "number") {
14138
+ await proxy.shutdown();
14139
+ return listener;
14140
+ }
13762
14141
  const actualPort = listener.port;
13763
14142
  const serverOrigins = buildServerOrigins(options.host, actualPort);
13764
14143
  const allowedHosts = buildAllowedHosts(options.host, actualPort, options.allowHosts);
@@ -13939,7 +14318,8 @@ async function runHttpServer(platform2, options, tools, call, serverName, versio
13939
14318
  return 2;
13940
14319
  }
13941
14320
  const allowedOrigins = new Set(options.allowOrigins);
13942
- const listener = await platform2.listen({ host: options.host, port: options.port });
14321
+ const listener = await listenOrReport(platform2, options, "mcpcat serve");
14322
+ if (typeof listener === "number") return listener;
13943
14323
  const actualPort = listener.port;
13944
14324
  const serverOrigins = buildServerOrigins(options.host, actualPort);
13945
14325
  const allowedHosts = buildAllowedHosts(options.host, actualPort, options.allowHosts);
@@ -16071,6 +16451,11 @@ async function runCall(platform2, conn, toolName, dataArg, toolArgs, opts) {
16071
16451
  if (toolArgs.length > 0) {
16072
16452
  const tools = await conn.listTools();
16073
16453
  const tool = tools.find((t) => t.name === toolName);
16454
+ const hidden = tool ? void 0 : conn.hiddenTool?.(toolName);
16455
+ if (hidden) {
16456
+ await writeErr(platform2, opts.json ?? false, "usage", hidden);
16457
+ return EXIT_USAGE;
16458
+ }
16074
16459
  const schema = tool?.inputSchema ?? { type: "object", properties: {} };
16075
16460
  const skipRequired = opts.noValidate || !!dataArg;
16076
16461
  const c = coerceArgsBySchema(toolArgs, schema, { noValidate: skipRequired });
@@ -17225,6 +17610,34 @@ function nodeListen(options) {
17225
17610
  });
17226
17611
  });
17227
17612
  }
17613
+ function hostWebSocket(url) {
17614
+ return new Promise((resolve, reject) => {
17615
+ const ws = new WebSocket(url);
17616
+ ws.binaryType = "arraybuffer";
17617
+ let feed;
17618
+ const readable = new ReadableStream({
17619
+ start(controller) {
17620
+ feed = controller;
17621
+ }
17622
+ });
17623
+ ws.onclose = () => feed.close();
17624
+ ws.onerror = () => reject(new Error(`websocket ${url} failed`));
17625
+ ws.onmessage = (event) => {
17626
+ feed.enqueue(typeof event.data === "string" ? event.data : new Uint8Array(event.data));
17627
+ };
17628
+ ws.onopen = () => {
17629
+ resolve({
17630
+ readable,
17631
+ writable: new WritableStream({
17632
+ write(chunk) {
17633
+ ws.send(chunk);
17634
+ }
17635
+ }),
17636
+ close: () => ws.close()
17637
+ });
17638
+ };
17639
+ });
17640
+ }
17228
17641
  function createHostPlatform() {
17229
17642
  const encoder2 = new TextEncoder();
17230
17643
  return {
@@ -17232,6 +17645,7 @@ function createHostPlatform() {
17232
17645
  fetch: globalThis.fetch,
17233
17646
  spawn: nodeSpawn,
17234
17647
  listen: nodeListen,
17648
+ websocket: hostWebSocket,
17235
17649
  fs: hostFs,
17236
17650
  env: process.env,
17237
17651
  cwd: process.cwd(),
@@ -17269,5 +17683,5 @@ try {
17269
17683
  });
17270
17684
  } catch {
17271
17685
  }
17272
- var code = await run(platform, "0.20.2", args);
17686
+ var code = await run(platform, "0.20.4", args);
17273
17687
  process.exit(code);