@ai-ecoverse/mcpcat 0.4.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1708,6 +1708,185 @@ var StreamableHttpTransport = class extends TransportEvents {
1708
1708
  }
1709
1709
  };
1710
1710
 
1711
+ // src/client/platform-stdio-transport.ts
1712
+ var DEFAULT_MAX_MESSAGE_BYTES2 = 4 * 1024 * 1024;
1713
+ var DEFAULT_MAX_STDERR_BYTES2 = 64 * 1024;
1714
+ var DEFAULT_CLOSE_TIMEOUT_MS2 = 2e3;
1715
+ var STDIN_CLOSE_GRACE_MS2 = 500;
1716
+ var PlatformStdioTransport = class {
1717
+ options;
1718
+ process;
1719
+ started = false;
1720
+ closed = false;
1721
+ closeEmitted = false;
1722
+ stderrText = "";
1723
+ decoder = new TextDecoder();
1724
+ encoder = new TextEncoder();
1725
+ messageListeners = [];
1726
+ errorListeners = [];
1727
+ closeListeners = [];
1728
+ constructor(options) {
1729
+ this.options = options;
1730
+ }
1731
+ get stderr() {
1732
+ return this.stderrText;
1733
+ }
1734
+ onMessage(listener) {
1735
+ this.messageListeners.push(listener);
1736
+ return () => {
1737
+ this.messageListeners = this.messageListeners.filter((l) => l !== listener);
1738
+ };
1739
+ }
1740
+ onError(listener) {
1741
+ this.errorListeners.push(listener);
1742
+ return () => {
1743
+ this.errorListeners = this.errorListeners.filter((l) => l !== listener);
1744
+ };
1745
+ }
1746
+ onClose(listener) {
1747
+ this.closeListeners.push(listener);
1748
+ return () => {
1749
+ this.closeListeners = this.closeListeners.filter((l) => l !== listener);
1750
+ };
1751
+ }
1752
+ emitMessage(message) {
1753
+ for (const l of [...this.messageListeners]) l(message);
1754
+ }
1755
+ emitError(error) {
1756
+ for (const l of [...this.errorListeners]) l(error);
1757
+ }
1758
+ emitClose() {
1759
+ if (this.closeEmitted) return;
1760
+ this.closeEmitted = true;
1761
+ for (const l of [...this.closeListeners]) l();
1762
+ }
1763
+ async start() {
1764
+ if (this.started) throw new Error("MCP stdio transport already started");
1765
+ if (this.closed) throw new Error("MCP connection closed");
1766
+ this.started = true;
1767
+ const env = this.options.inheritEnv === false ? { ...this.options.env ?? {} } : {
1768
+ ...this.options.platform.env,
1769
+ ...this.options.env
1770
+ };
1771
+ this.process = await this.options.platform.spawn({
1772
+ argv: [this.options.command, ...this.options.args ?? []],
1773
+ env,
1774
+ cwd: this.options.cwd
1775
+ });
1776
+ void this.readStdout();
1777
+ void this.readStderr();
1778
+ void this.waitForExit();
1779
+ }
1780
+ async send(message) {
1781
+ if (!this.started || this.closed || !this.process) throw new Error("MCP connection closed");
1782
+ const payload = `${JSON.stringify(message)}
1783
+ `;
1784
+ const writer = this.process.stdin.getWriter();
1785
+ try {
1786
+ await writer.write(this.encoder.encode(payload));
1787
+ } finally {
1788
+ writer.releaseLock();
1789
+ }
1790
+ }
1791
+ async close() {
1792
+ if (this.closed) return;
1793
+ this.closed = true;
1794
+ const proc = this.process;
1795
+ if (!proc) {
1796
+ this.emitClose();
1797
+ return;
1798
+ }
1799
+ const closeTimeoutMs = this.options.closeTimeoutMs ?? DEFAULT_CLOSE_TIMEOUT_MS2;
1800
+ const grace = Math.min(STDIN_CLOSE_GRACE_MS2, closeTimeoutMs);
1801
+ const done = proc.wait().then(() => true);
1802
+ const delay = (ms) => new Promise((r) => setTimeout(() => r(false), ms));
1803
+ try {
1804
+ await proc.stdin.close();
1805
+ } catch {
1806
+ }
1807
+ const exited = await Promise.race([done, delay(grace)]);
1808
+ if (!exited) {
1809
+ proc.kill("SIGTERM");
1810
+ const exitedAfterTerm = await Promise.race([done, delay(closeTimeoutMs)]);
1811
+ if (!exitedAfterTerm) {
1812
+ proc.kill("SIGKILL");
1813
+ await done;
1814
+ }
1815
+ }
1816
+ proc.kill("SIGTERM");
1817
+ this.emitClose();
1818
+ }
1819
+ async readStdout() {
1820
+ const proc = this.process;
1821
+ if (!proc) return;
1822
+ const reader = proc.stdout.getReader();
1823
+ const maxBytes = this.options.maxMessageBytes ?? DEFAULT_MAX_MESSAGE_BYTES2;
1824
+ let buffer = "";
1825
+ try {
1826
+ for (; ; ) {
1827
+ const { done, value } = await reader.read();
1828
+ if (done) break;
1829
+ buffer += this.decoder.decode(value, { stream: true });
1830
+ let nlIdx = buffer.indexOf("\n");
1831
+ while (nlIdx >= 0) {
1832
+ const line = buffer.slice(0, nlIdx).replace(/\r$/, "");
1833
+ buffer = buffer.slice(nlIdx + 1);
1834
+ if (line.trim()) {
1835
+ try {
1836
+ this.emitMessage(parseJsonRpcMessage(JSON.parse(line)));
1837
+ } catch (err) {
1838
+ this.emitError(err);
1839
+ }
1840
+ }
1841
+ nlIdx = buffer.indexOf("\n");
1842
+ }
1843
+ if (this.encoder.encode(buffer).length > maxBytes) {
1844
+ buffer = "";
1845
+ this.emitError(new Error(`MCP stdio message exceeds ${maxBytes} bytes`));
1846
+ }
1847
+ }
1848
+ buffer += this.decoder.decode();
1849
+ if (buffer.trim()) {
1850
+ this.emitError(new Error("MCP stdio server closed with an incomplete JSON-RPC message"));
1851
+ }
1852
+ } catch (err) {
1853
+ if (!this.closed) this.emitError(err);
1854
+ } finally {
1855
+ reader.releaseLock();
1856
+ }
1857
+ }
1858
+ async readStderr() {
1859
+ const proc = this.process;
1860
+ if (!proc) return;
1861
+ const reader = proc.stderr.getReader();
1862
+ const maxBytes = this.options.maxStderrBytes ?? DEFAULT_MAX_STDERR_BYTES2;
1863
+ try {
1864
+ for (; ; ) {
1865
+ const { done, value } = await reader.read();
1866
+ if (done) break;
1867
+ const chunk = this.decoder.decode(value, { stream: true });
1868
+ this.stderrText += chunk;
1869
+ if (this.encoder.encode(this.stderrText).length > maxBytes) {
1870
+ this.stderrText = this.stderrText.slice(-maxBytes);
1871
+ }
1872
+ this.options.onStderr?.(chunk);
1873
+ }
1874
+ } catch {
1875
+ }
1876
+ }
1877
+ async waitForExit() {
1878
+ const proc = this.process;
1879
+ if (!proc) return;
1880
+ try {
1881
+ await proc.wait();
1882
+ } catch {
1883
+ }
1884
+ if (!this.closed) {
1885
+ this.emitClose();
1886
+ }
1887
+ }
1888
+ };
1889
+
1711
1890
  // src/client/pi-connection.ts
1712
1891
  async function createPiConnection(options) {
1713
1892
  const clientOptions = {
@@ -1719,17 +1898,29 @@ async function createPiConnection(options) {
1719
1898
  if (options.url) {
1720
1899
  transport = new StreamableHttpTransport({
1721
1900
  url: options.url,
1722
- headers: options.headers
1901
+ headers: options.headers,
1902
+ fetch: options.fetchImpl
1723
1903
  });
1724
1904
  } else if (options.command) {
1725
- transport = new StdioTransport({
1726
- command: options.command,
1727
- args: options.args,
1728
- env: options.env,
1729
- cwd: options.cwd,
1730
- inheritEnv: true,
1731
- stderr: "pipe"
1732
- });
1905
+ if (options.platform) {
1906
+ transport = new PlatformStdioTransport({
1907
+ command: options.command,
1908
+ args: options.args,
1909
+ env: options.env,
1910
+ cwd: options.cwd,
1911
+ inheritEnv: true,
1912
+ platform: options.platform
1913
+ });
1914
+ } else {
1915
+ transport = new StdioTransport({
1916
+ command: options.command,
1917
+ args: options.args,
1918
+ env: options.env,
1919
+ cwd: options.cwd,
1920
+ inheritEnv: true,
1921
+ stderr: "pipe"
1922
+ });
1923
+ }
1733
1924
  } else {
1734
1925
  throw new Error("PiConnection requires either a url or a command");
1735
1926
  }
@@ -1943,7 +2134,8 @@ async function connect(platform2, options) {
1943
2134
  args: options.args,
1944
2135
  env: options.env,
1945
2136
  cwd: options.cwd,
1946
- version: options.version
2137
+ version: options.version,
2138
+ platform: platform2
1947
2139
  });
1948
2140
  }
1949
2141
  if (!options.url) {
@@ -1953,7 +2145,9 @@ async function connect(platform2, options) {
1953
2145
  return createPiConnection({
1954
2146
  url: options.url,
1955
2147
  headers: options.headers,
1956
- version: options.version
2148
+ version: options.version,
2149
+ fetchImpl: platform2.fetch,
2150
+ platform: platform2
1957
2151
  });
1958
2152
  }
1959
2153
  try {
@@ -1972,7 +2166,9 @@ async function connect(platform2, options) {
1972
2166
  return createPiConnection({
1973
2167
  url: options.url,
1974
2168
  headers: options.headers,
1975
- version: options.version
2169
+ version: options.version,
2170
+ fetchImpl: platform2.fetch,
2171
+ platform: platform2
1976
2172
  });
1977
2173
  }
1978
2174
  throw err;
@@ -4905,6 +5101,21 @@ function renderCallResult(result) {
4905
5101
  }
4906
5102
  return parts.join("\n");
4907
5103
  }
5104
+ var NETWORK_HINT = "connection failed (use slicc-node, slicc-swift, or the browser extension for servers without CORS)";
5105
+ function transportHint(err) {
5106
+ if (err instanceof TypeError) {
5107
+ const cause = err.cause;
5108
+ if (cause?.code === "ECONNREFUSED" || cause?.code === "ENETUNREACH") {
5109
+ return `connection failed: ${err.message}`;
5110
+ }
5111
+ }
5112
+ if (err instanceof Error && "status" in err && err.status === 502) {
5113
+ const msg = err.message;
5114
+ if (msg) return `${NETWORK_HINT}: ${msg}`;
5115
+ return NETWORK_HINT;
5116
+ }
5117
+ return void 0;
5118
+ }
4908
5119
  async function readDataArg(platform2, dataArg) {
4909
5120
  let raw;
4910
5121
  if (dataArg.startsWith("@-")) {
@@ -5146,7 +5357,8 @@ async function run(platform2, version, args2) {
5146
5357
  version
5147
5358
  });
5148
5359
  } catch (err) {
5149
- await platform2.write(2, `mcpcat: connection failed: ${err.message}
5360
+ const msg = transportHint(err) ?? `connection failed: ${err.message}`;
5361
+ await platform2.write(2, `mcpcat: ${msg}
5150
5362
  `);
5151
5363
  return 3;
5152
5364
  }
@@ -5176,11 +5388,13 @@ import { access, mkdir, readFile as readFile2, rename, rmdir, stat, writeFile }
5176
5388
  import { createServer } from "node:net";
5177
5389
  import { Readable, Writable } from "node:stream";
5178
5390
  import * as tty from "node:tty";
5391
+ var USE_PROCESS_GROUPS2 = process.platform !== "win32";
5179
5392
  function nodeSpawn(options) {
5180
5393
  const child = cpSpawn(options.argv[0], options.argv.slice(1), {
5181
5394
  env: options.env ?? process.env,
5182
5395
  cwd: options.cwd,
5183
- stdio: ["pipe", "pipe", "pipe"]
5396
+ stdio: ["pipe", "pipe", "pipe"],
5397
+ detached: USE_PROCESS_GROUPS2
5184
5398
  });
5185
5399
  const stdout = Readable.toWeb(child.stdout);
5186
5400
  const stderr = Readable.toWeb(child.stderr);
@@ -5200,7 +5414,19 @@ function nodeSpawn(options) {
5200
5414
  });
5201
5415
  },
5202
5416
  kill(signal) {
5203
- child.kill(signal ?? "SIGTERM");
5417
+ const sig = signal ?? "SIGTERM";
5418
+ const pid = child.pid;
5419
+ if (USE_PROCESS_GROUPS2 && pid !== void 0) {
5420
+ try {
5421
+ process.kill(-pid, sig);
5422
+ return;
5423
+ } catch {
5424
+ }
5425
+ }
5426
+ try {
5427
+ child.kill(sig);
5428
+ } catch {
5429
+ }
5204
5430
  }
5205
5431
  });
5206
5432
  }
@@ -5354,5 +5580,5 @@ function createHostPlatform() {
5354
5580
  // src/host.ts
5355
5581
  var platform = createHostPlatform();
5356
5582
  var args = process.argv.slice(2);
5357
- var code = await run(platform, "0.4.0", args);
5583
+ var code = await run(platform, "0.5.0", args);
5358
5584
  process.exit(code);
@@ -1,3 +1,5 @@
1
+ var __mcpcat_byteLength = (s) => new TextEncoder().encode(s).length;
2
+
1
3
  // node_modules/@earendil-works/pi-mcp/dist/protocol/jsonrpc.js
2
4
  var JSON_RPC_ERROR_CODES = {
3
5
  parseError: -32700,
@@ -772,7 +774,7 @@ async function consumeSseStream(stream, options) {
772
774
  if (value.startsWith(" "))
773
775
  value = value.slice(1);
774
776
  if (field === "data") {
775
- dataBytes += Buffer.byteLength(value) + (dataLines.length > 0 ? 1 : 0);
777
+ dataBytes += __mcpcat_byteLength(value) + (dataLines.length > 0 ? 1 : 0);
776
778
  if (dataBytes > maxEventBytes)
777
779
  throw new Error(`MCP SSE event exceeds ${maxEventBytes} bytes`);
778
780
  dataLines.push(value);
@@ -796,7 +798,7 @@ async function consumeSseStream(stream, options) {
796
798
  buffered = buffered.slice(newline + 1);
797
799
  newline = buffered.indexOf("\n");
798
800
  }
799
- if (Buffer.byteLength(buffered) > maxEventBytes)
801
+ if (__mcpcat_byteLength(buffered) > maxEventBytes)
800
802
  throw new Error(`MCP SSE event exceeds ${maxEventBytes} bytes`);
801
803
  }
802
804
  buffered += decoder2.decode();
@@ -1173,6 +1175,185 @@ var StreamableHttpTransport = class extends TransportEvents {
1173
1175
  }
1174
1176
  };
1175
1177
 
1178
+ // src/client/platform-stdio-transport.ts
1179
+ var DEFAULT_MAX_MESSAGE_BYTES2 = 4 * 1024 * 1024;
1180
+ var DEFAULT_MAX_STDERR_BYTES2 = 64 * 1024;
1181
+ var DEFAULT_CLOSE_TIMEOUT_MS2 = 2e3;
1182
+ var STDIN_CLOSE_GRACE_MS2 = 500;
1183
+ var PlatformStdioTransport = class {
1184
+ options;
1185
+ process;
1186
+ started = false;
1187
+ closed = false;
1188
+ closeEmitted = false;
1189
+ stderrText = "";
1190
+ decoder = new TextDecoder();
1191
+ encoder = new TextEncoder();
1192
+ messageListeners = [];
1193
+ errorListeners = [];
1194
+ closeListeners = [];
1195
+ constructor(options) {
1196
+ this.options = options;
1197
+ }
1198
+ get stderr() {
1199
+ return this.stderrText;
1200
+ }
1201
+ onMessage(listener) {
1202
+ this.messageListeners.push(listener);
1203
+ return () => {
1204
+ this.messageListeners = this.messageListeners.filter((l) => l !== listener);
1205
+ };
1206
+ }
1207
+ onError(listener) {
1208
+ this.errorListeners.push(listener);
1209
+ return () => {
1210
+ this.errorListeners = this.errorListeners.filter((l) => l !== listener);
1211
+ };
1212
+ }
1213
+ onClose(listener) {
1214
+ this.closeListeners.push(listener);
1215
+ return () => {
1216
+ this.closeListeners = this.closeListeners.filter((l) => l !== listener);
1217
+ };
1218
+ }
1219
+ emitMessage(message) {
1220
+ for (const l of [...this.messageListeners]) l(message);
1221
+ }
1222
+ emitError(error) {
1223
+ for (const l of [...this.errorListeners]) l(error);
1224
+ }
1225
+ emitClose() {
1226
+ if (this.closeEmitted) return;
1227
+ this.closeEmitted = true;
1228
+ for (const l of [...this.closeListeners]) l();
1229
+ }
1230
+ async start() {
1231
+ if (this.started) throw new Error("MCP stdio transport already started");
1232
+ if (this.closed) throw new Error("MCP connection closed");
1233
+ this.started = true;
1234
+ const env = this.options.inheritEnv === false ? { ...this.options.env ?? {} } : {
1235
+ ...this.options.platform.env,
1236
+ ...this.options.env
1237
+ };
1238
+ this.process = await this.options.platform.spawn({
1239
+ argv: [this.options.command, ...this.options.args ?? []],
1240
+ env,
1241
+ cwd: this.options.cwd
1242
+ });
1243
+ void this.readStdout();
1244
+ void this.readStderr();
1245
+ void this.waitForExit();
1246
+ }
1247
+ async send(message) {
1248
+ if (!this.started || this.closed || !this.process) throw new Error("MCP connection closed");
1249
+ const payload = `${JSON.stringify(message)}
1250
+ `;
1251
+ const writer = this.process.stdin.getWriter();
1252
+ try {
1253
+ await writer.write(this.encoder.encode(payload));
1254
+ } finally {
1255
+ writer.releaseLock();
1256
+ }
1257
+ }
1258
+ async close() {
1259
+ if (this.closed) return;
1260
+ this.closed = true;
1261
+ const proc = this.process;
1262
+ if (!proc) {
1263
+ this.emitClose();
1264
+ return;
1265
+ }
1266
+ const closeTimeoutMs = this.options.closeTimeoutMs ?? DEFAULT_CLOSE_TIMEOUT_MS2;
1267
+ const grace = Math.min(STDIN_CLOSE_GRACE_MS2, closeTimeoutMs);
1268
+ const done = proc.wait().then(() => true);
1269
+ const delay = (ms) => new Promise((r) => setTimeout(() => r(false), ms));
1270
+ try {
1271
+ await proc.stdin.close();
1272
+ } catch {
1273
+ }
1274
+ const exited = await Promise.race([done, delay(grace)]);
1275
+ if (!exited) {
1276
+ proc.kill("SIGTERM");
1277
+ const exitedAfterTerm = await Promise.race([done, delay(closeTimeoutMs)]);
1278
+ if (!exitedAfterTerm) {
1279
+ proc.kill("SIGKILL");
1280
+ await done;
1281
+ }
1282
+ }
1283
+ proc.kill("SIGTERM");
1284
+ this.emitClose();
1285
+ }
1286
+ async readStdout() {
1287
+ const proc = this.process;
1288
+ if (!proc) return;
1289
+ const reader = proc.stdout.getReader();
1290
+ const maxBytes = this.options.maxMessageBytes ?? DEFAULT_MAX_MESSAGE_BYTES2;
1291
+ let buffer = "";
1292
+ try {
1293
+ for (; ; ) {
1294
+ const { done, value } = await reader.read();
1295
+ if (done) break;
1296
+ buffer += this.decoder.decode(value, { stream: true });
1297
+ let nlIdx = buffer.indexOf("\n");
1298
+ while (nlIdx >= 0) {
1299
+ const line = buffer.slice(0, nlIdx).replace(/\r$/, "");
1300
+ buffer = buffer.slice(nlIdx + 1);
1301
+ if (line.trim()) {
1302
+ try {
1303
+ this.emitMessage(parseJsonRpcMessage(JSON.parse(line)));
1304
+ } catch (err) {
1305
+ this.emitError(err);
1306
+ }
1307
+ }
1308
+ nlIdx = buffer.indexOf("\n");
1309
+ }
1310
+ if (this.encoder.encode(buffer).length > maxBytes) {
1311
+ buffer = "";
1312
+ this.emitError(new Error(`MCP stdio message exceeds ${maxBytes} bytes`));
1313
+ }
1314
+ }
1315
+ buffer += this.decoder.decode();
1316
+ if (buffer.trim()) {
1317
+ this.emitError(new Error("MCP stdio server closed with an incomplete JSON-RPC message"));
1318
+ }
1319
+ } catch (err) {
1320
+ if (!this.closed) this.emitError(err);
1321
+ } finally {
1322
+ reader.releaseLock();
1323
+ }
1324
+ }
1325
+ async readStderr() {
1326
+ const proc = this.process;
1327
+ if (!proc) return;
1328
+ const reader = proc.stderr.getReader();
1329
+ const maxBytes = this.options.maxStderrBytes ?? DEFAULT_MAX_STDERR_BYTES2;
1330
+ try {
1331
+ for (; ; ) {
1332
+ const { done, value } = await reader.read();
1333
+ if (done) break;
1334
+ const chunk = this.decoder.decode(value, { stream: true });
1335
+ this.stderrText += chunk;
1336
+ if (this.encoder.encode(this.stderrText).length > maxBytes) {
1337
+ this.stderrText = this.stderrText.slice(-maxBytes);
1338
+ }
1339
+ this.options.onStderr?.(chunk);
1340
+ }
1341
+ } catch {
1342
+ }
1343
+ }
1344
+ async waitForExit() {
1345
+ const proc = this.process;
1346
+ if (!proc) return;
1347
+ try {
1348
+ await proc.wait();
1349
+ } catch {
1350
+ }
1351
+ if (!this.closed) {
1352
+ this.emitClose();
1353
+ }
1354
+ }
1355
+ };
1356
+
1176
1357
  // src/client/pi-connection.ts
1177
1358
  async function createPiConnection(options) {
1178
1359
  const clientOptions = {
@@ -1184,17 +1365,29 @@ async function createPiConnection(options) {
1184
1365
  if (options.url) {
1185
1366
  transport = new StreamableHttpTransport({
1186
1367
  url: options.url,
1187
- headers: options.headers
1368
+ headers: options.headers,
1369
+ fetch: options.fetchImpl
1188
1370
  });
1189
1371
  } else if (options.command) {
1190
- transport = new StdioTransport({
1191
- command: options.command,
1192
- args: options.args,
1193
- env: options.env,
1194
- cwd: options.cwd,
1195
- inheritEnv: true,
1196
- stderr: "pipe"
1197
- });
1372
+ if (options.platform) {
1373
+ transport = new PlatformStdioTransport({
1374
+ command: options.command,
1375
+ args: options.args,
1376
+ env: options.env,
1377
+ cwd: options.cwd,
1378
+ inheritEnv: true,
1379
+ platform: options.platform
1380
+ });
1381
+ } else {
1382
+ transport = new StdioTransport({
1383
+ command: options.command,
1384
+ args: options.args,
1385
+ env: options.env,
1386
+ cwd: options.cwd,
1387
+ inheritEnv: true,
1388
+ stderr: "pipe"
1389
+ });
1390
+ }
1198
1391
  } else {
1199
1392
  throw new Error("PiConnection requires either a url or a command");
1200
1393
  }
@@ -1408,7 +1601,8 @@ async function connect(platform, options) {
1408
1601
  args: options.args,
1409
1602
  env: options.env,
1410
1603
  cwd: options.cwd,
1411
- version: options.version
1604
+ version: options.version,
1605
+ platform
1412
1606
  });
1413
1607
  }
1414
1608
  if (!options.url) {
@@ -1418,7 +1612,9 @@ async function connect(platform, options) {
1418
1612
  return createPiConnection({
1419
1613
  url: options.url,
1420
1614
  headers: options.headers,
1421
- version: options.version
1615
+ version: options.version,
1616
+ fetchImpl: platform.fetch,
1617
+ platform
1422
1618
  });
1423
1619
  }
1424
1620
  try {
@@ -1437,7 +1633,9 @@ async function connect(platform, options) {
1437
1633
  return createPiConnection({
1438
1634
  url: options.url,
1439
1635
  headers: options.headers,
1440
- version: options.version
1636
+ version: options.version,
1637
+ fetchImpl: platform.fetch,
1638
+ platform
1441
1639
  });
1442
1640
  }
1443
1641
  throw err;
@@ -4370,6 +4568,21 @@ function renderCallResult(result) {
4370
4568
  }
4371
4569
  return parts.join("\n");
4372
4570
  }
4571
+ var NETWORK_HINT = "connection failed (use slicc-node, slicc-swift, or the browser extension for servers without CORS)";
4572
+ function transportHint(err) {
4573
+ if (err instanceof TypeError) {
4574
+ const cause = err.cause;
4575
+ if (cause?.code === "ECONNREFUSED" || cause?.code === "ENETUNREACH") {
4576
+ return `connection failed: ${err.message}`;
4577
+ }
4578
+ }
4579
+ if (err instanceof Error && "status" in err && err.status === 502) {
4580
+ const msg = err.message;
4581
+ if (msg) return `${NETWORK_HINT}: ${msg}`;
4582
+ return NETWORK_HINT;
4583
+ }
4584
+ return void 0;
4585
+ }
4373
4586
  async function readDataArg(platform, dataArg) {
4374
4587
  let raw;
4375
4588
  if (dataArg.startsWith("@-")) {
@@ -4611,7 +4824,8 @@ async function run(platform, version, args) {
4611
4824
  version
4612
4825
  });
4613
4826
  } catch (err) {
4614
- await platform.write(2, `mcpcat: connection failed: ${err.message}
4827
+ const msg = transportHint(err) ?? `connection failed: ${err.message}`;
4828
+ await platform.write(2, `mcpcat: ${msg}
4615
4829
  `);
4616
4830
  return 3;
4617
4831
  }
@@ -4664,12 +4878,22 @@ async function kernelSpawn(ctx, options) {
4664
4878
  stdout: "pipe",
4665
4879
  stderr: "pipe"
4666
4880
  });
4881
+ const childPid = result.pid;
4667
4882
  return {
4668
4883
  stdin: result.stdin,
4669
4884
  stdout: result.stdout,
4670
4885
  stderr: result.stderr,
4671
4886
  wait: () => result.wait(),
4672
- kill: (signal) => result.kill(signal)
4887
+ kill: (signal) => {
4888
+ try {
4889
+ ctx.kill(-childPid, signal);
4890
+ } catch {
4891
+ }
4892
+ try {
4893
+ ctx.kill(childPid, signal);
4894
+ } catch {
4895
+ }
4896
+ }
4673
4897
  };
4674
4898
  }
4675
4899
  async function kernelListen(ctx, options) {
@@ -4735,7 +4959,7 @@ function createKernelPlatform(ctx) {
4735
4959
  async function main(ctx) {
4736
4960
  const platform = createKernelPlatform(ctx);
4737
4961
  const args = ctx.argv.slice(1);
4738
- return run(platform, "0.4.0", args);
4962
+ return run(platform, "0.5.0", args);
4739
4963
  }
4740
4964
  export {
4741
4965
  main as default
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ai-ecoverse/mcpcat",
3
- "version": "0.4.0",
3
+ "version": "0.5.0",
4
4
  "description": "A curl for the Model Context Protocol",
5
5
  "type": "module",
6
6
  "license": "Apache-2.0",
@@ -17,7 +17,7 @@
17
17
  },
18
18
  "engines": {
19
19
  "node": ">=24",
20
- "slicc-kernel": ">=1.64.2"
20
+ "slicc-kernel": ">=1.65.2"
21
21
  },
22
22
  "publishConfig": {
23
23
  "access": "public",
@@ -46,9 +46,10 @@
46
46
  "build": "node scripts/build.mjs",
47
47
  "lint": "sh scripts/lint.sh",
48
48
  "typecheck": "tsc",
49
- "test": "npm run test:unit && npm run test:integration",
49
+ "test": "npm run test:unit && npm run test:integration && npm run test:kernel-min",
50
50
  "test:unit": "mcr -c node_modules/@ai-ecoverse/slicc-shared-web/mcr.config.mjs node --test 'test/unit/**/*.test.mjs'",
51
51
  "test:integration": "node --test --test-timeout=30000 test/integration/*.test.mjs",
52
+ "test:kernel-min": "node scripts/test-kernel-min.mjs",
52
53
  "cert": "node --test --test-timeout=120000 test/integration/cert.test.mjs test/integration/serve-soak.cert.mjs",
53
54
  "release": "semantic-release",
54
55
  "trust": "npx fledgling sync -y --skip-publish",