@ai-ecoverse/mcpcat 0.3.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.
@@ -1273,7 +1273,7 @@ var DEFAULT_RECONNECT_MAX_DELAY_MS = 3e4;
1273
1273
  var DEFAULT_RECONNECT_MAX_RETRIES = 5;
1274
1274
  async function consumeSseStream(stream, options) {
1275
1275
  const reader = stream.getReader();
1276
- const decoder = new TextDecoder();
1276
+ const decoder2 = new TextDecoder();
1277
1277
  let buffered = "";
1278
1278
  let eventName;
1279
1279
  let eventId;
@@ -1324,7 +1324,7 @@ async function consumeSseStream(stream, options) {
1324
1324
  const { done, value } = await reader.read();
1325
1325
  if (done)
1326
1326
  break;
1327
- buffered += decoder.decode(value, { stream: true });
1327
+ buffered += decoder2.decode(value, { stream: true });
1328
1328
  let newline = buffered.indexOf("\n");
1329
1329
  while (newline >= 0) {
1330
1330
  processLine(buffered.slice(0, newline));
@@ -1334,7 +1334,7 @@ async function consumeSseStream(stream, options) {
1334
1334
  if (Buffer.byteLength(buffered) > maxEventBytes)
1335
1335
  throw new Error(`MCP SSE event exceeds ${maxEventBytes} bytes`);
1336
1336
  }
1337
- buffered += decoder.decode();
1337
+ buffered += decoder2.decode();
1338
1338
  if (buffered)
1339
1339
  processLine(buffered);
1340
1340
  dispatch();
@@ -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
  }
@@ -1862,16 +2053,16 @@ async function createStatelessConnection(options) {
1862
2053
  }
1863
2054
  if (res.status >= 400) {
1864
2055
  const text = await res.text();
1865
- let rpcError;
2056
+ let rpcError2;
1866
2057
  try {
1867
2058
  const parsed = JSON.parse(text);
1868
2059
  if (parsed.error && typeof parsed.error.code === "number") {
1869
- rpcError = parsed.error;
2060
+ rpcError2 = parsed.error;
1870
2061
  }
1871
2062
  } catch {
1872
2063
  }
1873
- if (rpcError) {
1874
- throw new StatelessRpcError(rpcError.code, rpcError.message, rpcError.data);
2064
+ if (rpcError2) {
2065
+ throw new StatelessRpcError(rpcError2.code, rpcError2.message, rpcError2.data);
1875
2066
  }
1876
2067
  throw new StatelessHttpError(res.status, res.statusText, method, text.slice(0, 512));
1877
2068
  }
@@ -1888,10 +2079,10 @@ async function createStatelessConnection(options) {
1888
2079
  }
1889
2080
  return frame.result;
1890
2081
  }
1891
- const discoverResult = await request("server/discover", {
2082
+ const discoverResult2 = await request("server/discover", {
1892
2083
  protocolVersion: STATELESS_PROTOCOL_VERSION
1893
2084
  });
1894
- const supported = Array.isArray(discoverResult?.supportedVersions) ? discoverResult.supportedVersions.filter((v) => typeof v === "string") : [];
2085
+ const supported = Array.isArray(discoverResult2?.supportedVersions) ? discoverResult2.supportedVersions.filter((v) => typeof v === "string") : [];
1895
2086
  if (!supported.includes(STATELESS_PROTOCOL_VERSION)) {
1896
2087
  throw new Error(
1897
2088
  `Server does not support ${STATELESS_PROTOCOL_VERSION} (advertised: ${supported.join(", ") || "none"})`
@@ -1902,7 +2093,7 @@ async function createStatelessConnection(options) {
1902
2093
  return {
1903
2094
  async info() {
1904
2095
  if (serverInfoCache) return serverInfoCache;
1905
- const result = discoverResult;
2096
+ const result = discoverResult2;
1906
2097
  serverInfoCache = {
1907
2098
  name: result?.serverInfo?.name,
1908
2099
  version: result?.serverInfo?.version,
@@ -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;
@@ -2393,6 +2589,1399 @@ var MergedConfigSource = class _MergedConfigSource {
2393
2589
  }
2394
2590
  };
2395
2591
 
2592
+ // src/config/expand.ts
2593
+ var PLACEHOLDER = /\$\{([A-Za-z_][A-Za-z0-9_]*)\}/g;
2594
+ var PLACEHOLDER_TEST = /\$\{[A-Za-z_][A-Za-z0-9_]*\}/;
2595
+ function containsPlaceholders(value) {
2596
+ return PLACEHOLDER_TEST.test(value);
2597
+ }
2598
+ function expandPlaceholdersStrict(value, env) {
2599
+ let hasUndefined = false;
2600
+ const result = value.replace(PLACEHOLDER, (_, name) => {
2601
+ if (env[name] === void 0) {
2602
+ hasUndefined = true;
2603
+ return "";
2604
+ }
2605
+ return env[name];
2606
+ });
2607
+ if (hasUndefined) return void 0;
2608
+ return result;
2609
+ }
2610
+
2611
+ // src/serve/catalog.ts
2612
+ var NAME_RE = /^[A-Za-z][\w-]*$/;
2613
+ function sanitizeName(raw) {
2614
+ const cleaned = raw.toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_+|_+$/g, "");
2615
+ return cleaned || "cli";
2616
+ }
2617
+ function cliPrefix(help, filePath, explicit) {
2618
+ if (explicit) return sanitizeName(explicit);
2619
+ const fromUsage = usageProgramName(help);
2620
+ if (fromUsage) return sanitizeName(fromUsage);
2621
+ const base = filePath.split("/").pop() ?? filePath;
2622
+ return sanitizeName(base.replace(/\.jsh$/i, ""));
2623
+ }
2624
+ function parseHelp(help) {
2625
+ const block = commandsFromBlock(help);
2626
+ const program = usageProgramName(help);
2627
+ const commands = mergeCommandLists(block.commands, usageCommands(help, program));
2628
+ return {
2629
+ ...program ? { program } : {},
2630
+ commands: applyScopedFlags(commands, block.scoped),
2631
+ globalFlags: block.globalFlags
2632
+ };
2633
+ }
2634
+ function isGroupCandidate(command) {
2635
+ return command.path.length === 1 && command.positionals.length === 0 && command.flags.length === 0;
2636
+ }
2637
+ function nestGroup(commands, group, nested, topNames) {
2638
+ const children = attachGlobalFlags(nested.commands, nested.globalFlags);
2639
+ if (children.length === 0) return null;
2640
+ const childNames = children.map((child) => child.path[0] ?? "");
2641
+ const same = childNames.length === topNames.size && childNames.every((name) => topNames.has(name));
2642
+ if (same) return null;
2643
+ const nestedCommands = children.map((child) => ({ ...child, path: childPath(group, child) }));
2644
+ return commands.filter((command) => command.path[0] !== group).concat(nestedCommands);
2645
+ }
2646
+ function attachGlobalFlags(commands, globalFlags) {
2647
+ if (globalFlags.length === 0) return commands;
2648
+ return commands.map((command) => ({ ...command, flags: mergeFlags(command.flags, globalFlags) }));
2649
+ }
2650
+ function parseMcpOverride(stdout) {
2651
+ let value;
2652
+ try {
2653
+ value = JSON.parse(stdout);
2654
+ } catch {
2655
+ return null;
2656
+ }
2657
+ if (!value || typeof value !== "object" || Array.isArray(value)) return null;
2658
+ const commandsValue = Object.getOwnPropertyDescriptor(value, "commands")?.value;
2659
+ if (!Array.isArray(commandsValue)) return null;
2660
+ const commands = [];
2661
+ for (const entry of commandsValue) {
2662
+ const command = readMcpCommand(entry);
2663
+ if (command) commands.push(command);
2664
+ }
2665
+ if (commands.length === 0) return null;
2666
+ return { commands, globalFlags: [] };
2667
+ }
2668
+ function buildTools(prefix, commands) {
2669
+ const tools = [
2670
+ {
2671
+ name: `${prefix}_help`,
2672
+ description: `Show ${prefix} help.`,
2673
+ inputSchema: objectSchema({}),
2674
+ cli: prefix,
2675
+ kind: "help",
2676
+ commandPath: []
2677
+ },
2678
+ {
2679
+ name: `${prefix}_invoke`,
2680
+ description: `Run ${prefix} with an argv array (one element per argument) and optional stdin. A non-zero exit is a tool error.`,
2681
+ inputSchema: invokeSchema(),
2682
+ cli: prefix,
2683
+ kind: "invoke",
2684
+ commandPath: []
2685
+ }
2686
+ ];
2687
+ for (const command of commands) {
2688
+ const leaf = command.path.join("_");
2689
+ tools.push({
2690
+ name: `${prefix}_${leaf}`,
2691
+ description: command.description || `Run ${prefix} ${command.path.join(" ")}`,
2692
+ inputSchema: commandSchema(command),
2693
+ cli: prefix,
2694
+ kind: "command",
2695
+ commandPath: command.path
2696
+ });
2697
+ }
2698
+ return tools;
2699
+ }
2700
+ function readToolArguments(input) {
2701
+ if (input === void 0 || input === null) return { fields: [] };
2702
+ if (typeof input !== "object" || Array.isArray(input)) {
2703
+ return { fields: [], error: "arguments must be an object" };
2704
+ }
2705
+ const fields = [];
2706
+ let stdin;
2707
+ let passthrough;
2708
+ let argv;
2709
+ for (const key of Object.keys(input)) {
2710
+ const value = Object.getOwnPropertyDescriptor(input, key)?.value;
2711
+ const read = readArgument(key, value);
2712
+ if (read.error) return { fields, error: read.error };
2713
+ if (read.stdin !== void 0) stdin = read.stdin;
2714
+ else if (read.passthrough) passthrough = read.passthrough;
2715
+ else if (read.argv) argv = read.argv;
2716
+ else if (read.field) fields.push(read.field);
2717
+ }
2718
+ return {
2719
+ fields,
2720
+ ...stdin !== void 0 ? { stdin } : {},
2721
+ ...passthrough ? { passthrough } : {},
2722
+ ...argv ? { argv } : {}
2723
+ };
2724
+ }
2725
+ function argvForCommand(command, args2) {
2726
+ if (args2.error) return { error: args2.error };
2727
+ const argv = [...command.path];
2728
+ for (const positional of command.positionals) {
2729
+ const found = args2.fields.find((field) => field.name === positional.name);
2730
+ if (!found) {
2731
+ if (positional.required) return { error: `missing ${positional.name}` };
2732
+ continue;
2733
+ }
2734
+ if (typeof found.value !== "string" && typeof found.value !== "number") {
2735
+ return { error: `${positional.name} must be a string` };
2736
+ }
2737
+ argv.push(String(found.value));
2738
+ }
2739
+ const flags = appendFlags(command.flags, args2.fields);
2740
+ if ("error" in flags) return flags;
2741
+ argv.push(...flags.argv);
2742
+ if (args2.passthrough) argv.push(...args2.passthrough);
2743
+ return { argv, ...args2.stdin !== void 0 ? { stdin: args2.stdin } : {} };
2744
+ }
2745
+ function appendFlags(flags, fields) {
2746
+ const argv = [];
2747
+ for (const flag of flags) {
2748
+ const found = fields.find((field) => field.name === flag.name);
2749
+ if (!found || found.value === false) continue;
2750
+ const encoded = encodeFlag(flag, found.value);
2751
+ if ("error" in encoded) return encoded;
2752
+ argv.push(...encoded.argv);
2753
+ }
2754
+ return { argv };
2755
+ }
2756
+ function encodeFlag(flag, value) {
2757
+ if (flag.type === "boolean") {
2758
+ return value === true ? { argv: [`--${flag.name}`] } : { argv: [] };
2759
+ }
2760
+ if (flag.type === "string[]") {
2761
+ if (!Array.isArray(value)) return { error: `${flag.name} must be an array of strings` };
2762
+ const argv = [];
2763
+ for (const item of value) argv.push(`--${flag.name}`, item);
2764
+ return { argv };
2765
+ }
2766
+ if (typeof value !== "string" && typeof value !== "number") {
2767
+ return { error: `${flag.name} must be a string` };
2768
+ }
2769
+ return { argv: [`--${flag.name}`, String(value)] };
2770
+ }
2771
+ function commandsFromBlock(help) {
2772
+ let section = "none";
2773
+ const commands = [];
2774
+ const globalFlags = [];
2775
+ const scoped = [];
2776
+ for (const rawLine of help.split("\n")) {
2777
+ const line = rawLine.replace(/\t/g, " ");
2778
+ const header = classifyHeader(line);
2779
+ if (header) {
2780
+ section = header.kind === "other" ? "none" : header.kind;
2781
+ if (header.kind === "scoped" && header.name) scoped.push({ name: header.name, flags: [] });
2782
+ continue;
2783
+ }
2784
+ pushSectionLine(section, line, commands, globalFlags, scoped);
2785
+ }
2786
+ return { commands, globalFlags, scoped };
2787
+ }
2788
+ function pushSectionLine(section, line, commands, globalFlags, scoped) {
2789
+ if (section === "commands") {
2790
+ const command = parseCommandLine(line);
2791
+ if (command) commands.push(command);
2792
+ return;
2793
+ }
2794
+ const flag = parseFlagLine(line);
2795
+ if (!flag) return;
2796
+ if (section === "options") globalFlags.push(flag);
2797
+ if (section === "scoped") scoped[scoped.length - 1]?.flags.push(flag);
2798
+ }
2799
+ function classifyHeader(line) {
2800
+ const text = line.trim();
2801
+ if (/^commands:?$/i.test(text)) return { kind: "commands" };
2802
+ if (/^options:?$/i.test(text)) return { kind: "options" };
2803
+ const flags = /^([A-Za-z][\w-]*)\s+flags:?$/i.exec(text);
2804
+ if (flags?.[1]) return { kind: "scoped", name: flags[1].toLowerCase() };
2805
+ if (/^[A-Z][A-Z0-9 ]+:$/.test(text)) return { kind: "other" };
2806
+ return null;
2807
+ }
2808
+ function parseCommandLine(line) {
2809
+ if (!/^\s{2,}\S/.test(line)) return null;
2810
+ const parts = line.trim().split(/\s{2,}/);
2811
+ if (parts.length < 2) return null;
2812
+ const tokens = tokenize(parts[0] ?? "");
2813
+ const name = tokens[0] ?? "";
2814
+ if (!NAME_RE.test(name) || name.startsWith("-")) return null;
2815
+ const spec = emptySpec();
2816
+ for (const token of tokens.slice(1)) parseUsageToken(token, spec);
2817
+ return {
2818
+ path: [name],
2819
+ description: parts.slice(1).join(" ").trim(),
2820
+ positionals: spec.positionals,
2821
+ flags: spec.flags
2822
+ };
2823
+ }
2824
+ function usageProgramName(help) {
2825
+ for (const raw of help.split("\n")) {
2826
+ if (!/^[\s>*]*usage:/i.test(raw)) continue;
2827
+ const tokens = tokenize(raw);
2828
+ const name = tokens[0];
2829
+ if (name && NAME_RE.test(name)) return name;
2830
+ }
2831
+ return void 0;
2832
+ }
2833
+ function usageCommands(help, program) {
2834
+ const commands = [];
2835
+ for (const raw of help.split("\n")) {
2836
+ const command = usageCommand(raw, program);
2837
+ if (command) commands.push(command);
2838
+ }
2839
+ return commands;
2840
+ }
2841
+ function isCommandSlot(token) {
2842
+ const inner = token.replace(/^\[|\]$/g, "").replace(/^<|>$/g, "").toLowerCase();
2843
+ return inner === "command" || inner === "cmd" || inner === "subcommand" || inner === "verb";
2844
+ }
2845
+ function usageCommand(raw, program) {
2846
+ const line = raw.trim();
2847
+ const tokens = tokenize(line);
2848
+ if (tokens.length < 2) return null;
2849
+ const isUsage = /^usage:/i.test(line);
2850
+ if (!isUsage && tokens[0] !== program) return null;
2851
+ const path = [];
2852
+ let index = 1;
2853
+ while (index < tokens.length && path.length < 2 && NAME_RE.test(tokens[index] ?? "")) {
2854
+ path.push(tokens[index] ?? "");
2855
+ index++;
2856
+ }
2857
+ if (path.length === 0) return null;
2858
+ if (isCommandSlot(tokens[index] ?? "")) return null;
2859
+ const spec = emptySpec();
2860
+ for (const token of tokens.slice(index)) parseUsageToken(token, spec);
2861
+ return { path, description: "", positionals: spec.positionals, flags: spec.flags };
2862
+ }
2863
+ function emptySpec() {
2864
+ return { positionals: [], flags: [] };
2865
+ }
2866
+ function parseUsageToken(token, into) {
2867
+ const optional = token.startsWith("[") && token.endsWith("]");
2868
+ const inner = optional ? token.slice(1, -1).trim() : token;
2869
+ if (inner.startsWith("--")) {
2870
+ const flag = parseFlagToken(inner);
2871
+ if (flag) into.flags.push(flag);
2872
+ return;
2873
+ }
2874
+ if (inner.startsWith("<") && inner.endsWith(">")) {
2875
+ into.positionals.push({
2876
+ name: cleanName(inner.slice(1, -1)),
2877
+ required: !optional,
2878
+ description: ""
2879
+ });
2880
+ return;
2881
+ }
2882
+ if (NAME_RE.test(inner)) {
2883
+ into.positionals.push({ name: cleanName(inner), required: !optional, description: "" });
2884
+ }
2885
+ }
2886
+ function parseFlagToken(inner) {
2887
+ const match = /^--([A-Za-z][\w-]*)(?:(?:=|\s+)(\S+))?$/.exec(inner.trim());
2888
+ if (!match?.[1]) return null;
2889
+ const value = match[2] ?? "";
2890
+ if (!value) return { name: match[1], type: "boolean", description: "" };
2891
+ return { name: match[1], type: value.includes("...") ? "string[]" : "string", description: "" };
2892
+ }
2893
+ function parseFlagLine(line) {
2894
+ const trimmed = line.trim();
2895
+ if (!trimmed.includes("--")) return null;
2896
+ const match = /--([A-Za-z][\w-]*)(?:=(\S+)|(?:\s+(<[^>]+>|\[[^\]]+\]|[A-Z][A-Z0-9_-]+)))?/.exec(
2897
+ trimmed
2898
+ );
2899
+ if (!match?.[1]) return null;
2900
+ const value = match[2] || match[3] || "";
2901
+ const repeatable = /\brepeat|\.\.\.|one or more/i.test(trimmed);
2902
+ const description = flagDescription(trimmed);
2903
+ if (!value && !repeatable) return { name: match[1], type: "boolean", description };
2904
+ return { name: match[1], type: repeatable ? "string[]" : "string", description };
2905
+ }
2906
+ function flagDescription(line) {
2907
+ const parts = line.trim().split(/\s{2,}/);
2908
+ return parts.length > 1 ? parts.slice(1).join(" ").trim() : "";
2909
+ }
2910
+ function tokenize(line) {
2911
+ const source = line.trim().replace(/^usage:\s*/i, "");
2912
+ const tokens = [];
2913
+ let index = 0;
2914
+ while (index < source.length) {
2915
+ if (source[index] === " ") {
2916
+ index++;
2917
+ continue;
2918
+ }
2919
+ const closer = source[index] === "[" ? "]" : source[index] === "<" ? ">" : "";
2920
+ if (closer) {
2921
+ const end = source.indexOf(closer, index);
2922
+ if (end === -1) {
2923
+ tokens.push(source.slice(index));
2924
+ break;
2925
+ }
2926
+ tokens.push(source.slice(index, end + 1));
2927
+ index = end + 1;
2928
+ continue;
2929
+ }
2930
+ const next = source.indexOf(" ", index);
2931
+ if (next === -1) {
2932
+ tokens.push(source.slice(index));
2933
+ break;
2934
+ }
2935
+ tokens.push(source.slice(index, next));
2936
+ index = next;
2937
+ }
2938
+ return tokens;
2939
+ }
2940
+ function mergeCommandLists(primary, extra) {
2941
+ const merged = primary.map((command) => ({ ...command }));
2942
+ for (const command of extra) {
2943
+ const existing = merged.find((item) => samePath(item.path, command.path));
2944
+ if (!existing) {
2945
+ merged.push(command);
2946
+ continue;
2947
+ }
2948
+ if (existing.positionals.length === 0) existing.positionals = command.positionals;
2949
+ if (!existing.description) existing.description = command.description;
2950
+ existing.flags = mergeFlags(existing.flags, command.flags);
2951
+ }
2952
+ return merged;
2953
+ }
2954
+ function applyScopedFlags(commands, scoped) {
2955
+ return commands.map((command) => {
2956
+ const extra = scoped.find((scope) => scope.name === (command.path[0] ?? "").toLowerCase());
2957
+ if (!extra) return command;
2958
+ return { ...command, flags: mergeFlags(command.flags, extra.flags) };
2959
+ });
2960
+ }
2961
+ function mergeFlags(left, right) {
2962
+ const merged = [...left];
2963
+ for (const flag of right) {
2964
+ if (!merged.some((existing) => existing.name === flag.name)) merged.push(flag);
2965
+ }
2966
+ return merged;
2967
+ }
2968
+ function samePath(left, right) {
2969
+ return left.length === right.length && left.every((part, index) => part === right[index]);
2970
+ }
2971
+ function childPath(group, child) {
2972
+ if (child.path[0] === group) return child.path.slice(0, 2);
2973
+ return [group, child.path[0] ?? ""].filter((part) => part.length > 0).slice(0, 2);
2974
+ }
2975
+ function cleanName(raw) {
2976
+ const cleaned = raw.replace(/[^A-Za-z0-9_-]+/g, "").replace(/^-+/, "");
2977
+ return cleaned || "arg";
2978
+ }
2979
+ function readMcpCommand(value) {
2980
+ if (!value || typeof value !== "object" || Array.isArray(value)) return null;
2981
+ const name = ownString(value, "name");
2982
+ if (!name) return null;
2983
+ const path = name.split(/\s+/).filter((part) => NAME_RE.test(part)).slice(0, 2);
2984
+ if (path.length === 0) return null;
2985
+ return {
2986
+ path,
2987
+ description: ownString(value, "description") ?? "",
2988
+ positionals: readMcpPositionals(Object.getOwnPropertyDescriptor(value, "positionals")?.value),
2989
+ flags: readMcpFlags(Object.getOwnPropertyDescriptor(value, "flags")?.value)
2990
+ };
2991
+ }
2992
+ function readMcpPositionals(value) {
2993
+ if (!Array.isArray(value)) return [];
2994
+ const positionals = [];
2995
+ for (const entry of value) {
2996
+ if (!entry || typeof entry !== "object" || Array.isArray(entry)) continue;
2997
+ const name = ownString(entry, "name");
2998
+ if (!name) continue;
2999
+ const required = Object.getOwnPropertyDescriptor(entry, "required")?.value;
3000
+ positionals.push({ name, required: required !== false, description: "" });
3001
+ }
3002
+ return positionals;
3003
+ }
3004
+ function readMcpFlags(value) {
3005
+ if (!Array.isArray(value)) return [];
3006
+ const flags = [];
3007
+ for (const entry of value) {
3008
+ if (!entry || typeof entry !== "object" || Array.isArray(entry)) continue;
3009
+ const name = ownString(entry, "name");
3010
+ const type = ownString(entry, "type");
3011
+ if (!name || type !== "boolean" && type !== "string" && type !== "string[]") continue;
3012
+ flags.push({ name, type, description: "" });
3013
+ }
3014
+ return flags;
3015
+ }
3016
+ function ownString(value, key) {
3017
+ const found = Object.getOwnPropertyDescriptor(value, key)?.value;
3018
+ return typeof found === "string" && found.length > 0 ? found : void 0;
3019
+ }
3020
+ function objectSchema(properties, required) {
3021
+ return {
3022
+ type: "object",
3023
+ properties,
3024
+ additionalProperties: false,
3025
+ ...required && required.length > 0 ? { required } : {}
3026
+ };
3027
+ }
3028
+ function invokeSchema() {
3029
+ return objectSchema(
3030
+ {
3031
+ argv: {
3032
+ type: "array",
3033
+ items: { type: "string" },
3034
+ description: "Arguments, one element per argument. Never a shell string."
3035
+ },
3036
+ stdin: { type: "string", description: "Optional standard input." }
3037
+ },
3038
+ ["argv"]
3039
+ );
3040
+ }
3041
+ function commandSchema(command) {
3042
+ const properties = {};
3043
+ const required = [];
3044
+ for (const positional of command.positionals) {
3045
+ properties[positional.name] = {
3046
+ type: "string",
3047
+ description: positional.description || positional.name
3048
+ };
3049
+ if (positional.required) required.push(positional.name);
3050
+ }
3051
+ for (const flag of command.flags) {
3052
+ properties[flag.name] = flagProperty(flag);
3053
+ }
3054
+ properties.stdin = { type: "string", description: "Optional standard input." };
3055
+ properties.passthrough = {
3056
+ type: "array",
3057
+ items: { type: "string" },
3058
+ description: "Extra arguments appended as-is."
3059
+ };
3060
+ return objectSchema(properties, required);
3061
+ }
3062
+ function flagProperty(flag) {
3063
+ if (flag.type === "boolean")
3064
+ return { type: "boolean", description: flag.description || flag.name };
3065
+ if (flag.type === "string[]") {
3066
+ return { type: "array", items: { type: "string" }, description: flag.description || flag.name };
3067
+ }
3068
+ return { type: "string", description: flag.description || flag.name };
3069
+ }
3070
+ function readArgument(key, value) {
3071
+ if (key === "stdin") {
3072
+ return typeof value === "string" ? { stdin: value } : { error: "stdin must be a string" };
3073
+ }
3074
+ if (key === "passthrough") return readStringArray(value, "passthrough");
3075
+ if (key === "argv") {
3076
+ const read = readStringArray(value, "argv");
3077
+ return read.passthrough ? { argv: read.passthrough } : read;
3078
+ }
3079
+ if (!isArgValue(value)) return { error: `${key} has an unsupported type` };
3080
+ return { field: { name: key, value } };
3081
+ }
3082
+ function readStringArray(value, label) {
3083
+ if (!Array.isArray(value) || !value.every((item) => typeof item === "string")) {
3084
+ return { error: `${label} must be an array of strings` };
3085
+ }
3086
+ return { passthrough: value };
3087
+ }
3088
+ function isArgValue(value) {
3089
+ if (typeof value === "string" || typeof value === "boolean" || typeof value === "number")
3090
+ return true;
3091
+ return Array.isArray(value) && value.every((item) => typeof item === "string");
3092
+ }
3093
+ function parseToolTemplate(spec) {
3094
+ const colonIdx = spec.indexOf(":");
3095
+ if (colonIdx <= 0) return null;
3096
+ const name = spec.slice(0, colonIdx).trim();
3097
+ const template = spec.slice(colonIdx + 1).trim();
3098
+ if (!name || !template) return null;
3099
+ const placeholders = [];
3100
+ const re = /\{(\w+)\}/g;
3101
+ let m;
3102
+ while ((m = re.exec(template)) !== null) {
3103
+ if (!placeholders.includes(m[1])) placeholders.push(m[1]);
3104
+ }
3105
+ const properties = {};
3106
+ for (const ph of placeholders) {
3107
+ properties[ph] = { type: "string", description: ph };
3108
+ }
3109
+ return {
3110
+ name,
3111
+ description: template,
3112
+ inputSchema: {
3113
+ type: "object",
3114
+ properties,
3115
+ required: placeholders,
3116
+ additionalProperties: false
3117
+ },
3118
+ cli: "",
3119
+ kind: "command",
3120
+ commandPath: []
3121
+ };
3122
+ }
3123
+ function templateToArgv(template, args2) {
3124
+ let expanded = template;
3125
+ for (const [key, value] of Object.entries(args2)) {
3126
+ expanded = expanded.replaceAll(`{${key}}`, value);
3127
+ }
3128
+ return expanded.split(/\s+/).filter(Boolean);
3129
+ }
3130
+
3131
+ // src/serve/http.ts
3132
+ var encoder = new TextEncoder();
3133
+ var decoder = new TextDecoder();
3134
+ var MAX_HEADER_SIZE = 16384;
3135
+ var MAX_BODY_SIZE = 10485760;
3136
+ async function readUntil(reader, buf, bufLen, sentinel, maxSize) {
3137
+ for (; ; ) {
3138
+ const text = decoder.decode(new Uint8Array(buf.buffer, buf.byteOffset, bufLen));
3139
+ const pos = text.indexOf(sentinel);
3140
+ if (pos >= 0) return { buf, bufLen, pos };
3141
+ if (bufLen >= maxSize) return null;
3142
+ const { done, value } = await reader.read();
3143
+ if (done) return null;
3144
+ if (bufLen + value.length > buf.length) {
3145
+ const newBuf = new Uint8Array(Math.max(buf.length * 2, bufLen + value.length));
3146
+ newBuf.set(new Uint8Array(buf.buffer, buf.byteOffset, bufLen));
3147
+ buf = newBuf;
3148
+ }
3149
+ buf.set(value, bufLen);
3150
+ bufLen += value.length;
3151
+ }
3152
+ }
3153
+ async function readExact(reader, buf, bufLen, needed) {
3154
+ while (bufLen < needed) {
3155
+ const { done, value } = await reader.read();
3156
+ if (done) return null;
3157
+ if (bufLen + value.length > buf.length) {
3158
+ const newBuf = new Uint8Array(Math.max(buf.length * 2, bufLen + value.length));
3159
+ newBuf.set(new Uint8Array(buf.buffer, buf.byteOffset, bufLen));
3160
+ buf = newBuf;
3161
+ }
3162
+ buf.set(value, bufLen);
3163
+ bufLen += value.length;
3164
+ }
3165
+ return { buf, bufLen };
3166
+ }
3167
+ function parseRequestLine(buf, endPos) {
3168
+ const headText = decoder.decode(new Uint8Array(buf.buffer, buf.byteOffset, endPos));
3169
+ const lines = headText.split("\r\n");
3170
+ const requestLine = lines[0];
3171
+ if (!requestLine) return null;
3172
+ const parts = requestLine.split(" ");
3173
+ if (parts.length < 2) return null;
3174
+ const method = parts[0];
3175
+ const target = parts[1];
3176
+ const headers = {};
3177
+ for (let i = 1; i < lines.length; i++) {
3178
+ const line = lines[i];
3179
+ if (!line) continue;
3180
+ const colonIdx = line.indexOf(":");
3181
+ if (colonIdx <= 0) continue;
3182
+ headers[line.slice(0, colonIdx).trim().toLowerCase()] = line.slice(colonIdx + 1).trim();
3183
+ }
3184
+ return { method, target, headers, headerEnd: endPos + 4 };
3185
+ }
3186
+ async function handleConnection(readable, writable, handler) {
3187
+ const reader = readable.getReader();
3188
+ const writer = writable.getWriter();
3189
+ let buf = new Uint8Array(4096);
3190
+ let bufLen = 0;
3191
+ try {
3192
+ for (; ; ) {
3193
+ const headerResult = await readUntil(reader, buf, bufLen, "\r\n\r\n", MAX_HEADER_SIZE);
3194
+ if (!headerResult) break;
3195
+ buf = headerResult.buf;
3196
+ bufLen = headerResult.bufLen;
3197
+ const parsed = parseRequestLine(buf, headerResult.pos);
3198
+ if (!parsed) {
3199
+ await writeResponse(writer, {
3200
+ status: 400,
3201
+ reason: "Bad Request",
3202
+ headers: { "content-type": "text/plain" },
3203
+ body: "Bad Request"
3204
+ });
3205
+ break;
3206
+ }
3207
+ const contentLength = Number(parsed.headers["content-length"] ?? 0);
3208
+ if (contentLength > MAX_BODY_SIZE) {
3209
+ await writeResponse(writer, {
3210
+ status: 413,
3211
+ reason: "Payload Too Large",
3212
+ headers: { "content-type": "text/plain" },
3213
+ body: "Payload Too Large"
3214
+ });
3215
+ break;
3216
+ }
3217
+ const bodyStart = parsed.headerEnd;
3218
+ const totalNeeded = bodyStart + contentLength;
3219
+ const bodyResult = await readExact(reader, buf, bufLen, totalNeeded);
3220
+ if (!bodyResult && contentLength > 0) break;
3221
+ if (bodyResult) {
3222
+ buf = bodyResult.buf;
3223
+ bufLen = bodyResult.bufLen;
3224
+ }
3225
+ const bodyBytes = buf.slice(bodyStart, bodyStart + contentLength);
3226
+ const body = decoder.decode(bodyBytes);
3227
+ const request = {
3228
+ method: parsed.method,
3229
+ target: parsed.target,
3230
+ headers: parsed.headers,
3231
+ body
3232
+ };
3233
+ const response = await handler(request);
3234
+ await writeResponse(writer, response);
3235
+ const connectionHeader = (parsed.headers.connection ?? "").toLowerCase();
3236
+ if (connectionHeader === "close") break;
3237
+ const consumed = bodyStart + contentLength;
3238
+ const remaining = bufLen - consumed;
3239
+ if (remaining > 0) {
3240
+ const newBuf = new Uint8Array(Math.max(4096, remaining));
3241
+ newBuf.set(new Uint8Array(buf.buffer, buf.byteOffset + consumed, remaining));
3242
+ buf = newBuf;
3243
+ bufLen = remaining;
3244
+ } else {
3245
+ buf = new Uint8Array(4096);
3246
+ bufLen = 0;
3247
+ }
3248
+ }
3249
+ } finally {
3250
+ reader.releaseLock();
3251
+ try {
3252
+ await writer.close();
3253
+ } catch {
3254
+ }
3255
+ }
3256
+ }
3257
+ async function writeResponse(writer, response) {
3258
+ const bodyLength = new TextEncoder().encode(response.body).length;
3259
+ const headers = { ...response.headers, "content-length": String(bodyLength) };
3260
+ const lines = [`HTTP/1.1 ${response.status} ${response.reason}`];
3261
+ for (const [name, value] of Object.entries(headers)) {
3262
+ lines.push(`${name}: ${value}`);
3263
+ }
3264
+ lines.push("", "");
3265
+ const headBytes = encoder.encode(lines.join("\r\n"));
3266
+ await writer.write(headBytes);
3267
+ if (response.body.length > 0) {
3268
+ const bodyBytes = encoder.encode(response.body);
3269
+ await writer.write(bodyBytes);
3270
+ }
3271
+ }
3272
+
3273
+ // src/serve/jsonrpc.ts
3274
+ var PROTOCOL_VERSIONS = ["2026-07-28", "2025-11-25", "2025-06-18"];
3275
+ var OUTPUT_CAP = 1048576;
3276
+ var JSON_CT = "application/json; charset=utf-8";
3277
+ async function handleJsonRpc(raw, tools, call, serverName, serverVersion) {
3278
+ let parsed;
3279
+ try {
3280
+ parsed = JSON.parse(raw);
3281
+ } catch {
3282
+ return rpcError(null, -32700, "Parse error");
3283
+ }
3284
+ if (Array.isArray(parsed)) return rpcError(null, -32600, "Batch requests are not supported");
3285
+ if (!parsed || typeof parsed !== "object") return rpcError(null, -32600, "Invalid request");
3286
+ const method = readString(parsed, "method");
3287
+ if (!method) return rpcError(readId(parsed), -32600, "Invalid request");
3288
+ if (!hasOwn(parsed, "id")) return { status: 202, contentType: "text/plain", body: "" };
3289
+ const id = readId(parsed);
3290
+ const params = Object.getOwnPropertyDescriptor(parsed, "params")?.value;
3291
+ if (method === "initialize")
3292
+ return rpcOk(id, initializeResult(params, serverName, serverVersion));
3293
+ if (method === "server/discover") return rpcOk(id, discoverResult(serverName, serverVersion));
3294
+ if (method === "tools/list") return rpcOk(id, { tools });
3295
+ if (method === "ping") return rpcOk(id, {});
3296
+ if (method === "tools/call") return toolCall(id, params, tools, call);
3297
+ return rpcError(id, -32601, `Method not found: ${method}`);
3298
+ }
3299
+ async function toolCall(id, params, tools, call) {
3300
+ const name = params && typeof params === "object" ? readString(params, "name") : void 0;
3301
+ if (!name) return rpcError(id, -32602, "tools/call requires a name");
3302
+ if (!tools.some((tool) => tool.name === name))
3303
+ return rpcError(id, -32602, `Unknown tool: ${name}`);
3304
+ const args2 = params && typeof params === "object" ? Object.getOwnPropertyDescriptor(params, "arguments")?.value : void 0;
3305
+ try {
3306
+ const result = await call(name, args2);
3307
+ return rpcOk(id, toolResult(result));
3308
+ } catch (err) {
3309
+ const message = err instanceof Error ? err.message : "tool call failed";
3310
+ return rpcError(id, -32e3, message);
3311
+ }
3312
+ }
3313
+ function toolResult(result) {
3314
+ const stdout = cap(result.stdout);
3315
+ const stderr = cap(result.stderr);
3316
+ const text = stderr ? stdout ? `${stdout}
3317
+ ${stderr}` : stderr : stdout;
3318
+ return {
3319
+ content: [{ type: "text", text }],
3320
+ isError: result.exitCode !== 0,
3321
+ structuredContent: { stdout, stderr, exitCode: result.exitCode }
3322
+ };
3323
+ }
3324
+ function initializeResult(params, serverName, serverVersion) {
3325
+ const requested = params && typeof params === "object" ? readString(params, "protocolVersion") : void 0;
3326
+ const protocolVersion = PROTOCOL_VERSIONS.find((version) => version === requested) ?? PROTOCOL_VERSIONS[0];
3327
+ return {
3328
+ protocolVersion,
3329
+ capabilities: { tools: { listChanged: false } },
3330
+ serverInfo: { name: serverName, version: serverVersion }
3331
+ };
3332
+ }
3333
+ function discoverResult(serverName, serverVersion) {
3334
+ return {
3335
+ protocolVersion: PROTOCOL_VERSIONS[0],
3336
+ supportedVersions: PROTOCOL_VERSIONS,
3337
+ capabilities: { tools: { listChanged: false } },
3338
+ serverInfo: { name: serverName, version: serverVersion }
3339
+ };
3340
+ }
3341
+ function cap(value) {
3342
+ if (value.length <= OUTPUT_CAP) return value;
3343
+ const half = Math.floor(OUTPUT_CAP / 2);
3344
+ const cut = value.length - OUTPUT_CAP;
3345
+ return `${value.slice(0, half)}
3346
+ \u2026 ${cut} characters cut \u2026
3347
+ ${value.slice(value.length - half)}`;
3348
+ }
3349
+ function rpcOk(id, result) {
3350
+ return {
3351
+ status: 200,
3352
+ contentType: JSON_CT,
3353
+ body: JSON.stringify({ jsonrpc: "2.0", id, result })
3354
+ };
3355
+ }
3356
+ function rpcError(id, code2, message) {
3357
+ return {
3358
+ status: 200,
3359
+ contentType: JSON_CT,
3360
+ body: JSON.stringify({ jsonrpc: "2.0", id, error: { code: code2, message } })
3361
+ };
3362
+ }
3363
+ function readId(value) {
3364
+ const id = Object.getOwnPropertyDescriptor(value, "id")?.value;
3365
+ if (typeof id === "string" || typeof id === "number" || id === null) return id;
3366
+ return null;
3367
+ }
3368
+ function readString(value, key) {
3369
+ const found = Object.getOwnPropertyDescriptor(value, key)?.value;
3370
+ return typeof found === "string" ? found : void 0;
3371
+ }
3372
+ function hasOwn(value, key) {
3373
+ return Object.prototype.hasOwnProperty.call(value, key);
3374
+ }
3375
+
3376
+ // src/serve/origin.ts
3377
+ var LOOPBACK_HOSTS = /* @__PURE__ */ new Set(["localhost", "127.0.0.1", "[::1]"]);
3378
+ function isLoopbackOrigin(origin) {
3379
+ try {
3380
+ const url = new URL(origin);
3381
+ const hostname = url.hostname;
3382
+ if (LOOPBACK_HOSTS.has(hostname)) return true;
3383
+ if (hostname === "::1") return true;
3384
+ if (hostname.endsWith(".localhost")) return true;
3385
+ return false;
3386
+ } catch {
3387
+ return false;
3388
+ }
3389
+ }
3390
+ function isLoopbackHost(host) {
3391
+ const hostname = host.replace(/:\d+$/, "");
3392
+ if (LOOPBACK_HOSTS.has(hostname)) return true;
3393
+ if (hostname === "::1") return true;
3394
+ if (hostname.endsWith(".localhost")) return true;
3395
+ return false;
3396
+ }
3397
+ function checkOrigin(origin, allowedOrigins, boundLoopback) {
3398
+ if (origin === void 0) return { ok: true };
3399
+ if (allowedOrigins.has(origin)) return { ok: true };
3400
+ if (isLoopbackOrigin(origin)) return { ok: true };
3401
+ if (!boundLoopback) return { ok: true };
3402
+ return { ok: false, reason: `origin ${origin} is not allowed` };
3403
+ }
3404
+ function checkHost(host, boundLoopback) {
3405
+ if (!boundLoopback) return { ok: true };
3406
+ if (!host) return { ok: false, reason: "missing Host header" };
3407
+ if (isLoopbackHost(host)) return { ok: true };
3408
+ return { ok: false, reason: `host ${host} is not loopback` };
3409
+ }
3410
+
3411
+ // src/serve/runtime.ts
3412
+ var CALL_TIMEOUT_MS = 12e4;
3413
+ var MAX_OUTPUT_DEFAULT = 1048576;
3414
+ var ALLOWED_ENV_KEYS = /* @__PURE__ */ new Set(["PATH", "HOME", "LANG"]);
3415
+ function parseHttpFlag(args2, i) {
3416
+ const next = args2[i + 1];
3417
+ if (!next || next.startsWith("-")) return { host: "127.0.0.1", port: 0, next: i };
3418
+ const colonIdx = next.lastIndexOf(":");
3419
+ if (colonIdx > 0 && colonIdx < next.length - 1) {
3420
+ return { host: next.slice(0, colonIdx), port: Number(next.slice(colonIdx + 1)), next: i + 1 };
3421
+ }
3422
+ return { host: "127.0.0.1", port: Number(next), next: i + 1 };
3423
+ }
3424
+ function parseCommandTarget(arg) {
3425
+ const eqIdx = arg.indexOf("=");
3426
+ if (eqIdx > 0 && !arg.slice(0, eqIdx).includes("/")) {
3427
+ return { name: arg.slice(0, eqIdx), path: arg.slice(eqIdx + 1) };
3428
+ }
3429
+ return { path: arg };
3430
+ }
3431
+ function parseSingleFlag(args2, i, opts) {
3432
+ const arg = args2[i];
3433
+ if (arg === "--stdio") {
3434
+ opts.transport = "stdio";
3435
+ } else if (arg === "--http") {
3436
+ opts.transport = "http";
3437
+ const parsed = parseHttpFlag(args2, i);
3438
+ opts.host = parsed.host;
3439
+ opts.port = parsed.port;
3440
+ return parsed.next;
3441
+ } else if (arg === "--bearer" && args2[i + 1]) {
3442
+ opts.bearer = args2[++i];
3443
+ } else if (arg === "--allow-origin" && args2[i + 1]) {
3444
+ opts.allowOrigins.push(args2[++i]);
3445
+ } else if (arg === "--env" && args2[i + 1]) {
3446
+ opts.envAllow.push(args2[++i]);
3447
+ } else if (arg === "--tool" && args2[i + 1]) {
3448
+ opts.toolTemplates.push(args2[++i]);
3449
+ } else if (arg === "--call-timeout" && args2[i + 1]) {
3450
+ opts.callTimeout = Number(args2[++i]) * 1e3;
3451
+ } else if (arg === "--concurrency" && args2[i + 1]) {
3452
+ opts.concurrency = Number(args2[++i]);
3453
+ } else if (arg === "--max-output" && args2[i + 1]) {
3454
+ opts.maxOutput = Number(args2[++i]);
3455
+ } else if (arg === "--cwd" && args2[i + 1]) {
3456
+ opts.cwd = args2[++i];
3457
+ } else if (arg === "--json") {
3458
+ opts.json = true;
3459
+ } else if (!arg.startsWith("-")) {
3460
+ opts.commands.push(parseCommandTarget(arg));
3461
+ }
3462
+ return i;
3463
+ }
3464
+ function parseServeFlags(args2) {
3465
+ const opts = {
3466
+ transport: "stdio",
3467
+ host: "127.0.0.1",
3468
+ port: 0,
3469
+ bearer: void 0,
3470
+ allowOrigins: [],
3471
+ envAllow: [],
3472
+ toolTemplates: [],
3473
+ callTimeout: CALL_TIMEOUT_MS,
3474
+ concurrency: 1,
3475
+ maxOutput: MAX_OUTPUT_DEFAULT,
3476
+ cwd: void 0,
3477
+ json: false,
3478
+ commands: []
3479
+ };
3480
+ for (let i = 0; i < args2.length; i++) {
3481
+ i = parseSingleFlag(args2, i, opts);
3482
+ }
3483
+ return opts;
3484
+ }
3485
+ function expandBearerToken(template, env) {
3486
+ if (!containsPlaceholders(template)) return void 0;
3487
+ return expandPlaceholdersStrict(template, env);
3488
+ }
3489
+ function constantTimeEqual(a, b) {
3490
+ if (a.length !== b.length) return false;
3491
+ let mismatch = 0;
3492
+ for (let i = 0; i < a.length; i++) {
3493
+ mismatch |= a.charCodeAt(i) ^ b.charCodeAt(i);
3494
+ }
3495
+ return mismatch === 0;
3496
+ }
3497
+ function buildChildEnv(platform2, envAllow) {
3498
+ const env = {};
3499
+ for (const key of ALLOWED_ENV_KEYS) {
3500
+ if (platform2.env[key]) env[key] = platform2.env[key];
3501
+ }
3502
+ for (const entry of envAllow) {
3503
+ const eqIdx = entry.indexOf("=");
3504
+ if (eqIdx > 0) {
3505
+ env[entry.slice(0, eqIdx)] = entry.slice(eqIdx + 1);
3506
+ } else if (platform2.env[entry]) {
3507
+ env[entry] = platform2.env[entry];
3508
+ }
3509
+ }
3510
+ return env;
3511
+ }
3512
+ function capOutput(value, limit) {
3513
+ if (value.length <= limit) return value;
3514
+ const half = Math.floor(limit / 2);
3515
+ const cut = value.length - limit;
3516
+ return `${value.slice(0, half)}
3517
+ \u2026 ${cut} characters cut \u2026
3518
+ ${value.slice(value.length - half)}`;
3519
+ }
3520
+ var inflightProcs = /* @__PURE__ */ new Set();
3521
+ async function runCommand(platform2, cmdPath, argv, childEnv, stdin, timeoutMs, maxOutput, cwd) {
3522
+ let proc;
3523
+ try {
3524
+ proc = await platform2.spawn({
3525
+ argv: [cmdPath, ...argv],
3526
+ env: childEnv,
3527
+ cwd
3528
+ });
3529
+ } catch (err) {
3530
+ return { stdout: "", stderr: `spawn failed: ${err.message}`, exitCode: 127 };
3531
+ }
3532
+ inflightProcs.add(proc);
3533
+ if (stdin !== void 0) {
3534
+ const writer = proc.stdin.getWriter();
3535
+ await writer.write(new TextEncoder().encode(stdin));
3536
+ await writer.close();
3537
+ } else {
3538
+ try {
3539
+ await proc.stdin.getWriter().close();
3540
+ } catch {
3541
+ }
3542
+ }
3543
+ const stdoutChunks = [];
3544
+ const stderrChunks = [];
3545
+ const readStream = async (stream, into) => {
3546
+ const r = stream.getReader();
3547
+ try {
3548
+ for (; ; ) {
3549
+ const { done, value } = await r.read();
3550
+ if (done) break;
3551
+ into.push(value);
3552
+ }
3553
+ } finally {
3554
+ r.releaseLock();
3555
+ }
3556
+ };
3557
+ const timer = new Promise(
3558
+ (resolve) => setTimeout(() => resolve("timeout"), timeoutMs)
3559
+ );
3560
+ const work = Promise.all([
3561
+ readStream(proc.stdout, stdoutChunks),
3562
+ readStream(proc.stderr, stderrChunks),
3563
+ proc.wait()
3564
+ ]);
3565
+ const result = await Promise.race([work, timer]);
3566
+ if (result === "timeout") {
3567
+ proc.kill("SIGTERM");
3568
+ await new Promise((resolve) => setTimeout(resolve, 5e3));
3569
+ try {
3570
+ proc.kill("SIGKILL");
3571
+ } catch {
3572
+ }
3573
+ inflightProcs.delete(proc);
3574
+ return { stdout: "", stderr: "timed out", exitCode: 124 };
3575
+ }
3576
+ inflightProcs.delete(proc);
3577
+ const [, , exitResult] = result;
3578
+ const dec = new TextDecoder();
3579
+ return {
3580
+ stdout: capOutput(dec.decode(concat(stdoutChunks)), maxOutput),
3581
+ stderr: capOutput(dec.decode(concat(stderrChunks)), maxOutput),
3582
+ exitCode: exitResult.status
3583
+ };
3584
+ }
3585
+ function concat(chunks) {
3586
+ const total = chunks.reduce((s, c) => s + c.length, 0);
3587
+ const out = new Uint8Array(total);
3588
+ let offset = 0;
3589
+ for (const c of chunks) {
3590
+ out.set(c, offset);
3591
+ offset += c.length;
3592
+ }
3593
+ return out;
3594
+ }
3595
+ async function killInflight() {
3596
+ for (const proc of inflightProcs) {
3597
+ try {
3598
+ proc.kill("SIGTERM");
3599
+ } catch {
3600
+ }
3601
+ }
3602
+ if (inflightProcs.size > 0) {
3603
+ await new Promise((resolve) => setTimeout(resolve, 5e3));
3604
+ for (const proc of inflightProcs) {
3605
+ try {
3606
+ proc.kill("SIGKILL");
3607
+ } catch {
3608
+ }
3609
+ }
3610
+ }
3611
+ inflightProcs.clear();
3612
+ }
3613
+ async function runHelpForCli(platform2, filePath, childEnv, cwd) {
3614
+ const dashed = await runCommand(
3615
+ platform2,
3616
+ filePath,
3617
+ ["--help"],
3618
+ childEnv,
3619
+ void 0,
3620
+ 1e4,
3621
+ MAX_OUTPUT_DEFAULT,
3622
+ cwd
3623
+ );
3624
+ if (dashed.exitCode === 0 && dashed.stdout.trim()) return dashed.stdout;
3625
+ const plain = await runCommand(
3626
+ platform2,
3627
+ filePath,
3628
+ ["help"],
3629
+ childEnv,
3630
+ void 0,
3631
+ 1e4,
3632
+ MAX_OUTPUT_DEFAULT,
3633
+ cwd
3634
+ );
3635
+ if (plain.exitCode === 0 && plain.stdout.trim()) return plain.stdout;
3636
+ return dashed.stdout.trim() ? dashed.stdout : "";
3637
+ }
3638
+ async function expandGroupCommands(platform2, filePath, commands, childEnv, cwd) {
3639
+ const topNames = new Set(commands.map((c) => c.path[0] ?? ""));
3640
+ let current = commands;
3641
+ for (const command of commands) {
3642
+ if (!isGroupCandidate(command)) continue;
3643
+ const group = command.path[0] ?? "";
3644
+ const nested = await runCommand(
3645
+ platform2,
3646
+ filePath,
3647
+ [group, "--help"],
3648
+ childEnv,
3649
+ void 0,
3650
+ 1e4,
3651
+ MAX_OUTPUT_DEFAULT,
3652
+ cwd
3653
+ );
3654
+ if (nested.exitCode !== 0 || !nested.stdout.trim()) continue;
3655
+ const replaced = nestGroup(current, group, parseHelp(nested.stdout), topNames);
3656
+ if (replaced) current = replaced;
3657
+ }
3658
+ return current;
3659
+ }
3660
+ async function discoverCli(platform2, filePath, explicitName, childEnv, cwd) {
3661
+ const mcp = await runCommand(
3662
+ platform2,
3663
+ filePath,
3664
+ ["--mcp"],
3665
+ childEnv,
3666
+ void 0,
3667
+ 1e4,
3668
+ MAX_OUTPUT_DEFAULT,
3669
+ cwd
3670
+ );
3671
+ const override = mcp.exitCode === 0 ? parseMcpOverride(mcp.stdout) : null;
3672
+ const helpText2 = override ? "" : await runHelpForCli(platform2, filePath, childEnv, cwd);
3673
+ if (!override && !helpText2.trim()) {
3674
+ throw new Error(`${filePath} did not print help or --mcp descriptor`);
3675
+ }
3676
+ const parsed = override ?? parseHelp(helpText2);
3677
+ let commands = parsed.commands;
3678
+ if (!override) {
3679
+ commands = attachGlobalFlags(commands, parsed.globalFlags);
3680
+ commands = await expandGroupCommands(platform2, filePath, commands, childEnv, cwd);
3681
+ }
3682
+ return { name: cliPrefix(helpText2, filePath, explicitName), path: filePath, commands };
3683
+ }
3684
+ function createToolExecutor(platform2, clis, allTools, childEnv, options, serveCwd) {
3685
+ const rpcTools = allTools.map((t) => ({
3686
+ name: t.name,
3687
+ description: t.description,
3688
+ inputSchema: t.inputSchema
3689
+ }));
3690
+ const queues = /* @__PURE__ */ new Map();
3691
+ function enqueue(key, job) {
3692
+ const previous = queues.get(key) ?? Promise.resolve();
3693
+ const run2 = previous.then(job, job);
3694
+ queues.set(
3695
+ key,
3696
+ run2.then(
3697
+ () => void 0,
3698
+ () => void 0
3699
+ )
3700
+ );
3701
+ return run2;
3702
+ }
3703
+ function invokeTemplateTool(tool, args2) {
3704
+ const templateArgs = {};
3705
+ if (args2 && typeof args2 === "object") {
3706
+ for (const [k, v] of Object.entries(args2)) {
3707
+ templateArgs[k] = String(v);
3708
+ }
3709
+ }
3710
+ const argv = templateToArgv(tool.description, templateArgs);
3711
+ return enqueue(
3712
+ "__template__",
3713
+ () => runCommand(
3714
+ platform2,
3715
+ argv[0],
3716
+ argv.slice(1),
3717
+ childEnv,
3718
+ void 0,
3719
+ options.callTimeout,
3720
+ options.maxOutput,
3721
+ serveCwd
3722
+ )
3723
+ );
3724
+ }
3725
+ async function executeTool(tool, cli, args2) {
3726
+ if (tool.kind === "help") {
3727
+ const result = await runCommand(
3728
+ platform2,
3729
+ cli.path,
3730
+ ["--help"],
3731
+ childEnv,
3732
+ void 0,
3733
+ options.callTimeout,
3734
+ options.maxOutput,
3735
+ serveCwd
3736
+ );
3737
+ if (result.exitCode === 0) return result;
3738
+ return runCommand(
3739
+ platform2,
3740
+ cli.path,
3741
+ ["help"],
3742
+ childEnv,
3743
+ void 0,
3744
+ options.callTimeout,
3745
+ options.maxOutput,
3746
+ serveCwd
3747
+ );
3748
+ }
3749
+ if (tool.kind === "invoke") {
3750
+ const parsed2 = readToolArguments(args2 ?? {});
3751
+ if (parsed2.error) return { stdout: "", stderr: parsed2.error, exitCode: 2 };
3752
+ if (!parsed2.argv) return { stdout: "", stderr: "argv is required", exitCode: 2 };
3753
+ return runCommand(
3754
+ platform2,
3755
+ cli.path,
3756
+ parsed2.argv,
3757
+ childEnv,
3758
+ parsed2.stdin,
3759
+ options.callTimeout,
3760
+ options.maxOutput,
3761
+ serveCwd
3762
+ );
3763
+ }
3764
+ const parsed = readToolArguments(args2 ?? {});
3765
+ const command = cli.commands.find((c) => c.path.join("_") === tool.commandPath.join("_"));
3766
+ if (!command) return { stdout: "", stderr: `unknown command ${tool.name}`, exitCode: 2 };
3767
+ const planned = argvForCommand(command, parsed);
3768
+ if ("error" in planned) return { stdout: "", stderr: planned.error, exitCode: 2 };
3769
+ return runCommand(
3770
+ platform2,
3771
+ cli.path,
3772
+ planned.argv,
3773
+ childEnv,
3774
+ planned.stdin,
3775
+ options.callTimeout,
3776
+ options.maxOutput,
3777
+ serveCwd
3778
+ );
3779
+ }
3780
+ async function invoke(name, args2) {
3781
+ const tool = allTools.find((t) => t.name === name);
3782
+ if (!tool) return { stdout: "", stderr: `unknown tool ${name}`, exitCode: 1 };
3783
+ if (tool.cli === "" && tool.commandPath.length === 0) {
3784
+ return invokeTemplateTool(tool, args2);
3785
+ }
3786
+ const cli = clis.find((c) => c.name === tool.cli);
3787
+ if (!cli) return { stdout: "", stderr: `unknown cli ${tool.cli}`, exitCode: 1 };
3788
+ return enqueue(cli.name, () => executeTool(tool, cli, args2));
3789
+ }
3790
+ return { invoke, rpcTools };
3791
+ }
3792
+ async function runServe(platform2, options, version) {
3793
+ let resolvedBearer;
3794
+ if (options.bearer) {
3795
+ const expanded = expandBearerToken(options.bearer, platform2.env);
3796
+ if (!expanded) {
3797
+ await platform2.write(
3798
+ 2,
3799
+ "mcpcat serve: --bearer requires a ${NAME} variable reference, not a literal token. Use --bearer '${MY_TOKEN}' and set MY_TOKEN in the environment.\n"
3800
+ );
3801
+ return 2;
3802
+ }
3803
+ resolvedBearer = expanded;
3804
+ }
3805
+ const serveCwd = options.cwd ?? platform2.cwd;
3806
+ const childEnv = buildChildEnv(platform2, options.envAllow);
3807
+ const clis = [];
3808
+ for (const cmd of options.commands) {
3809
+ try {
3810
+ const cli = await discoverCli(platform2, cmd.path, cmd.name, childEnv, serveCwd);
3811
+ clis.push(cli);
3812
+ } catch (err) {
3813
+ await platform2.write(2, `mcpcat serve: ${err.message}
3814
+ `);
3815
+ return 2;
3816
+ }
3817
+ }
3818
+ const allTools = clis.flatMap((cli) => buildTools(cli.name, cli.commands));
3819
+ for (const spec of options.toolTemplates) {
3820
+ const tool = parseToolTemplate(spec);
3821
+ if (!tool) {
3822
+ await platform2.write(2, `mcpcat serve: invalid --tool spec: ${spec}
3823
+ `);
3824
+ return 2;
3825
+ }
3826
+ allTools.push(tool);
3827
+ }
3828
+ const executor = createToolExecutor(platform2, clis, allTools, childEnv, options, serveCwd);
3829
+ const serverName = "mcpcat-serve";
3830
+ if (options.transport === "stdio") {
3831
+ return runStdioServer(platform2, executor.rpcTools, executor.invoke, serverName, version);
3832
+ }
3833
+ return runHttpServer(
3834
+ platform2,
3835
+ options,
3836
+ executor.rpcTools,
3837
+ executor.invoke,
3838
+ serverName,
3839
+ version,
3840
+ resolvedBearer
3841
+ );
3842
+ }
3843
+ async function runStdioServer(platform2, tools, call, serverName, version) {
3844
+ const reader = platform2.stdin.getReader();
3845
+ let buffer = "";
3846
+ const dec = new TextDecoder();
3847
+ const enc = new TextEncoder();
3848
+ try {
3849
+ for (; ; ) {
3850
+ const { done, value } = await reader.read();
3851
+ if (done) break;
3852
+ buffer += dec.decode(value, { stream: true });
3853
+ let nlIdx;
3854
+ while ((nlIdx = buffer.indexOf("\n")) >= 0) {
3855
+ const line = buffer.slice(0, nlIdx).trim();
3856
+ buffer = buffer.slice(nlIdx + 1);
3857
+ if (!line) continue;
3858
+ const outcome = await handleJsonRpc(line, tools, call, serverName, version);
3859
+ if (outcome.status === 202) continue;
3860
+ const writer = platform2.stdout.getWriter();
3861
+ await writer.write(enc.encode(`${outcome.body}
3862
+ `));
3863
+ writer.releaseLock();
3864
+ }
3865
+ }
3866
+ } finally {
3867
+ reader.releaseLock();
3868
+ }
3869
+ return 0;
3870
+ }
3871
+ async function runHttpServer(platform2, options, tools, call, serverName, version, resolvedBearer) {
3872
+ if (!platform2.listen) {
3873
+ await platform2.write(2, "mcpcat serve: HTTP transport requires platform.listen\n");
3874
+ return 3;
3875
+ }
3876
+ const boundLoopback = options.host === "127.0.0.1" || options.host === "localhost" || options.host === "::1";
3877
+ if (!boundLoopback && !resolvedBearer) {
3878
+ await platform2.write(2, "mcpcat serve: --bearer is required when binding off loopback\n");
3879
+ return 2;
3880
+ }
3881
+ const allowedOrigins = new Set(options.allowOrigins);
3882
+ const listener = await platform2.listen({ host: options.host, port: options.port });
3883
+ const actualPort = listener.port;
3884
+ const listenUrl = `http://${options.host}:${actualPort}/mcp`;
3885
+ if (options.json) {
3886
+ await platform2.write(1, `${JSON.stringify({ url: listenUrl })}
3887
+ `);
3888
+ }
3889
+ await platform2.write(2, `mcpcat serve: listening on ${listenUrl}
3890
+ `);
3891
+ let shutdownRequested = false;
3892
+ const shutdown = () => {
3893
+ if (shutdownRequested) return;
3894
+ shutdownRequested = true;
3895
+ listener.close();
3896
+ void killInflight();
3897
+ };
3898
+ if (platform2.onSignal) {
3899
+ platform2.onSignal("SIGINT", shutdown);
3900
+ platform2.onSignal("SIGTERM", shutdown);
3901
+ }
3902
+ const handler = (request) => handleMcpRequest(
3903
+ request,
3904
+ tools,
3905
+ call,
3906
+ serverName,
3907
+ version,
3908
+ allowedOrigins,
3909
+ boundLoopback,
3910
+ resolvedBearer
3911
+ );
3912
+ try {
3913
+ for await (const connection of listener) {
3914
+ if (shutdownRequested) break;
3915
+ const conn = connection;
3916
+ handleConnection(conn.readable, conn.writable, handler).catch(() => {
3917
+ }).finally(() => {
3918
+ try {
3919
+ conn.close();
3920
+ } catch {
3921
+ }
3922
+ });
3923
+ }
3924
+ } catch {
3925
+ if (!shutdownRequested) throw new Error("listener closed unexpectedly");
3926
+ }
3927
+ return 0;
3928
+ }
3929
+ async function handleMcpRequest(request, tools, call, serverName, version, allowedOrigins, boundLoopback, resolvedBearer) {
3930
+ if (resolvedBearer) {
3931
+ const authHeader = request.headers.authorization;
3932
+ const provided = authHeader?.startsWith("Bearer ") ? authHeader.slice(7) : "";
3933
+ if (!provided || !constantTimeEqual(provided, resolvedBearer)) {
3934
+ return {
3935
+ status: 401,
3936
+ reason: "Unauthorized",
3937
+ headers: { "content-type": "text/plain" },
3938
+ body: "Unauthorized"
3939
+ };
3940
+ }
3941
+ }
3942
+ const originCheck = checkOrigin(request.headers.origin, allowedOrigins, boundLoopback);
3943
+ if (!originCheck.ok) {
3944
+ return {
3945
+ status: 403,
3946
+ reason: "Forbidden",
3947
+ headers: { "content-type": "text/plain" },
3948
+ body: originCheck.reason ?? "Forbidden"
3949
+ };
3950
+ }
3951
+ const hostCheck = checkHost(request.headers.host, boundLoopback);
3952
+ if (!hostCheck.ok) {
3953
+ return {
3954
+ status: 403,
3955
+ reason: "Forbidden",
3956
+ headers: { "content-type": "text/plain" },
3957
+ body: hostCheck.reason ?? "Forbidden"
3958
+ };
3959
+ }
3960
+ if (request.target !== "/mcp") {
3961
+ return {
3962
+ status: 404,
3963
+ reason: "Not Found",
3964
+ headers: { "content-type": "text/plain" },
3965
+ body: "Not Found"
3966
+ };
3967
+ }
3968
+ if (request.method !== "POST") {
3969
+ return {
3970
+ status: 405,
3971
+ reason: "Method Not Allowed",
3972
+ headers: { "content-type": "text/plain", allow: "POST" },
3973
+ body: "Method Not Allowed"
3974
+ };
3975
+ }
3976
+ const outcome = await handleJsonRpc(request.body, tools, call, serverName, version);
3977
+ return {
3978
+ status: outcome.status,
3979
+ reason: outcome.status === 200 ? "OK" : "Accepted",
3980
+ headers: { "content-type": outcome.contentType },
3981
+ body: outcome.body
3982
+ };
3983
+ }
3984
+
2396
3985
  // src/config/lock.ts
2397
3986
  var LOCK_STALE_MS = 1e4;
2398
3987
  var LOCK_POLL_MS = 50;
@@ -2810,12 +4399,12 @@ function importFile(text, filename, names) {
2810
4399
 
2811
4400
  // src/config/warnings.ts
2812
4401
  var SECRET_HEADER = /authorization|cookie|token|key|secret|password|session/i;
2813
- var PLACEHOLDER = /\$\{([A-Za-z_][A-Za-z0-9_]*)\}/g;
4402
+ var PLACEHOLDER2 = /\$\{([A-Za-z_][A-Za-z0-9_]*)\}/g;
2814
4403
  var SCHEME = /^\s*(?:bearer|basic|token|apikey|api-key)\s+/i;
2815
4404
  var SECRET_PARAM = /token|key|secret|password|signature|sig|auth|code/i;
2816
4405
  function literalSecret(header, value) {
2817
4406
  if (!SECRET_HEADER.test(header)) return false;
2818
- return value.replace(PLACEHOLDER, "").replace(SCHEME, "").trim() !== "";
4407
+ return value.replace(PLACEHOLDER2, "").replace(SCHEME, "").trim() !== "";
2819
4408
  }
2820
4409
  function urlHasSecret(url) {
2821
4410
  if (!URL.canParse(url)) return void 0;
@@ -3350,12 +4939,11 @@ var UNIMPLEMENTED_VERBS = /* @__PURE__ */ new Set([
3350
4939
  "task",
3351
4940
  "login",
3352
4941
  "logout",
3353
- "serve",
3354
4942
  "code",
3355
4943
  "discover",
3356
4944
  "inbox"
3357
4945
  ]);
3358
- var CLIENT_VERBS = /* @__PURE__ */ new Set(["info", "tools", "call", "describe"]);
4946
+ var CLIENT_VERBS = /* @__PURE__ */ new Set(["info", "tools", "call", "describe", "serve"]);
3359
4947
  function helpText(version) {
3360
4948
  return `mcpcat ${version} \u2013 a curl for the Model Context Protocol
3361
4949
 
@@ -3379,7 +4967,7 @@ Configuration verbs:
3379
4967
  logout <target> Remove tokens (not implemented yet)
3380
4968
 
3381
4969
  Server and code verbs:
3382
- serve [--stdio|--http] \u2026 Expose commands as MCP server (not implemented yet)
4970
+ serve [--stdio|--http] \u2026 Expose commands as MCP server
3383
4971
  code [-e src|file.js] Run a codemode script (not implemented yet)
3384
4972
  discover <url> Find MCP servers (not implemented yet)
3385
4973
  inbox Discovery inbox (not implemented yet)
@@ -3513,6 +5101,21 @@ function renderCallResult(result) {
3513
5101
  }
3514
5102
  return parts.join("\n");
3515
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
+ }
3516
5119
  async function readDataArg(platform2, dataArg) {
3517
5120
  let raw;
3518
5121
  if (dataArg.startsWith("@-")) {
@@ -3709,6 +5312,15 @@ async function run(platform2, version, args2) {
3709
5312
  `);
3710
5313
  return 1;
3711
5314
  }
5315
+ if (parsed.verb === "serve") {
5316
+ const serveArgs = args2.slice(args2.indexOf("serve") + 1);
5317
+ const serveOptions = parseServeFlags(serveArgs);
5318
+ if (serveOptions.commands.length === 0 && serveOptions.toolTemplates.length === 0) {
5319
+ await platform2.write(2, "mcpcat serve: no commands or --tool templates given\n");
5320
+ return 2;
5321
+ }
5322
+ return runServe(platform2, serveOptions, version);
5323
+ }
3712
5324
  if (!CLIENT_VERBS.has(parsed.verb)) {
3713
5325
  await platform2.write(2, `mcpcat: unknown command '${parsed.verb}'
3714
5326
  `);
@@ -3745,7 +5357,8 @@ async function run(platform2, version, args2) {
3745
5357
  version
3746
5358
  });
3747
5359
  } catch (err) {
3748
- 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}
3749
5362
  `);
3750
5363
  return 3;
3751
5364
  }
@@ -3772,13 +5385,16 @@ async function run(platform2, version, args2) {
3772
5385
  import { spawn as cpSpawn } from "node:child_process";
3773
5386
  import { constants } from "node:fs";
3774
5387
  import { access, mkdir, readFile as readFile2, rename, rmdir, stat, writeFile } from "node:fs/promises";
5388
+ import { createServer } from "node:net";
3775
5389
  import { Readable, Writable } from "node:stream";
3776
5390
  import * as tty from "node:tty";
5391
+ var USE_PROCESS_GROUPS2 = process.platform !== "win32";
3777
5392
  function nodeSpawn(options) {
3778
5393
  const child = cpSpawn(options.argv[0], options.argv.slice(1), {
3779
5394
  env: options.env ?? process.env,
3780
5395
  cwd: options.cwd,
3781
- stdio: ["pipe", "pipe", "pipe"]
5396
+ stdio: ["pipe", "pipe", "pipe"],
5397
+ detached: USE_PROCESS_GROUPS2
3782
5398
  });
3783
5399
  const stdout = Readable.toWeb(child.stdout);
3784
5400
  const stderr = Readable.toWeb(child.stderr);
@@ -3798,7 +5414,19 @@ function nodeSpawn(options) {
3798
5414
  });
3799
5415
  },
3800
5416
  kill(signal) {
3801
- 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
+ }
3802
5430
  }
3803
5431
  });
3804
5432
  }
@@ -3853,11 +5481,77 @@ function createStdoutStream() {
3853
5481
  function createStderrStream() {
3854
5482
  return Writable.toWeb(process.stderr);
3855
5483
  }
5484
+ function nodeListen(options) {
5485
+ return new Promise((resolve, reject) => {
5486
+ const server = createServer();
5487
+ const pending = [];
5488
+ const ready = [];
5489
+ let closed = false;
5490
+ server.on("connection", (socket) => {
5491
+ const conn = {
5492
+ readable: Readable.toWeb(socket),
5493
+ writable: Writable.toWeb(socket),
5494
+ remoteAddress: socket.remoteAddress ?? "",
5495
+ close() {
5496
+ socket.destroy();
5497
+ }
5498
+ };
5499
+ const waiter = pending.shift();
5500
+ if (waiter) {
5501
+ waiter.resolve(conn);
5502
+ } else {
5503
+ ready.push(conn);
5504
+ }
5505
+ });
5506
+ server.on("error", (err) => {
5507
+ if (!closed) reject(err);
5508
+ for (const waiter of pending) waiter.reject(err);
5509
+ pending.length = 0;
5510
+ });
5511
+ server.listen(options.port, options.host ?? "127.0.0.1", () => {
5512
+ const addr = server.address();
5513
+ const actualPort = typeof addr === "object" && addr ? addr.port : options.port;
5514
+ const listener = {
5515
+ get port() {
5516
+ return actualPort;
5517
+ },
5518
+ accept() {
5519
+ const queued = ready.shift();
5520
+ if (queued) return Promise.resolve(queued);
5521
+ return new Promise((res, rej) => {
5522
+ if (closed) {
5523
+ rej(new Error("listener closed"));
5524
+ return;
5525
+ }
5526
+ pending.push({ resolve: res, reject: rej });
5527
+ });
5528
+ },
5529
+ close() {
5530
+ closed = true;
5531
+ server.close();
5532
+ for (const waiter of pending) waiter.reject(new Error("listener closed"));
5533
+ pending.length = 0;
5534
+ },
5535
+ async *[Symbol.asyncIterator]() {
5536
+ while (!closed) {
5537
+ try {
5538
+ yield await this.accept();
5539
+ } catch {
5540
+ break;
5541
+ }
5542
+ }
5543
+ }
5544
+ };
5545
+ resolve(listener);
5546
+ });
5547
+ });
5548
+ }
3856
5549
  function createHostPlatform() {
3857
- const encoder = new TextEncoder();
5550
+ const encoder2 = new TextEncoder();
3858
5551
  return {
3859
5552
  fetch: globalThis.fetch,
3860
5553
  spawn: nodeSpawn,
5554
+ listen: nodeListen,
3861
5555
  fs: hostFs,
3862
5556
  env: process.env,
3863
5557
  cwd: process.cwd(),
@@ -3868,7 +5562,7 @@ function createHostPlatform() {
3868
5562
  stdout: createStdoutStream(),
3869
5563
  stderr: createStderrStream(),
3870
5564
  async write(fd, data) {
3871
- const bytes = typeof data === "string" ? encoder.encode(data) : data;
5565
+ const bytes = typeof data === "string" ? encoder2.encode(data) : data;
3872
5566
  const stream = fd === 1 ? process.stdout : process.stderr;
3873
5567
  return new Promise((resolve, reject) => {
3874
5568
  stream.write(bytes, (err) => err ? reject(err) : resolve());
@@ -3876,6 +5570,9 @@ function createHostPlatform() {
3876
5570
  },
3877
5571
  exit(code2) {
3878
5572
  process.exit(code2);
5573
+ },
5574
+ onSignal(signal, handler) {
5575
+ process.on(signal, handler);
3879
5576
  }
3880
5577
  };
3881
5578
  }
@@ -3883,5 +5580,5 @@ function createHostPlatform() {
3883
5580
  // src/host.ts
3884
5581
  var platform = createHostPlatform();
3885
5582
  var args = process.argv.slice(2);
3886
- var code = await run(platform, "0.3.0", args);
5583
+ var code = await run(platform, "0.5.0", args);
3887
5584
  process.exit(code);