@ai-ecoverse/mcpcat 0.7.0 → 0.9.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.
@@ -4899,6 +4899,197 @@ var init_portable = __esm({
4899
4899
  }
4900
4900
  });
4901
4901
 
4902
+ // src/args/coerce.ts
4903
+ function asSchemaObject(schema) {
4904
+ return schema ?? {};
4905
+ }
4906
+ function emptyArgObject() {
4907
+ return /* @__PURE__ */ Object.create(null);
4908
+ }
4909
+ function isUnsafeObjectKey(key) {
4910
+ return key === "__proto__" || key === "prototype" || key === "constructor";
4911
+ }
4912
+ function isPlainObject(value) {
4913
+ return typeof value === "object" && value !== null && !Array.isArray(value) && value !== Object.prototype;
4914
+ }
4915
+ function splitFlag(a) {
4916
+ const eq = a.indexOf("=");
4917
+ if (eq > 2) return { key: a.slice(2, eq), inlineValue: a.slice(eq + 1) };
4918
+ return { key: a.slice(2), inlineValue: void 0 };
4919
+ }
4920
+ function coerceScalar(raw, type) {
4921
+ switch (type) {
4922
+ case "integer": {
4923
+ if (!/^-?\d+$/.test(raw)) return { ok: false, error: `expected integer, got "${raw}"` };
4924
+ return { ok: true, value: Number(raw) };
4925
+ }
4926
+ case "number": {
4927
+ const n = Number(raw);
4928
+ if (!Number.isFinite(n)) return { ok: false, error: `expected number, got "${raw}"` };
4929
+ return { ok: true, value: n };
4930
+ }
4931
+ case "boolean": {
4932
+ if (raw === "true" || raw === "1" || raw === "yes") return { ok: true, value: true };
4933
+ if (raw === "false" || raw === "0" || raw === "no") return { ok: true, value: false };
4934
+ return { ok: false, error: `expected boolean, got "${raw}"` };
4935
+ }
4936
+ case "object":
4937
+ return coerceJsonObject(raw);
4938
+ default:
4939
+ return { ok: true, value: raw };
4940
+ }
4941
+ }
4942
+ function coerceJsonObject(raw) {
4943
+ let parsed;
4944
+ try {
4945
+ parsed = JSON.parse(raw);
4946
+ } catch {
4947
+ return { ok: false, error: `expected object JSON, got "${raw}"` };
4948
+ }
4949
+ if (!isPlainObject(parsed)) {
4950
+ return { ok: false, error: `expected object JSON, got "${raw}"` };
4951
+ }
4952
+ return { ok: true, value: parsed };
4953
+ }
4954
+ function resolveToolFlag(key, properties) {
4955
+ if (Object.hasOwn(properties, key)) {
4956
+ return { ok: true, rootKey: key, dottedPath: null, meta: properties[key] ?? {} };
4957
+ }
4958
+ const dot = key.indexOf(".");
4959
+ if (dot <= 0) return { ok: false, error: `unknown flag: --${key}` };
4960
+ const rootKey = key.slice(0, dot);
4961
+ const rest = key.slice(dot + 1);
4962
+ if (!Object.hasOwn(properties, rootKey) || rest.length === 0) {
4963
+ return { ok: false, error: `unknown flag: --${key}` };
4964
+ }
4965
+ const parts = rest.split(".");
4966
+ if (isUnsafeObjectKey(rootKey) || parts.some((p) => p.length === 0 || isUnsafeObjectKey(p))) {
4967
+ return { ok: false, error: `unknown flag: --${key}` };
4968
+ }
4969
+ return { ok: true, rootKey, dottedPath: parts, meta: properties[rootKey] ?? {} };
4970
+ }
4971
+ function readFlagRaw(args, i, key, inlineValue, type, dottedPath) {
4972
+ if (inlineValue !== void 0) {
4973
+ return { ok: true, raw: inlineValue, nextIndex: i + 1, bareBoolean: false };
4974
+ }
4975
+ const next = args[i + 1];
4976
+ if (!dottedPath && type === "boolean" && (next === void 0 || next.startsWith("--"))) {
4977
+ return { ok: true, raw: "", nextIndex: i + 1, bareBoolean: true };
4978
+ }
4979
+ if (next === void 0) {
4980
+ return { ok: false, error: `flag --${key} requires a value` };
4981
+ }
4982
+ return { ok: true, raw: next, nextIndex: i + 2, bareBoolean: false };
4983
+ }
4984
+ function setDotted(out, rootKey, path, value) {
4985
+ if (isUnsafeObjectKey(rootKey) || path.some((p) => p.length === 0 || isUnsafeObjectKey(p))) {
4986
+ return { ok: false, error: `unknown flag: --${rootKey}.${path.join(".")}` };
4987
+ }
4988
+ const existing = Object.hasOwn(out, rootKey) ? out[rootKey] : void 0;
4989
+ if (existing === void 0) {
4990
+ out[rootKey] = emptyArgObject();
4991
+ } else if (!isPlainObject(existing)) {
4992
+ return { ok: false, error: `--${rootKey}: cannot nest into a ${typeof existing} value` };
4993
+ }
4994
+ let cursor = out[rootKey];
4995
+ for (let i = 0; i < path.length - 1; i++) {
4996
+ const seg = path[i];
4997
+ const next = Object.hasOwn(cursor, seg) ? cursor[seg] : void 0;
4998
+ if (next === void 0) {
4999
+ cursor[seg] = emptyArgObject();
5000
+ } else if (!isPlainObject(next)) {
5001
+ return {
5002
+ ok: false,
5003
+ error: `--${rootKey}.${path.slice(0, i + 1).join(".")}: cannot nest into a ${typeof next} value`
5004
+ };
5005
+ }
5006
+ cursor = cursor[seg];
5007
+ }
5008
+ cursor[path[path.length - 1]] = value;
5009
+ return { ok: true };
5010
+ }
5011
+ function assignFlagValue(out, rootKey, dottedPath, value, isArray) {
5012
+ if (dottedPath) return setDotted(out, rootKey, dottedPath, value);
5013
+ if (isArray) {
5014
+ const prev = out[rootKey];
5015
+ if (Array.isArray(prev)) prev.push(value);
5016
+ else out[rootKey] = [value];
5017
+ return { ok: true };
5018
+ }
5019
+ const existing = out[rootKey];
5020
+ if (isPlainObject(existing) && isPlainObject(value)) {
5021
+ Object.assign(existing, value);
5022
+ return { ok: true };
5023
+ }
5024
+ out[rootKey] = value;
5025
+ return { ok: true };
5026
+ }
5027
+ function parseOneFlag(args, i, properties, out) {
5028
+ const a = args[i];
5029
+ if (a === "--") return { ok: true, nextIndex: args.length };
5030
+ if (a.startsWith("--no-") && a.length > 5) {
5031
+ const key2 = a.slice(5);
5032
+ if (Object.hasOwn(properties, key2) && properties[key2]?.type === "boolean") {
5033
+ out[key2] = false;
5034
+ return { ok: true, nextIndex: i + 1 };
5035
+ }
5036
+ return { ok: false, error: `unknown flag: ${a}` };
5037
+ }
5038
+ if (!a.startsWith("--")) {
5039
+ return { ok: false, error: `unexpected positional argument "${a}"` };
5040
+ }
5041
+ const { key, inlineValue } = splitFlag(a);
5042
+ const resolved = resolveToolFlag(key, properties);
5043
+ if (!resolved.ok) return resolved;
5044
+ const { rootKey, dottedPath, meta } = resolved;
5045
+ const declaredType = meta.type;
5046
+ const type = declaredType ?? "string";
5047
+ const isArray = type === "array";
5048
+ const itemType = (isArray ? meta.items?.type : void 0) ?? "string";
5049
+ if (dottedPath && declaredType !== void 0 && declaredType !== "object") {
5050
+ return { ok: false, error: `unknown flag: --${key}` };
5051
+ }
5052
+ const taken = readFlagRaw(args, i, key, inlineValue, type, dottedPath);
5053
+ if (!taken.ok) return taken;
5054
+ if (taken.bareBoolean) {
5055
+ out[rootKey] = true;
5056
+ return { ok: true, nextIndex: taken.nextIndex };
5057
+ }
5058
+ const valueType = dottedPath ? "string" : isArray ? itemType : type;
5059
+ const coerced = coerceScalar(taken.raw, valueType);
5060
+ if (!coerced.ok) return { ok: false, error: `--${key}: ${coerced.error}` };
5061
+ const assigned = assignFlagValue(out, rootKey, dottedPath, coerced.value, isArray);
5062
+ if (!assigned.ok) return assigned;
5063
+ return { ok: true, nextIndex: taken.nextIndex };
5064
+ }
5065
+ function coerceArgsBySchema(args, schema, options) {
5066
+ const s = asSchemaObject(schema);
5067
+ const properties = s.properties ?? {};
5068
+ const required = new Set(Array.isArray(s.required) ? s.required : []);
5069
+ const out = emptyArgObject();
5070
+ let i = 0;
5071
+ while (i < args.length) {
5072
+ const result = parseOneFlag(args, i, properties, out);
5073
+ if (!result.ok) return result;
5074
+ i = result.nextIndex;
5075
+ }
5076
+ if (!options?.noValidate) {
5077
+ for (const r of required) {
5078
+ if (!Object.hasOwn(out, r)) {
5079
+ return { ok: false, error: `missing required flag --${r}` };
5080
+ }
5081
+ }
5082
+ }
5083
+ return { ok: true, value: out };
5084
+ }
5085
+ function mergeArgs(base, flags) {
5086
+ const result = { ...base };
5087
+ for (const [k, v] of Object.entries(flags)) {
5088
+ result[k] = v;
5089
+ }
5090
+ return result;
5091
+ }
5092
+
4902
5093
  // node_modules/@earendil-works/pi-mcp/dist/protocol/jsonrpc.js
4903
5094
  var JSON_RPC_ERROR_CODES = {
4904
5095
  parseError: -32700,
@@ -6252,67 +6443,6 @@ var PlatformStdioTransport = class {
6252
6443
  }
6253
6444
  };
6254
6445
 
6255
- // src/client/pi-connection.ts
6256
- async function createPiConnection(options) {
6257
- const clientOptions = {
6258
- name: "mcpcat",
6259
- version: options.version ?? "0.0.0"
6260
- };
6261
- const client = new McpClient(clientOptions);
6262
- let transport;
6263
- if (options.url) {
6264
- transport = new StreamableHttpTransport({
6265
- url: options.url,
6266
- headers: options.headers,
6267
- fetch: options.fetchImpl
6268
- });
6269
- } else if (options.command) {
6270
- if (options.platform) {
6271
- transport = new PlatformStdioTransport({
6272
- command: options.command,
6273
- args: options.args,
6274
- env: options.env,
6275
- cwd: options.cwd,
6276
- inheritEnv: true,
6277
- platform: options.platform
6278
- });
6279
- } else {
6280
- transport = new StdioTransport({
6281
- command: options.command,
6282
- args: options.args,
6283
- env: options.env,
6284
- cwd: options.cwd,
6285
- inheritEnv: true,
6286
- stderr: "pipe"
6287
- });
6288
- }
6289
- } else {
6290
- throw new Error("PiConnection requires either a url or a command");
6291
- }
6292
- const initResult = await client.connect(transport);
6293
- return {
6294
- async info() {
6295
- return {
6296
- name: initResult.serverInfo?.name,
6297
- version: initResult.serverInfo?.version,
6298
- title: initResult.serverInfo?.title,
6299
- protocolVersion: initResult.protocolVersion,
6300
- capabilities: initResult.capabilities,
6301
- instructions: initResult.instructions
6302
- };
6303
- },
6304
- async listTools() {
6305
- return client.listTools();
6306
- },
6307
- async callTool(name, args) {
6308
- return client.callTool(name, args);
6309
- },
6310
- async close() {
6311
- await client.close();
6312
- }
6313
- };
6314
- }
6315
-
6316
6446
  // src/client/stateless-connection.ts
6317
6447
  var STATELESS_PROTOCOL_VERSION = "2026-07-28";
6318
6448
  var StatelessRpcError = class extends Error {
@@ -6341,6 +6471,22 @@ var InputRequiredError = class extends Error {
6341
6471
  this.requestJson = requestJson;
6342
6472
  }
6343
6473
  };
6474
+ var McpTimeoutError2 = class extends Error {
6475
+ constructor(method, ms) {
6476
+ super(`timeout after ${ms}ms waiting for ${method}`);
6477
+ this.name = "McpTimeoutError";
6478
+ }
6479
+ };
6480
+ var McpAuthError = class extends Error {
6481
+ status;
6482
+ target;
6483
+ constructor(status, target) {
6484
+ super(`HTTP ${status}: authentication required. Run: mcpcat login ${target}`);
6485
+ this.name = "McpAuthError";
6486
+ this.status = status;
6487
+ this.target = target;
6488
+ }
6489
+ };
6344
6490
  function selectSseFrame(body, targetId) {
6345
6491
  const text = new TextDecoder().decode(body);
6346
6492
  const blocks = text.replace(/\r\n/g, "\n").split(/\n\n/);
@@ -6371,25 +6517,66 @@ async function createStatelessConnection(options) {
6371
6517
  headers: extraHeaders,
6372
6518
  fetchImpl,
6373
6519
  timeoutMs = 6e4,
6374
- clientInfo = { name: "mcpcat", version: "0.0.0" }
6520
+ clientInfo = { name: "mcpcat", version: "0.0.0" },
6521
+ signal: externalSignal,
6522
+ verbose
6375
6523
  } = options;
6376
6524
  let nextId2 = 1;
6377
6525
  let negotiatedVersion;
6378
- async function request(method, params) {
6379
- const id = nextId2++;
6380
- const headers = {
6526
+ function buildHeaders(method, params) {
6527
+ const h = {
6381
6528
  "Content-Type": "application/json",
6382
6529
  Accept: "application/json, text/event-stream",
6383
6530
  "Mcp-Method": method,
6384
6531
  ...extraHeaders
6385
6532
  };
6386
- const mcpName = mcpNameForMethod(method, params);
6387
- if (mcpName) {
6388
- headers["Mcp-Name"] = mcpName;
6533
+ const n = mcpNameForMethod(method, params);
6534
+ if (n) h["Mcp-Name"] = n;
6535
+ if (negotiatedVersion) h["Mcp-Protocol-Version"] = negotiatedVersion;
6536
+ return h;
6537
+ }
6538
+ function makeAbort() {
6539
+ const controller = new AbortController();
6540
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
6541
+ if (externalSignal) {
6542
+ if (externalSignal.aborted) {
6543
+ clearTimeout(timer);
6544
+ controller.abort();
6545
+ } else {
6546
+ externalSignal.addEventListener(
6547
+ "abort",
6548
+ () => {
6549
+ clearTimeout(timer);
6550
+ controller.abort();
6551
+ },
6552
+ { once: true }
6553
+ );
6554
+ }
6555
+ }
6556
+ return { controller, timer };
6557
+ }
6558
+ function logHeaders(dir, entries) {
6559
+ if (!verbose?.enabled) return;
6560
+ for (const [k, v] of entries) {
6561
+ if (dir === ">") verbose.headerOut(k, v);
6562
+ else verbose.headerIn(k, v);
6389
6563
  }
6390
- if (negotiatedVersion) {
6391
- headers["Mcp-Protocol-Version"] = negotiatedVersion;
6564
+ }
6565
+ async function handleErrorStatus(res, method) {
6566
+ if (res.status === 401 || res.status === 403) throw new McpAuthError(res.status, url);
6567
+ const text = await res.text();
6568
+ let rpc;
6569
+ try {
6570
+ const p = JSON.parse(text);
6571
+ if (p.error && typeof p.error.code === "number") rpc = p.error;
6572
+ } catch {
6392
6573
  }
6574
+ if (rpc) throw new StatelessRpcError(rpc.code, rpc.message, rpc.data);
6575
+ throw new StatelessHttpError(res.status, res.statusText, method, text.slice(0, 512));
6576
+ }
6577
+ async function request(method, params) {
6578
+ const id = nextId2++;
6579
+ const headers = buildHeaders(method, params);
6393
6580
  const meta = {
6394
6581
  protocolVersion: negotiatedVersion ?? STATELESS_PROTOCOL_VERSION,
6395
6582
  clientInfo
@@ -6400,8 +6587,11 @@ async function createStatelessConnection(options) {
6400
6587
  method,
6401
6588
  params: { ...params, _meta: meta }
6402
6589
  });
6403
- const controller = new AbortController();
6404
- const timer = setTimeout(() => controller.abort(), timeoutMs);
6590
+ if (verbose?.enabled) {
6591
+ verbose.request(method, body);
6592
+ logHeaders(">", Object.entries(headers));
6593
+ }
6594
+ const { controller, timer } = makeAbort();
6405
6595
  let res;
6406
6596
  try {
6407
6597
  res = await fetchImpl(url, {
@@ -6410,35 +6600,28 @@ async function createStatelessConnection(options) {
6410
6600
  body,
6411
6601
  signal: controller.signal
6412
6602
  });
6603
+ } catch (err) {
6604
+ clearTimeout(timer);
6605
+ if (controller.signal.aborted && !externalSignal?.aborted) {
6606
+ throw new McpTimeoutError2(method, timeoutMs);
6607
+ }
6608
+ throw err;
6413
6609
  } finally {
6414
6610
  clearTimeout(timer);
6415
6611
  }
6416
- if (res.status === 401) {
6417
- throw new StatelessHttpError(401, "Unauthorized", method, "");
6418
- }
6419
- if (res.status >= 400) {
6420
- const text = await res.text();
6421
- let rpcError2;
6422
- try {
6423
- const parsed = JSON.parse(text);
6424
- if (parsed.error && typeof parsed.error.code === "number") {
6425
- rpcError2 = parsed.error;
6426
- }
6427
- } catch {
6428
- }
6429
- if (rpcError2) {
6430
- throw new StatelessRpcError(rpcError2.code, rpcError2.message, rpcError2.data);
6431
- }
6432
- throw new StatelessHttpError(res.status, res.statusText, method, text.slice(0, 512));
6612
+ if (verbose?.enabled) {
6613
+ verbose.http("<", `HTTP/1.1 ${res.status} ${res.statusText}`);
6614
+ logHeaders("<", res.headers.entries());
6433
6615
  }
6616
+ if (res.status >= 400) await handleErrorStatus(res, method);
6434
6617
  const ct = res.headers.get("content-type") ?? "";
6435
6618
  let frame;
6436
6619
  if (ct.toLowerCase().includes("text/event-stream")) {
6437
- const bytes = new Uint8Array(await res.arrayBuffer());
6438
- frame = selectSseFrame(bytes, id);
6620
+ frame = selectSseFrame(new Uint8Array(await res.arrayBuffer()), id);
6439
6621
  } else {
6440
6622
  frame = await res.json();
6441
6623
  }
6624
+ if (verbose?.enabled) verbose.response(JSON.stringify(frame));
6442
6625
  if (frame.error) {
6443
6626
  throw new StatelessRpcError(frame.error.code, frame.error.message, frame.error.data);
6444
6627
  }
@@ -6458,28 +6641,26 @@ async function createStatelessConnection(options) {
6458
6641
  return {
6459
6642
  async info() {
6460
6643
  if (serverInfoCache) return serverInfoCache;
6461
- const result = discoverResult2;
6644
+ const r = discoverResult2;
6462
6645
  serverInfoCache = {
6463
- name: result?.serverInfo?.name,
6464
- version: result?.serverInfo?.version,
6465
- title: result?.serverInfo?.title,
6646
+ name: r?.serverInfo?.name,
6647
+ version: r?.serverInfo?.version,
6648
+ title: r?.serverInfo?.title,
6466
6649
  protocolVersion: STATELESS_PROTOCOL_VERSION,
6467
- capabilities: result?.capabilities ?? {},
6468
- instructions: result?.instructions
6650
+ capabilities: r?.capabilities ?? {},
6651
+ instructions: r?.instructions
6469
6652
  };
6470
6653
  return serverInfoCache;
6471
6654
  },
6472
6655
  async listTools() {
6473
- const result = await request("tools/list", {});
6474
- return result?.tools ?? [];
6656
+ return (await request("tools/list", {}))?.tools ?? [];
6475
6657
  },
6476
- async callTool(name, args) {
6658
+ async callTool(name, args, _callOptions) {
6477
6659
  const result = await request("tools/call", {
6478
6660
  name,
6479
6661
  arguments: args ?? {}
6480
6662
  });
6481
- const resultType = result.resultType ?? "complete";
6482
- if (resultType === "input_required") {
6663
+ if ((result.resultType ?? "complete") === "input_required") {
6483
6664
  throw new InputRequiredError(
6484
6665
  JSON.stringify({ name, arguments: args ?? {}, result }, null, 2)
6485
6666
  );
@@ -6491,28 +6672,138 @@ async function createStatelessConnection(options) {
6491
6672
  };
6492
6673
  }
6493
6674
 
6675
+ // src/client/pi-connection.ts
6676
+ function buildStderrHandler(options) {
6677
+ if (!options.verbose?.enabled) return void 0;
6678
+ return (chunk) => {
6679
+ for (const l of chunk.split("\n")) {
6680
+ if (l.trim()) options.verbose.server(l);
6681
+ }
6682
+ };
6683
+ }
6684
+ function buildTransport(options) {
6685
+ if (options.url) {
6686
+ return new StreamableHttpTransport({
6687
+ url: options.url,
6688
+ headers: options.headers,
6689
+ fetch: options.fetchImpl
6690
+ });
6691
+ }
6692
+ if (!options.command) throw new Error("PiConnection requires either a url or a command");
6693
+ if (options.platform) {
6694
+ return new PlatformStdioTransport({
6695
+ command: options.command,
6696
+ args: options.args,
6697
+ env: options.env,
6698
+ cwd: options.cwd,
6699
+ inheritEnv: true,
6700
+ platform: options.platform,
6701
+ onStderr: buildStderrHandler(options)
6702
+ });
6703
+ }
6704
+ return new StdioTransport({
6705
+ command: options.command,
6706
+ args: options.args,
6707
+ env: options.env,
6708
+ cwd: options.cwd,
6709
+ inheritEnv: true,
6710
+ stderr: "pipe",
6711
+ onStderr: buildStderrHandler(options)
6712
+ });
6713
+ }
6714
+ async function createPiConnection(options) {
6715
+ const clientOptions = {
6716
+ name: "mcpcat",
6717
+ version: options.version ?? "0.0.0",
6718
+ requestTimeoutMs: options.timeoutMs
6719
+ };
6720
+ const client = new McpClient(clientOptions);
6721
+ const transport = buildTransport(options);
6722
+ if (options.verbose?.enabled) {
6723
+ transport.onMessage((msg) => {
6724
+ options.verbose.response(JSON.stringify(msg));
6725
+ });
6726
+ }
6727
+ try {
6728
+ let reqOpts2 = function() {
6729
+ const o = {};
6730
+ if (options.signal) o.signal = options.signal;
6731
+ if (options.verbose?.enabled) {
6732
+ o.onProgress = (p) => {
6733
+ options.verbose.progress(`progress: ${p.message ?? ""}`);
6734
+ };
6735
+ }
6736
+ return o;
6737
+ };
6738
+ var reqOpts = reqOpts2;
6739
+ const initResult = await client.connect(transport);
6740
+ return {
6741
+ async info() {
6742
+ return {
6743
+ name: initResult.serverInfo?.name,
6744
+ version: initResult.serverInfo?.version,
6745
+ title: initResult.serverInfo?.title,
6746
+ protocolVersion: initResult.protocolVersion,
6747
+ capabilities: initResult.capabilities,
6748
+ instructions: initResult.instructions
6749
+ };
6750
+ },
6751
+ async listTools() {
6752
+ return client.listTools(reqOpts2());
6753
+ },
6754
+ async callTool(name, args, callOptions) {
6755
+ const o = reqOpts2();
6756
+ if (callOptions?.onProgress) {
6757
+ const outer = callOptions.onProgress;
6758
+ const inner = o.onProgress;
6759
+ o.onProgress = (p) => {
6760
+ outer(p.progress, p.total, p.message);
6761
+ inner?.(p);
6762
+ };
6763
+ }
6764
+ return client.callTool(name, args, o);
6765
+ },
6766
+ async close() {
6767
+ await client.close();
6768
+ }
6769
+ };
6770
+ } catch (err) {
6771
+ const msg = err.message ?? "";
6772
+ if (msg.includes("401") || msg.includes("403") || msg.includes("Unauthorized")) {
6773
+ throw new McpAuthError(401, options.url ?? options.command ?? "");
6774
+ }
6775
+ if (msg.includes("timeout") || msg.includes("Timeout") || msg.includes("ETIMEDOUT")) {
6776
+ throw new McpTimeoutError2("connect", options.timeoutMs ?? 6e4);
6777
+ }
6778
+ throw err;
6779
+ }
6780
+ }
6781
+
6494
6782
  // src/client/connect.ts
6495
6783
  async function connect(platform2, options) {
6784
+ const shared = {
6785
+ version: options.version,
6786
+ timeoutMs: options.timeoutMs,
6787
+ signal: options.signal,
6788
+ verbose: options.verbose,
6789
+ platform: platform2
6790
+ };
6496
6791
  if (options.command) {
6497
6792
  return createPiConnection({
6498
6793
  command: options.command,
6499
6794
  args: options.args,
6500
6795
  env: options.env,
6501
6796
  cwd: options.cwd,
6502
- version: options.version,
6503
- platform: platform2
6797
+ ...shared
6504
6798
  });
6505
6799
  }
6506
- if (!options.url) {
6507
- throw new Error("connect requires either a url or a command");
6508
- }
6800
+ if (!options.url) throw new Error("connect requires either a url or a command");
6509
6801
  if (options.protocolVersion && !options.protocolVersion.startsWith("2026")) {
6510
6802
  return createPiConnection({
6511
6803
  url: options.url,
6512
6804
  headers: options.headers,
6513
- version: options.version,
6514
6805
  fetchImpl: platform2.fetch,
6515
- platform: platform2
6806
+ ...shared
6516
6807
  });
6517
6808
  }
6518
6809
  try {
@@ -6520,20 +6811,20 @@ async function connect(platform2, options) {
6520
6811
  url: options.url,
6521
6812
  headers: options.headers,
6522
6813
  fetchImpl: platform2.fetch,
6523
- clientInfo: { name: "mcpcat", version: options.version ?? "0.0.0" }
6814
+ clientInfo: { name: "mcpcat", version: options.version ?? "0.0.0" },
6815
+ timeoutMs: options.timeoutMs,
6816
+ signal: options.signal,
6817
+ verbose: options.verbose
6524
6818
  });
6525
6819
  } catch (err) {
6526
6820
  const isMethodNotFound = err instanceof StatelessRpcError && err.code === -32601 || err instanceof StatelessHttpError && err.status >= 400 && err.status < 500;
6527
- if (options.protocolVersion?.startsWith("2026")) {
6528
- throw err;
6529
- }
6821
+ if (options.protocolVersion?.startsWith("2026")) throw err;
6530
6822
  if (isMethodNotFound) {
6531
6823
  return createPiConnection({
6532
6824
  url: options.url,
6533
6825
  headers: options.headers,
6534
- version: options.version,
6535
6826
  fetchImpl: platform2.fetch,
6536
- platform: platform2
6827
+ ...shared
6537
6828
  });
6538
6829
  }
6539
6830
  throw err;
@@ -7085,6 +7376,28 @@ async function readStream(stream) {
7085
7376
  return new TextDecoder().decode(merged);
7086
7377
  }
7087
7378
 
7379
+ // src/config/expand.ts
7380
+ var PLACEHOLDER = /\$\{([A-Za-z_][A-Za-z0-9_]*)\}/g;
7381
+ var PLACEHOLDER_TEST = /\$\{[A-Za-z_][A-Za-z0-9_]*\}/;
7382
+ function containsPlaceholders(value) {
7383
+ return PLACEHOLDER_TEST.test(value);
7384
+ }
7385
+ function expandPlaceholders(value, env2) {
7386
+ return value.replace(PLACEHOLDER, (_, name) => env2[name] ?? "");
7387
+ }
7388
+ function expandPlaceholdersStrict(value, env2) {
7389
+ let hasUndefined = false;
7390
+ const result = value.replace(PLACEHOLDER, (_, name) => {
7391
+ if (env2[name] === void 0) {
7392
+ hasUndefined = true;
7393
+ return "";
7394
+ }
7395
+ return env2[name];
7396
+ });
7397
+ if (hasUndefined) return void 0;
7398
+ return result;
7399
+ }
7400
+
7088
7401
  // src/config/parse.ts
7089
7402
  var SERVER_NAME = /^[A-Za-z0-9_-]+$/;
7090
7403
  var EXPOSURES = ["codemode", "deferred", "direct", "hidden"];
@@ -8321,23 +8634,98 @@ async function runDiscover(platform2, args) {
8321
8634
  return 0;
8322
8635
  }
8323
8636
 
8324
- // src/config/expand.ts
8325
- var PLACEHOLDER = /\$\{([A-Za-z_][A-Za-z0-9_]*)\}/g;
8326
- var PLACEHOLDER_TEST = /\$\{[A-Za-z_][A-Za-z0-9_]*\}/;
8327
- function containsPlaceholders(value) {
8328
- return PLACEHOLDER_TEST.test(value);
8637
+ // src/render/result.ts
8638
+ function formatBytes(n) {
8639
+ if (n < 1024) return `${n} bytes`;
8640
+ const kb = n / 1024;
8641
+ if (kb < 1024) return `${kb.toFixed(1)} KB`;
8642
+ return `${(kb / 1024).toFixed(1)} MB`;
8643
+ }
8644
+ function truncateMiddle(text, max) {
8645
+ if (text.length <= max) return text;
8646
+ const half = Math.floor((max - 5) / 2);
8647
+ return `${text.slice(0, half)}
8648
+ [\u2026]
8649
+ ${text.slice(-half)}`;
8650
+ }
8651
+ function renderContentBlock(block) {
8652
+ if (block.type === "text") return block.text;
8653
+ if (block.type === "image") {
8654
+ const size = formatBytes(Math.ceil(block.data.length * 3 / 4));
8655
+ return `[image: ${block.mimeType}, ${size}]`;
8656
+ }
8657
+ if (block.type === "audio") {
8658
+ const audioBlock = block;
8659
+ const size = formatBytes(Math.ceil(audioBlock.data.length * 3 / 4));
8660
+ return `[audio: ${audioBlock.mimeType}, ${size}]`;
8661
+ }
8662
+ if (block.type === "resource_link") return `[resource: ${block.uri}]`;
8663
+ if (block.type === "resource") {
8664
+ return "text" in block.resource ? block.resource.text : `[blob: ${block.resource.uri}]`;
8665
+ }
8666
+ return void 0;
8329
8667
  }
8330
- function expandPlaceholdersStrict(value, env2) {
8331
- let hasUndefined = false;
8332
- const result = value.replace(PLACEHOLDER, (_, name) => {
8333
- if (env2[name] === void 0) {
8334
- hasUndefined = true;
8335
- return "";
8668
+ function renderCallResultText(result, options) {
8669
+ const parts = [];
8670
+ if (result.content) {
8671
+ for (const block of result.content) {
8672
+ const rendered = renderContentBlock(block);
8673
+ if (rendered !== void 0) parts.push(rendered);
8336
8674
  }
8337
- return env2[name];
8338
- });
8339
- if (hasUndefined) return void 0;
8340
- return result;
8675
+ }
8676
+ if (parts.length === 0 && result.structuredContent !== void 0) {
8677
+ parts.push(JSON.stringify(result.structuredContent, null, 2));
8678
+ }
8679
+ let text = parts.join("\n");
8680
+ if (options?.maxChars && text.length > options.maxChars) {
8681
+ text = truncateMiddle(text, options.maxChars);
8682
+ }
8683
+ return text;
8684
+ }
8685
+ function renderCallResultJson(result) {
8686
+ return JSON.stringify(result, null, 2);
8687
+ }
8688
+ async function renderCallResult(platform2, result, options) {
8689
+ if (options?.json) {
8690
+ const json = renderCallResultJson(result);
8691
+ if (result.isError) {
8692
+ await platform2.write(
8693
+ 2,
8694
+ `${JSON.stringify({ error: { kind: "tool_error", message: json } }, null, 2)}
8695
+ `
8696
+ );
8697
+ return 1;
8698
+ }
8699
+ await platform2.write(1, `${json}
8700
+ `);
8701
+ return 0;
8702
+ }
8703
+ const text = renderCallResultText(result, options);
8704
+ if (result.isError) {
8705
+ await platform2.write(2, `${text || "(tool reported error)"}
8706
+ `);
8707
+ return 1;
8708
+ }
8709
+ if (text) {
8710
+ await platform2.write(1, `${text}
8711
+ `);
8712
+ }
8713
+ return 0;
8714
+ }
8715
+ async function renderToolsJson(platform2, tools) {
8716
+ await platform2.write(1, `${JSON.stringify({ tools }, null, 2)}
8717
+ `);
8718
+ }
8719
+ async function renderInfoJson(platform2, info) {
8720
+ await platform2.write(1, `${JSON.stringify(info, null, 2)}
8721
+ `);
8722
+ }
8723
+ async function renderErrorJson(platform2, kind, message, code, data) {
8724
+ const errorObj = { kind, message };
8725
+ if (code !== void 0) errorObj.code = code;
8726
+ if (data !== void 0) errorObj.data = data;
8727
+ await platform2.write(2, `${JSON.stringify({ error: errorObj }, null, 2)}
8728
+ `);
8341
8729
  }
8342
8730
 
8343
8731
  // src/serve/catalog.ts
@@ -9140,28 +9528,230 @@ function checkHost(host, boundLoopback) {
9140
9528
  return { ok: false, reason: `host ${host} is not loopback` };
9141
9529
  }
9142
9530
 
9143
- // src/serve/runtime.ts
9144
- var CALL_TIMEOUT_MS = 12e4;
9145
- var MAX_OUTPUT_DEFAULT = 1048576;
9146
- var ALLOWED_ENV_KEYS = /* @__PURE__ */ new Set(["PATH", "HOME", "LANG"]);
9147
- function parseHttpFlag(args, i) {
9148
- const next = args[i + 1];
9149
- if (!next || next.startsWith("-")) return { host: "127.0.0.1", port: 0, next: i };
9150
- const colonIdx = next.lastIndexOf(":");
9151
- if (colonIdx > 0 && colonIdx < next.length - 1) {
9152
- return { host: next.slice(0, colonIdx), port: Number(next.slice(colonIdx + 1)), next: i + 1 };
9153
- }
9154
- return { host: "127.0.0.1", port: Number(next), next: i + 1 };
9531
+ // src/serve/proxy.ts
9532
+ var IDEMPOTENT_METHODS = /* @__PURE__ */ new Set([
9533
+ "initialize",
9534
+ "server/discover",
9535
+ "tools/list",
9536
+ "resources/list",
9537
+ "prompts/list",
9538
+ "ping"
9539
+ ]);
9540
+ var PROTOCOL_VERSIONS2 = ["2026-07-28", "2025-11-25", "2025-06-18"];
9541
+ var JSON_CT2 = "application/json; charset=utf-8";
9542
+ function readId2(value) {
9543
+ const id = Object.getOwnPropertyDescriptor(value, "id")?.value;
9544
+ if (typeof id === "string" || typeof id === "number" || id === null) return id;
9545
+ return null;
9155
9546
  }
9156
- function parseCommandTarget(arg) {
9157
- const eqIdx = arg.indexOf("=");
9158
- if (eqIdx > 0 && !arg.slice(0, eqIdx).includes("/")) {
9159
- return { name: arg.slice(0, eqIdx), path: arg.slice(eqIdx + 1) };
9160
- }
9161
- return { path: arg };
9547
+ function readString2(value, key) {
9548
+ const found = Object.getOwnPropertyDescriptor(value, key)?.value;
9549
+ return typeof found === "string" ? found : void 0;
9162
9550
  }
9163
- function parseSingleFlag(args, i, opts) {
9164
- const arg = args[i];
9551
+ function hasOwn2(value, key) {
9552
+ return Object.prototype.hasOwnProperty.call(value, key);
9553
+ }
9554
+ function rpcOk2(id, result) {
9555
+ return {
9556
+ status: 200,
9557
+ contentType: JSON_CT2,
9558
+ body: JSON.stringify({ jsonrpc: "2.0", id, result })
9559
+ };
9560
+ }
9561
+ function rpcError2(id, code, message) {
9562
+ return {
9563
+ status: 200,
9564
+ contentType: JSON_CT2,
9565
+ body: JSON.stringify({ jsonrpc: "2.0", id, error: { code, message } })
9566
+ };
9567
+ }
9568
+ function scrubValue(value, secrets) {
9569
+ if (typeof value === "string") return scrubString(value, secrets);
9570
+ if (Array.isArray(value)) return value.map((v) => scrubValue(v, secrets));
9571
+ if (value && typeof value === "object") {
9572
+ const out = {};
9573
+ for (const [k, v] of Object.entries(value)) {
9574
+ out[k] = scrubValue(v, secrets);
9575
+ }
9576
+ return out;
9577
+ }
9578
+ return value;
9579
+ }
9580
+ function scrubString(s, secrets) {
9581
+ let result = s;
9582
+ for (const h of secrets) {
9583
+ if (result.includes(h)) {
9584
+ result = result.replaceAll(h, "***");
9585
+ }
9586
+ }
9587
+ return result;
9588
+ }
9589
+ function translateCapabilities(info) {
9590
+ const caps = {};
9591
+ if (info.capabilities.tools) caps.tools = { listChanged: false };
9592
+ if (info.capabilities.resources) caps.resources = { listChanged: false };
9593
+ if (info.capabilities.prompts) caps.prompts = { listChanged: false };
9594
+ return caps;
9595
+ }
9596
+ function buildServerResult(info, protocolVersion) {
9597
+ return {
9598
+ protocolVersion,
9599
+ capabilities: translateCapabilities(info),
9600
+ serverInfo: {
9601
+ name: info.name ?? "mcpcat-proxy",
9602
+ version: info.version ?? "0.0.0",
9603
+ ...info.title ? { title: info.title } : {}
9604
+ },
9605
+ ...info.instructions ? { instructions: info.instructions } : {}
9606
+ };
9607
+ }
9608
+ function createProxyHandler(options) {
9609
+ let conn = null;
9610
+ let cachedInfo = null;
9611
+ let connecting = null;
9612
+ const secrets = options.hiddenHeaders;
9613
+ async function doConnect() {
9614
+ const c = await connect(options.platform, options.connectOpts);
9615
+ cachedInfo = await c.info();
9616
+ return c;
9617
+ }
9618
+ async function getConnection() {
9619
+ if (conn) return conn;
9620
+ if (connecting !== null) return connecting;
9621
+ connecting = doConnect();
9622
+ try {
9623
+ conn = await connecting;
9624
+ return conn;
9625
+ } finally {
9626
+ connecting = null;
9627
+ }
9628
+ }
9629
+ async function reconnect() {
9630
+ if (conn) {
9631
+ try {
9632
+ await conn.close();
9633
+ } catch {
9634
+ }
9635
+ }
9636
+ conn = null;
9637
+ cachedInfo = null;
9638
+ return getConnection();
9639
+ }
9640
+ function scrubOk(id, result) {
9641
+ return rpcOk2(id, scrubValue(result, secrets));
9642
+ }
9643
+ function scrubErr(id, code, message) {
9644
+ return rpcError2(id, code, scrubString(message, secrets));
9645
+ }
9646
+ function handleInitialize(id, params) {
9647
+ const requested = params && typeof params === "object" ? Object.getOwnPropertyDescriptor(params, "protocolVersion")?.value : void 0;
9648
+ const pv = PROTOCOL_VERSIONS2.find((v) => v === requested) ?? PROTOCOL_VERSIONS2[0];
9649
+ return scrubOk(id, buildServerResult(cachedInfo, pv));
9650
+ }
9651
+ function handleDiscover(id) {
9652
+ return scrubOk(id, {
9653
+ ...buildServerResult(cachedInfo, PROTOCOL_VERSIONS2[0]),
9654
+ supportedVersions: PROTOCOL_VERSIONS2
9655
+ });
9656
+ }
9657
+ async function forwardWithRetry(method, doForward, id) {
9658
+ try {
9659
+ return scrubOk(id, await doForward());
9660
+ } catch (err) {
9661
+ if (!IDEMPOTENT_METHODS.has(method)) {
9662
+ return scrubErr(id, -32e3, err.message);
9663
+ }
9664
+ try {
9665
+ await reconnect();
9666
+ return scrubOk(id, await doForward());
9667
+ } catch (retryErr) {
9668
+ return scrubErr(id, -32e3, retryErr.message);
9669
+ }
9670
+ }
9671
+ }
9672
+ async function handleCall(id, params, c) {
9673
+ const name = params && typeof params === "object" ? readString2(params, "name") : void 0;
9674
+ if (!name) return scrubErr(id, -32602, "tools/call requires a name");
9675
+ const args = params && typeof params === "object" ? Object.getOwnPropertyDescriptor(params, "arguments")?.value : void 0;
9676
+ try {
9677
+ return scrubOk(id, await c.callTool(name, args));
9678
+ } catch (err) {
9679
+ return scrubErr(id, -32e3, err.message);
9680
+ }
9681
+ }
9682
+ async function dispatch(method, id, params) {
9683
+ try {
9684
+ await getConnection();
9685
+ } catch (err) {
9686
+ if (method === "ping") return rpcOk2(id, {});
9687
+ return scrubErr(id, -32e3, `upstream connection failed: ${err.message}`);
9688
+ }
9689
+ if (method === "initialize") return handleInitialize(id, params);
9690
+ if (method === "server/discover") return handleDiscover(id);
9691
+ if (method === "ping") return rpcOk2(id, {});
9692
+ if (method === "tools/list") {
9693
+ return forwardWithRetry(
9694
+ method,
9695
+ async () => {
9696
+ const tools = await (await getConnection()).listTools();
9697
+ return { tools };
9698
+ },
9699
+ id
9700
+ );
9701
+ }
9702
+ if (method === "tools/call") return handleCall(id, params, conn);
9703
+ return scrubErr(id, -32601, `Method not found: ${method}`);
9704
+ }
9705
+ async function handle(raw) {
9706
+ let parsed;
9707
+ try {
9708
+ parsed = JSON.parse(raw);
9709
+ } catch {
9710
+ return rpcError2(null, -32700, "Parse error");
9711
+ }
9712
+ if (Array.isArray(parsed)) return rpcError2(null, -32600, "Batch requests are not supported");
9713
+ if (!parsed || typeof parsed !== "object") return rpcError2(null, -32600, "Invalid request");
9714
+ const method = readString2(parsed, "method");
9715
+ if (!method) return rpcError2(readId2(parsed), -32600, "Invalid request");
9716
+ if (!hasOwn2(parsed, "id")) return { status: 202, contentType: "text/plain", body: "" };
9717
+ const id = readId2(parsed);
9718
+ const params = Object.getOwnPropertyDescriptor(parsed, "params")?.value;
9719
+ return dispatch(method, id, params);
9720
+ }
9721
+ async function shutdown() {
9722
+ if (conn) {
9723
+ try {
9724
+ await conn.close();
9725
+ } catch {
9726
+ }
9727
+ conn = null;
9728
+ }
9729
+ }
9730
+ return { handle, shutdown };
9731
+ }
9732
+
9733
+ // src/serve/runtime.ts
9734
+ var CALL_TIMEOUT_MS = 12e4;
9735
+ var MAX_OUTPUT_DEFAULT = 1048576;
9736
+ var ALLOWED_ENV_KEYS = /* @__PURE__ */ new Set(["PATH", "HOME", "LANG"]);
9737
+ function parseHttpFlag(args, i) {
9738
+ const next = args[i + 1];
9739
+ if (!next || next.startsWith("-")) return { host: "127.0.0.1", port: 0, next: i };
9740
+ const colonIdx = next.lastIndexOf(":");
9741
+ if (colonIdx > 0 && colonIdx < next.length - 1) {
9742
+ return { host: next.slice(0, colonIdx), port: Number(next.slice(colonIdx + 1)), next: i + 1 };
9743
+ }
9744
+ return { host: "127.0.0.1", port: Number(next), next: i + 1 };
9745
+ }
9746
+ function parseCommandTarget(arg) {
9747
+ const eqIdx = arg.indexOf("=");
9748
+ if (eqIdx > 0 && !arg.slice(0, eqIdx).includes("/")) {
9749
+ return { name: arg.slice(0, eqIdx), path: arg.slice(eqIdx + 1) };
9750
+ }
9751
+ return { path: arg };
9752
+ }
9753
+ function parseSingleFlag(args, i, opts) {
9754
+ const arg = args[i];
9165
9755
  if (arg === "--stdio") {
9166
9756
  opts.transport = "stdio";
9167
9757
  } else if (arg === "--http") {
@@ -9170,26 +9760,57 @@ function parseSingleFlag(args, i, opts) {
9170
9760
  opts.host = parsed.host;
9171
9761
  opts.port = parsed.port;
9172
9762
  return parsed.next;
9173
- } else if (arg === "--bearer" && args[i + 1]) {
9174
- opts.bearer = args[++i];
9175
- } else if (arg === "--allow-origin" && args[i + 1]) {
9176
- opts.allowOrigins.push(args[++i]);
9177
- } else if (arg === "--env" && args[i + 1]) {
9178
- opts.envAllow.push(args[++i]);
9179
- } else if (arg === "--tool" && args[i + 1]) {
9180
- opts.toolTemplates.push(args[++i]);
9181
- } else if (arg === "--call-timeout" && args[i + 1]) {
9182
- opts.callTimeout = Number(args[++i]) * 1e3;
9183
- } else if (arg === "--concurrency" && args[i + 1]) {
9184
- opts.concurrency = Number(args[++i]);
9185
- } else if (arg === "--max-output" && args[i + 1]) {
9186
- opts.maxOutput = Number(args[++i]);
9187
- } else if (arg === "--cwd" && args[i + 1]) {
9188
- opts.cwd = args[++i];
9189
9763
  } else if (arg === "--json") {
9190
9764
  opts.json = true;
9191
9765
  } else if (!arg.startsWith("-")) {
9192
9766
  opts.commands.push(parseCommandTarget(arg));
9767
+ } else {
9768
+ return parseValueFlag(args, i, opts);
9769
+ }
9770
+ return i;
9771
+ }
9772
+ function parseValueFlag(args, i, opts) {
9773
+ const arg = args[i];
9774
+ if (!args[i + 1]) return i;
9775
+ switch (arg) {
9776
+ case "--proxy":
9777
+ opts.proxy = args[++i];
9778
+ break;
9779
+ case "--bearer":
9780
+ opts.bearer = args[++i];
9781
+ break;
9782
+ case "--allow-origin":
9783
+ opts.allowOrigins.push(args[++i]);
9784
+ break;
9785
+ case "--env":
9786
+ opts.envAllow.push(args[++i]);
9787
+ break;
9788
+ case "--tool":
9789
+ opts.toolTemplates.push(args[++i]);
9790
+ break;
9791
+ case "--call-timeout":
9792
+ opts.callTimeout = Number(args[++i]) * 1e3;
9793
+ break;
9794
+ case "--concurrency":
9795
+ opts.concurrency = Number(args[++i]);
9796
+ break;
9797
+ case "--max-output":
9798
+ opts.maxOutput = Number(args[++i]);
9799
+ break;
9800
+ case "--cwd":
9801
+ opts.cwd = args[++i];
9802
+ break;
9803
+ case "-H":
9804
+ case "--header": {
9805
+ const hdr = args[++i];
9806
+ const colonIdx = hdr.indexOf(":");
9807
+ if (colonIdx > 0) {
9808
+ opts.proxyHeaders.push([hdr.slice(0, colonIdx).trim(), hdr.slice(colonIdx + 1).trim()]);
9809
+ }
9810
+ break;
9811
+ }
9812
+ default:
9813
+ break;
9193
9814
  }
9194
9815
  return i;
9195
9816
  }
@@ -9207,7 +9828,9 @@ function parseServeFlags(args) {
9207
9828
  maxOutput: MAX_OUTPUT_DEFAULT,
9208
9829
  cwd: void 0,
9209
9830
  json: false,
9210
- commands: []
9831
+ commands: [],
9832
+ proxy: void 0,
9833
+ proxyHeaders: []
9211
9834
  };
9212
9835
  for (let i = 0; i < args.length; i++) {
9213
9836
  i = parseSingleFlag(args, i, opts);
@@ -9534,6 +10157,9 @@ async function runServe(platform2, options, version) {
9534
10157
  }
9535
10158
  resolvedBearer = expanded;
9536
10159
  }
10160
+ if (options.proxy) {
10161
+ return runProxyServe(platform2, options, version, resolvedBearer);
10162
+ }
9537
10163
  const serveCwd = options.cwd ?? platform2.cwd;
9538
10164
  const childEnv = buildChildEnv(platform2, options.envAllow);
9539
10165
  const clis = [];
@@ -9572,6 +10198,240 @@ async function runServe(platform2, options, version) {
9572
10198
  resolvedBearer
9573
10199
  );
9574
10200
  }
10201
+ function resolveProxyStdio(target, version) {
10202
+ const rest = target.slice(6);
10203
+ const parts = rest.split(/\s+/).filter(Boolean);
10204
+ return { command: parts[0], args: parts.slice(1), version };
10205
+ }
10206
+ function resolveProxyUrl(target, headers, version) {
10207
+ const url = target.startsWith("http") ? target : `https://${target}`;
10208
+ return { url, headers, version };
10209
+ }
10210
+ async function resolveProxyConfig(platform2, target, connectHeaders, hiddenValues, version) {
10211
+ const config = await MergedConfigSource.load(platform2);
10212
+ const entry = await config.get(target);
10213
+ if (!entry) return `unknown target '${target}'`;
10214
+ if ("url" in entry && entry.url) {
10215
+ const merged = { ...entry.headers, ...connectHeaders };
10216
+ if (entry.headers) {
10217
+ for (const v of Object.values(entry.headers)) hiddenValues.add(v);
10218
+ }
10219
+ return { url: entry.url, headers: merged, version };
10220
+ }
10221
+ if ("command" in entry && entry.command) {
10222
+ return {
10223
+ command: entry.command,
10224
+ args: entry.args,
10225
+ env: entry.env,
10226
+ cwd: entry.cwd,
10227
+ version
10228
+ };
10229
+ }
10230
+ return `target '${target}' has no url or command`;
10231
+ }
10232
+ async function buildProxyConnectOpts(platform2, options, version) {
10233
+ const target = options.proxy;
10234
+ const connectHeaders = {};
10235
+ const hiddenValues = /* @__PURE__ */ new Set();
10236
+ for (const [k, v] of options.proxyHeaders) {
10237
+ const expanded = expandPlaceholdersStrict(v, platform2.env);
10238
+ if (expanded === void 0) return { error: `unset variable in header '${k}'` };
10239
+ const key = k.toLowerCase() === "authorization" ? "Authorization" : k;
10240
+ connectHeaders[key] = expanded;
10241
+ hiddenValues.add(expanded);
10242
+ }
10243
+ const isUrl = target.startsWith("http://") || target.startsWith("https://") || target.includes(".");
10244
+ let connectOpts;
10245
+ if (options.proxyCommand && options.proxyCommand.length > 0) {
10246
+ connectOpts = {
10247
+ command: options.proxyCommand[0],
10248
+ args: options.proxyCommand.slice(1),
10249
+ version
10250
+ };
10251
+ } else if (target.startsWith("stdio:")) {
10252
+ connectOpts = resolveProxyStdio(target, version);
10253
+ } else if (isUrl) {
10254
+ connectOpts = resolveProxyUrl(target, connectHeaders, version);
10255
+ } else {
10256
+ const result = await resolveProxyConfig(
10257
+ platform2,
10258
+ target,
10259
+ connectHeaders,
10260
+ hiddenValues,
10261
+ version
10262
+ );
10263
+ if (typeof result === "string") return { error: result };
10264
+ connectOpts = result;
10265
+ }
10266
+ if (connectOpts.headers) {
10267
+ for (const v of Object.values(connectOpts.headers)) {
10268
+ if (v) hiddenValues.add(v);
10269
+ }
10270
+ }
10271
+ return { connectOpts, hiddenValues };
10272
+ }
10273
+ async function runProxyServe(platform2, options, version, resolvedBearer) {
10274
+ const built = await buildProxyConnectOpts(platform2, options, version);
10275
+ if ("error" in built) {
10276
+ await platform2.write(2, `mcpcat serve --proxy: ${built.error}
10277
+ `);
10278
+ return 2;
10279
+ }
10280
+ const { connectOpts, hiddenValues } = built;
10281
+ if (resolvedBearer) hiddenValues.add(resolvedBearer);
10282
+ const proxy = createProxyHandler({
10283
+ target: options.proxy,
10284
+ connectOpts,
10285
+ platform: platform2,
10286
+ hiddenHeaders: hiddenValues
10287
+ });
10288
+ if (options.transport === "stdio") {
10289
+ return runProxyStdio(platform2, proxy);
10290
+ }
10291
+ return runProxyHttp(platform2, options, proxy, resolvedBearer);
10292
+ }
10293
+ async function runProxyStdio(platform2, proxy) {
10294
+ const reader = platform2.stdin.getReader();
10295
+ let buffer = "";
10296
+ const dec = new TextDecoder();
10297
+ const enc = new TextEncoder();
10298
+ try {
10299
+ for (; ; ) {
10300
+ const { done, value } = await reader.read();
10301
+ if (done) break;
10302
+ buffer += dec.decode(value, { stream: true });
10303
+ let nlIdx;
10304
+ while ((nlIdx = buffer.indexOf("\n")) >= 0) {
10305
+ const line = buffer.slice(0, nlIdx).trim();
10306
+ buffer = buffer.slice(nlIdx + 1);
10307
+ if (!line) continue;
10308
+ const outcome = await proxy.handle(line);
10309
+ if (outcome.status === 202) continue;
10310
+ const writer = platform2.stdout.getWriter();
10311
+ await writer.write(enc.encode(`${outcome.body}
10312
+ `));
10313
+ writer.releaseLock();
10314
+ }
10315
+ }
10316
+ } finally {
10317
+ reader.releaseLock();
10318
+ await proxy.shutdown();
10319
+ }
10320
+ return 0;
10321
+ }
10322
+ async function runProxyHttp(platform2, options, proxy, resolvedBearer) {
10323
+ if (!platform2.listen) {
10324
+ await platform2.write(2, "mcpcat serve --proxy: HTTP transport requires platform.listen\n");
10325
+ return 3;
10326
+ }
10327
+ const boundLoopback = options.host === "127.0.0.1" || options.host === "localhost" || options.host === "::1";
10328
+ if (!boundLoopback && !resolvedBearer) {
10329
+ await platform2.write(
10330
+ 2,
10331
+ "mcpcat serve --proxy: --bearer is required when binding off loopback\n"
10332
+ );
10333
+ return 2;
10334
+ }
10335
+ const allowedOrigins = new Set(options.allowOrigins);
10336
+ const listener = await platform2.listen({
10337
+ host: options.host,
10338
+ port: options.port
10339
+ });
10340
+ const actualPort = listener.port;
10341
+ const listenUrl = `http://${options.host}:${actualPort}/mcp`;
10342
+ if (options.json) {
10343
+ await platform2.write(1, `${JSON.stringify({ url: listenUrl })}
10344
+ `);
10345
+ }
10346
+ await platform2.write(2, `mcpcat serve --proxy: listening on ${listenUrl}
10347
+ `);
10348
+ if (options.onListening) {
10349
+ await options.onListening(actualPort);
10350
+ }
10351
+ let shutdownRequested = false;
10352
+ const shutdown = () => {
10353
+ if (shutdownRequested) return;
10354
+ shutdownRequested = true;
10355
+ listener.close();
10356
+ void proxy.shutdown();
10357
+ };
10358
+ if (platform2.onSignal) {
10359
+ platform2.onSignal("SIGINT", shutdown);
10360
+ platform2.onSignal("SIGTERM", shutdown);
10361
+ }
10362
+ const handler = (request) => handleProxyMcpRequest(request, proxy, allowedOrigins, boundLoopback, resolvedBearer);
10363
+ try {
10364
+ for await (const connection of listener) {
10365
+ if (shutdownRequested) break;
10366
+ const conn = connection;
10367
+ handleConnection(conn.readable, conn.writable, handler).catch(() => {
10368
+ }).finally(() => {
10369
+ try {
10370
+ conn.close();
10371
+ } catch {
10372
+ }
10373
+ });
10374
+ }
10375
+ } catch {
10376
+ if (!shutdownRequested) throw new Error("listener closed unexpectedly");
10377
+ }
10378
+ return 0;
10379
+ }
10380
+ async function handleProxyMcpRequest(request, proxy, allowedOrigins, boundLoopback, resolvedBearer) {
10381
+ if (resolvedBearer) {
10382
+ const authHeader = request.headers.authorization;
10383
+ const provided = authHeader?.startsWith("Bearer ") ? authHeader.slice(7) : "";
10384
+ if (!provided || !constantTimeEqual(provided, resolvedBearer)) {
10385
+ return {
10386
+ status: 401,
10387
+ reason: "Unauthorized",
10388
+ headers: { "content-type": "text/plain" },
10389
+ body: "Unauthorized"
10390
+ };
10391
+ }
10392
+ }
10393
+ const originCheck = checkOrigin(request.headers.origin, allowedOrigins, boundLoopback);
10394
+ if (!originCheck.ok) {
10395
+ return {
10396
+ status: 403,
10397
+ reason: "Forbidden",
10398
+ headers: { "content-type": "text/plain" },
10399
+ body: originCheck.reason ?? "Forbidden"
10400
+ };
10401
+ }
10402
+ const hostCheck = checkHost(request.headers.host, boundLoopback);
10403
+ if (!hostCheck.ok) {
10404
+ return {
10405
+ status: 403,
10406
+ reason: "Forbidden",
10407
+ headers: { "content-type": "text/plain" },
10408
+ body: hostCheck.reason ?? "Forbidden"
10409
+ };
10410
+ }
10411
+ if (request.target !== "/mcp") {
10412
+ return {
10413
+ status: 404,
10414
+ reason: "Not Found",
10415
+ headers: { "content-type": "text/plain" },
10416
+ body: "Not Found"
10417
+ };
10418
+ }
10419
+ if (request.method !== "POST") {
10420
+ return {
10421
+ status: 405,
10422
+ reason: "Method Not Allowed",
10423
+ headers: { "content-type": "text/plain", allow: "POST" },
10424
+ body: "Method Not Allowed"
10425
+ };
10426
+ }
10427
+ const outcome = await proxy.handle(request.body);
10428
+ return {
10429
+ status: outcome.status,
10430
+ reason: outcome.status === 200 ? "OK" : "Accepted",
10431
+ headers: { "content-type": outcome.contentType },
10432
+ body: outcome.body
10433
+ };
10434
+ }
9575
10435
  async function runStdioServer(platform2, tools, call, serverName, version) {
9576
10436
  const reader = platform2.stdin.getReader();
9577
10437
  let buffer = "";
@@ -10078,6 +10938,14 @@ function parseAddFlagAt(flagArgs, i, flags) {
10078
10938
  flags.noProbe = true;
10079
10939
  return { next: i + 1 };
10080
10940
  }
10941
+ if (arg === "--bridge") {
10942
+ flags.bridge = true;
10943
+ return { next: i + 1 };
10944
+ }
10945
+ if (arg === "--bearer" && i + 1 < flagArgs.length) {
10946
+ flags.bearer = flagArgs[i + 1];
10947
+ return { next: i + 2 };
10948
+ }
10081
10949
  if (arg === "--config" && i + 1 < flagArgs.length) {
10082
10950
  flags.configFile = flagArgs[i + 1];
10083
10951
  return { next: i + 2 };
@@ -10114,6 +10982,7 @@ function parseAddFlags(args) {
10114
10982
  const flags = {
10115
10983
  local: false,
10116
10984
  noProbe: false,
10985
+ bridge: false,
10117
10986
  headers: [],
10118
10987
  envPairs: [],
10119
10988
  positional: [],
@@ -10476,6 +11345,139 @@ async function runImport(platform2, args) {
10476
11345
  }
10477
11346
  return 0;
10478
11347
  }
11348
+ async function runBridge(platform2, args, version) {
11349
+ if (platform2.kind === "host") {
11350
+ await platform2.write(
11351
+ 2,
11352
+ "mcpcat add --bridge: not available on the host.\nOn the host, add stdio entries directly with: mcpcat add <name> -- <cmd>\nTo expose a stdio server over HTTP: mcpcat serve --proxy <target> --http <port>\n"
11353
+ );
11354
+ return 2;
11355
+ }
11356
+ const flags = parseAddFlags(args);
11357
+ if (typeof flags === "string") {
11358
+ await platform2.write(2, `mcpcat add --bridge: ${flags}
11359
+ `);
11360
+ return 2;
11361
+ }
11362
+ const name = flags.positional[0];
11363
+ if (!name) {
11364
+ await platform2.write(2, "mcpcat add --bridge: requires a server name\n");
11365
+ return 2;
11366
+ }
11367
+ if (flags.cmdArgs.length === 0) {
11368
+ await platform2.write(2, "mcpcat add --bridge: requires -- <command> [args...]\n");
11369
+ return 2;
11370
+ }
11371
+ if (flags.bearer) {
11372
+ await platform2.write(
11373
+ 2,
11374
+ "mcpcat add --bridge: --bearer is not needed on the kernel loopback.\nThe kernel loopback is only reachable from inside the kernel and the agent.\n"
11375
+ );
11376
+ return 2;
11377
+ }
11378
+ const serveOptions = parseServeFlags(["--proxy", "__bridge__", "--http", "0", "--json"]);
11379
+ serveOptions.proxyCommand = flags.cmdArgs;
11380
+ const path = flags.configFile ?? (flags.local ? projectConfigPath(platform2) : userConfigPath(platform2));
11381
+ serveOptions.onListening = async (actualPort) => {
11382
+ const bridgeUrl = `http://${actualPort}.kernel.localhost/mcp`;
11383
+ const httpConfig = { url: bridgeUrl };
11384
+ if (flags.description) httpConfig.description = flags.description;
11385
+ if (flags.exposure) httpConfig.exposure = flags.exposure;
11386
+ const { replaced } = await addServer(platform2.fs, path, name, httpConfig);
11387
+ await platform2.write(
11388
+ 1,
11389
+ `${replaced ? "replaced" : "added"} ${name} in ${path}
11390
+ bridge does not survive a reload; runs in the foreground (start with &)
11391
+ `
11392
+ );
11393
+ };
11394
+ return runServe(platform2, serveOptions, version);
11395
+ }
11396
+
11397
+ // src/cli/exit-codes.ts
11398
+ var EXIT_OK = 0;
11399
+ var EXIT_TOOL_ERROR = 1;
11400
+ var EXIT_USAGE = 2;
11401
+ var EXIT_CONNECTION = 3;
11402
+ var EXIT_AUTH = 4;
11403
+ var EXIT_RPC_ERROR = 5;
11404
+ var EXIT_INPUT_REQUIRED = 6;
11405
+ var EXIT_TIMEOUT = 124;
11406
+ var EXIT_INTERRUPTED = 130;
11407
+ var McpcatError = class extends Error {
11408
+ exitCode;
11409
+ constructor(exitCode, message) {
11410
+ super(message);
11411
+ this.name = "McpcatError";
11412
+ this.exitCode = exitCode;
11413
+ }
11414
+ };
11415
+
11416
+ // src/cli/verbose.ts
11417
+ var SENSITIVE_HEADER_RE = /^(authorization|cookie|proxy-authorization)$/i;
11418
+ var SENSITIVE_NAME_RE = /token|key|secret|password|session/i;
11419
+ function isSensitiveHeader(name) {
11420
+ return SENSITIVE_HEADER_RE.test(name) || SENSITIVE_NAME_RE.test(name);
11421
+ }
11422
+ function redact(text, secrets) {
11423
+ let result = text;
11424
+ for (const secret of secrets) {
11425
+ if (secret.length > 0) {
11426
+ while (result.includes(secret)) {
11427
+ result = result.replace(secret, "[REDACTED]");
11428
+ }
11429
+ }
11430
+ }
11431
+ return result;
11432
+ }
11433
+ function createVerboseLogger(platform2, enabled) {
11434
+ const secrets = /* @__PURE__ */ new Set();
11435
+ function write(line) {
11436
+ const safe = redact(line, secrets);
11437
+ platform2.write(2, `${safe}
11438
+ `).catch(() => {
11439
+ });
11440
+ }
11441
+ return {
11442
+ enabled,
11443
+ secrets,
11444
+ request(method, body) {
11445
+ if (!enabled) return;
11446
+ write(`> ${method}`);
11447
+ for (const line of body.split("\n")) {
11448
+ write(`> ${line}`);
11449
+ }
11450
+ },
11451
+ response(body) {
11452
+ if (!enabled) return;
11453
+ for (const line of body.split("\n")) {
11454
+ write(`< ${line}`);
11455
+ }
11456
+ },
11457
+ http(direction, line) {
11458
+ if (!enabled) return;
11459
+ write(`${direction} ${line}`);
11460
+ },
11461
+ server(line) {
11462
+ if (!enabled) return;
11463
+ write(`[server] ${line}`);
11464
+ },
11465
+ headerOut(name, value) {
11466
+ if (!enabled) return;
11467
+ const display = isSensitiveHeader(name) ? "[REDACTED]" : redact(value, secrets);
11468
+ write(`> ${name}: ${display}`);
11469
+ },
11470
+ headerIn(name, value) {
11471
+ if (!enabled) return;
11472
+ const display = isSensitiveHeader(name) ? "[REDACTED]" : redact(value, secrets);
11473
+ write(`< ${name}: ${display}`);
11474
+ },
11475
+ progress(message) {
11476
+ if (!enabled) return;
11477
+ write(`* ${message}`);
11478
+ }
11479
+ };
11480
+ }
10479
11481
 
10480
11482
  // src/cli/main.ts
10481
11483
  var VERBS = [
@@ -10540,6 +11542,9 @@ Client verbs:
10540
11542
 
10541
11543
  Configuration verbs:
10542
11544
  add <name> <url> Add a server to mcp.json
11545
+ add <name> --bridge --
11546
+ Bridge a stdio server to HTTP (kernel only,
11547
+ runs in foreground, does not survive reload)
10543
11548
  rm <name> Remove a server
10544
11549
  list List configured servers
10545
11550
  enable|disable <name> Toggle a server
@@ -10549,7 +11554,7 @@ Configuration verbs:
10549
11554
  logout <target> Remove tokens (not implemented yet)
10550
11555
 
10551
11556
  Server and code verbs:
10552
- serve [--stdio|--http] \u2026 Expose commands as MCP server
11557
+ serve [--stdio|--http] \u2026 Expose commands or proxy upstream as MCP server
10553
11558
  code [-e src|file.js] Run a codemode script
10554
11559
  grep <pattern> [target\u2026] Search tools by name or description
10555
11560
  discover <url> Find MCP servers at a URL
@@ -10591,6 +11596,8 @@ function parseFlags(args) {
10591
11596
  const serverTargets = [];
10592
11597
  let allowShell = false;
10593
11598
  let timeoutMs;
11599
+ let verbose = false;
11600
+ let silent = false;
10594
11601
  const positional = [];
10595
11602
  let trailingCommand;
10596
11603
  for (let i = 0; i < args.length; i++) {
@@ -10627,6 +11634,10 @@ function parseFlags(args) {
10627
11634
  serverTargets.push(args[++i]);
10628
11635
  } else if (arg === "--allow-shell") {
10629
11636
  allowShell = true;
11637
+ } else if (arg === "-v" || arg === "--verbose") {
11638
+ verbose = true;
11639
+ } else if (arg === "--silent") {
11640
+ silent = true;
10630
11641
  } else if ((arg === "-m" || arg === "--timeout") && args[i + 1]) {
10631
11642
  timeoutMs = Number(args[++i]) * 1e3;
10632
11643
  } else if (!arg.startsWith("-")) {
@@ -10647,6 +11658,8 @@ function parseFlags(args) {
10647
11658
  serverTargets,
10648
11659
  allowShell,
10649
11660
  timeoutMs,
11661
+ verbose,
11662
+ silent,
10650
11663
  positional,
10651
11664
  trailingCommand
10652
11665
  };
@@ -10695,7 +11708,9 @@ function parseArgs(args) {
10695
11708
  inlineSource: flags.inlineSource,
10696
11709
  serverTargets: flags.serverTargets,
10697
11710
  allowShell: flags.allowShell,
10698
- timeoutMs: flags.timeoutMs
11711
+ timeoutMs: flags.timeoutMs,
11712
+ verbose: flags.verbose,
11713
+ silent: flags.silent
10699
11714
  };
10700
11715
  }
10701
11716
  function formatToolLine(tool) {
@@ -10719,26 +11734,6 @@ function formatToolSchema(tool) {
10719
11734
  }
10720
11735
  return lines.join("\n");
10721
11736
  }
10722
- function renderCallResult(result) {
10723
- const parts = [];
10724
- if (result.content) {
10725
- for (const block of result.content) {
10726
- if (block.type === "text") parts.push(block.text);
10727
- else if (block.type === "image")
10728
- parts.push(`[image: ${block.mimeType}, ${Math.ceil(block.data.length * 3 / 4)} bytes]`);
10729
- else if (block.type === "resource_link") parts.push(`[resource: ${block.uri}]`);
10730
- else if (block.type === "resource") {
10731
- parts.push(
10732
- "text" in block.resource ? block.resource.text : `[blob: ${block.resource.uri}]`
10733
- );
10734
- }
10735
- }
10736
- }
10737
- if (parts.length === 0 && result.structuredContent !== void 0) {
10738
- parts.push(JSON.stringify(result.structuredContent, null, 2));
10739
- }
10740
- return parts.join("\n");
10741
- }
10742
11737
  var NETWORK_HINT = "connection failed (use slicc-node, slicc-swift, or the browser extension for servers without CORS)";
10743
11738
  function transportHint(err) {
10744
11739
  if (err instanceof TypeError) {
@@ -10781,8 +11776,12 @@ async function readDataArg(platform2, dataArg) {
10781
11776
  }
10782
11777
  return JSON.parse(raw);
10783
11778
  }
10784
- async function runInfo(platform2, conn) {
11779
+ async function runInfo(platform2, conn, json) {
10785
11780
  const info = await conn.info();
11781
+ if (json) {
11782
+ await renderInfoJson(platform2, info);
11783
+ return EXIT_OK;
11784
+ }
10786
11785
  const lines = [];
10787
11786
  if (info.name) lines.push(`Server: ${info.name}${info.version ? ` ${info.version}` : ""}`);
10788
11787
  if (info.title) lines.push(`Title: ${info.title}`);
@@ -10799,48 +11798,66 @@ async function runInfo(platform2, conn) {
10799
11798
  }
10800
11799
  await platform2.write(1, `${lines.join("\n")}
10801
11800
  `);
10802
- return 0;
11801
+ return EXIT_OK;
10803
11802
  }
10804
- async function runTools(platform2, conn) {
11803
+ async function runTools(platform2, conn, json) {
10805
11804
  const tools = await conn.listTools();
10806
- if (tools.length === 0) {
10807
- await platform2.write(1, "(no tools)\n");
10808
- } else {
10809
- await platform2.write(1, `${tools.map(formatToolLine).join("\n")}
10810
- `);
11805
+ if (json) {
11806
+ await renderToolsJson(platform2, tools);
11807
+ return EXIT_OK;
10811
11808
  }
10812
- return 0;
11809
+ await platform2.write(
11810
+ 1,
11811
+ tools.length === 0 ? "(no tools)\n" : `${tools.map(formatToolLine).join("\n")}
11812
+ `
11813
+ );
11814
+ return EXIT_OK;
10813
11815
  }
10814
- async function runCall(platform2, conn, toolName, dataArg) {
10815
- let toolArgs = {};
11816
+ async function runCall(platform2, conn, toolName, dataArg, toolArgs, opts) {
11817
+ let base = {};
10816
11818
  if (dataArg) {
10817
11819
  try {
10818
- toolArgs = await readDataArg(platform2, dataArg);
11820
+ base = await readDataArg(platform2, dataArg);
10819
11821
  } catch (err) {
10820
- await platform2.write(2, `mcpcat: invalid -d argument: ${err.message}
10821
- `);
10822
- return 2;
11822
+ await writeErr(
11823
+ platform2,
11824
+ opts.json ?? false,
11825
+ "usage",
11826
+ `invalid -d argument: ${err.message}`
11827
+ );
11828
+ return EXIT_USAGE;
10823
11829
  }
10824
11830
  }
10825
- const result = await conn.callTool(toolName, toolArgs);
10826
- const output = renderCallResult(result);
10827
- if (result.isError) {
10828
- await platform2.write(2, `${output}
10829
- `);
10830
- return 1;
11831
+ let fin = base;
11832
+ if (toolArgs.length > 0) {
11833
+ const tools = await conn.listTools();
11834
+ const tool = tools.find((t) => t.name === toolName);
11835
+ const schema = tool?.inputSchema ?? { type: "object", properties: {} };
11836
+ const c = coerceArgsBySchema(toolArgs, schema, { noValidate: opts.noValidate });
11837
+ if (!c.ok) {
11838
+ await writeErr(platform2, opts.json ?? false, "usage", c.error);
11839
+ return EXIT_USAGE;
11840
+ }
11841
+ fin = mergeArgs(base, c.value);
10831
11842
  }
10832
- await platform2.write(1, `${output}
10833
- `);
10834
- return 0;
11843
+ return renderCallResult(
11844
+ platform2,
11845
+ await conn.callTool(toolName, fin, { onProgress: opts.onProgress }),
11846
+ { json: opts.json }
11847
+ );
10835
11848
  }
10836
- async function runDescribe(platform2, conn, toolName, showSchema) {
11849
+ async function runDescribe(platform2, conn, toolName, showSchema, json) {
10837
11850
  const tools = await conn.listTools();
10838
11851
  if (toolName) {
10839
11852
  const tool = tools.find((t) => t.name === toolName);
10840
11853
  if (!tool) {
10841
- await platform2.write(2, `mcpcat: tool '${toolName}' not found
11854
+ await writeErr(platform2, json, "usage", `tool '${toolName}' not found`);
11855
+ return EXIT_USAGE;
11856
+ }
11857
+ if (json) {
11858
+ await platform2.write(1, `${JSON.stringify(tool, null, 2)}
10842
11859
  `);
10843
- return 2;
11860
+ return EXIT_OK;
10844
11861
  }
10845
11862
  if (showSchema && tool.inputSchema) {
10846
11863
  await platform2.write(1, `${JSON.stringify(tool.inputSchema, null, 2)}
@@ -10850,28 +11867,36 @@ async function runDescribe(platform2, conn, toolName, showSchema) {
10850
11867
  `);
10851
11868
  }
10852
11869
  } else {
11870
+ if (json) {
11871
+ await platform2.write(1, `${JSON.stringify({ tools }, null, 2)}
11872
+ `);
11873
+ return EXIT_OK;
11874
+ }
10853
11875
  for (const tool of tools) {
10854
11876
  await platform2.write(1, `${formatToolSchema(tool)}
10855
11877
 
10856
11878
  `);
10857
11879
  }
10858
11880
  }
10859
- return 0;
11881
+ return EXIT_OK;
10860
11882
  }
10861
- async function runVerb(platform2, parsed, conn) {
11883
+ async function runVerb(platform2, parsed, conn, onProgress) {
10862
11884
  switch (parsed.verb) {
10863
11885
  case "info":
10864
- return runInfo(platform2, conn);
11886
+ return runInfo(platform2, conn, parsed.jsonOutput);
10865
11887
  case "tools":
10866
- return runTools(platform2, conn);
11888
+ return runTools(platform2, conn, parsed.jsonOutput);
10867
11889
  case "call":
10868
- return runCall(platform2, conn, parsed.toolName, parsed.dataArg);
11890
+ return runCall(platform2, conn, parsed.toolName, parsed.dataArg, [], {
11891
+ json: parsed.jsonOutput,
11892
+ onProgress
11893
+ });
10869
11894
  case "describe":
10870
- return runDescribe(platform2, conn, parsed.toolName, parsed.showSchema);
11895
+ return runDescribe(platform2, conn, parsed.toolName, parsed.showSchema, parsed.jsonOutput);
10871
11896
  default:
10872
11897
  await platform2.write(2, `mcpcat: unknown command '${parsed.verb}'
10873
11898
  `);
10874
- return 2;
11899
+ return EXIT_USAGE;
10875
11900
  }
10876
11901
  }
10877
11902
  async function probeServer(_name, config, platform2) {
@@ -10902,10 +11927,11 @@ async function probeServer(_name, config, platform2) {
10902
11927
  return err.message;
10903
11928
  }
10904
11929
  }
10905
- async function dispatchConfigVerb(platform2, verb, args) {
11930
+ async function dispatchConfigVerb(platform2, verb, args, version) {
10906
11931
  const rest = args.slice(1);
10907
11932
  switch (verb) {
10908
11933
  case "add":
11934
+ if (rest.includes("--bridge")) return runBridge(platform2, rest, version);
10909
11935
  return runAdd(platform2, rest, probeServer);
10910
11936
  case "rm":
10911
11937
  return runRm(platform2, rest);
@@ -10948,7 +11974,7 @@ async function dispatchDiscoveryVerb(platform2, parsed, args) {
10948
11974
  async function dispatchServeVerb(platform2, args, version) {
10949
11975
  const serveArgs = args.slice(args.indexOf("serve") + 1);
10950
11976
  const serveOptions = parseServeFlags(serveArgs);
10951
- if (serveOptions.commands.length === 0 && serveOptions.toolTemplates.length === 0) {
11977
+ if (!serveOptions.proxy && serveOptions.commands.length === 0 && serveOptions.toolTemplates.length === 0) {
10952
11978
  await platform2.write(2, "mcpcat serve: no commands or --tool templates given\n");
10953
11979
  return 2;
10954
11980
  }
@@ -11112,14 +12138,14 @@ async function run(platform2, version, args) {
11112
12138
  if (parsed.verb in CONFIG_VERBS) {
11113
12139
  const disc = await dispatchDiscoveryVerb(platform2, parsed, args);
11114
12140
  if (disc !== void 0) return disc;
11115
- return dispatchConfigVerb(platform2, parsed.verb, args);
12141
+ return dispatchConfigVerb(platform2, parsed.verb, args, version);
11116
12142
  }
11117
12143
  const discResult = await dispatchDiscoveryVerb(platform2, parsed, args);
11118
12144
  if (discResult !== void 0) return discResult;
11119
12145
  if (UNIMPLEMENTED_VERBS.has(parsed.verb)) {
11120
12146
  await platform2.write(2, `mcpcat: '${parsed.verb}' is not implemented yet
11121
12147
  `);
11122
- return 1;
12148
+ return EXIT_TOOL_ERROR;
11123
12149
  }
11124
12150
  if (parsed.verb === "serve") {
11125
12151
  return dispatchServeVerb(platform2, args, version);
@@ -11141,44 +12167,100 @@ async function run(platform2, version, args) {
11141
12167
  `);
11142
12168
  return 2;
11143
12169
  }
12170
+ return runClientVerb(platform2, version, parsed);
12171
+ }
12172
+ function expandAndCollect(headers, env2) {
12173
+ const secrets = [];
12174
+ const result = {};
12175
+ for (const [k, v] of Object.entries(headers)) {
12176
+ const e = expandPlaceholders(v, env2);
12177
+ result[k] = e;
12178
+ if (e !== v) secrets.push(e);
12179
+ if (v.length > 0) secrets.push(v);
12180
+ }
12181
+ return { expanded: result, secrets };
12182
+ }
12183
+ function classifyError(err) {
12184
+ if (err instanceof InputRequiredError) {
12185
+ return { code: EXIT_INPUT_REQUIRED, kind: "input_required", msg: err.requestJson };
12186
+ }
12187
+ if (err instanceof McpTimeoutError2) {
12188
+ return { code: EXIT_TIMEOUT, kind: "timeout", msg: err.message };
12189
+ }
12190
+ if (err instanceof McpAuthError) {
12191
+ return { code: EXIT_AUTH, kind: "auth", msg: err.message };
12192
+ }
12193
+ if (err instanceof McpcatError) {
12194
+ return { code: err.exitCode, kind: "error", msg: err.message };
12195
+ }
12196
+ const msg = err.message ?? String(err);
12197
+ const name = err.name ?? "";
12198
+ if (name === "McpTimeoutError" || msg.includes("timed out") || msg.includes("timeout") || msg.includes("Timeout")) {
12199
+ return { code: EXIT_TIMEOUT, kind: "timeout", msg };
12200
+ }
12201
+ return { code: EXIT_RPC_ERROR, kind: "rpc_error", msg };
12202
+ }
12203
+ async function runClientVerb(platform2, version, parsed) {
12204
+ const { expanded, secrets } = expandAndCollect(parsed.headers, platform2.env);
12205
+ const verbose = createVerboseLogger(platform2, parsed.verbose);
12206
+ for (const s of secrets) verbose.secrets.add(s);
12207
+ const timeoutMs = parsed.timeoutMs;
12208
+ const sigintController = new AbortController();
12209
+ let interrupted = false;
12210
+ platform2.onSignal?.("SIGINT", () => {
12211
+ interrupted = true;
12212
+ sigintController.abort();
12213
+ });
12214
+ const showProgress = !parsed.silent && (parsed.verbose || platform2.isatty(2));
12215
+ const onProgress = (progress, total, message) => {
12216
+ if (!showProgress) return;
12217
+ const pct = total ? ` ${Math.round(progress / total * 100)}%` : "";
12218
+ const msg = message ? ` ${message}` : "";
12219
+ platform2.write(2, `* progress:${pct}${msg}
12220
+ `).catch(() => {
12221
+ });
12222
+ };
11144
12223
  const config = await MergedConfigSource.load(platform2);
12224
+ const target = parsed.target === "__trailing__" ? "" : parsed.target;
11145
12225
  let resolved;
11146
12226
  try {
11147
- resolved = await resolveTarget(
11148
- parsed.target === "__trailing__" ? "" : parsed.target,
11149
- parsed.trailingCommand,
11150
- config
11151
- );
12227
+ resolved = await resolveTarget(target, parsed.trailingCommand, config);
11152
12228
  } catch (err) {
11153
- await platform2.write(2, `mcpcat: ${err.message}
11154
- `);
11155
- return 2;
12229
+ await writeErr(platform2, parsed.jsonOutput, "usage", err.message);
12230
+ return EXIT_USAGE;
11156
12231
  }
11157
12232
  let conn;
11158
12233
  try {
11159
12234
  conn = await connect(platform2, {
11160
12235
  ...resolved,
11161
- headers: { ...resolved.headers, ...parsed.headers },
12236
+ headers: { ...resolved.headers, ...expanded },
11162
12237
  protocolVersion: parsed.protocolVersion,
11163
- version
12238
+ version,
12239
+ timeoutMs,
12240
+ signal: sigintController.signal,
12241
+ verbose
11164
12242
  });
11165
12243
  } catch (err) {
12244
+ if (err instanceof McpAuthError) {
12245
+ await writeErr(platform2, parsed.jsonOutput, "auth", err.message);
12246
+ return EXIT_AUTH;
12247
+ }
11166
12248
  const msg = transportHint(err) ?? `connection failed: ${err.message}`;
11167
- await platform2.write(2, `mcpcat: ${msg}
11168
- `);
11169
- return 3;
12249
+ await writeErr(platform2, parsed.jsonOutput, "connection", msg);
12250
+ return EXIT_CONNECTION;
11170
12251
  }
11171
12252
  try {
11172
- return await runVerb(platform2, parsed, conn);
12253
+ return await runVerb(platform2, parsed, conn, onProgress);
11173
12254
  } catch (err) {
11174
- if (err instanceof InputRequiredError) {
11175
- await platform2.write(2, `${err.requestJson}
11176
- `);
11177
- return 6;
12255
+ if (interrupted) {
12256
+ await platform2.write(2, "mcpcat: interrupted\n");
12257
+ return EXIT_INTERRUPTED;
11178
12258
  }
11179
- await platform2.write(2, `mcpcat: ${err.message}
12259
+ const e = classifyError(err);
12260
+ if (err instanceof InputRequiredError) await platform2.write(2, `${e.msg}
11180
12261
  `);
11181
- return 5;
12262
+ else await writeErr(platform2, parsed.jsonOutput, e.kind, e.msg);
12263
+ return e.code;
11182
12264
  } finally {
11183
12265
  try {
11184
12266
  await conn.close();
@@ -11186,6 +12268,11 @@ async function run(platform2, version, args) {
11186
12268
  }
11187
12269
  }
11188
12270
  }
12271
+ async function writeErr(p, json, kind, msg) {
12272
+ if (json) await renderErrorJson(p, kind, msg);
12273
+ else await p.write(2, `mcpcat: ${msg}
12274
+ `);
12275
+ }
11189
12276
 
11190
12277
  // src/platform/kernel.ts
11191
12278
  function kernelFs(ctx) {
@@ -11272,6 +12359,7 @@ async function kernelListen(ctx, options) {
11272
12359
  function createKernelPlatform(ctx, options) {
11273
12360
  const encoder2 = new TextEncoder();
11274
12361
  return {
12362
+ kind: "kernel",
11275
12363
  fetch: ctx.fetch,
11276
12364
  spawn: (options2) => kernelSpawn(ctx, options2),
11277
12365
  listen: (options2) => kernelListen(ctx, options2),
@@ -11301,7 +12389,7 @@ async function main(ctx) {
11301
12389
  packageDir: "/node_modules/@ai-ecoverse/mcpcat"
11302
12390
  });
11303
12391
  const args = ctx.argv.slice(1);
11304
- return run(platform2, "0.0.0", args);
12392
+ return run(platform2, "0.9.0", args);
11305
12393
  }
11306
12394
  export {
11307
12395
  main as default