@ai-ecoverse/mcpcat 0.4.0 → 0.6.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.
@@ -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
  }
@@ -1282,10 +1475,10 @@ async function createStatelessConnection(options) {
1282
1475
  timeoutMs = 6e4,
1283
1476
  clientInfo = { name: "mcpcat", version: "0.0.0" }
1284
1477
  } = options;
1285
- let nextId = 1;
1478
+ let nextId2 = 1;
1286
1479
  let negotiatedVersion;
1287
1480
  async function request(method, params) {
1288
- const id = nextId++;
1481
+ const id = nextId2++;
1289
1482
  const headers = {
1290
1483
  "Content-Type": "application/json",
1291
1484
  Accept: "application/json, text/event-stream",
@@ -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;
@@ -1798,65 +1996,981 @@ async function loadConfig(input) {
1798
1996
  loadProjectServers(parsed, input.projectPath, servers, problems);
1799
1997
  }
1800
1998
  }
1801
- return {
1802
- servers: [...servers.values()],
1803
- problems,
1804
- ...autoEnableCodemode !== void 0 ? { autoEnableCodemode } : {}
1805
- };
1806
- }
1807
-
1808
- // src/config/paths.ts
1809
- function userConfigDir(platform) {
1810
- const piDir = platform.env.PI_CODING_AGENT_DIR;
1811
- if (piDir) return piDir;
1812
- const home = platform.env.HOME ?? platform.env.USERPROFILE ?? "/";
1813
- return `${home}/.pi/agent`;
1814
- }
1815
- function userConfigPath(platform) {
1816
- return `${userConfigDir(platform)}/mcp.json`;
1817
- }
1818
- function projectConfigPath(platform) {
1819
- return `${platform.cwd}/.pi/mcp.json`;
1999
+ return {
2000
+ servers: [...servers.values()],
2001
+ problems,
2002
+ ...autoEnableCodemode !== void 0 ? { autoEnableCodemode } : {}
2003
+ };
2004
+ }
2005
+
2006
+ // src/config/paths.ts
2007
+ function userConfigDir(platform) {
2008
+ const piDir = platform.env.PI_CODING_AGENT_DIR;
2009
+ if (piDir) return piDir;
2010
+ const home = platform.env.HOME ?? platform.env.USERPROFILE ?? "/";
2011
+ return `${home}/.pi/agent`;
2012
+ }
2013
+ function userConfigPath(platform) {
2014
+ return `${userConfigDir(platform)}/mcp.json`;
2015
+ }
2016
+ function projectConfigPath(platform) {
2017
+ return `${platform.cwd}/.pi/mcp.json`;
2018
+ }
2019
+ function stateDir(platform) {
2020
+ if (platform.env.XDG_STATE_HOME) return `${platform.env.XDG_STATE_HOME}/mcpcat`;
2021
+ const home = platform.env.HOME ?? platform.env.USERPROFILE ?? "/";
2022
+ if (platform.env.LOCALAPPDATA) return `${platform.env.LOCALAPPDATA}\\mcpcat\\state`;
2023
+ if (platform.env.SLICC_KERNEL) return `${home}/.local/state/mcpcat`;
2024
+ return `${home}/Library/Application Support/mcpcat`;
2025
+ }
2026
+
2027
+ // src/config/index.ts
2028
+ var MergedConfigSource = class _MergedConfigSource {
2029
+ entries;
2030
+ config;
2031
+ constructor(config) {
2032
+ this.config = config;
2033
+ this.entries = /* @__PURE__ */ new Map();
2034
+ for (const entry of config.servers) {
2035
+ this.entries.set(entry.name, entry);
2036
+ }
2037
+ }
2038
+ async get(name) {
2039
+ const normalizedName = name.replace(/[-_]/g, "").toLowerCase();
2040
+ for (const [key, entry] of this.entries) {
2041
+ if (key.replace(/[-_]/g, "").toLowerCase() === normalizedName) {
2042
+ return entry.config;
2043
+ }
2044
+ }
2045
+ return void 0;
2046
+ }
2047
+ entry(name) {
2048
+ return this.entries.get(name);
2049
+ }
2050
+ list() {
2051
+ return [...this.entries.values()];
2052
+ }
2053
+ static async load(platform, options) {
2054
+ const userPath = options?.configFile ?? userConfigPath(platform);
2055
+ const projectPath = options?.configFile ? void 0 : projectConfigPath(platform);
2056
+ const config = await loadConfig({
2057
+ userPath,
2058
+ projectPath,
2059
+ trustProject: options?.trustProject ?? false,
2060
+ fs: platform.fs
2061
+ });
2062
+ return new _MergedConfigSource(config);
2063
+ }
2064
+ };
2065
+
2066
+ // src/config/lock.ts
2067
+ var LOCK_STALE_MS = 1e4;
2068
+ var LOCK_POLL_MS = 50;
2069
+ var LOCK_MAX_WAIT_MS = 5e3;
2070
+ function isEexist(err) {
2071
+ if (err instanceof Error && "code" in err)
2072
+ return err.code === "EEXIST";
2073
+ if (err instanceof Error) return err.message.includes("EEXIST");
2074
+ return false;
2075
+ }
2076
+ async function removeStaleLock(fs, lockPath) {
2077
+ try {
2078
+ const s = await fs.stat(lockPath);
2079
+ if (Date.now() - s.mtimeMs > LOCK_STALE_MS) {
2080
+ try {
2081
+ await fs.rmdir(lockPath);
2082
+ } catch {
2083
+ }
2084
+ return true;
2085
+ }
2086
+ } catch {
2087
+ }
2088
+ return false;
2089
+ }
2090
+ async function acquireLock(fs, configPath) {
2091
+ const lockPath = `${configPath}.lock`;
2092
+ const start = Date.now();
2093
+ while (true) {
2094
+ try {
2095
+ await fs.mkdirExclusive(lockPath);
2096
+ break;
2097
+ } catch (err) {
2098
+ if (!isEexist(err)) throw err;
2099
+ await removeStaleLock(fs, lockPath);
2100
+ }
2101
+ if (Date.now() - start > LOCK_MAX_WAIT_MS) {
2102
+ throw new Error(`mcpcat: could not acquire lock ${lockPath} within ${LOCK_MAX_WAIT_MS}ms`);
2103
+ }
2104
+ await new Promise((r) => setTimeout(r, LOCK_POLL_MS));
2105
+ }
2106
+ let released = false;
2107
+ return async () => {
2108
+ if (released) return;
2109
+ released = true;
2110
+ try {
2111
+ await fs.rmdir(lockPath);
2112
+ } catch {
2113
+ }
2114
+ };
2115
+ }
2116
+
2117
+ // src/config/edit.ts
2118
+ function isRecord2(value) {
2119
+ return typeof value === "object" && value !== null && !Array.isArray(value);
2120
+ }
2121
+ function detectIndent(text) {
2122
+ const match = /^([ \t]+)\S/m.exec(text);
2123
+ return match ? match[1] : " ";
2124
+ }
2125
+ async function readText(fs, path) {
2126
+ try {
2127
+ const data = await fs.readFile(path);
2128
+ return new TextDecoder().decode(data);
2129
+ } catch {
2130
+ return void 0;
2131
+ }
2132
+ }
2133
+ async function editMcpServers(fs, path, edit) {
2134
+ const release = await acquireLock(fs, path);
2135
+ try {
2136
+ const text = await readText(fs, path);
2137
+ const parsed = text === void 0 ? {} : JSON.parse(text);
2138
+ if (!isRecord2(parsed) || parsed.mcpServers !== void 0 && !isRecord2(parsed.mcpServers))
2139
+ throw new Error(`${path}: expected an object with an "mcpServers" object`);
2140
+ const servers = isRecord2(parsed.mcpServers) ? parsed.mcpServers : void 0;
2141
+ if (!edit(servers, parsed)) return { changed: false };
2142
+ const indent = text ? detectIndent(text) : " ";
2143
+ const output = `${JSON.stringify(parsed, null, indent)}
2144
+ `;
2145
+ const dir = path.slice(0, path.lastIndexOf("/"));
2146
+ if (dir) {
2147
+ try {
2148
+ await fs.mkdir(dir);
2149
+ } catch {
2150
+ }
2151
+ }
2152
+ const tmpPath = `${path}.tmp.${Date.now()}`;
2153
+ await fs.writeFile(tmpPath, output);
2154
+ await fs.rename(tmpPath, path);
2155
+ return { changed: true };
2156
+ } finally {
2157
+ await release();
2158
+ }
2159
+ }
2160
+ async function addServer(fs, path, name, config) {
2161
+ let replaced = false;
2162
+ await editMcpServers(fs, path, (servers, parsed) => {
2163
+ const target = servers ?? {};
2164
+ replaced = target[name] !== void 0;
2165
+ target[name] = config;
2166
+ parsed.mcpServers = target;
2167
+ return true;
2168
+ });
2169
+ return { replaced };
2170
+ }
2171
+ async function removeServer(fs, path, name) {
2172
+ let removed = false;
2173
+ await editMcpServers(fs, path, (servers) => {
2174
+ if (!servers || servers[name] === void 0) return false;
2175
+ delete servers[name];
2176
+ removed = true;
2177
+ return true;
2178
+ });
2179
+ return removed;
2180
+ }
2181
+ async function updateServer(fs, path, name, patch) {
2182
+ let updated = false;
2183
+ await editMcpServers(fs, path, (servers) => {
2184
+ if (!servers || !isRecord2(servers[name])) return false;
2185
+ const entry = servers[name];
2186
+ if (patch.enabled !== void 0) entry.enabled = patch.enabled;
2187
+ if (patch.exposure !== void 0) entry.exposure = patch.exposure;
2188
+ if (patch.toolExposure !== void 0) entry.toolExposure = patch.toolExposure;
2189
+ updated = true;
2190
+ return true;
2191
+ });
2192
+ return updated;
2193
+ }
2194
+ async function setExposure(fs, path, name, exposure, toolGlob) {
2195
+ let updated = false;
2196
+ await editMcpServers(fs, path, (servers) => {
2197
+ if (!servers || !isRecord2(servers[name])) return false;
2198
+ const entry = servers[name];
2199
+ if (toolGlob) {
2200
+ const te = isRecord2(entry.toolExposure) ? { ...entry.toolExposure } : {};
2201
+ te[toolGlob] = exposure;
2202
+ entry.toolExposure = te;
2203
+ } else {
2204
+ entry.exposure = exposure;
2205
+ }
2206
+ updated = true;
2207
+ return true;
2208
+ });
2209
+ return updated;
2210
+ }
2211
+
2212
+ // src/discovery/extract.ts
2213
+ var nextId = 1;
2214
+ function candidateId() {
2215
+ return String(nextId++);
2216
+ }
2217
+ function resetIdCounter(start = 1) {
2218
+ nextId = start;
2219
+ }
2220
+ function isRecord3(v) {
2221
+ return typeof v === "object" && v !== null && !Array.isArray(v);
2222
+ }
2223
+ function checkUrnPublisher(identifier, originHost) {
2224
+ const urnMatch = /^urn:air:([^:]+):/.exec(identifier);
2225
+ if (!urnMatch) return void 0;
2226
+ const urnDomain = urnMatch[1];
2227
+ if (domainMatch(urnDomain, originHost)) {
2228
+ return { type: "urn-publisher", detail: `${urnDomain} matches ${originHost}` };
2229
+ }
2230
+ return { type: "urn-publisher-mismatch", detail: `${urnDomain} does not match ${originHost}` };
2231
+ }
2232
+ function checkTrustManifest(entry, originHost) {
2233
+ if (!isRecord3(entry.trustManifest) || typeof entry.trustManifest.identity !== "string")
2234
+ return void 0;
2235
+ const identity = entry.trustManifest.identity;
2236
+ if (domainMatch(identity, originHost)) {
2237
+ return { type: "trust-manifest", detail: `identity ${identity} matches origin` };
2238
+ }
2239
+ return {
2240
+ type: "trust-manifest-mismatch",
2241
+ detail: `identity ${identity} does not match ${originHost}`
2242
+ };
2243
+ }
2244
+ function extractArd(event) {
2245
+ const candidates = [];
2246
+ const summary = event.summary;
2247
+ if (!isRecord3(summary)) return candidates;
2248
+ const entries = summary.entries;
2249
+ if (!Array.isArray(entries)) return candidates;
2250
+ for (const entry of entries) {
2251
+ if (!isRecord3(entry)) continue;
2252
+ if (entry.type !== "application/mcp-server-card+json") continue;
2253
+ const url = typeof entry.url === "string" ? entry.url : void 0;
2254
+ if (!url) continue;
2255
+ const trust = [];
2256
+ const originHost = hostFromUrl(event.origin);
2257
+ const identifier = typeof entry.identifier === "string" ? entry.identifier : void 0;
2258
+ if (identifier) {
2259
+ const urnSignal = checkUrnPublisher(identifier, originHost);
2260
+ if (urnSignal) trust.push(urnSignal);
2261
+ }
2262
+ const tmSignal = checkTrustManifest(entry, originHost);
2263
+ if (tmSignal) trust.push(tmSignal);
2264
+ candidates.push({
2265
+ id: candidateId(),
2266
+ endpoint: url,
2267
+ standard: "ard",
2268
+ origin: event.origin,
2269
+ name: typeof entry.displayName === "string" ? entry.displayName : void 0,
2270
+ transport: "http",
2271
+ trust,
2272
+ status: event.status,
2273
+ reason: event.reason,
2274
+ discoveredAt: (/* @__PURE__ */ new Date()).toISOString()
2275
+ });
2276
+ }
2277
+ return candidates;
2278
+ }
2279
+ function extractAiCatalog(event) {
2280
+ const candidates = [];
2281
+ const summary = event.summary;
2282
+ if (!isRecord3(summary)) return candidates;
2283
+ const entries = summary.entries;
2284
+ if (!Array.isArray(entries)) return candidates;
2285
+ for (const entry of entries) {
2286
+ if (!isRecord3(entry)) continue;
2287
+ if (entry.type !== "application/mcp-server-card+json" && entry.type !== "mcp-server") continue;
2288
+ const url = typeof entry.url === "string" ? entry.url : void 0;
2289
+ if (!url) continue;
2290
+ candidates.push({
2291
+ id: candidateId(),
2292
+ endpoint: url,
2293
+ standard: "ai-catalog",
2294
+ origin: event.origin,
2295
+ name: typeof entry.displayName === "string" ? entry.displayName : void 0,
2296
+ transport: "http",
2297
+ trust: [],
2298
+ status: event.status,
2299
+ reason: event.reason,
2300
+ discoveredAt: (/* @__PURE__ */ new Date()).toISOString()
2301
+ });
2302
+ }
2303
+ return candidates;
2304
+ }
2305
+ function extractUcp(event) {
2306
+ const candidates = [];
2307
+ const summary = event.summary;
2308
+ if (!isRecord3(summary)) return candidates;
2309
+ const services = summary.services;
2310
+ if (!isRecord3(services)) return candidates;
2311
+ for (const [svcName, svcValue] of Object.entries(services)) {
2312
+ if (!Array.isArray(svcValue)) continue;
2313
+ for (const transport of svcValue) {
2314
+ if (!isRecord3(transport)) continue;
2315
+ if (transport.transport !== "mcp") continue;
2316
+ const endpoint = typeof transport.endpoint === "string" ? transport.endpoint : void 0;
2317
+ if (!endpoint) continue;
2318
+ candidates.push({
2319
+ id: candidateId(),
2320
+ endpoint,
2321
+ standard: "ucp",
2322
+ origin: event.origin,
2323
+ name: svcName,
2324
+ transport: "http",
2325
+ trust: [],
2326
+ status: event.status,
2327
+ reason: event.reason,
2328
+ discoveredAt: (/* @__PURE__ */ new Date()).toISOString()
2329
+ });
2330
+ }
2331
+ }
2332
+ return candidates;
2333
+ }
2334
+ function extractPoppy(event) {
2335
+ const candidates = [];
2336
+ const summary = event.summary;
2337
+ if (!isRecord3(summary)) return candidates;
2338
+ const apis = summary.apis;
2339
+ if (!Array.isArray(apis)) return candidates;
2340
+ for (const api of apis) {
2341
+ if (!isRecord3(api)) continue;
2342
+ if (api.type !== "mcp") continue;
2343
+ const url = typeof api.url === "string" ? api.url : void 0;
2344
+ if (!url) continue;
2345
+ candidates.push({
2346
+ id: candidateId(),
2347
+ endpoint: url,
2348
+ standard: "poppy",
2349
+ origin: event.origin,
2350
+ transport: "http",
2351
+ trust: [],
2352
+ status: event.status,
2353
+ reason: event.reason,
2354
+ discoveredAt: (/* @__PURE__ */ new Date()).toISOString()
2355
+ });
2356
+ }
2357
+ return candidates;
2358
+ }
2359
+ function extractNlweb(event) {
2360
+ const summary = event.summary;
2361
+ if (!isRecord3(summary)) return [];
2362
+ const mcpUrl = typeof summary.mcpUrl === "string" ? summary.mcpUrl : void 0;
2363
+ if (!mcpUrl) return [];
2364
+ return [
2365
+ {
2366
+ id: candidateId(),
2367
+ endpoint: mcpUrl,
2368
+ standard: "nlweb",
2369
+ origin: event.origin,
2370
+ transport: "http",
2371
+ trust: [],
2372
+ status: event.status,
2373
+ reason: event.reason,
2374
+ discoveredAt: (/* @__PURE__ */ new Date()).toISOString()
2375
+ }
2376
+ ];
2377
+ }
2378
+ function extractWebmcp(event) {
2379
+ return [
2380
+ {
2381
+ id: candidateId(),
2382
+ endpoint: event.url,
2383
+ standard: "webmcp",
2384
+ origin: event.origin,
2385
+ transport: "webmcp",
2386
+ trust: [],
2387
+ status: event.status,
2388
+ reason: event.reason,
2389
+ suggestion: "mcpcat serve --webmcp",
2390
+ discoveredAt: (/* @__PURE__ */ new Date()).toISOString()
2391
+ }
2392
+ ];
2393
+ }
2394
+ function hostFromUrl(url) {
2395
+ try {
2396
+ return new URL(url).hostname;
2397
+ } catch {
2398
+ return "";
2399
+ }
2400
+ }
2401
+ var TWO_LABEL_TLDS = /* @__PURE__ */ new Set([
2402
+ "co.uk",
2403
+ "org.uk",
2404
+ "ac.uk",
2405
+ "co.jp",
2406
+ "or.jp",
2407
+ "co.kr",
2408
+ "co.nz",
2409
+ "co.za",
2410
+ "com.au",
2411
+ "com.br",
2412
+ "com.cn",
2413
+ "com.mx",
2414
+ "com.tw",
2415
+ "net.au",
2416
+ "org.au",
2417
+ "co.in",
2418
+ "com.sg",
2419
+ "co.il"
2420
+ ]);
2421
+ function registrableDomain(host) {
2422
+ const h = host.toLowerCase().replace(/^www\./, "");
2423
+ const parts = h.split(".");
2424
+ if (parts.length <= 2) return h;
2425
+ const lastTwo = parts.slice(-2).join(".");
2426
+ if (TWO_LABEL_TLDS.has(lastTwo) && parts.length > 2) {
2427
+ return parts.slice(-3).join(".");
2428
+ }
2429
+ return lastTwo;
2430
+ }
2431
+ function domainMatch(a, b) {
2432
+ return registrableDomain(a) === registrableDomain(b);
2433
+ }
2434
+ function extractMcp(event) {
2435
+ const summary = event.summary;
2436
+ if (!isRecord3(summary)) return [];
2437
+ const endpoint = typeof summary.endpoint === "string" ? summary.endpoint : void 0;
2438
+ if (!endpoint) return [];
2439
+ const trust = [];
2440
+ if (Array.isArray(summary.trust)) {
2441
+ for (const t of summary.trust) {
2442
+ if (isRecord3(t) && typeof t.type === "string" && typeof t.detail === "string") {
2443
+ trust.push({ type: t.type, detail: t.detail });
2444
+ }
2445
+ }
2446
+ }
2447
+ const si = isRecord3(summary.serverInfo) ? summary.serverInfo : void 0;
2448
+ const name = si && typeof si.name === "string" ? si.name : void 0;
2449
+ return [
2450
+ {
2451
+ id: candidateId(),
2452
+ endpoint,
2453
+ standard: "mcp",
2454
+ origin: event.origin,
2455
+ name,
2456
+ transport: "http",
2457
+ trust,
2458
+ status: event.status,
2459
+ reason: event.reason,
2460
+ discoveredAt: (/* @__PURE__ */ new Date()).toISOString()
2461
+ }
2462
+ ];
2463
+ }
2464
+ var EXTRACTORS = {
2465
+ ard: extractArd,
2466
+ "ai-catalog": extractAiCatalog,
2467
+ ucp: extractUcp,
2468
+ poppy: extractPoppy,
2469
+ nlweb: extractNlweb,
2470
+ webmcp: extractWebmcp,
2471
+ mcp: extractMcp
2472
+ };
2473
+ function extractCandidates(event) {
2474
+ const extractor = EXTRACTORS[event.standard];
2475
+ if (!extractor) return [];
2476
+ return extractor(event);
2477
+ }
2478
+ function deduplicateCandidates(candidates) {
2479
+ const seen = /* @__PURE__ */ new Set();
2480
+ const result = [];
2481
+ for (const c of candidates) {
2482
+ const key = c.endpoint;
2483
+ if (seen.has(key)) continue;
2484
+ seen.add(key);
2485
+ result.push(c);
2486
+ }
2487
+ return result;
2488
+ }
2489
+
2490
+ // src/discovery/inbox.ts
2491
+ function inboxPath(stateDir2) {
2492
+ return `${stateDir2}/inbox.json`;
2493
+ }
2494
+ async function readInbox(fs, stateDir2) {
2495
+ try {
2496
+ const data = await fs.readFile(inboxPath(stateDir2));
2497
+ const parsed = JSON.parse(new TextDecoder().decode(data));
2498
+ if (parsed && Array.isArray(parsed.candidates)) return parsed;
2499
+ } catch {
2500
+ }
2501
+ return { candidates: [] };
2502
+ }
2503
+ async function writeInbox(fs, stateDir2, inbox) {
2504
+ try {
2505
+ await fs.mkdir(stateDir2);
2506
+ } catch {
2507
+ }
2508
+ const json = JSON.stringify(inbox, null, 2) + "\n";
2509
+ await fs.writeFile(inboxPath(stateDir2), json);
2510
+ }
2511
+ async function appendCandidates(fs, stateDir2, candidates) {
2512
+ const inbox = await readInbox(fs, stateDir2);
2513
+ const existing = new Set(inbox.candidates.map((c) => c.endpoint));
2514
+ for (const c of candidates) {
2515
+ if (existing.has(c.endpoint)) continue;
2516
+ existing.add(c.endpoint);
2517
+ inbox.candidates.push(c);
2518
+ }
2519
+ await writeInbox(fs, stateDir2, inbox);
2520
+ }
2521
+ async function listCandidates(fs, stateDir2) {
2522
+ const inbox = await readInbox(fs, stateDir2);
2523
+ return inbox.candidates.slice().reverse();
2524
+ }
2525
+ async function getCandidate(fs, stateDir2, id) {
2526
+ const inbox = await readInbox(fs, stateDir2);
2527
+ return inbox.candidates.find((c) => c.id === id);
2528
+ }
2529
+
2530
+ // src/discovery/probe.ts
2531
+ function isJsonContentType(ct) {
2532
+ if (!ct) return false;
2533
+ const lower = ct.toLowerCase();
2534
+ if (lower.includes("text/html")) return false;
2535
+ return lower.includes("json") || lower.includes("+json");
2536
+ }
2537
+ function parseLinkHeaders(headers) {
2538
+ const links = [];
2539
+ const raw = headers.get("link");
2540
+ if (!raw) return links;
2541
+ const parts = raw.split(/,(?=\s*<)/);
2542
+ for (const part of parts) {
2543
+ const urlMatch = /<([^>]+)>/.exec(part);
2544
+ const relMatch = /rel\s*=\s*"?([^";,\s]+)"?/i.exec(part);
2545
+ if (urlMatch && relMatch) {
2546
+ links.push({ url: urlMatch[1], rel: relMatch[1].toLowerCase() });
2547
+ }
2548
+ }
2549
+ return links;
2550
+ }
2551
+ function wasRedirected(requestUrl, resp) {
2552
+ if (resp.redirected) return true;
2553
+ if (resp.url && resp.url !== requestUrl) return true;
2554
+ return false;
2555
+ }
2556
+ async function probeUrl(fetcher, url, accept) {
2557
+ let resp;
2558
+ try {
2559
+ resp = await fetcher(url, {
2560
+ redirect: "manual",
2561
+ headers: accept ? { Accept: accept } : void 0,
2562
+ credentials: "omit"
2563
+ });
2564
+ } catch {
2565
+ try {
2566
+ resp = await fetcher(url, {
2567
+ redirect: "follow",
2568
+ headers: accept ? { Accept: accept } : void 0,
2569
+ credentials: "omit"
2570
+ });
2571
+ if (wasRedirected(url, resp)) {
2572
+ return { ok: false, status: 301, headers: resp.headers, body: "" };
2573
+ }
2574
+ } catch {
2575
+ return void 0;
2576
+ }
2577
+ }
2578
+ if (resp.status !== 200)
2579
+ return { ok: false, status: resp.status, headers: resp.headers, body: "" };
2580
+ const ct = resp.headers.get("content-type");
2581
+ if (!isJsonContentType(ct))
2582
+ return { ok: false, status: resp.status, headers: resp.headers, body: "" };
2583
+ const body = await resp.text();
2584
+ return { ok: true, status: 200, headers: resp.headers, body };
2585
+ }
2586
+ function isRecord4(v) {
2587
+ return typeof v === "object" && v !== null && !Array.isArray(v);
2588
+ }
2589
+ function makeEvent(standard, origin, url, via, page, status, summary, reason) {
2590
+ return {
2591
+ v: 1,
2592
+ standard,
2593
+ origin,
2594
+ url,
2595
+ via,
2596
+ page,
2597
+ targetId: "",
2598
+ status,
2599
+ summary,
2600
+ ...reason ? { reason } : {}
2601
+ };
2602
+ }
2603
+ function tryParseJson(body) {
2604
+ try {
2605
+ return JSON.parse(body);
2606
+ } catch {
2607
+ return void 0;
2608
+ }
2609
+ }
2610
+ function classifyArd(origin, url, via, page, body) {
2611
+ const parsed = tryParseJson(body);
2612
+ if (!isRecord4(parsed))
2613
+ return makeEvent("ard", origin, url, via, page, "invalid", null, "invalid JSON");
2614
+ if (!Array.isArray(parsed.entries))
2615
+ return makeEvent("ard", origin, url, via, page, "invalid", null, "missing entries[]");
2616
+ const entries = [];
2617
+ for (const entry of parsed.entries) {
2618
+ if (!isRecord4(entry)) continue;
2619
+ entries.push({
2620
+ identifier: entry.identifier,
2621
+ displayName: entry.displayName,
2622
+ type: entry.type,
2623
+ url: entry.url,
2624
+ capabilities: entry.capabilities,
2625
+ representativeQueries: entry.representativeQueries,
2626
+ trustManifest: entry.trustManifest
2627
+ });
2628
+ }
2629
+ return makeEvent("ard", origin, url, via, page, "valid", { entries });
2630
+ }
2631
+ function classifyAiCatalog(origin, url, via, page, body) {
2632
+ const parsed = tryParseJson(body);
2633
+ if (!isRecord4(parsed))
2634
+ return makeEvent("ai-catalog", origin, url, via, page, "invalid", null, "invalid JSON");
2635
+ const entries = [];
2636
+ const items = Array.isArray(parsed.entries) ? parsed.entries : Array.isArray(parsed.items) ? parsed.items : void 0;
2637
+ if (!items)
2638
+ return makeEvent("ai-catalog", origin, url, via, page, "invalid", null, "no entries or items");
2639
+ for (const entry of items) {
2640
+ if (!isRecord4(entry)) continue;
2641
+ entries.push({
2642
+ displayName: entry.displayName ?? entry.name,
2643
+ type: entry.type,
2644
+ url: entry.url
2645
+ });
2646
+ }
2647
+ return makeEvent("ai-catalog", origin, url, via, page, "valid", { entries });
2648
+ }
2649
+ async function probeWellKnown(fetcher, origin, page) {
2650
+ const events = [];
2651
+ const ardUrl = `${origin}/.well-known/ard.json`;
2652
+ const ardResult = await probeUrl(fetcher, ardUrl, "application/json");
2653
+ if (ardResult?.ok) {
2654
+ const ev = classifyArd(origin, ardUrl, "well-known", page, ardResult.body);
2655
+ if (ev) events.push(ev);
2656
+ }
2657
+ const aiCatUrl = `${origin}/.well-known/ai-catalog.json`;
2658
+ const aiCatResult = await probeUrl(fetcher, aiCatUrl, "application/json");
2659
+ if (aiCatResult?.ok) {
2660
+ const ev = classifyAiCatalog(origin, aiCatUrl, "well-known", page, aiCatResult.body);
2661
+ if (ev) events.push(ev);
2662
+ }
2663
+ return events;
2664
+ }
2665
+ async function probeLinkHeaders(fetcher, targetUrl, origin, page) {
2666
+ const events = [];
2667
+ const targetResult = await probeUrl(fetcher, targetUrl);
2668
+ if (!targetResult) return events;
2669
+ const links = parseLinkHeaders(targetResult.headers);
2670
+ for (const link of links) {
2671
+ const resolved = new URL(link.url, targetUrl).href;
2672
+ const classifier = link.rel === "ard" ? classifyArd : link.rel === "ai-catalog" ? classifyAiCatalog : null;
2673
+ if (!classifier) continue;
2674
+ const linkResult = await probeUrl(fetcher, resolved, "application/json");
2675
+ if (linkResult?.ok) {
2676
+ const ev = classifier(origin, resolved, "link-header", page, linkResult.body);
2677
+ if (ev) events.push(ev);
2678
+ }
2679
+ }
2680
+ return events;
2681
+ }
2682
+ function mcpJsonRpc(method, params, id = 1) {
2683
+ return JSON.stringify({ jsonrpc: "2.0", id, method, params });
2684
+ }
2685
+ function parseServerInfo(result) {
2686
+ const si = isRecord4(result.serverInfo) ? result.serverInfo : void 0;
2687
+ if (!si) return void 0;
2688
+ return {
2689
+ name: typeof si.name === "string" ? si.name : void 0,
2690
+ version: typeof si.version === "string" ? si.version : void 0
2691
+ };
2692
+ }
2693
+ function checkAuthResponse(resp) {
2694
+ if (resp.status !== 401) return void 0;
2695
+ const wwwAuth = resp.headers.get("www-authenticate");
2696
+ if (!wwwAuth) return void 0;
2697
+ return { type: "auth-required", detail: wwwAuth.split(",")[0].trim() };
2698
+ }
2699
+ async function tryMcpPost(fetcher, url, body, extraHeaders) {
2700
+ const headers = {
2701
+ "Content-Type": "application/json",
2702
+ Accept: "application/json",
2703
+ ...extraHeaders
2704
+ };
2705
+ try {
2706
+ const resp = await fetcher(url, {
2707
+ method: "POST",
2708
+ headers,
2709
+ body,
2710
+ redirect: "manual",
2711
+ credentials: "omit"
2712
+ });
2713
+ const authSignal = checkAuthResponse(resp);
2714
+ if (authSignal) return { url, trust: [authSignal] };
2715
+ if (resp.status === 200) {
2716
+ const text = await resp.text();
2717
+ const parsed = tryParseJson(text);
2718
+ if (isRecord4(parsed) && isRecord4(parsed.result)) {
2719
+ return { url, serverInfo: parseServerInfo(parsed.result), trust: [] };
2720
+ }
2721
+ }
2722
+ } catch {
2723
+ }
2724
+ return void 0;
2725
+ }
2726
+ async function probeMcpEndpoint(fetcher, url) {
2727
+ const discoverBody = mcpJsonRpc("server/discover", {
2728
+ protocolVersion: "2026-07-28",
2729
+ clientInfo: { name: "mcpcat", version: "0.0.0" }
2730
+ });
2731
+ const hit = await tryMcpPost(fetcher, url, discoverBody, {
2732
+ "Mcp-Method": "server/discover"
2733
+ });
2734
+ if (hit) return hit;
2735
+ const initBody = mcpJsonRpc("initialize", {
2736
+ protocolVersion: "2025-11-25",
2737
+ clientInfo: { name: "mcpcat", version: "0.0.0" },
2738
+ capabilities: {}
2739
+ });
2740
+ return tryMcpPost(fetcher, url, initBody);
2741
+ }
2742
+ async function probeMcp(fetcher, origin, page, targetUrl) {
2743
+ const events = [];
2744
+ const urls = [targetUrl];
2745
+ const mcpUrl = `${origin}/mcp`;
2746
+ if (mcpUrl !== targetUrl) urls.push(mcpUrl);
2747
+ for (const url of urls) {
2748
+ const hit = await probeMcpEndpoint(fetcher, url);
2749
+ if (hit) {
2750
+ const summary = { endpoint: hit.url };
2751
+ if (hit.serverInfo) summary.serverInfo = hit.serverInfo;
2752
+ if (hit.trust.length > 0) summary.trust = hit.trust;
2753
+ events.push(makeEvent("mcp", origin, hit.url, "well-known", page, "valid", summary));
2754
+ }
2755
+ }
2756
+ return events;
2757
+ }
2758
+ async function probeDiscover(platform, targetUrl) {
2759
+ let origin;
2760
+ try {
2761
+ const u = new URL(targetUrl);
2762
+ origin = u.origin;
2763
+ } catch {
2764
+ return { events: [] };
2765
+ }
2766
+ const page = targetUrl;
2767
+ const fetcher = platform.fetch;
2768
+ const events = [
2769
+ ...await probeWellKnown(fetcher, origin, page),
2770
+ ...await probeLinkHeaders(fetcher, targetUrl, origin, page),
2771
+ ...await probeMcp(fetcher, origin, page, targetUrl)
2772
+ ];
2773
+ return { events };
2774
+ }
2775
+
2776
+ // src/discovery/verbs.ts
2777
+ async function readStdin(platform) {
2778
+ const reader = platform.stdin.getReader();
2779
+ const chunks = [];
2780
+ for (; ; ) {
2781
+ const { done, value } = await reader.read();
2782
+ if (done) break;
2783
+ chunks.push(value);
2784
+ }
2785
+ reader.releaseLock();
2786
+ const total = chunks.reduce((s, c) => s + c.length, 0);
2787
+ const merged = new Uint8Array(total);
2788
+ let offset = 0;
2789
+ for (const c of chunks) {
2790
+ merged.set(c, offset);
2791
+ offset += c.length;
2792
+ }
2793
+ return new TextDecoder().decode(merged);
2794
+ }
2795
+ function parseEvents(text) {
2796
+ const lines = text.trim().split("\n").filter((l) => l.trim().length > 0);
2797
+ const events = [];
2798
+ for (const line of lines) {
2799
+ let parsed;
2800
+ try {
2801
+ parsed = JSON.parse(line);
2802
+ } catch {
2803
+ return { error: `invalid JSON: ${line.slice(0, 80)}`, code: 2 };
2804
+ }
2805
+ const ev = parsed;
2806
+ if (typeof ev.v !== "number") return { error: "missing event version", code: 2 };
2807
+ if (ev.v !== 1) return { error: `unknown event version: ${ev.v}`, code: 2 };
2808
+ events.push(ev);
2809
+ }
2810
+ return events;
2811
+ }
2812
+ function formatCandidate(c) {
2813
+ const parts = [`#${c.id} ${c.standard} ${c.endpoint}`];
2814
+ if (c.name) parts[0] += ` (${c.name})`;
2815
+ if (c.status === "invalid") parts.push(` status: invalid \u2013 ${c.reason ?? "unknown"}`);
2816
+ if (c.suggestion) parts.push(` suggestion: ${c.suggestion}`);
2817
+ for (const t of c.trust) {
2818
+ const icon = t.type.includes("mismatch") ? "\u26A0" : "\u2713";
2819
+ parts.push(` ${icon} ${t.type}: ${t.detail}`);
2820
+ }
2821
+ return parts.join("\n");
2822
+ }
2823
+ function formatCandidateJson(c) {
2824
+ return {
2825
+ id: c.id,
2826
+ endpoint: c.endpoint,
2827
+ standard: c.standard,
2828
+ origin: c.origin,
2829
+ name: c.name,
2830
+ transport: c.transport,
2831
+ trust: c.trust,
2832
+ status: c.status,
2833
+ reason: c.reason,
2834
+ suggestion: c.suggestion,
2835
+ discoveredAt: c.discoveredAt
2836
+ };
2837
+ }
2838
+ async function runFromDiscovery(platform, args) {
2839
+ let source = "-";
2840
+ for (const arg of args) {
2841
+ if (arg !== "--from-discovery" && !arg.startsWith("-")) {
2842
+ source = arg;
2843
+ }
2844
+ }
2845
+ let text;
2846
+ if (source === "-") {
2847
+ text = await readStdin(platform);
2848
+ } else {
2849
+ try {
2850
+ const data = await platform.fs.readFile(source);
2851
+ text = new TextDecoder().decode(data);
2852
+ } catch {
2853
+ await platform.write(2, `mcpcat add --from-discovery: cannot read '${source}'
2854
+ `);
2855
+ return 2;
2856
+ }
2857
+ }
2858
+ const result = parseEvents(text);
2859
+ if ("error" in result) {
2860
+ await platform.write(2, `mcpcat add --from-discovery: ${result.error}
2861
+ `);
2862
+ return result.code;
2863
+ }
2864
+ resetIdCounter();
2865
+ let allCandidates = [];
2866
+ for (const event of result) {
2867
+ allCandidates.push(...extractCandidates(event));
2868
+ }
2869
+ allCandidates = deduplicateCandidates(allCandidates);
2870
+ if (allCandidates.length === 0) {
2871
+ await platform.write(1, "no MCP candidates found\n");
2872
+ return 0;
2873
+ }
2874
+ const sd = stateDir(platform);
2875
+ await appendCandidates(platform.fs, sd, allCandidates);
2876
+ for (const c of allCandidates) {
2877
+ await platform.write(1, `${formatCandidate(c)}
2878
+ `);
2879
+ }
2880
+ return 0;
2881
+ }
2882
+ async function runInbox(platform, args) {
2883
+ const json = args.includes("--json");
2884
+ const sd = stateDir(platform);
2885
+ const candidates = await listCandidates(platform.fs, sd);
2886
+ if (candidates.length === 0) {
2887
+ await platform.write(1, "inbox is empty\n");
2888
+ return 0;
2889
+ }
2890
+ if (json) {
2891
+ await platform.write(1, JSON.stringify(candidates.map(formatCandidateJson), null, 2) + "\n");
2892
+ } else {
2893
+ for (const c of candidates) {
2894
+ await platform.write(1, `${formatCandidate(c)}
2895
+ `);
2896
+ }
2897
+ }
2898
+ return 0;
1820
2899
  }
1821
-
1822
- // src/config/index.ts
1823
- var MergedConfigSource = class _MergedConfigSource {
1824
- entries;
1825
- config;
1826
- constructor(config) {
1827
- this.config = config;
1828
- this.entries = /* @__PURE__ */ new Map();
1829
- for (const entry of config.servers) {
1830
- this.entries.set(entry.name, entry);
2900
+ async function runAddCandidate(platform, name, candidateId2, configFile) {
2901
+ const sd = stateDir(platform);
2902
+ const candidate = await getCandidate(platform.fs, sd, candidateId2);
2903
+ if (!candidate) {
2904
+ await platform.write(2, `mcpcat add: candidate #${candidateId2} not found in inbox
2905
+ `);
2906
+ return 1;
2907
+ }
2908
+ if (candidate.transport === "webmcp") {
2909
+ await platform.write(
2910
+ 2,
2911
+ `mcpcat add: WebMCP candidates cannot be added directly; use: ${candidate.suggestion}
2912
+ `
2913
+ );
2914
+ return 2;
2915
+ }
2916
+ const path = configFile ?? userConfigPath(platform);
2917
+ const config = { url: candidate.endpoint };
2918
+ const { replaced } = await addServer(platform.fs, path, name, config);
2919
+ for (const t of candidate.trust) {
2920
+ if (t.type.includes("mismatch")) {
2921
+ await platform.write(2, `mcpcat add: warning: ${t.type}: ${t.detail}
2922
+ `);
1831
2923
  }
1832
2924
  }
1833
- async get(name) {
1834
- const normalizedName = name.replace(/[-_]/g, "").toLowerCase();
1835
- for (const [key, entry] of this.entries) {
1836
- if (key.replace(/[-_]/g, "").toLowerCase() === normalizedName) {
1837
- return entry.config;
1838
- }
2925
+ await platform.write(1, `${replaced ? "replaced" : "added"} ${name} in ${path}
2926
+ `);
2927
+ return 0;
2928
+ }
2929
+ async function runDiscover(platform, args) {
2930
+ const positional = [];
2931
+ let json = false;
2932
+ for (const arg of args) {
2933
+ if (arg === "--json") {
2934
+ json = true;
2935
+ } else if (!arg.startsWith("-")) {
2936
+ positional.push(arg);
1839
2937
  }
1840
- return void 0;
1841
2938
  }
1842
- entry(name) {
1843
- return this.entries.get(name);
2939
+ if (positional.length < 1) {
2940
+ await platform.write(2, "mcpcat discover: requires a URL\n");
2941
+ return 2;
1844
2942
  }
1845
- list() {
1846
- return [...this.entries.values()];
2943
+ const url = positional[0];
2944
+ if (!URL.canParse(url)) {
2945
+ await platform.write(2, `mcpcat discover: '${url}' is not a valid URL
2946
+ `);
2947
+ return 2;
1847
2948
  }
1848
- static async load(platform, options) {
1849
- const userPath = options?.configFile ?? userConfigPath(platform);
1850
- const projectPath = options?.configFile ? void 0 : projectConfigPath(platform);
1851
- const config = await loadConfig({
1852
- userPath,
1853
- projectPath,
1854
- trustProject: options?.trustProject ?? false,
1855
- fs: platform.fs
1856
- });
1857
- return new _MergedConfigSource(config);
2949
+ const { events } = await probeDiscover(platform, url);
2950
+ resetIdCounter();
2951
+ let allCandidates = [];
2952
+ for (const event of events) {
2953
+ allCandidates.push(...extractCandidates(event));
1858
2954
  }
1859
- };
2955
+ allCandidates = deduplicateCandidates(allCandidates);
2956
+ if (allCandidates.length > 0) {
2957
+ const sd = stateDir(platform);
2958
+ await appendCandidates(platform.fs, sd, allCandidates);
2959
+ }
2960
+ if (allCandidates.length === 0) {
2961
+ await platform.write(1, "no MCP candidates found\n");
2962
+ return 0;
2963
+ }
2964
+ if (json) {
2965
+ await platform.write(1, JSON.stringify(allCandidates.map(formatCandidateJson), null, 2) + "\n");
2966
+ } else {
2967
+ for (const c of allCandidates) {
2968
+ await platform.write(1, `${formatCandidate(c)}
2969
+ `);
2970
+ }
2971
+ }
2972
+ return 0;
2973
+ }
1860
2974
 
1861
2975
  // src/config/expand.ts
1862
2976
  var PLACEHOLDER = /\$\{([A-Za-z_][A-Za-z0-9_]*)\}/g;
@@ -3251,158 +4365,12 @@ async function handleMcpRequest(request, tools, call, serverName, version, allow
3251
4365
  };
3252
4366
  }
3253
4367
 
3254
- // src/config/lock.ts
3255
- var LOCK_STALE_MS = 1e4;
3256
- var LOCK_POLL_MS = 50;
3257
- var LOCK_MAX_WAIT_MS = 5e3;
3258
- function isEexist(err) {
3259
- if (err instanceof Error && "code" in err)
3260
- return err.code === "EEXIST";
3261
- if (err instanceof Error) return err.message.includes("EEXIST");
3262
- return false;
3263
- }
3264
- async function removeStaleLock(fs, lockPath) {
3265
- try {
3266
- const s = await fs.stat(lockPath);
3267
- if (Date.now() - s.mtimeMs > LOCK_STALE_MS) {
3268
- try {
3269
- await fs.rmdir(lockPath);
3270
- } catch {
3271
- }
3272
- return true;
3273
- }
3274
- } catch {
3275
- }
3276
- return false;
3277
- }
3278
- async function acquireLock(fs, configPath) {
3279
- const lockPath = `${configPath}.lock`;
3280
- const start = Date.now();
3281
- while (true) {
3282
- try {
3283
- await fs.mkdirExclusive(lockPath);
3284
- break;
3285
- } catch (err) {
3286
- if (!isEexist(err)) throw err;
3287
- await removeStaleLock(fs, lockPath);
3288
- }
3289
- if (Date.now() - start > LOCK_MAX_WAIT_MS) {
3290
- throw new Error(`mcpcat: could not acquire lock ${lockPath} within ${LOCK_MAX_WAIT_MS}ms`);
3291
- }
3292
- await new Promise((r) => setTimeout(r, LOCK_POLL_MS));
3293
- }
3294
- let released = false;
3295
- return async () => {
3296
- if (released) return;
3297
- released = true;
3298
- try {
3299
- await fs.rmdir(lockPath);
3300
- } catch {
3301
- }
3302
- };
3303
- }
3304
-
3305
- // src/config/edit.ts
3306
- function isRecord2(value) {
3307
- return typeof value === "object" && value !== null && !Array.isArray(value);
3308
- }
3309
- function detectIndent(text) {
3310
- const match = /^([ \t]+)\S/m.exec(text);
3311
- return match ? match[1] : " ";
3312
- }
3313
- async function readText(fs, path) {
3314
- try {
3315
- const data = await fs.readFile(path);
3316
- return new TextDecoder().decode(data);
3317
- } catch {
3318
- return void 0;
3319
- }
3320
- }
3321
- async function editMcpServers(fs, path, edit) {
3322
- const release = await acquireLock(fs, path);
3323
- try {
3324
- const text = await readText(fs, path);
3325
- const parsed = text === void 0 ? {} : JSON.parse(text);
3326
- if (!isRecord2(parsed) || parsed.mcpServers !== void 0 && !isRecord2(parsed.mcpServers))
3327
- throw new Error(`${path}: expected an object with an "mcpServers" object`);
3328
- const servers = isRecord2(parsed.mcpServers) ? parsed.mcpServers : void 0;
3329
- if (!edit(servers, parsed)) return { changed: false };
3330
- const indent = text ? detectIndent(text) : " ";
3331
- const output = `${JSON.stringify(parsed, null, indent)}
3332
- `;
3333
- const dir = path.slice(0, path.lastIndexOf("/"));
3334
- if (dir) {
3335
- try {
3336
- await fs.mkdir(dir);
3337
- } catch {
3338
- }
3339
- }
3340
- const tmpPath = `${path}.tmp.${Date.now()}`;
3341
- await fs.writeFile(tmpPath, output);
3342
- await fs.rename(tmpPath, path);
3343
- return { changed: true };
3344
- } finally {
3345
- await release();
3346
- }
3347
- }
3348
- async function addServer(fs, path, name, config) {
3349
- let replaced = false;
3350
- await editMcpServers(fs, path, (servers, parsed) => {
3351
- const target = servers ?? {};
3352
- replaced = target[name] !== void 0;
3353
- target[name] = config;
3354
- parsed.mcpServers = target;
3355
- return true;
3356
- });
3357
- return { replaced };
3358
- }
3359
- async function removeServer(fs, path, name) {
3360
- let removed = false;
3361
- await editMcpServers(fs, path, (servers) => {
3362
- if (!servers || servers[name] === void 0) return false;
3363
- delete servers[name];
3364
- removed = true;
3365
- return true;
3366
- });
3367
- return removed;
3368
- }
3369
- async function updateServer(fs, path, name, patch) {
3370
- let updated = false;
3371
- await editMcpServers(fs, path, (servers) => {
3372
- if (!servers || !isRecord2(servers[name])) return false;
3373
- const entry = servers[name];
3374
- if (patch.enabled !== void 0) entry.enabled = patch.enabled;
3375
- if (patch.exposure !== void 0) entry.exposure = patch.exposure;
3376
- if (patch.toolExposure !== void 0) entry.toolExposure = patch.toolExposure;
3377
- updated = true;
3378
- return true;
3379
- });
3380
- return updated;
3381
- }
3382
- async function setExposure(fs, path, name, exposure, toolGlob) {
3383
- let updated = false;
3384
- await editMcpServers(fs, path, (servers) => {
3385
- if (!servers || !isRecord2(servers[name])) return false;
3386
- const entry = servers[name];
3387
- if (toolGlob) {
3388
- const te = isRecord2(entry.toolExposure) ? { ...entry.toolExposure } : {};
3389
- te[toolGlob] = exposure;
3390
- entry.toolExposure = te;
3391
- } else {
3392
- entry.exposure = exposure;
3393
- }
3394
- updated = true;
3395
- return true;
3396
- });
3397
- return updated;
3398
- }
3399
-
3400
4368
  // src/config/import.ts
3401
- function isRecord3(value) {
4369
+ function isRecord5(value) {
3402
4370
  return typeof value === "object" && value !== null && !Array.isArray(value);
3403
4371
  }
3404
4372
  function isStringRecord2(value) {
3405
- return isRecord3(value) && Object.values(value).every((v) => typeof v === "string");
4373
+ return isRecord5(value) && Object.values(value).every((v) => typeof v === "string");
3406
4374
  }
3407
4375
  function tryParse(text) {
3408
4376
  try {
@@ -3432,17 +4400,17 @@ function importClaude(text, names) {
3432
4400
  result.errors.push(error);
3433
4401
  return result;
3434
4402
  }
3435
- if (!isRecord3(parsed)) {
4403
+ if (!isRecord5(parsed)) {
3436
4404
  result.errors.push("expected an object");
3437
4405
  return result;
3438
4406
  }
3439
4407
  const mcpServers = parsed.mcpServers;
3440
- if (!isRecord3(mcpServers)) {
4408
+ if (!isRecord5(mcpServers)) {
3441
4409
  result.errors.push('no "mcpServers" object');
3442
4410
  return result;
3443
4411
  }
3444
4412
  for (const [name, entry] of filterNames(Object.entries(mcpServers), names)) {
3445
- if (!isRecord3(entry)) {
4413
+ if (!isRecord5(entry)) {
3446
4414
  result.errors.push(`${name}: not an object`);
3447
4415
  continue;
3448
4416
  }
@@ -3457,17 +4425,17 @@ function importVSCode(text, names) {
3457
4425
  result.errors.push(error);
3458
4426
  return result;
3459
4427
  }
3460
- if (!isRecord3(parsed)) {
4428
+ if (!isRecord5(parsed)) {
3461
4429
  result.errors.push("expected an object");
3462
4430
  return result;
3463
4431
  }
3464
4432
  const servers = parsed.servers ?? parsed.mcpServers;
3465
- if (!isRecord3(servers)) {
4433
+ if (!isRecord5(servers)) {
3466
4434
  result.errors.push('no "servers" or "mcpServers" object');
3467
4435
  return result;
3468
4436
  }
3469
4437
  for (const [name, entry] of filterNames(Object.entries(servers), names)) {
3470
- if (!isRecord3(entry)) {
4438
+ if (!isRecord5(entry)) {
3471
4439
  result.errors.push(`${name}: not an object`);
3472
4440
  continue;
3473
4441
  }
@@ -3501,17 +4469,17 @@ function importSixServers(text, names) {
3501
4469
  result.errors.push(error);
3502
4470
  return result;
3503
4471
  }
3504
- if (!isRecord3(parsed)) {
4472
+ if (!isRecord5(parsed)) {
3505
4473
  result.errors.push("expected an object");
3506
4474
  return result;
3507
4475
  }
3508
4476
  const servers = parsed.servers;
3509
- if (!isRecord3(servers)) {
4477
+ if (!isRecord5(servers)) {
3510
4478
  result.errors.push('no "servers" object');
3511
4479
  return result;
3512
4480
  }
3513
4481
  for (const [name, entry] of filterNames(Object.entries(servers), names)) {
3514
- if (!isRecord3(entry)) {
4482
+ if (!isRecord5(entry)) {
3515
4483
  result.errors.push(`${name}: not an object`);
3516
4484
  continue;
3517
4485
  }
@@ -3519,7 +4487,7 @@ function importSixServers(text, names) {
3519
4487
  if (typeof entry.url === "string") config.url = entry.url;
3520
4488
  if (isStringRecord2(entry.headers)) config.headers = entry.headers;
3521
4489
  if (typeof entry.exposure === "string") config.exposure = entry.exposure;
3522
- if (isRecord3(entry.toolExposure)) config.toolExposure = entry.toolExposure;
4490
+ if (isRecord5(entry.toolExposure)) config.toolExposure = entry.toolExposure;
3523
4491
  result.servers.push({ name, config });
3524
4492
  }
3525
4493
  return result;
@@ -3536,7 +4504,7 @@ function importCodexToml(text, names) {
3536
4504
  if (fields.command) config.command = fields.command;
3537
4505
  if (fields.args) config.args = fields.args;
3538
4506
  if (fields.url) config.url = fields.url;
3539
- if (fields.env && isRecord3(fields.env)) config.env = fields.env;
4507
+ if (fields.env && isRecord5(fields.env)) config.env = fields.env;
3540
4508
  result.servers.push({ name, config });
3541
4509
  }
3542
4510
  return result;
@@ -3588,7 +4556,7 @@ function convertOpenCodeLocal(entry) {
3588
4556
  } else if (typeof entry.command === "string") {
3589
4557
  config.command = entry.command;
3590
4558
  }
3591
- if (isRecord3(entry.environment)) {
4559
+ if (isRecord5(entry.environment)) {
3592
4560
  const env = {};
3593
4561
  for (const [k, v] of Object.entries(entry.environment)) {
3594
4562
  if (typeof v === "string")
@@ -3611,17 +4579,17 @@ function importOpenCode(text, names) {
3611
4579
  result.errors.push(error);
3612
4580
  return result;
3613
4581
  }
3614
- if (!isRecord3(parsed)) {
4582
+ if (!isRecord5(parsed)) {
3615
4583
  result.errors.push("expected an object");
3616
4584
  return result;
3617
4585
  }
3618
4586
  const mcpServers = parsed.mcpServers ?? parsed.mcp;
3619
- if (!isRecord3(mcpServers)) {
4587
+ if (!isRecord5(mcpServers)) {
3620
4588
  result.errors.push("no mcp servers found");
3621
4589
  return result;
3622
4590
  }
3623
4591
  for (const [name, entry] of filterNames(Object.entries(mcpServers), names)) {
3624
- if (!isRecord3(entry)) {
4592
+ if (!isRecord5(entry)) {
3625
4593
  result.errors.push(`${name}: not an object`);
3626
4594
  continue;
3627
4595
  }
@@ -3637,9 +4605,9 @@ function detectFormat(text, filename) {
3637
4605
  if (filename.includes(".vscode")) return "vscode";
3638
4606
  try {
3639
4607
  const parsed = JSON.parse(text);
3640
- if (isRecord3(parsed)) {
3641
- if (parsed.servers && isRecord3(parsed.servers) && parsed.version !== void 0) return "six";
3642
- if (parsed.servers && isRecord3(parsed.servers)) return "vscode";
4608
+ if (isRecord5(parsed)) {
4609
+ if (parsed.servers && isRecord5(parsed.servers) && parsed.version !== void 0) return "six";
4610
+ if (parsed.servers && isRecord5(parsed.servers)) return "vscode";
3643
4611
  if (parsed.mcpServers) return "claude";
3644
4612
  if (parsed.mcp) return "opencode";
3645
4613
  }
@@ -4208,9 +5176,7 @@ var UNIMPLEMENTED_VERBS = /* @__PURE__ */ new Set([
4208
5176
  "task",
4209
5177
  "login",
4210
5178
  "logout",
4211
- "code",
4212
- "discover",
4213
- "inbox"
5179
+ "code"
4214
5180
  ]);
4215
5181
  var CLIENT_VERBS = /* @__PURE__ */ new Set(["info", "tools", "call", "describe", "serve"]);
4216
5182
  function helpText(version) {
@@ -4238,8 +5204,8 @@ Configuration verbs:
4238
5204
  Server and code verbs:
4239
5205
  serve [--stdio|--http] \u2026 Expose commands as MCP server
4240
5206
  code [-e src|file.js] Run a codemode script (not implemented yet)
4241
- discover <url> Find MCP servers (not implemented yet)
4242
- inbox Discovery inbox (not implemented yet)
5207
+ discover <url> Find MCP servers at a URL
5208
+ inbox [--json] List discovery candidates
4243
5209
  help [verb] Show help
4244
5210
 
4245
5211
  Options:
@@ -4370,6 +5336,21 @@ function renderCallResult(result) {
4370
5336
  }
4371
5337
  return parts.join("\n");
4372
5338
  }
5339
+ var NETWORK_HINT = "connection failed (use slicc-node, slicc-swift, or the browser extension for servers without CORS)";
5340
+ function transportHint(err) {
5341
+ if (err instanceof TypeError) {
5342
+ const cause = err.cause;
5343
+ if (cause?.code === "ECONNREFUSED" || cause?.code === "ENETUNREACH") {
5344
+ return `connection failed: ${err.message}`;
5345
+ }
5346
+ }
5347
+ if (err instanceof Error && "status" in err && err.status === 502) {
5348
+ const msg = err.message;
5349
+ if (msg) return `${NETWORK_HINT}: ${msg}`;
5350
+ return NETWORK_HINT;
5351
+ }
5352
+ return void 0;
5353
+ }
4373
5354
  async function readDataArg(platform, dataArg) {
4374
5355
  let raw;
4375
5356
  if (dataArg.startsWith("@-")) {
@@ -4539,6 +5520,37 @@ async function dispatchConfigVerb(platform, verb, args) {
4539
5520
  return 1;
4540
5521
  }
4541
5522
  }
5523
+ async function dispatchDiscoveryVerb(platform, parsed, args) {
5524
+ if (parsed.verb === "add" && args.includes("--from-discovery")) {
5525
+ return runFromDiscovery(platform, args.slice(1));
5526
+ }
5527
+ if (parsed.verb === "add" && args.includes("--candidate")) {
5528
+ const rest = args.slice(1);
5529
+ const nameIdx = rest.findIndex((a) => !a.startsWith("-") && a !== "--candidate");
5530
+ const candidateIdx = rest.indexOf("--candidate");
5531
+ if (nameIdx < 0 || candidateIdx < 0 || candidateIdx + 1 >= rest.length) {
5532
+ await platform.write(2, "mcpcat add --candidate: requires <name> --candidate <id>\n");
5533
+ return 2;
5534
+ }
5535
+ const name = rest[nameIdx];
5536
+ const candidateId2 = rest[candidateIdx + 1];
5537
+ const configIdx = rest.indexOf("--config");
5538
+ const configFile = configIdx >= 0 && configIdx + 1 < rest.length ? rest[configIdx + 1] : void 0;
5539
+ return runAddCandidate(platform, name, candidateId2, configFile);
5540
+ }
5541
+ if (parsed.verb === "discover") return runDiscover(platform, args.slice(1));
5542
+ if (parsed.verb === "inbox") return runInbox(platform, args.slice(1));
5543
+ return void 0;
5544
+ }
5545
+ async function dispatchServeVerb(platform, args, version) {
5546
+ const serveArgs = args.slice(args.indexOf("serve") + 1);
5547
+ const serveOptions = parseServeFlags(serveArgs);
5548
+ if (serveOptions.commands.length === 0 && serveOptions.toolTemplates.length === 0) {
5549
+ await platform.write(2, "mcpcat serve: no commands or --tool templates given\n");
5550
+ return 2;
5551
+ }
5552
+ return runServe(platform, serveOptions, version);
5553
+ }
4542
5554
  async function run(platform, version, args) {
4543
5555
  if (args.includes("--version") || args.includes("-V")) {
4544
5556
  await platform.write(1, `${version}
@@ -4559,21 +5571,19 @@ async function run(platform, version, args) {
4559
5571
  return 2;
4560
5572
  }
4561
5573
  if (parsed.verb in CONFIG_VERBS) {
5574
+ const disc = await dispatchDiscoveryVerb(platform, parsed, args);
5575
+ if (disc !== void 0) return disc;
4562
5576
  return dispatchConfigVerb(platform, parsed.verb, args);
4563
5577
  }
5578
+ const discResult = await dispatchDiscoveryVerb(platform, parsed, args);
5579
+ if (discResult !== void 0) return discResult;
4564
5580
  if (UNIMPLEMENTED_VERBS.has(parsed.verb)) {
4565
5581
  await platform.write(2, `mcpcat: '${parsed.verb}' is not implemented yet
4566
5582
  `);
4567
5583
  return 1;
4568
5584
  }
4569
5585
  if (parsed.verb === "serve") {
4570
- const serveArgs = args.slice(args.indexOf("serve") + 1);
4571
- const serveOptions = parseServeFlags(serveArgs);
4572
- if (serveOptions.commands.length === 0 && serveOptions.toolTemplates.length === 0) {
4573
- await platform.write(2, "mcpcat serve: no commands or --tool templates given\n");
4574
- return 2;
4575
- }
4576
- return runServe(platform, serveOptions, version);
5586
+ return dispatchServeVerb(platform, args, version);
4577
5587
  }
4578
5588
  if (!CLIENT_VERBS.has(parsed.verb)) {
4579
5589
  await platform.write(2, `mcpcat: unknown command '${parsed.verb}'
@@ -4611,7 +5621,8 @@ async function run(platform, version, args) {
4611
5621
  version
4612
5622
  });
4613
5623
  } catch (err) {
4614
- await platform.write(2, `mcpcat: connection failed: ${err.message}
5624
+ const msg = transportHint(err) ?? `connection failed: ${err.message}`;
5625
+ await platform.write(2, `mcpcat: ${msg}
4615
5626
  `);
4616
5627
  return 3;
4617
5628
  }
@@ -4664,12 +5675,22 @@ async function kernelSpawn(ctx, options) {
4664
5675
  stdout: "pipe",
4665
5676
  stderr: "pipe"
4666
5677
  });
5678
+ const childPid = result.pid;
4667
5679
  return {
4668
5680
  stdin: result.stdin,
4669
5681
  stdout: result.stdout,
4670
5682
  stderr: result.stderr,
4671
5683
  wait: () => result.wait(),
4672
- kill: (signal) => result.kill(signal)
5684
+ kill: (signal) => {
5685
+ try {
5686
+ ctx.kill(-childPid, signal);
5687
+ } catch {
5688
+ }
5689
+ try {
5690
+ ctx.kill(childPid, signal);
5691
+ } catch {
5692
+ }
5693
+ }
4673
5694
  };
4674
5695
  }
4675
5696
  async function kernelListen(ctx, options) {
@@ -4735,7 +5756,7 @@ function createKernelPlatform(ctx) {
4735
5756
  async function main(ctx) {
4736
5757
  const platform = createKernelPlatform(ctx);
4737
5758
  const args = ctx.argv.slice(1);
4738
- return run(platform, "0.4.0", args);
5759
+ return run(platform, "0.6.0", args);
4739
5760
  }
4740
5761
  export {
4741
5762
  main as default