@ai-ecoverse/mcpcat 0.1.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,9 +1,3312 @@
1
1
  #!/usr/bin/env node
2
+ import { createRequire as __mcpcat_createRequire } from 'node:module';
3
+ const require = __mcpcat_createRequire(import.meta.url);
4
+ var __create = Object.create;
5
+ var __defProp = Object.defineProperty;
6
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
7
+ var __getOwnPropNames = Object.getOwnPropertyNames;
8
+ var __getProtoOf = Object.getPrototypeOf;
9
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
10
+ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
11
+ get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
12
+ }) : x)(function(x) {
13
+ if (typeof require !== "undefined") return require.apply(this, arguments);
14
+ throw Error('Dynamic require of "' + x + '" is not supported');
15
+ });
16
+ var __commonJS = (cb, mod) => function __require2() {
17
+ try {
18
+ return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
19
+ } catch (e) {
20
+ throw mod = 0, e;
21
+ }
22
+ };
23
+ var __copyProps = (to, from, except, desc) => {
24
+ if (from && typeof from === "object" || typeof from === "function") {
25
+ for (let key of __getOwnPropNames(from))
26
+ if (!__hasOwnProp.call(to, key) && key !== except)
27
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
28
+ }
29
+ return to;
30
+ };
31
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
32
+ // If the importer is in node compatibility mode or this is not an ESM
33
+ // file that has been converted to a CommonJS file using a Babel-
34
+ // compatible transform (i.e. "__esModule" has not been set), then set
35
+ // "default" to the CommonJS "module.exports" for node compatibility.
36
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
37
+ mod
38
+ ));
39
+
40
+ // node_modules/isexe/windows.js
41
+ var require_windows = __commonJS({
42
+ "node_modules/isexe/windows.js"(exports, module) {
43
+ module.exports = isexe;
44
+ isexe.sync = sync;
45
+ var fs = __require("fs");
46
+ function checkPathExt(path, options) {
47
+ var pathext = options.pathExt !== void 0 ? options.pathExt : process.env.PATHEXT;
48
+ if (!pathext) {
49
+ return true;
50
+ }
51
+ pathext = pathext.split(";");
52
+ if (pathext.indexOf("") !== -1) {
53
+ return true;
54
+ }
55
+ for (var i = 0; i < pathext.length; i++) {
56
+ var p = pathext[i].toLowerCase();
57
+ if (p && path.substr(-p.length).toLowerCase() === p) {
58
+ return true;
59
+ }
60
+ }
61
+ return false;
62
+ }
63
+ function checkStat(stat2, path, options) {
64
+ if (!stat2.isSymbolicLink() && !stat2.isFile()) {
65
+ return false;
66
+ }
67
+ return checkPathExt(path, options);
68
+ }
69
+ function isexe(path, options, cb) {
70
+ fs.stat(path, function(er, stat2) {
71
+ cb(er, er ? false : checkStat(stat2, path, options));
72
+ });
73
+ }
74
+ function sync(path, options) {
75
+ return checkStat(fs.statSync(path), path, options);
76
+ }
77
+ }
78
+ });
79
+
80
+ // node_modules/isexe/mode.js
81
+ var require_mode = __commonJS({
82
+ "node_modules/isexe/mode.js"(exports, module) {
83
+ module.exports = isexe;
84
+ isexe.sync = sync;
85
+ var fs = __require("fs");
86
+ function isexe(path, options, cb) {
87
+ fs.stat(path, function(er, stat2) {
88
+ cb(er, er ? false : checkStat(stat2, options));
89
+ });
90
+ }
91
+ function sync(path, options) {
92
+ return checkStat(fs.statSync(path), options);
93
+ }
94
+ function checkStat(stat2, options) {
95
+ return stat2.isFile() && checkMode(stat2, options);
96
+ }
97
+ function checkMode(stat2, options) {
98
+ var mod = stat2.mode;
99
+ var uid = stat2.uid;
100
+ var gid = stat2.gid;
101
+ var myUid = options.uid !== void 0 ? options.uid : process.getuid && process.getuid();
102
+ var myGid = options.gid !== void 0 ? options.gid : process.getgid && process.getgid();
103
+ var u = parseInt("100", 8);
104
+ var g = parseInt("010", 8);
105
+ var o = parseInt("001", 8);
106
+ var ug = u | g;
107
+ var ret = mod & o || mod & g && gid === myGid || mod & u && uid === myUid || mod & ug && myUid === 0;
108
+ return ret;
109
+ }
110
+ }
111
+ });
112
+
113
+ // node_modules/isexe/index.js
114
+ var require_isexe = __commonJS({
115
+ "node_modules/isexe/index.js"(exports, module) {
116
+ var fs = __require("fs");
117
+ var core;
118
+ if (process.platform === "win32" || global.TESTING_WINDOWS) {
119
+ core = require_windows();
120
+ } else {
121
+ core = require_mode();
122
+ }
123
+ module.exports = isexe;
124
+ isexe.sync = sync;
125
+ function isexe(path, options, cb) {
126
+ if (typeof options === "function") {
127
+ cb = options;
128
+ options = {};
129
+ }
130
+ if (!cb) {
131
+ if (typeof Promise !== "function") {
132
+ throw new TypeError("callback not provided");
133
+ }
134
+ return new Promise(function(resolve, reject) {
135
+ isexe(path, options || {}, function(er, is) {
136
+ if (er) {
137
+ reject(er);
138
+ } else {
139
+ resolve(is);
140
+ }
141
+ });
142
+ });
143
+ }
144
+ core(path, options || {}, function(er, is) {
145
+ if (er) {
146
+ if (er.code === "EACCES" || options && options.ignoreErrors) {
147
+ er = null;
148
+ is = false;
149
+ }
150
+ }
151
+ cb(er, is);
152
+ });
153
+ }
154
+ function sync(path, options) {
155
+ try {
156
+ return core.sync(path, options || {});
157
+ } catch (er) {
158
+ if (options && options.ignoreErrors || er.code === "EACCES") {
159
+ return false;
160
+ } else {
161
+ throw er;
162
+ }
163
+ }
164
+ }
165
+ }
166
+ });
167
+
168
+ // node_modules/which/which.js
169
+ var require_which = __commonJS({
170
+ "node_modules/which/which.js"(exports, module) {
171
+ var isWindows = process.platform === "win32" || process.env.OSTYPE === "cygwin" || process.env.OSTYPE === "msys";
172
+ var path = __require("path");
173
+ var COLON = isWindows ? ";" : ":";
174
+ var isexe = require_isexe();
175
+ var getNotFoundError = (cmd) => Object.assign(new Error(`not found: ${cmd}`), { code: "ENOENT" });
176
+ var getPathInfo = (cmd, opt) => {
177
+ const colon = opt.colon || COLON;
178
+ const pathEnv = cmd.match(/\//) || isWindows && cmd.match(/\\/) ? [""] : [
179
+ // windows always checks the cwd first
180
+ ...isWindows ? [process.cwd()] : [],
181
+ ...(opt.path || process.env.PATH || /* istanbul ignore next: very unusual */
182
+ "").split(colon)
183
+ ];
184
+ const pathExtExe = isWindows ? opt.pathExt || process.env.PATHEXT || ".EXE;.CMD;.BAT;.COM" : "";
185
+ const pathExt = isWindows ? pathExtExe.split(colon) : [""];
186
+ if (isWindows) {
187
+ if (cmd.indexOf(".") !== -1 && pathExt[0] !== "")
188
+ pathExt.unshift("");
189
+ }
190
+ return {
191
+ pathEnv,
192
+ pathExt,
193
+ pathExtExe
194
+ };
195
+ };
196
+ var which = (cmd, opt, cb) => {
197
+ if (typeof opt === "function") {
198
+ cb = opt;
199
+ opt = {};
200
+ }
201
+ if (!opt)
202
+ opt = {};
203
+ const { pathEnv, pathExt, pathExtExe } = getPathInfo(cmd, opt);
204
+ const found = [];
205
+ const step = (i) => new Promise((resolve, reject) => {
206
+ if (i === pathEnv.length)
207
+ return opt.all && found.length ? resolve(found) : reject(getNotFoundError(cmd));
208
+ const ppRaw = pathEnv[i];
209
+ const pathPart = /^".*"$/.test(ppRaw) ? ppRaw.slice(1, -1) : ppRaw;
210
+ const pCmd = path.join(pathPart, cmd);
211
+ const p = !pathPart && /^\.[\\\/]/.test(cmd) ? cmd.slice(0, 2) + pCmd : pCmd;
212
+ resolve(subStep(p, i, 0));
213
+ });
214
+ const subStep = (p, i, ii) => new Promise((resolve, reject) => {
215
+ if (ii === pathExt.length)
216
+ return resolve(step(i + 1));
217
+ const ext = pathExt[ii];
218
+ isexe(p + ext, { pathExt: pathExtExe }, (er, is) => {
219
+ if (!er && is) {
220
+ if (opt.all)
221
+ found.push(p + ext);
222
+ else
223
+ return resolve(p + ext);
224
+ }
225
+ return resolve(subStep(p, i, ii + 1));
226
+ });
227
+ });
228
+ return cb ? step(0).then((res) => cb(null, res), cb) : step(0);
229
+ };
230
+ var whichSync = (cmd, opt) => {
231
+ opt = opt || {};
232
+ const { pathEnv, pathExt, pathExtExe } = getPathInfo(cmd, opt);
233
+ const found = [];
234
+ for (let i = 0; i < pathEnv.length; i++) {
235
+ const ppRaw = pathEnv[i];
236
+ const pathPart = /^".*"$/.test(ppRaw) ? ppRaw.slice(1, -1) : ppRaw;
237
+ const pCmd = path.join(pathPart, cmd);
238
+ const p = !pathPart && /^\.[\\\/]/.test(cmd) ? cmd.slice(0, 2) + pCmd : pCmd;
239
+ for (let j = 0; j < pathExt.length; j++) {
240
+ const cur = p + pathExt[j];
241
+ try {
242
+ const is = isexe.sync(cur, { pathExt: pathExtExe });
243
+ if (is) {
244
+ if (opt.all)
245
+ found.push(cur);
246
+ else
247
+ return cur;
248
+ }
249
+ } catch (ex) {
250
+ }
251
+ }
252
+ }
253
+ if (opt.all && found.length)
254
+ return found;
255
+ if (opt.nothrow)
256
+ return null;
257
+ throw getNotFoundError(cmd);
258
+ };
259
+ module.exports = which;
260
+ which.sync = whichSync;
261
+ }
262
+ });
263
+
264
+ // node_modules/path-key/index.js
265
+ var require_path_key = __commonJS({
266
+ "node_modules/path-key/index.js"(exports, module) {
267
+ "use strict";
268
+ var pathKey = (options = {}) => {
269
+ const environment = options.env || process.env;
270
+ const platform2 = options.platform || process.platform;
271
+ if (platform2 !== "win32") {
272
+ return "PATH";
273
+ }
274
+ return Object.keys(environment).reverse().find((key) => key.toUpperCase() === "PATH") || "Path";
275
+ };
276
+ module.exports = pathKey;
277
+ module.exports.default = pathKey;
278
+ }
279
+ });
280
+
281
+ // node_modules/cross-spawn/lib/util/resolveCommand.js
282
+ var require_resolveCommand = __commonJS({
283
+ "node_modules/cross-spawn/lib/util/resolveCommand.js"(exports, module) {
284
+ "use strict";
285
+ var path = __require("path");
286
+ var which = require_which();
287
+ var getPathKey = require_path_key();
288
+ function resolveCommandAttempt(parsed, withoutPathExt) {
289
+ const env = parsed.options.env || process.env;
290
+ const cwd = process.cwd();
291
+ const hasCustomCwd = parsed.options.cwd != null;
292
+ const shouldSwitchCwd = hasCustomCwd && process.chdir !== void 0 && !process.chdir.disabled;
293
+ if (shouldSwitchCwd) {
294
+ try {
295
+ process.chdir(parsed.options.cwd);
296
+ } catch (err) {
297
+ }
298
+ }
299
+ let resolved;
300
+ try {
301
+ resolved = which.sync(parsed.command, {
302
+ path: env[getPathKey({ env })],
303
+ pathExt: withoutPathExt ? path.delimiter : void 0
304
+ });
305
+ } catch (e) {
306
+ } finally {
307
+ if (shouldSwitchCwd) {
308
+ process.chdir(cwd);
309
+ }
310
+ }
311
+ if (resolved) {
312
+ resolved = path.resolve(hasCustomCwd ? parsed.options.cwd : "", resolved);
313
+ }
314
+ return resolved;
315
+ }
316
+ function resolveCommand(parsed) {
317
+ return resolveCommandAttempt(parsed) || resolveCommandAttempt(parsed, true);
318
+ }
319
+ module.exports = resolveCommand;
320
+ }
321
+ });
322
+
323
+ // node_modules/cross-spawn/lib/util/escape.js
324
+ var require_escape = __commonJS({
325
+ "node_modules/cross-spawn/lib/util/escape.js"(exports, module) {
326
+ "use strict";
327
+ var metaCharsRegExp = /([()\][%!^"`<>&|;, *?])/g;
328
+ function escapeCommand(arg) {
329
+ arg = arg.replace(metaCharsRegExp, "^$1");
330
+ return arg;
331
+ }
332
+ function escapeArgument(arg, doubleEscapeMetaChars) {
333
+ arg = `${arg}`;
334
+ arg = arg.replace(/(?=(\\+?)?)\1"/g, '$1$1\\"');
335
+ arg = arg.replace(/(?=(\\+?)?)\1$/, "$1$1");
336
+ arg = `"${arg}"`;
337
+ arg = arg.replace(metaCharsRegExp, "^$1");
338
+ if (doubleEscapeMetaChars) {
339
+ arg = arg.replace(metaCharsRegExp, "^$1");
340
+ }
341
+ return arg;
342
+ }
343
+ module.exports.command = escapeCommand;
344
+ module.exports.argument = escapeArgument;
345
+ }
346
+ });
347
+
348
+ // node_modules/shebang-regex/index.js
349
+ var require_shebang_regex = __commonJS({
350
+ "node_modules/shebang-regex/index.js"(exports, module) {
351
+ "use strict";
352
+ module.exports = /^#!(.*)/;
353
+ }
354
+ });
355
+
356
+ // node_modules/shebang-command/index.js
357
+ var require_shebang_command = __commonJS({
358
+ "node_modules/shebang-command/index.js"(exports, module) {
359
+ "use strict";
360
+ var shebangRegex = require_shebang_regex();
361
+ module.exports = (string = "") => {
362
+ const match = string.match(shebangRegex);
363
+ if (!match) {
364
+ return null;
365
+ }
366
+ const [path, argument] = match[0].replace(/#! ?/, "").split(" ");
367
+ const binary = path.split("/").pop();
368
+ if (binary === "env") {
369
+ return argument;
370
+ }
371
+ return argument ? `${binary} ${argument}` : binary;
372
+ };
373
+ }
374
+ });
375
+
376
+ // node_modules/cross-spawn/lib/util/readShebang.js
377
+ var require_readShebang = __commonJS({
378
+ "node_modules/cross-spawn/lib/util/readShebang.js"(exports, module) {
379
+ "use strict";
380
+ var fs = __require("fs");
381
+ var shebangCommand = require_shebang_command();
382
+ function readShebang(command) {
383
+ const size = 150;
384
+ const buffer = Buffer.alloc(size);
385
+ let fd;
386
+ try {
387
+ fd = fs.openSync(command, "r");
388
+ fs.readSync(fd, buffer, 0, size, 0);
389
+ fs.closeSync(fd);
390
+ } catch (e) {
391
+ }
392
+ return shebangCommand(buffer.toString());
393
+ }
394
+ module.exports = readShebang;
395
+ }
396
+ });
397
+
398
+ // node_modules/cross-spawn/lib/parse.js
399
+ var require_parse = __commonJS({
400
+ "node_modules/cross-spawn/lib/parse.js"(exports, module) {
401
+ "use strict";
402
+ var path = __require("path");
403
+ var resolveCommand = require_resolveCommand();
404
+ var escape = require_escape();
405
+ var readShebang = require_readShebang();
406
+ var isWin = process.platform === "win32";
407
+ var isExecutableRegExp = /\.(?:com|exe)$/i;
408
+ var isCmdShimRegExp = /node_modules[\\/].bin[\\/][^\\/]+\.cmd$/i;
409
+ function detectShebang(parsed) {
410
+ parsed.file = resolveCommand(parsed);
411
+ const shebang = parsed.file && readShebang(parsed.file);
412
+ if (shebang) {
413
+ parsed.args.unshift(parsed.file);
414
+ parsed.command = shebang;
415
+ return resolveCommand(parsed);
416
+ }
417
+ return parsed.file;
418
+ }
419
+ function parseNonShell(parsed) {
420
+ if (!isWin) {
421
+ return parsed;
422
+ }
423
+ const commandFile = detectShebang(parsed);
424
+ const needsShell = !isExecutableRegExp.test(commandFile);
425
+ if (parsed.options.forceShell || needsShell) {
426
+ const needsDoubleEscapeMetaChars = isCmdShimRegExp.test(commandFile);
427
+ parsed.command = path.normalize(parsed.command);
428
+ parsed.command = escape.command(parsed.command);
429
+ parsed.args = parsed.args.map((arg) => escape.argument(arg, needsDoubleEscapeMetaChars));
430
+ const shellCommand = [parsed.command].concat(parsed.args).join(" ");
431
+ parsed.args = ["/d", "/s", "/c", `"${shellCommand}"`];
432
+ parsed.command = process.env.comspec || "cmd.exe";
433
+ parsed.options.windowsVerbatimArguments = true;
434
+ }
435
+ return parsed;
436
+ }
437
+ function parse(command, args2, options) {
438
+ if (args2 && !Array.isArray(args2)) {
439
+ options = args2;
440
+ args2 = null;
441
+ }
442
+ args2 = args2 ? args2.slice(0) : [];
443
+ options = Object.assign({}, options);
444
+ const parsed = {
445
+ command,
446
+ args: args2,
447
+ options,
448
+ file: void 0,
449
+ original: {
450
+ command,
451
+ args: args2
452
+ }
453
+ };
454
+ return options.shell ? parsed : parseNonShell(parsed);
455
+ }
456
+ module.exports = parse;
457
+ }
458
+ });
459
+
460
+ // node_modules/cross-spawn/lib/enoent.js
461
+ var require_enoent = __commonJS({
462
+ "node_modules/cross-spawn/lib/enoent.js"(exports, module) {
463
+ "use strict";
464
+ var isWin = process.platform === "win32";
465
+ function notFoundError(original, syscall) {
466
+ return Object.assign(new Error(`${syscall} ${original.command} ENOENT`), {
467
+ code: "ENOENT",
468
+ errno: "ENOENT",
469
+ syscall: `${syscall} ${original.command}`,
470
+ path: original.command,
471
+ spawnargs: original.args
472
+ });
473
+ }
474
+ function hookChildProcess(cp, parsed) {
475
+ if (!isWin) {
476
+ return;
477
+ }
478
+ const originalEmit = cp.emit;
479
+ cp.emit = function(name, arg1) {
480
+ if (name === "exit") {
481
+ const err = verifyENOENT(arg1, parsed);
482
+ if (err) {
483
+ return originalEmit.call(cp, "error", err);
484
+ }
485
+ }
486
+ return originalEmit.apply(cp, arguments);
487
+ };
488
+ }
489
+ function verifyENOENT(status, parsed) {
490
+ if (isWin && status === 1 && !parsed.file) {
491
+ return notFoundError(parsed.original, "spawn");
492
+ }
493
+ return null;
494
+ }
495
+ function verifyENOENTSync(status, parsed) {
496
+ if (isWin && status === 1 && !parsed.file) {
497
+ return notFoundError(parsed.original, "spawnSync");
498
+ }
499
+ return null;
500
+ }
501
+ module.exports = {
502
+ hookChildProcess,
503
+ verifyENOENT,
504
+ verifyENOENTSync,
505
+ notFoundError
506
+ };
507
+ }
508
+ });
509
+
510
+ // node_modules/cross-spawn/index.js
511
+ var require_cross_spawn = __commonJS({
512
+ "node_modules/cross-spawn/index.js"(exports, module) {
513
+ "use strict";
514
+ var cp = __require("child_process");
515
+ var parse = require_parse();
516
+ var enoent = require_enoent();
517
+ function spawn2(command, args2, options) {
518
+ const parsed = parse(command, args2, options);
519
+ const spawned = cp.spawn(parsed.command, parsed.args, parsed.options);
520
+ enoent.hookChildProcess(spawned, parsed);
521
+ return spawned;
522
+ }
523
+ function spawnSync(command, args2, options) {
524
+ const parsed = parse(command, args2, options);
525
+ const result = cp.spawnSync(parsed.command, parsed.args, parsed.options);
526
+ result.error = result.error || enoent.verifyENOENTSync(result.status, parsed);
527
+ return result;
528
+ }
529
+ module.exports = spawn2;
530
+ module.exports.spawn = spawn2;
531
+ module.exports.sync = spawnSync;
532
+ module.exports._parse = parse;
533
+ module.exports._enoent = enoent;
534
+ }
535
+ });
536
+
537
+ // node_modules/@earendil-works/pi-mcp/dist/protocol/jsonrpc.js
538
+ var JSON_RPC_ERROR_CODES = {
539
+ parseError: -32700,
540
+ invalidRequest: -32600,
541
+ methodNotFound: -32601,
542
+ invalidParams: -32602,
543
+ internalError: -32603
544
+ };
545
+ var McpError = class extends Error {
546
+ code;
547
+ data;
548
+ constructor(code2, message, data) {
549
+ super(message);
550
+ this.name = "McpError";
551
+ this.code = code2;
552
+ this.data = data;
553
+ }
554
+ };
555
+ var McpConnectionClosedError = class extends Error {
556
+ constructor(message = "MCP connection closed") {
557
+ super(message);
558
+ this.name = "McpConnectionClosedError";
559
+ }
560
+ };
561
+ var McpTimeoutError = class extends Error {
562
+ timeoutMs;
563
+ constructor(timeoutMs) {
564
+ super(`MCP request timed out after ${timeoutMs}ms`);
565
+ this.name = "McpTimeoutError";
566
+ this.timeoutMs = timeoutMs;
567
+ }
568
+ };
569
+ var McpAbortError = class extends Error {
570
+ constructor(message = "MCP request aborted") {
571
+ super(message);
572
+ this.name = "AbortError";
573
+ }
574
+ };
575
+ function isObject(value) {
576
+ return typeof value === "object" && value !== null && !Array.isArray(value);
577
+ }
578
+ function toError(value) {
579
+ return value instanceof Error ? value : new Error(String(value));
580
+ }
581
+ function isJsonRpcId(value) {
582
+ return typeof value === "string" || typeof value === "number" && Number.isFinite(value);
583
+ }
584
+ function isJsonRpcRequest(message) {
585
+ return isObject(message) && message.jsonrpc === "2.0" && isJsonRpcId(message.id) && typeof message.method === "string";
586
+ }
587
+ function isJsonRpcNotification(message) {
588
+ return isObject(message) && message.jsonrpc === "2.0" && !("id" in message) && typeof message.method === "string";
589
+ }
590
+ function isJsonRpcResponse(message) {
591
+ if (!isObject(message) || message.jsonrpc !== "2.0" || !isJsonRpcId(message.id))
592
+ return false;
593
+ if ("result" in message)
594
+ return !("error" in message);
595
+ if (!("error" in message) || !isObject(message.error))
596
+ return false;
597
+ return typeof message.error.code === "number" && typeof message.error.message === "string";
598
+ }
599
+ function parseJsonRpcMessage(value) {
600
+ if (isJsonRpcRequest(value) || isJsonRpcNotification(value) || isJsonRpcResponse(value))
601
+ return value;
602
+ throw new McpError(JSON_RPC_ERROR_CODES.invalidRequest, "Invalid JSON-RPC message");
603
+ }
604
+
605
+ // node_modules/@earendil-works/pi-mcp/dist/protocol/types.js
606
+ var LATEST_PROTOCOL_VERSION = "2025-11-25";
607
+ var SUPPORTED_PROTOCOL_VERSIONS = [LATEST_PROTOCOL_VERSION, "2025-06-18", "2025-03-26", "2024-11-05"];
608
+
609
+ // node_modules/@earendil-works/pi-mcp/dist/client.js
610
+ var DEFAULT_REQUEST_TIMEOUT_MS = 3e4;
611
+ var MAX_LIST_PAGES = 1e3;
612
+ function validateInitializeResult(value) {
613
+ if (!isObject(value) || typeof value.protocolVersion !== "string" || !isObject(value.capabilities) || !isObject(value.serverInfo) || typeof value.serverInfo.name !== "string" || typeof value.serverInfo.version !== "string" || value.instructions !== void 0 && typeof value.instructions !== "string") {
614
+ throw new McpError(JSON_RPC_ERROR_CODES.invalidRequest, "Invalid MCP initialize result");
615
+ }
616
+ return value;
617
+ }
618
+ function invalid(message) {
619
+ return new McpError(JSON_RPC_ERROR_CODES.invalidRequest, message);
620
+ }
621
+ function validateListPage(method, key, value, isItem) {
622
+ const items = isObject(value) ? value[key] : void 0;
623
+ if (!isObject(value) || !Array.isArray(items))
624
+ throw invalid(`Invalid MCP ${method} result`);
625
+ for (const item of items) {
626
+ if (!isObject(item) || !isItem(item))
627
+ throw invalid(`Invalid entry in MCP ${method} result`);
628
+ }
629
+ const nextCursor = value.nextCursor === null || value.nextCursor === "" ? void 0 : value.nextCursor;
630
+ if (nextCursor !== void 0 && typeof nextCursor !== "string")
631
+ throw invalid(`Invalid MCP ${method} cursor`);
632
+ return { items, ...nextCursor === void 0 ? {} : { nextCursor } };
633
+ }
634
+ var isTool = (tool) => typeof tool.name === "string" && isObject(tool.inputSchema);
635
+ var isResource = (resource) => typeof resource.uri === "string" && (resource.name === void 0 || typeof resource.name === "string");
636
+ var isResourceTemplate = (template) => typeof template.uriTemplate === "string" && (template.name === void 0 || typeof template.name === "string");
637
+ function toResource(item) {
638
+ return { ...item, name: item.name ?? item.uri };
639
+ }
640
+ function toResourceTemplate(item) {
641
+ return { ...item, name: item.name ?? item.uriTemplate };
642
+ }
643
+ function pageCursor(page) {
644
+ return page.nextCursor === void 0 ? {} : { nextCursor: page.nextCursor };
645
+ }
646
+ function validateReadResourceResult(value) {
647
+ if (!isObject(value) || !Array.isArray(value.contents))
648
+ throw invalid("Invalid MCP resources/read result");
649
+ for (const contents of value.contents) {
650
+ if (!isObject(contents) || typeof contents.uri !== "string" || typeof contents.text !== "string" && typeof contents.blob !== "string") {
651
+ throw invalid("Invalid contents in MCP resources/read result");
652
+ }
653
+ }
654
+ return value;
655
+ }
656
+ function validateCallToolResult(value) {
657
+ if (!isObject(value) || value.content !== void 0 && !Array.isArray(value.content)) {
658
+ throw new McpError(JSON_RPC_ERROR_CODES.invalidRequest, "Invalid MCP tools/call result");
659
+ }
660
+ return value.content === void 0 ? { ...value, content: [] } : value;
661
+ }
662
+ var McpClient = class {
663
+ options;
664
+ state = "idle";
665
+ transport;
666
+ nextRequestId = 1;
667
+ serverInfoValue;
668
+ serverCapabilitiesValue;
669
+ instructionsValue;
670
+ protocolVersionValue;
671
+ pending = /* @__PURE__ */ new Map();
672
+ progressRequests = /* @__PURE__ */ new Map();
673
+ incoming = /* @__PURE__ */ new Map();
674
+ requestHandlers = /* @__PURE__ */ new Map();
675
+ notificationListeners = /* @__PURE__ */ new Map();
676
+ errorListeners = /* @__PURE__ */ new Set();
677
+ closeListeners = /* @__PURE__ */ new Set();
678
+ disposers = [];
679
+ constructor(options) {
680
+ this.options = Object.freeze({ ...options });
681
+ this.requestHandlers.set("ping", () => ({}));
682
+ const roots = options.roots;
683
+ if (roots) {
684
+ this.requestHandlers.set("roots/list", async () => ({
685
+ roots: [...typeof roots === "function" ? await roots() : roots]
686
+ }));
687
+ }
688
+ }
689
+ get connectionState() {
690
+ return this.state;
691
+ }
692
+ get serverInfo() {
693
+ return this.serverInfoValue;
694
+ }
695
+ get serverCapabilities() {
696
+ return this.serverCapabilitiesValue;
697
+ }
698
+ get instructions() {
699
+ return this.instructionsValue;
700
+ }
701
+ get protocolVersion() {
702
+ return this.protocolVersionValue;
703
+ }
704
+ async connect(transport) {
705
+ if (this.state !== "idle")
706
+ throw new Error(`Cannot connect MCP client in ${this.state} state`);
707
+ this.state = "connecting";
708
+ this.transport = transport;
709
+ this.disposers = [
710
+ transport.onMessage((message) => this.handleMessage(message)),
711
+ // Transport errors are reported only. Pending requests fail when the transport closes.
712
+ transport.onError((error) => this.emitError(error)),
713
+ transport.onClose(() => this.handleTransportClose())
714
+ ];
715
+ try {
716
+ await transport.start();
717
+ const capabilities = { ...this.options.capabilities };
718
+ if (this.options.roots && capabilities.roots === void 0)
719
+ capabilities.roots = {};
720
+ const result = validateInitializeResult(await this.requestInternal("initialize", {
721
+ protocolVersion: this.options.protocolVersion ?? LATEST_PROTOCOL_VERSION,
722
+ capabilities,
723
+ clientInfo: {
724
+ name: this.options.name,
725
+ version: this.options.version,
726
+ ...this.options.title === void 0 ? {} : { title: this.options.title }
727
+ }
728
+ }, {}, true));
729
+ if (!SUPPORTED_PROTOCOL_VERSIONS.includes(result.protocolVersion)) {
730
+ throw new Error(`MCP server selected unsupported protocol version ${result.protocolVersion}`);
731
+ }
732
+ this.protocolVersionValue = result.protocolVersion;
733
+ this.serverInfoValue = result.serverInfo;
734
+ this.serverCapabilitiesValue = result.capabilities;
735
+ this.instructionsValue = result.instructions;
736
+ transport.setProtocolVersion?.(result.protocolVersion);
737
+ await this.notifyInternal("notifications/initialized", void 0, true);
738
+ this.state = "connected";
739
+ return result;
740
+ } catch (error) {
741
+ await this.close().catch(() => {
742
+ });
743
+ throw error;
744
+ }
745
+ }
746
+ request(method, params, options = {}) {
747
+ return this.requestInternal(method, params, options, false);
748
+ }
749
+ notify(method, params) {
750
+ return this.notifyInternal(method, params, false);
751
+ }
752
+ setRequestHandler(method, handler) {
753
+ this.requestHandlers.set(method, handler);
754
+ return () => {
755
+ if (this.requestHandlers.get(method) === handler)
756
+ this.requestHandlers.delete(method);
757
+ };
758
+ }
759
+ onNotification(method, listener) {
760
+ const listeners = this.notificationListeners.get(method) ?? /* @__PURE__ */ new Set();
761
+ this.notificationListeners.set(method, listeners);
762
+ listeners.add(listener);
763
+ return () => {
764
+ listeners.delete(listener);
765
+ if (listeners.size === 0)
766
+ this.notificationListeners.delete(method);
767
+ };
768
+ }
769
+ onError(listener) {
770
+ this.errorListeners.add(listener);
771
+ return () => this.errorListeners.delete(listener);
772
+ }
773
+ /** Called once when the connection closes, whether the transport dropped or `close()` was called. */
774
+ onClose(listener) {
775
+ this.closeListeners.add(listener);
776
+ return () => this.closeListeners.delete(listener);
777
+ }
778
+ async ping(options = {}) {
779
+ await this.request("ping", void 0, options);
780
+ }
781
+ async listTools(options = {}) {
782
+ return await this.listAll("tools/list", "tools", isTool, options);
783
+ }
784
+ /** Every resource, following `nextCursor` through all pages. */
785
+ async listResources(options = {}) {
786
+ return (await this.listAll("resources/list", "resources", isResource, options)).map(toResource);
787
+ }
788
+ /** One page of resources, starting at `cursor`. */
789
+ async listResourcesPage(cursor, options = {}) {
790
+ const page = await this.listPage("resources/list", "resources", isResource, cursor, options);
791
+ return { resources: page.items.map(toResource), ...pageCursor(page) };
792
+ }
793
+ /** Every resource template, following `nextCursor` through all pages. */
794
+ async listResourceTemplates(options = {}) {
795
+ const templates = await this.listAll("resources/templates/list", "resourceTemplates", isResourceTemplate, options);
796
+ return templates.map(toResourceTemplate);
797
+ }
798
+ /** One page of resource templates, starting at `cursor`. */
799
+ async listResourceTemplatesPage(cursor, options = {}) {
800
+ const page = await this.listPage("resources/templates/list", "resourceTemplates", isResourceTemplate, cursor, options);
801
+ return { resourceTemplates: page.items.map(toResourceTemplate), ...pageCursor(page) };
802
+ }
803
+ async readResource(uri, options = {}) {
804
+ return validateReadResourceResult(await this.request("resources/read", { uri }, options));
805
+ }
806
+ async listPage(method, key, isItem, cursor, options) {
807
+ return validateListPage(method, key, await this.request(method, cursor === void 0 ? void 0 : { cursor }, options), isItem);
808
+ }
809
+ /** Every item of a paginated list method. */
810
+ async listAll(method, key, isItem, options) {
811
+ const items = [];
812
+ const cursors = /* @__PURE__ */ new Set();
813
+ let cursor;
814
+ for (let pageNumber = 0; pageNumber < MAX_LIST_PAGES; pageNumber++) {
815
+ const page = await this.listPage(method, key, isItem, cursor, options);
816
+ items.push(...page.items);
817
+ if (page.nextCursor === void 0)
818
+ return items;
819
+ if (cursors.has(page.nextCursor))
820
+ throw new Error(`MCP ${method} returned duplicate cursor: ${page.nextCursor}`);
821
+ cursors.add(page.nextCursor);
822
+ cursor = page.nextCursor;
823
+ }
824
+ throw new Error(`MCP ${method} exceeded ${MAX_LIST_PAGES} pages`);
825
+ }
826
+ async callTool(name, args2, options = {}) {
827
+ return validateCallToolResult(await this.request("tools/call", { name, ...args2 === void 0 ? {} : { arguments: args2 } }, options));
828
+ }
829
+ async close() {
830
+ const transport = this.transport;
831
+ this.transport = void 0;
832
+ this.disposeTransportListeners();
833
+ this.markClosed(new McpConnectionClosedError());
834
+ await transport?.close();
835
+ }
836
+ async requestInternal(method, params, options, allowConnecting) {
837
+ const transport = this.requireTransport(allowConnecting);
838
+ if (options.signal?.aborted)
839
+ throw new McpAbortError();
840
+ const id = this.nextRequestId++;
841
+ const progressToken = options.onProgress ? id : void 0;
842
+ const requestParams = progressToken === void 0 ? params : { ...params, _meta: { ...isObject(params?._meta) ? params._meta : {}, progressToken } };
843
+ const message = {
844
+ jsonrpc: "2.0",
845
+ id,
846
+ method,
847
+ ...requestParams === void 0 ? {} : { params: requestParams }
848
+ };
849
+ return new Promise((resolve, reject) => {
850
+ const entry = {
851
+ resolve,
852
+ reject,
853
+ timeoutMs: options.timeoutMs ?? this.options.requestTimeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS,
854
+ timer: void 0,
855
+ signal: options.signal,
856
+ // The spec forbids cancelling `initialize`.
857
+ onAbort: () => this.cancelPending(id, new McpAbortError(), method !== "initialize", String(options.signal?.reason ?? "Aborted")),
858
+ sendController: new AbortController(),
859
+ cancellable: method !== "initialize",
860
+ onProgress: options.onProgress,
861
+ progressToken
862
+ };
863
+ this.pending.set(id, entry);
864
+ if (progressToken !== void 0)
865
+ this.progressRequests.set(progressToken, id);
866
+ options.signal?.addEventListener("abort", entry.onAbort, { once: true });
867
+ this.armTimeout(id, entry);
868
+ transport.send(message, { signal: entry.sendController.signal }).catch((error) => this.cancelPending(id, error, false));
869
+ });
870
+ }
871
+ async notifyInternal(method, params, allowConnecting) {
872
+ await this.requireTransport(allowConnecting).send({
873
+ jsonrpc: "2.0",
874
+ method,
875
+ ...params === void 0 ? {} : { params }
876
+ });
877
+ }
878
+ requireTransport(allowConnecting) {
879
+ if (this.transport && (this.state === "connected" || allowConnecting && this.state === "connecting")) {
880
+ return this.transport;
881
+ }
882
+ throw new McpConnectionClosedError(`MCP client is ${this.state}`);
883
+ }
884
+ handleMessage(message) {
885
+ if (isJsonRpcResponse(message)) {
886
+ this.handleResponse(message);
887
+ return;
888
+ }
889
+ if (isJsonRpcRequest(message)) {
890
+ void this.handleRequest(message);
891
+ return;
892
+ }
893
+ if (isJsonRpcNotification(message)) {
894
+ this.handleNotification(message.method, message.params);
895
+ return;
896
+ }
897
+ this.emitError(new McpError(JSON_RPC_ERROR_CODES.invalidRequest, "Received invalid JSON-RPC message"));
898
+ }
899
+ handleResponse(message) {
900
+ const entry = this.pending.get(message.id);
901
+ if (!entry) {
902
+ this.emitError(new Error(`Received response for unknown MCP request ${String(message.id)}`));
903
+ return;
904
+ }
905
+ this.removePending(message.id, entry);
906
+ if ("error" in message)
907
+ entry.reject(new McpError(message.error.code, message.error.message, message.error.data));
908
+ else
909
+ entry.resolve(message.result);
910
+ }
911
+ async handleRequest(message) {
912
+ const transport = this.transport;
913
+ if (!transport)
914
+ return;
915
+ const handler = this.requestHandlers.get(message.method);
916
+ if (!handler) {
917
+ await transport.send({
918
+ jsonrpc: "2.0",
919
+ id: message.id,
920
+ error: { code: JSON_RPC_ERROR_CODES.methodNotFound, message: `Method not found: ${message.method}` }
921
+ }).catch((error) => this.emitError(error));
922
+ return;
923
+ }
924
+ const controller = new AbortController();
925
+ this.incoming.set(message.id, controller);
926
+ try {
927
+ const result = await handler(message.params, { signal: controller.signal });
928
+ await transport.send({ jsonrpc: "2.0", id: message.id, result: result ?? {} });
929
+ } catch (error) {
930
+ const responseError = error instanceof McpError ? { code: error.code, message: error.message, data: error.data } : { code: JSON_RPC_ERROR_CODES.internalError, message: toError(error).message };
931
+ await transport.send({ jsonrpc: "2.0", id: message.id, error: responseError }).catch((sendError) => this.emitError(sendError));
932
+ } finally {
933
+ this.incoming.delete(message.id);
934
+ }
935
+ }
936
+ handleNotification(method, params) {
937
+ if (method === "notifications/progress")
938
+ this.handleProgress(params);
939
+ else if (method === "notifications/cancelled")
940
+ this.handleCancelled(params);
941
+ for (const listener of this.notificationListeners.get(method) ?? []) {
942
+ try {
943
+ listener(params);
944
+ } catch (error) {
945
+ this.emitError(error);
946
+ }
947
+ }
948
+ }
949
+ handleProgress(params) {
950
+ if (!isObject(params) || !isJsonRpcId(params.progressToken) || typeof params.progress !== "number")
951
+ return;
952
+ const requestId = this.progressRequests.get(params.progressToken);
953
+ const entry = requestId === void 0 ? void 0 : this.pending.get(requestId);
954
+ if (requestId === void 0 || !entry)
955
+ return;
956
+ this.armTimeout(requestId, entry);
957
+ try {
958
+ entry.onProgress?.(params);
959
+ } catch (error) {
960
+ this.emitError(error);
961
+ }
962
+ }
963
+ handleCancelled(params) {
964
+ if (isObject(params) && isJsonRpcId(params.requestId))
965
+ this.incoming.get(params.requestId)?.abort(params.reason);
966
+ }
967
+ armTimeout(id, entry) {
968
+ if (entry.timer)
969
+ clearTimeout(entry.timer);
970
+ if (!Number.isFinite(entry.timeoutMs) || entry.timeoutMs <= 0)
971
+ return;
972
+ entry.timer = setTimeout(() => {
973
+ this.cancelPending(id, new McpTimeoutError(entry.timeoutMs), entry.cancellable, "Request timed out");
974
+ }, entry.timeoutMs);
975
+ }
976
+ cancelPending(id, error, notifyServer, reason) {
977
+ const entry = this.pending.get(id);
978
+ if (!entry)
979
+ return;
980
+ this.removePending(id, entry);
981
+ entry.reject(error);
982
+ if (notifyServer && this.transport) {
983
+ void this.transport.send({
984
+ jsonrpc: "2.0",
985
+ method: "notifications/cancelled",
986
+ params: { requestId: id, ...reason ? { reason } : {} }
987
+ }).catch((sendError) => this.emitError(sendError));
988
+ }
989
+ }
990
+ removePending(id, entry) {
991
+ this.pending.delete(id);
992
+ if (entry.timer)
993
+ clearTimeout(entry.timer);
994
+ if (entry.progressToken !== void 0)
995
+ this.progressRequests.delete(entry.progressToken);
996
+ entry.signal?.removeEventListener("abort", entry.onAbort);
997
+ entry.sendController.abort();
998
+ }
999
+ rejectPending(error) {
1000
+ for (const [id, entry] of this.pending) {
1001
+ this.removePending(id, entry);
1002
+ entry.reject(error);
1003
+ }
1004
+ }
1005
+ handleTransportClose() {
1006
+ this.markClosed(new McpConnectionClosedError());
1007
+ }
1008
+ /** Idempotent: rejects in-flight requests, aborts server requests we are serving, and flips the state. */
1009
+ markClosed(error) {
1010
+ const wasClosed = this.state === "closed";
1011
+ this.state = "closed";
1012
+ this.rejectPending(error);
1013
+ for (const controller of this.incoming.values())
1014
+ controller.abort(error);
1015
+ this.incoming.clear();
1016
+ if (wasClosed)
1017
+ return;
1018
+ for (const listener of [...this.closeListeners]) {
1019
+ try {
1020
+ listener();
1021
+ } catch (listenerError) {
1022
+ this.emitError(listenerError);
1023
+ }
1024
+ }
1025
+ }
1026
+ emitError(error) {
1027
+ const normalized = toError(error);
1028
+ for (const listener of this.errorListeners)
1029
+ listener(normalized);
1030
+ }
1031
+ disposeTransportListeners() {
1032
+ for (const dispose of this.disposers.splice(0))
1033
+ dispose();
1034
+ }
1035
+ };
1036
+
1037
+ // node_modules/@earendil-works/pi-mcp/dist/transports/stdio.js
1038
+ var import_cross_spawn = __toESM(require_cross_spawn(), 1);
1039
+ import { spawn } from "node:child_process";
1040
+ import process2 from "node:process";
1041
+
1042
+ // node_modules/@earendil-works/pi-mcp/dist/transports/transport.js
1043
+ var DEFAULT_MAX_MESSAGE_BYTES = 16 * 1024 * 1024;
1044
+ var TransportEvents = class {
1045
+ messageListeners = /* @__PURE__ */ new Set();
1046
+ errorListeners = /* @__PURE__ */ new Set();
1047
+ closeListeners = /* @__PURE__ */ new Set();
1048
+ closeEmitted = false;
1049
+ onMessage(listener) {
1050
+ this.messageListeners.add(listener);
1051
+ return () => this.messageListeners.delete(listener);
1052
+ }
1053
+ onError(listener) {
1054
+ this.errorListeners.add(listener);
1055
+ return () => this.errorListeners.delete(listener);
1056
+ }
1057
+ onClose(listener) {
1058
+ this.closeListeners.add(listener);
1059
+ return () => this.closeListeners.delete(listener);
1060
+ }
1061
+ emitMessage(message) {
1062
+ for (const listener of this.messageListeners)
1063
+ listener(message);
1064
+ }
1065
+ emitError(error) {
1066
+ const normalized = toError(error);
1067
+ for (const listener of this.errorListeners)
1068
+ listener(normalized);
1069
+ }
1070
+ emitClose() {
1071
+ if (this.closeEmitted)
1072
+ return;
1073
+ this.closeEmitted = true;
1074
+ for (const listener of this.closeListeners)
1075
+ listener();
1076
+ }
1077
+ };
1078
+
1079
+ // node_modules/@earendil-works/pi-mcp/dist/transports/stdio.js
1080
+ var DEFAULT_MAX_STDERR_BYTES = 64 * 1024;
1081
+ var DEFAULT_CLOSE_TIMEOUT_MS = 2e3;
1082
+ var STDIN_CLOSE_GRACE_MS = 500;
1083
+ var USE_PROCESS_GROUPS = process2.platform !== "win32";
1084
+ var liveProcessGroups = /* @__PURE__ */ new Set();
1085
+ var exitHookInstalled = false;
1086
+ function killProcessTree(child, signal) {
1087
+ const pid = child.pid;
1088
+ if (process2.platform === "win32" && pid !== void 0) {
1089
+ if (child.exitCode !== null)
1090
+ return;
1091
+ spawn("taskkill", ["/pid", String(pid), "/T", "/F"], { stdio: "ignore", windowsHide: true }).on("error", () => {
1092
+ });
1093
+ return;
1094
+ }
1095
+ if (USE_PROCESS_GROUPS && pid !== void 0) {
1096
+ try {
1097
+ process2.kill(-pid, signal);
1098
+ return;
1099
+ } catch {
1100
+ }
1101
+ }
1102
+ try {
1103
+ child.kill(signal);
1104
+ } catch {
1105
+ }
1106
+ }
1107
+ function installExitHook() {
1108
+ if (exitHookInstalled)
1109
+ return;
1110
+ exitHookInstalled = true;
1111
+ process2.once("exit", () => {
1112
+ for (const pid of liveProcessGroups) {
1113
+ try {
1114
+ process2.kill(-pid, "SIGTERM");
1115
+ } catch {
1116
+ }
1117
+ }
1118
+ });
1119
+ }
1120
+ var StdioTransport = class extends TransportEvents {
1121
+ options;
1122
+ child;
1123
+ stdoutBuffer = Buffer.alloc(0);
1124
+ stderrBuffer = Buffer.alloc(0);
1125
+ started = false;
1126
+ closed = false;
1127
+ constructor(options) {
1128
+ super();
1129
+ this.options = Object.freeze({ ...options, args: options.args ? [...options.args] : void 0 });
1130
+ }
1131
+ get pid() {
1132
+ return this.child?.pid;
1133
+ }
1134
+ get stderr() {
1135
+ return this.stderrBuffer.toString("utf8");
1136
+ }
1137
+ async start() {
1138
+ if (this.started)
1139
+ throw new Error("MCP stdio transport already started");
1140
+ if (this.closed)
1141
+ throw new McpConnectionClosedError();
1142
+ this.started = true;
1143
+ const env = this.options.inheritEnv === false ? { ...this.options.env } : { ...process2.env, ...this.options.env };
1144
+ const child = (0, import_cross_spawn.default)(this.options.command, this.options.args ?? [], {
1145
+ cwd: this.options.cwd,
1146
+ env,
1147
+ stdio: ["pipe", "pipe", this.options.stderr === "inherit" ? "inherit" : "pipe"],
1148
+ windowsHide: true,
1149
+ // Own process group, so closing the transport can terminate the server's children too.
1150
+ detached: USE_PROCESS_GROUPS
1151
+ });
1152
+ this.child = child;
1153
+ const pid = child.pid;
1154
+ if (USE_PROCESS_GROUPS && pid !== void 0) {
1155
+ installExitHook();
1156
+ liveProcessGroups.add(pid);
1157
+ child.once("exit", () => liveProcessGroups.delete(pid));
1158
+ }
1159
+ child.stdout?.on("data", (chunk) => this.handleStdout(chunk));
1160
+ child.stdout?.on("error", (error) => this.emitError(error));
1161
+ child.stdin?.on("error", (error) => {
1162
+ if (!this.closed)
1163
+ this.emitError(error);
1164
+ });
1165
+ child.stderr?.on("data", (chunk) => this.handleStderr(chunk));
1166
+ child.stderr?.on("error", (error) => this.emitError(error));
1167
+ child.on("close", () => {
1168
+ this.child = void 0;
1169
+ if (this.stdoutBuffer.toString("utf8").trim()) {
1170
+ this.emitError(new Error("MCP stdio server closed with an incomplete JSON-RPC message"));
1171
+ }
1172
+ this.stdoutBuffer = Buffer.alloc(0);
1173
+ this.emitClose();
1174
+ });
1175
+ await new Promise((resolve, reject) => {
1176
+ const onSpawn = () => {
1177
+ child.off("error", onError);
1178
+ resolve();
1179
+ };
1180
+ const onError = (error) => {
1181
+ child.off("spawn", onSpawn);
1182
+ reject(error);
1183
+ };
1184
+ child.once("spawn", onSpawn);
1185
+ child.once("error", onError);
1186
+ });
1187
+ child.on("error", (error) => {
1188
+ if (!this.closed)
1189
+ this.emitError(error);
1190
+ });
1191
+ }
1192
+ async send(message) {
1193
+ const stdin = this.child?.stdin;
1194
+ if (!this.started || this.closed || !stdin?.writable)
1195
+ throw new McpConnectionClosedError();
1196
+ const payload = `${JSON.stringify(message)}
1197
+ `;
1198
+ await new Promise((resolve, reject) => {
1199
+ stdin.write(payload, (error) => error ? reject(error) : resolve());
1200
+ });
1201
+ }
1202
+ async close() {
1203
+ if (this.closed)
1204
+ return;
1205
+ this.closed = true;
1206
+ const child = this.child;
1207
+ if (!child) {
1208
+ this.emitClose();
1209
+ return;
1210
+ }
1211
+ if (child.exitCode !== null || child.signalCode !== null) {
1212
+ child.stdin?.end();
1213
+ return;
1214
+ }
1215
+ const closeTimeoutMs = this.options.closeTimeoutMs ?? DEFAULT_CLOSE_TIMEOUT_MS;
1216
+ await new Promise((resolve) => {
1217
+ const timers = [];
1218
+ child.once("close", () => {
1219
+ for (const timer of timers)
1220
+ clearTimeout(timer);
1221
+ killProcessTree(child, "SIGTERM");
1222
+ resolve();
1223
+ });
1224
+ const grace = Math.min(STDIN_CLOSE_GRACE_MS, closeTimeoutMs);
1225
+ timers.push(setTimeout(() => killProcessTree(child, "SIGTERM"), grace));
1226
+ timers.push(setTimeout(() => killProcessTree(child, "SIGKILL"), grace + closeTimeoutMs));
1227
+ child.stdin?.end();
1228
+ });
1229
+ }
1230
+ handleStdout(chunk) {
1231
+ this.stdoutBuffer = Buffer.concat([this.stdoutBuffer, Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk)]);
1232
+ const maxMessageBytes = this.options.maxMessageBytes ?? DEFAULT_MAX_MESSAGE_BYTES;
1233
+ while (true) {
1234
+ const newline = this.stdoutBuffer.indexOf(10);
1235
+ if (newline < 0) {
1236
+ if (this.stdoutBuffer.length > maxMessageBytes) {
1237
+ this.stdoutBuffer = Buffer.alloc(0);
1238
+ this.emitError(new Error(`MCP stdio message exceeds ${maxMessageBytes} bytes`));
1239
+ }
1240
+ return;
1241
+ }
1242
+ const line = this.stdoutBuffer.subarray(0, newline);
1243
+ this.stdoutBuffer = this.stdoutBuffer.subarray(newline + 1);
1244
+ if (line.length > maxMessageBytes) {
1245
+ this.emitError(new Error(`MCP stdio message exceeds ${maxMessageBytes} bytes`));
1246
+ continue;
1247
+ }
1248
+ const text = line.toString("utf8").replace(/\r$/, "");
1249
+ if (!text.trim())
1250
+ continue;
1251
+ try {
1252
+ this.emitMessage(parseJsonRpcMessage(JSON.parse(text)));
1253
+ } catch (error) {
1254
+ this.emitError(error);
1255
+ }
1256
+ }
1257
+ }
1258
+ handleStderr(chunk) {
1259
+ const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
1260
+ const maxStderrBytes = this.options.maxStderrBytes ?? DEFAULT_MAX_STDERR_BYTES;
1261
+ this.stderrBuffer = Buffer.concat([this.stderrBuffer, buffer]);
1262
+ if (this.stderrBuffer.length > maxStderrBytes)
1263
+ this.stderrBuffer = this.stderrBuffer.subarray(-maxStderrBytes);
1264
+ this.options.onStderr?.(buffer.toString("utf8"));
1265
+ }
1266
+ };
1267
+
1268
+ // node_modules/@earendil-works/pi-mcp/dist/transports/streamable-http.js
1269
+ var MAX_ERROR_BODY_BYTES = 8 * 1024;
1270
+ var ERROR_MESSAGE_BODY_CHARS = 500;
1271
+ var DEFAULT_RECONNECT_INITIAL_DELAY_MS = 1e3;
1272
+ var DEFAULT_RECONNECT_MAX_DELAY_MS = 3e4;
1273
+ var DEFAULT_RECONNECT_MAX_RETRIES = 5;
1274
+ async function consumeSseStream(stream, options) {
1275
+ const reader = stream.getReader();
1276
+ const decoder = new TextDecoder();
1277
+ let buffered = "";
1278
+ let eventName;
1279
+ let eventId;
1280
+ let dataLines = [];
1281
+ let dataBytes = 0;
1282
+ const maxEventBytes = options.maxEventBytes ?? DEFAULT_MAX_MESSAGE_BYTES;
1283
+ const dispatch = () => {
1284
+ if (dataLines.length === 0) {
1285
+ eventName = void 0;
1286
+ eventId = void 0;
1287
+ return;
1288
+ }
1289
+ const data = dataLines.join("\n");
1290
+ options.onEvent({ ...eventName ? { event: eventName } : {}, data, ...eventId ? { id: eventId } : {} });
1291
+ eventName = void 0;
1292
+ eventId = void 0;
1293
+ dataLines = [];
1294
+ dataBytes = 0;
1295
+ };
1296
+ const processLine = (rawLine) => {
1297
+ const line = rawLine.endsWith("\r") ? rawLine.slice(0, -1) : rawLine;
1298
+ if (line === "") {
1299
+ dispatch();
1300
+ return;
1301
+ }
1302
+ if (line.startsWith(":"))
1303
+ return;
1304
+ const colon = line.indexOf(":");
1305
+ const field = colon < 0 ? line : line.slice(0, colon);
1306
+ let value = colon < 0 ? "" : line.slice(colon + 1);
1307
+ if (value.startsWith(" "))
1308
+ value = value.slice(1);
1309
+ if (field === "data") {
1310
+ dataBytes += Buffer.byteLength(value) + (dataLines.length > 0 ? 1 : 0);
1311
+ if (dataBytes > maxEventBytes)
1312
+ throw new Error(`MCP SSE event exceeds ${maxEventBytes} bytes`);
1313
+ dataLines.push(value);
1314
+ } else if (field === "event")
1315
+ eventName = value;
1316
+ else if (field === "id" && !value.includes("\0")) {
1317
+ eventId = value;
1318
+ options.onId?.(value);
1319
+ } else if (field === "retry" && /^\d+$/.test(value))
1320
+ options.onRetry?.(Number(value));
1321
+ };
1322
+ try {
1323
+ while (true) {
1324
+ const { done, value } = await reader.read();
1325
+ if (done)
1326
+ break;
1327
+ buffered += decoder.decode(value, { stream: true });
1328
+ let newline = buffered.indexOf("\n");
1329
+ while (newline >= 0) {
1330
+ processLine(buffered.slice(0, newline));
1331
+ buffered = buffered.slice(newline + 1);
1332
+ newline = buffered.indexOf("\n");
1333
+ }
1334
+ if (Buffer.byteLength(buffered) > maxEventBytes)
1335
+ throw new Error(`MCP SSE event exceeds ${maxEventBytes} bytes`);
1336
+ }
1337
+ buffered += decoder.decode();
1338
+ if (buffered)
1339
+ processLine(buffered);
1340
+ dispatch();
1341
+ } finally {
1342
+ reader.releaseLock();
1343
+ }
1344
+ }
1345
+ var McpHttpError = class extends Error {
1346
+ status;
1347
+ body;
1348
+ constructor(status, message, body = "") {
1349
+ super(message);
1350
+ this.name = "McpHttpError";
1351
+ this.status = status;
1352
+ this.body = body;
1353
+ }
1354
+ };
1355
+ var McpAuthRequiredError = class extends McpHttpError {
1356
+ wwwAuthenticate;
1357
+ constructor(response, body = "") {
1358
+ super(401, "MCP server requires authentication", body);
1359
+ this.name = "McpAuthRequiredError";
1360
+ this.wwwAuthenticate = response.headers.get("www-authenticate");
1361
+ }
1362
+ };
1363
+ var McpSessionExpiredError = class extends McpHttpError {
1364
+ constructor(body = "") {
1365
+ super(404, "MCP session expired", body);
1366
+ this.name = "McpSessionExpiredError";
1367
+ }
1368
+ };
1369
+ function contentType(response) {
1370
+ return response.headers.get("content-type")?.split(";", 1)[0]?.trim().toLowerCase();
1371
+ }
1372
+ function needsAuthorization(response) {
1373
+ if (response.status === 401)
1374
+ return true;
1375
+ if (response.status !== 403)
1376
+ return false;
1377
+ return /(?:^|[\s,])error="?insufficient_scope"?/i.test(response.headers.get("www-authenticate") ?? "");
1378
+ }
1379
+ function isTransientStatus(status) {
1380
+ return status === 408 || status === 429 || status >= 500;
1381
+ }
1382
+ function discard(response) {
1383
+ return response.body?.cancel().catch(() => {
1384
+ }) ?? Promise.resolve();
1385
+ }
1386
+ function describeHttpFailure(status, body) {
1387
+ const text = body.trim();
1388
+ const snippet = text.length > ERROR_MESSAGE_BODY_CHARS ? `${text.slice(0, ERROR_MESSAGE_BODY_CHARS - 3)}...` : text;
1389
+ return `MCP HTTP request failed with status ${status}${snippet ? `: ${snippet}` : ""}`;
1390
+ }
1391
+ var StreamableHttpTransport = class extends TransportEvents {
1392
+ url;
1393
+ options;
1394
+ fetch;
1395
+ controller = new AbortController();
1396
+ started = false;
1397
+ closed = false;
1398
+ sessionIdValue;
1399
+ protocolVersion;
1400
+ getStreamStarted = false;
1401
+ /** Access token of the latest request, which `close()` reuses instead of asking the auth provider. */
1402
+ lastToken;
1403
+ constructor(options) {
1404
+ super();
1405
+ this.options = Object.freeze({ ...options, headers: options.headers ? { ...options.headers } : void 0 });
1406
+ this.url = new URL(options.url);
1407
+ const fetch = options.fetch ?? globalThis.fetch;
1408
+ this.fetch = (input, init) => fetch(input, init);
1409
+ }
1410
+ get sessionId() {
1411
+ return this.sessionIdValue;
1412
+ }
1413
+ async start() {
1414
+ if (this.started)
1415
+ throw new Error("MCP Streamable HTTP transport already started");
1416
+ if (this.closed)
1417
+ throw new McpConnectionClosedError();
1418
+ this.started = true;
1419
+ }
1420
+ setProtocolVersion(version) {
1421
+ this.protocolVersion = version;
1422
+ }
1423
+ async send(message, { signal } = {}) {
1424
+ if (!this.started || this.closed)
1425
+ throw new McpConnectionClosedError();
1426
+ const response = await this.authorizedFetch("POST", {
1427
+ headers: { accept: "application/json, text/event-stream", "content-type": "application/json" },
1428
+ body: JSON.stringify(message)
1429
+ }, signal);
1430
+ await this.checkResponse(response);
1431
+ this.captureSession(response);
1432
+ if (!isJsonRpcRequest(message)) {
1433
+ await discard(response);
1434
+ if ("method" in message && message.method === "notifications/initialized")
1435
+ this.startGetStream();
1436
+ return;
1437
+ }
1438
+ if (response.status === 202 || response.status === 204) {
1439
+ throw new McpHttpError(response.status, `MCP server accepted request ${message.method} without a response`);
1440
+ }
1441
+ const type = contentType(response);
1442
+ if (type === "application/json") {
1443
+ const body = await response.json();
1444
+ for (const item of Array.isArray(body) ? body : [body])
1445
+ this.emitMessage(parseJsonRpcMessage(item));
1446
+ return;
1447
+ }
1448
+ if (type === "text/event-stream" && response.body) {
1449
+ void this.consumeResponseStream(response.body, message.id);
1450
+ return;
1451
+ }
1452
+ await discard(response);
1453
+ throw new McpHttpError(response.status, `Unsupported MCP response content type: ${type ?? "missing"}`);
1454
+ }
1455
+ async close() {
1456
+ if (this.closed)
1457
+ return;
1458
+ this.closed = true;
1459
+ this.controller.abort();
1460
+ if (this.started && this.sessionIdValue) {
1461
+ try {
1462
+ const response = await this.fetch(this.url, {
1463
+ method: "DELETE",
1464
+ headers: this.buildHeaders({}, this.lastToken),
1465
+ signal: AbortSignal.timeout(1e3)
1466
+ });
1467
+ await discard(response);
1468
+ } catch {
1469
+ }
1470
+ }
1471
+ this.emitClose();
1472
+ }
1473
+ /**
1474
+ * Fetch with auth headers. A 401 (or a 403 asking for more scope) is handed to the auth provider
1475
+ * once, and the request is retried with whatever credentials it left behind.
1476
+ *
1477
+ * `signal` only stops dispatch: getting a token or the authorization recovery can take a while, and a
1478
+ * request its sender gave up on in the meantime must not reach the server. It does not abort the shared
1479
+ * recovery, which other requests may wait for.
1480
+ */
1481
+ async authorizedFetch(method, init, signal) {
1482
+ const onUnauthorized = this.options.authProvider?.onUnauthorized?.bind(this.options.authProvider);
1483
+ for (let attempt = 0; ; attempt++) {
1484
+ const { headers, token } = await this.headers(init.headers);
1485
+ if (signal?.aborted)
1486
+ throw new McpAbortError();
1487
+ const response = await this.fetch(this.url, {
1488
+ method,
1489
+ headers,
1490
+ body: init.body,
1491
+ signal: this.controller.signal
1492
+ });
1493
+ if (attempt > 0 || !onUnauthorized || !needsAuthorization(response))
1494
+ return response;
1495
+ try {
1496
+ await onUnauthorized({ response, serverUrl: this.url, fetch: this.fetch, token });
1497
+ } finally {
1498
+ await discard(response);
1499
+ }
1500
+ }
1501
+ }
1502
+ async headers(extra = {}) {
1503
+ const token = await this.options.authProvider?.token();
1504
+ this.lastToken = token;
1505
+ return { headers: this.buildHeaders(extra, token), ...token ? { token } : {} };
1506
+ }
1507
+ buildHeaders(extra, token) {
1508
+ const headers = new Headers(this.options.headers);
1509
+ for (const [name, value] of Object.entries(extra))
1510
+ headers.set(name, value);
1511
+ if (this.sessionIdValue)
1512
+ headers.set("Mcp-Session-Id", this.sessionIdValue);
1513
+ if (this.protocolVersion)
1514
+ headers.set("MCP-Protocol-Version", this.protocolVersion);
1515
+ if (token)
1516
+ headers.set("Authorization", `Bearer ${token}`);
1517
+ return headers;
1518
+ }
1519
+ captureSession(response) {
1520
+ const sessionId = response.headers.get("mcp-session-id");
1521
+ if (sessionId)
1522
+ this.sessionIdValue = sessionId;
1523
+ }
1524
+ async checkResponse(response) {
1525
+ if (response.ok)
1526
+ return;
1527
+ const body = (await response.text().catch(() => "")).slice(0, MAX_ERROR_BODY_BYTES);
1528
+ if (response.status === 401)
1529
+ throw new McpAuthRequiredError(response, body);
1530
+ if (response.status === 404 && this.sessionIdValue)
1531
+ throw new McpSessionExpiredError(body);
1532
+ throw new McpHttpError(response.status, describeHttpFailure(response.status, body), body);
1533
+ }
1534
+ async consumeSse(stream, cursor, onMessage) {
1535
+ await consumeSseStream(stream, {
1536
+ maxEventBytes: this.options.maxMessageBytes ?? DEFAULT_MAX_MESSAGE_BYTES,
1537
+ onId: (id) => {
1538
+ cursor.lastEventId = id;
1539
+ },
1540
+ onRetry: (delayMs) => {
1541
+ cursor.retryMs = delayMs;
1542
+ },
1543
+ onEvent: (event) => {
1544
+ cursor.received = true;
1545
+ if (!event.data.trim() || event.event !== void 0 && event.event !== "message")
1546
+ return;
1547
+ let message;
1548
+ try {
1549
+ message = parseJsonRpcMessage(JSON.parse(event.data));
1550
+ } catch (error) {
1551
+ this.emitError(error);
1552
+ return;
1553
+ }
1554
+ onMessage?.(message);
1555
+ this.emitMessage(message);
1556
+ }
1557
+ });
1558
+ }
1559
+ /**
1560
+ * Read the SSE stream answering one request. When the stream ends or breaks before the response
1561
+ * arrives and the server assigned event IDs, resume it with GET and `Last-Event-ID`, as the
1562
+ * server may close response streams at will. Otherwise only this request fails.
1563
+ */
1564
+ async consumeResponseStream(body, requestId) {
1565
+ const cursor = { lastEventId: void 0, retryMs: void 0, received: false };
1566
+ let answered = false;
1567
+ const onMessage = (message) => {
1568
+ if (isJsonRpcResponse(message) && message.id === requestId)
1569
+ answered = true;
1570
+ };
1571
+ let stream = body;
1572
+ let failure;
1573
+ for (let attempt = 0; ; ) {
1574
+ if (stream) {
1575
+ try {
1576
+ await this.consumeSse(stream, cursor, onMessage);
1577
+ failure = void 0;
1578
+ } catch (error) {
1579
+ failure = error;
1580
+ }
1581
+ }
1582
+ if (answered || this.closed)
1583
+ return;
1584
+ if (failure !== void 0 && !this.isRetryable(failure))
1585
+ break;
1586
+ if (cursor.lastEventId === void 0 || attempt >= this.maxRetries())
1587
+ break;
1588
+ if (cursor.received)
1589
+ attempt = 0;
1590
+ cursor.received = false;
1591
+ if (!await this.sleep(this.reconnectDelay(attempt++, cursor.retryMs)))
1592
+ return;
1593
+ try {
1594
+ stream = await this.openSseStream(cursor.lastEventId);
1595
+ } catch (error) {
1596
+ failure = error;
1597
+ if (!this.isRetryable(error))
1598
+ break;
1599
+ stream = void 0;
1600
+ }
1601
+ }
1602
+ if (this.closed)
1603
+ return;
1604
+ const reason = failure === void 0 ? "stream ended without a response" : toError(failure).message;
1605
+ this.emitMessage({
1606
+ jsonrpc: "2.0",
1607
+ id: requestId,
1608
+ error: { code: JSON_RPC_ERROR_CODES.internalError, message: `MCP response stream failed: ${reason}` }
1609
+ });
1610
+ }
1611
+ startGetStream() {
1612
+ if (this.options.openGetStream === false || this.getStreamStarted || this.closed)
1613
+ return;
1614
+ this.getStreamStarted = true;
1615
+ void this.runGetStream();
1616
+ }
1617
+ /** Keep the server-to-client stream open, reconnecting with backoff when it drops. */
1618
+ async runGetStream() {
1619
+ const cursor = { lastEventId: void 0, retryMs: void 0, received: false };
1620
+ for (let attempt = 0; !this.closed; ) {
1621
+ try {
1622
+ const stream = await this.openSseStream(cursor.lastEventId);
1623
+ if (!stream)
1624
+ return;
1625
+ const openedAt = Date.now();
1626
+ await this.consumeSse(stream, cursor);
1627
+ if (cursor.received || Date.now() - openedAt > this.maxDelay())
1628
+ attempt = 0;
1629
+ } catch (error) {
1630
+ if (this.closed)
1631
+ return;
1632
+ if (!this.isRetryable(error)) {
1633
+ this.emitError(error);
1634
+ return;
1635
+ }
1636
+ }
1637
+ cursor.received = false;
1638
+ if (attempt >= this.maxRetries()) {
1639
+ this.emitError(new Error("MCP server-to-client stream dropped and could not be reopened"));
1640
+ return;
1641
+ }
1642
+ if (!await this.sleep(this.reconnectDelay(attempt++, cursor.retryMs)))
1643
+ return;
1644
+ }
1645
+ }
1646
+ /** Open a GET SSE stream. Resolves to undefined when the server answers 405 (no GET stream). */
1647
+ async openSseStream(lastEventId) {
1648
+ const response = await this.authorizedFetch("GET", {
1649
+ headers: {
1650
+ accept: "text/event-stream",
1651
+ ...lastEventId === void 0 ? {} : { "last-event-id": lastEventId }
1652
+ }
1653
+ });
1654
+ if (response.status === 405) {
1655
+ await discard(response);
1656
+ return void 0;
1657
+ }
1658
+ await this.checkResponse(response);
1659
+ this.captureSession(response);
1660
+ const type = contentType(response);
1661
+ if (type !== "text/event-stream" || !response.body) {
1662
+ await discard(response);
1663
+ throw new McpHttpError(response.status, `Unsupported MCP GET response content type: ${type ?? "missing"}`);
1664
+ }
1665
+ return response.body;
1666
+ }
1667
+ /**
1668
+ * Network failures and transient statuses are retried; auth, session, and protocol errors are not.
1669
+ * `fetch` reports network failures, including a connection dropped mid-body, as `TypeError`.
1670
+ */
1671
+ isRetryable(error) {
1672
+ if (error instanceof McpHttpError)
1673
+ return isTransientStatus(error.status);
1674
+ if (error instanceof TypeError)
1675
+ return true;
1676
+ const code2 = error?.code;
1677
+ return typeof code2 === "string" && (code2.startsWith("E") || code2.startsWith("UND_ERR"));
1678
+ }
1679
+ reconnectDelay(attempt, serverDelayMs) {
1680
+ if (serverDelayMs !== void 0)
1681
+ return serverDelayMs;
1682
+ const initial = this.options.reconnect?.initialDelayMs ?? DEFAULT_RECONNECT_INITIAL_DELAY_MS;
1683
+ return Math.min(initial * 2 ** attempt, this.maxDelay());
1684
+ }
1685
+ maxDelay() {
1686
+ return this.options.reconnect?.maxDelayMs ?? DEFAULT_RECONNECT_MAX_DELAY_MS;
1687
+ }
1688
+ maxRetries() {
1689
+ return this.options.reconnect?.maxRetries ?? DEFAULT_RECONNECT_MAX_RETRIES;
1690
+ }
1691
+ /** Resolves false when the transport closed while waiting. */
1692
+ sleep(ms) {
1693
+ const signal = this.controller.signal;
1694
+ if (signal.aborted)
1695
+ return Promise.resolve(false);
1696
+ return new Promise((resolve) => {
1697
+ const onAbort = () => {
1698
+ clearTimeout(timer);
1699
+ resolve(false);
1700
+ };
1701
+ const timer = setTimeout(() => {
1702
+ signal.removeEventListener("abort", onAbort);
1703
+ resolve(true);
1704
+ }, ms);
1705
+ timer.unref?.();
1706
+ signal.addEventListener("abort", onAbort, { once: true });
1707
+ });
1708
+ }
1709
+ };
1710
+
1711
+ // src/client/pi-connection.ts
1712
+ async function createPiConnection(options) {
1713
+ const clientOptions = {
1714
+ name: "mcpcat",
1715
+ version: options.version ?? "0.0.0"
1716
+ };
1717
+ const client = new McpClient(clientOptions);
1718
+ let transport;
1719
+ if (options.url) {
1720
+ transport = new StreamableHttpTransport({
1721
+ url: options.url,
1722
+ headers: options.headers
1723
+ });
1724
+ } 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
+ });
1733
+ } else {
1734
+ throw new Error("PiConnection requires either a url or a command");
1735
+ }
1736
+ const initResult = await client.connect(transport);
1737
+ return {
1738
+ async info() {
1739
+ return {
1740
+ name: initResult.serverInfo?.name,
1741
+ version: initResult.serverInfo?.version,
1742
+ title: initResult.serverInfo?.title,
1743
+ protocolVersion: initResult.protocolVersion,
1744
+ capabilities: initResult.capabilities,
1745
+ instructions: initResult.instructions
1746
+ };
1747
+ },
1748
+ async listTools() {
1749
+ return client.listTools();
1750
+ },
1751
+ async callTool(name, args2) {
1752
+ return client.callTool(name, args2);
1753
+ },
1754
+ async close() {
1755
+ await client.close();
1756
+ }
1757
+ };
1758
+ }
1759
+
1760
+ // src/client/stateless-connection.ts
1761
+ var STATELESS_PROTOCOL_VERSION = "2026-07-28";
1762
+ var StatelessRpcError = class extends Error {
1763
+ code;
1764
+ data;
1765
+ constructor(code2, message, data) {
1766
+ super(`MCP RPC error ${code2}: ${message}`);
1767
+ this.name = "StatelessRpcError";
1768
+ this.code = code2;
1769
+ this.data = data;
1770
+ }
1771
+ };
1772
+ var StatelessHttpError = class extends Error {
1773
+ status;
1774
+ constructor(status, statusText, method, snippet) {
1775
+ super(`MCP HTTP ${status} ${statusText} for ${method}: ${snippet || "(empty body)"}`);
1776
+ this.name = "StatelessHttpError";
1777
+ this.status = status;
1778
+ }
1779
+ };
1780
+ var InputRequiredError = class extends Error {
1781
+ requestJson;
1782
+ constructor(requestJson) {
1783
+ super("Server requires input (resultType: input_required)");
1784
+ this.name = "InputRequiredError";
1785
+ this.requestJson = requestJson;
1786
+ }
1787
+ };
1788
+ function selectSseFrame(body, targetId) {
1789
+ const text = new TextDecoder().decode(body);
1790
+ const blocks = text.replace(/\r\n/g, "\n").split(/\n\n/);
1791
+ for (const block of blocks) {
1792
+ if (!block.trim()) continue;
1793
+ const dataLines = [];
1794
+ for (const line of block.split("\n")) {
1795
+ if (line.startsWith("data:")) dataLines.push(line.slice(5).trimStart());
1796
+ }
1797
+ if (dataLines.length === 0) continue;
1798
+ try {
1799
+ const parsed = JSON.parse(dataLines.join("\n"));
1800
+ if (parsed.id === targetId) return parsed;
1801
+ } catch {
1802
+ }
1803
+ }
1804
+ throw new Error(`SSE stream ended without response for id ${targetId}`);
1805
+ }
1806
+ function mcpNameForMethod(method, params) {
1807
+ if (method === "tools/call") return params.name;
1808
+ if (method === "prompts/get") return params.name;
1809
+ if (method === "resources/read") return params.uri;
1810
+ return void 0;
1811
+ }
1812
+ async function createStatelessConnection(options) {
1813
+ const {
1814
+ url,
1815
+ headers: extraHeaders,
1816
+ fetchImpl,
1817
+ timeoutMs = 6e4,
1818
+ clientInfo = { name: "mcpcat", version: "0.0.0" }
1819
+ } = options;
1820
+ let nextId = 1;
1821
+ let negotiatedVersion;
1822
+ async function request(method, params) {
1823
+ const id = nextId++;
1824
+ const headers = {
1825
+ "Content-Type": "application/json",
1826
+ Accept: "application/json, text/event-stream",
1827
+ "Mcp-Method": method,
1828
+ ...extraHeaders
1829
+ };
1830
+ const mcpName = mcpNameForMethod(method, params);
1831
+ if (mcpName) {
1832
+ headers["Mcp-Name"] = mcpName;
1833
+ }
1834
+ if (negotiatedVersion) {
1835
+ headers["Mcp-Protocol-Version"] = negotiatedVersion;
1836
+ }
1837
+ const meta = {
1838
+ protocolVersion: negotiatedVersion ?? STATELESS_PROTOCOL_VERSION,
1839
+ clientInfo
1840
+ };
1841
+ const body = JSON.stringify({
1842
+ jsonrpc: "2.0",
1843
+ id,
1844
+ method,
1845
+ params: { ...params, _meta: meta }
1846
+ });
1847
+ const controller = new AbortController();
1848
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
1849
+ let res;
1850
+ try {
1851
+ res = await fetchImpl(url, {
1852
+ method: "POST",
1853
+ headers,
1854
+ body,
1855
+ signal: controller.signal
1856
+ });
1857
+ } finally {
1858
+ clearTimeout(timer);
1859
+ }
1860
+ if (res.status === 401) {
1861
+ throw new StatelessHttpError(401, "Unauthorized", method, "");
1862
+ }
1863
+ if (res.status >= 400) {
1864
+ const text = await res.text();
1865
+ let rpcError;
1866
+ try {
1867
+ const parsed = JSON.parse(text);
1868
+ if (parsed.error && typeof parsed.error.code === "number") {
1869
+ rpcError = parsed.error;
1870
+ }
1871
+ } catch {
1872
+ }
1873
+ if (rpcError) {
1874
+ throw new StatelessRpcError(rpcError.code, rpcError.message, rpcError.data);
1875
+ }
1876
+ throw new StatelessHttpError(res.status, res.statusText, method, text.slice(0, 512));
1877
+ }
1878
+ const ct = res.headers.get("content-type") ?? "";
1879
+ let frame;
1880
+ if (ct.toLowerCase().includes("text/event-stream")) {
1881
+ const bytes = new Uint8Array(await res.arrayBuffer());
1882
+ frame = selectSseFrame(bytes, id);
1883
+ } else {
1884
+ frame = await res.json();
1885
+ }
1886
+ if (frame.error) {
1887
+ throw new StatelessRpcError(frame.error.code, frame.error.message, frame.error.data);
1888
+ }
1889
+ return frame.result;
1890
+ }
1891
+ const discoverResult = await request("server/discover", {
1892
+ protocolVersion: STATELESS_PROTOCOL_VERSION
1893
+ });
1894
+ const supported = Array.isArray(discoverResult?.supportedVersions) ? discoverResult.supportedVersions.filter((v) => typeof v === "string") : [];
1895
+ if (!supported.includes(STATELESS_PROTOCOL_VERSION)) {
1896
+ throw new Error(
1897
+ `Server does not support ${STATELESS_PROTOCOL_VERSION} (advertised: ${supported.join(", ") || "none"})`
1898
+ );
1899
+ }
1900
+ negotiatedVersion = STATELESS_PROTOCOL_VERSION;
1901
+ let serverInfoCache;
1902
+ return {
1903
+ async info() {
1904
+ if (serverInfoCache) return serverInfoCache;
1905
+ const result = discoverResult;
1906
+ serverInfoCache = {
1907
+ name: result?.serverInfo?.name,
1908
+ version: result?.serverInfo?.version,
1909
+ title: result?.serverInfo?.title,
1910
+ protocolVersion: STATELESS_PROTOCOL_VERSION,
1911
+ capabilities: result?.capabilities ?? {},
1912
+ instructions: result?.instructions
1913
+ };
1914
+ return serverInfoCache;
1915
+ },
1916
+ async listTools() {
1917
+ const result = await request("tools/list", {});
1918
+ return result?.tools ?? [];
1919
+ },
1920
+ async callTool(name, args2) {
1921
+ const result = await request("tools/call", {
1922
+ name,
1923
+ arguments: args2 ?? {}
1924
+ });
1925
+ const resultType = result.resultType ?? "complete";
1926
+ if (resultType === "input_required") {
1927
+ throw new InputRequiredError(
1928
+ JSON.stringify({ name, arguments: args2 ?? {}, result }, null, 2)
1929
+ );
1930
+ }
1931
+ return result;
1932
+ },
1933
+ async close() {
1934
+ }
1935
+ };
1936
+ }
1937
+
1938
+ // src/client/connect.ts
1939
+ async function connect(platform2, options) {
1940
+ if (options.command) {
1941
+ return createPiConnection({
1942
+ command: options.command,
1943
+ args: options.args,
1944
+ env: options.env,
1945
+ cwd: options.cwd,
1946
+ version: options.version
1947
+ });
1948
+ }
1949
+ if (!options.url) {
1950
+ throw new Error("connect requires either a url or a command");
1951
+ }
1952
+ if (options.protocolVersion && !options.protocolVersion.startsWith("2026")) {
1953
+ return createPiConnection({
1954
+ url: options.url,
1955
+ headers: options.headers,
1956
+ version: options.version
1957
+ });
1958
+ }
1959
+ try {
1960
+ return await createStatelessConnection({
1961
+ url: options.url,
1962
+ headers: options.headers,
1963
+ fetchImpl: platform2.fetch,
1964
+ clientInfo: { name: "mcpcat", version: options.version ?? "0.0.0" }
1965
+ });
1966
+ } catch (err) {
1967
+ const isMethodNotFound = err instanceof StatelessRpcError && err.code === -32601 || err instanceof StatelessHttpError && err.status >= 400 && err.status < 500;
1968
+ if (options.protocolVersion?.startsWith("2026")) {
1969
+ throw err;
1970
+ }
1971
+ if (isMethodNotFound) {
1972
+ return createPiConnection({
1973
+ url: options.url,
1974
+ headers: options.headers,
1975
+ version: options.version
1976
+ });
1977
+ }
1978
+ throw err;
1979
+ }
1980
+ }
1981
+
1982
+ // src/client/targets.ts
1983
+ function splitShellStyle(input) {
1984
+ const result = [];
1985
+ let current = "";
1986
+ let inSingle = false;
1987
+ let inDouble = false;
1988
+ for (let i = 0; i < input.length; i++) {
1989
+ const c = input[i];
1990
+ if (inSingle) {
1991
+ if (c === "'") {
1992
+ inSingle = false;
1993
+ } else {
1994
+ current += c;
1995
+ }
1996
+ } else if (inDouble) {
1997
+ if (c === '"') {
1998
+ inDouble = false;
1999
+ } else if (c === "\\" && i + 1 < input.length) {
2000
+ current += input[++i];
2001
+ } else {
2002
+ current += c;
2003
+ }
2004
+ } else if (c === "'") {
2005
+ inSingle = true;
2006
+ } else if (c === '"') {
2007
+ inDouble = true;
2008
+ } else if (c === " " || c === " ") {
2009
+ if (current.length > 0) {
2010
+ result.push(current);
2011
+ current = "";
2012
+ }
2013
+ } else if (c === "\\" && i + 1 < input.length) {
2014
+ current += input[++i];
2015
+ } else {
2016
+ current += c;
2017
+ }
2018
+ }
2019
+ if (current.length > 0) result.push(current);
2020
+ return result;
2021
+ }
2022
+ function normalizeUrl(raw) {
2023
+ if (raw.startsWith("http://") || raw.startsWith("https://")) return raw;
2024
+ if (raw.startsWith("localhost") || raw.startsWith("127.0.0.1") || raw.startsWith("[::1]")) {
2025
+ return `http://${raw}`;
2026
+ }
2027
+ return `https://${raw}`;
2028
+ }
2029
+ function looksLikeUrl(value) {
2030
+ if (value.startsWith("http://") || value.startsWith("https://")) return true;
2031
+ if (value.includes(".") && !value.includes(" ")) return true;
2032
+ if (value.startsWith("localhost") || value.startsWith("127.0.0.1") || value.startsWith("[::1]"))
2033
+ return true;
2034
+ return false;
2035
+ }
2036
+ async function resolveTarget(target, trailingArgs, config) {
2037
+ if (trailingArgs && trailingArgs.length > 0) {
2038
+ return { command: trailingArgs[0], args: trailingArgs.slice(1) };
2039
+ }
2040
+ if (!target) {
2041
+ throw new Error("no target given");
2042
+ }
2043
+ if (target.startsWith("stdio:")) {
2044
+ const rest = target.slice(6);
2045
+ const parts = splitShellStyle(rest);
2046
+ if (parts.length === 0) throw new Error("stdio: target requires a command");
2047
+ return { command: parts[0], args: parts.slice(1) };
2048
+ }
2049
+ if (looksLikeUrl(target)) {
2050
+ return { url: normalizeUrl(target) };
2051
+ }
2052
+ const entry = await config.get(target);
2053
+ if (entry) {
2054
+ return configToTarget(entry);
2055
+ }
2056
+ throw new Error(`unknown target '${target}': not a URL and not found in mcp.json`);
2057
+ }
2058
+ function configToTarget(entry) {
2059
+ if (entry.url) {
2060
+ return {
2061
+ url: entry.url,
2062
+ headers: entry.headers
2063
+ };
2064
+ }
2065
+ if (entry.command) {
2066
+ return {
2067
+ command: entry.command,
2068
+ args: entry.args,
2069
+ env: entry.env,
2070
+ cwd: entry.cwd
2071
+ };
2072
+ }
2073
+ throw new Error("config entry has neither url nor command");
2074
+ }
2075
+
2076
+ // src/config/parse.ts
2077
+ var SERVER_NAME = /^[A-Za-z0-9_-]+$/;
2078
+ var EXPOSURES = ["codemode", "deferred", "direct", "hidden"];
2079
+ var EXPOSURE_ALIASES = { "codemode-deferred": "codemode" };
2080
+ function isRecord(value) {
2081
+ return typeof value === "object" && value !== null && !Array.isArray(value);
2082
+ }
2083
+ function isStringRecord(value) {
2084
+ return isRecord(value) && Object.values(value).every((v) => typeof v === "string");
2085
+ }
2086
+ function isExposure(value) {
2087
+ return typeof value === "string" && EXPOSURES.includes(value);
2088
+ }
2089
+ function resolveAlias(value) {
2090
+ return typeof value === "string" ? EXPOSURE_ALIASES[value] ?? value : value;
2091
+ }
2092
+ function resolveAliases(value) {
2093
+ const out = { ...value };
2094
+ if (out.exposure !== void 0) out.exposure = resolveAlias(out.exposure);
2095
+ if (isRecord(out.toolExposure)) {
2096
+ out.toolExposure = Object.fromEntries(
2097
+ Object.entries(out.toolExposure).map(([k, v]) => [k, resolveAlias(v)])
2098
+ );
2099
+ }
2100
+ return out;
2101
+ }
2102
+ function fail(server, message, field) {
2103
+ return { server, ...field ? { field } : {}, message };
2104
+ }
2105
+ function validateCommon(name, value) {
2106
+ const exposures = EXPOSURES.map((e) => `"${e}"`).join(", ");
2107
+ if (value.exposure !== void 0 && !isExposure(value.exposure))
2108
+ return fail(name, `exposure must be one of ${exposures}`, "exposure");
2109
+ if (value.toolExposure !== void 0) {
2110
+ if (!isRecord(value.toolExposure))
2111
+ return fail(name, "toolExposure must map tool names to exposures", "toolExposure");
2112
+ for (const [tool, exp] of Object.entries(value.toolExposure))
2113
+ if (!isExposure(exp))
2114
+ return fail(
2115
+ name,
2116
+ `toolExposure "${tool}" must be one of ${exposures}`,
2117
+ `toolExposure.${tool}`
2118
+ );
2119
+ }
2120
+ if (value.enabled !== void 0 && typeof value.enabled !== "boolean")
2121
+ return fail(name, "enabled must be a boolean", "enabled");
2122
+ if (value.description !== void 0 && typeof value.description !== "string")
2123
+ return fail(name, "description must be a string", "description");
2124
+ if (value.timeout !== void 0 && (typeof value.timeout !== "number" || !(value.timeout > 0)))
2125
+ return fail(name, "timeout must be a positive number of seconds", "timeout");
2126
+ return void 0;
2127
+ }
2128
+ function validateOAuthFields(name, oauth) {
2129
+ if (oauth.clientId !== void 0 && typeof oauth.clientId !== "string")
2130
+ return fail(name, "oauth.clientId must be a string", "oauth.clientId");
2131
+ if (oauth.clientSecret !== void 0 && typeof oauth.clientSecret !== "string")
2132
+ return fail(name, "oauth.clientSecret must be a string", "oauth.clientSecret");
2133
+ const port = oauth.callbackPort;
2134
+ if (port !== void 0 && (typeof port !== "number" || !Number.isInteger(port) || port < 1 || port > 65535))
2135
+ return fail(name, "oauth.callbackPort must be a port number", "oauth.callbackPort");
2136
+ if (oauth.callbackUrl !== void 0 && typeof oauth.callbackUrl !== "string")
2137
+ return fail(name, "oauth.callbackUrl must be a string", "oauth.callbackUrl");
2138
+ if (oauth.scope !== void 0 && typeof oauth.scope !== "string")
2139
+ return fail(name, "oauth.scope must be a string", "oauth.scope");
2140
+ if (oauth.clientName !== void 0 && (typeof oauth.clientName !== "string" || !oauth.clientName.trim()))
2141
+ return fail(name, "oauth.clientName must be a non-empty string", "oauth.clientName");
2142
+ return void 0;
2143
+ }
2144
+ function validateOAuthExtras(name, oauth) {
2145
+ if (oauth.clientRegistration !== void 0 && oauth.clientRegistration !== "dcr" && oauth.clientRegistration !== "cimd")
2146
+ return fail(
2147
+ name,
2148
+ 'oauth.clientRegistration must be "dcr" or "cimd"',
2149
+ "oauth.clientRegistration"
2150
+ );
2151
+ if (oauth.authServerMetadataUrl !== void 0) {
2152
+ const url = typeof oauth.authServerMetadataUrl === "string" && URL.canParse(oauth.authServerMetadataUrl) ? new URL(oauth.authServerMetadataUrl) : void 0;
2153
+ if (!url || url.protocol !== "https:" && !(url.protocol === "http:" && ["localhost", "127.0.0.1", "[::1]"].includes(url.hostname)))
2154
+ return fail(
2155
+ name,
2156
+ "oauth.authServerMetadataUrl must be an https URL",
2157
+ "oauth.authServerMetadataUrl"
2158
+ );
2159
+ }
2160
+ return void 0;
2161
+ }
2162
+ function validateOAuth(name, value) {
2163
+ const { oauth } = value;
2164
+ if (oauth === void 0) return void 0;
2165
+ if (!isRecord(oauth)) return fail(name, "oauth must be an object", "oauth");
2166
+ return validateOAuthFields(name, oauth) ?? validateOAuthExtras(name, oauth);
2167
+ }
2168
+ function validateHttpServer(name, value) {
2169
+ if (typeof value.url !== "string" || value.type !== void 0 && value.type !== "http" && value.type !== "streamable-http")
2170
+ return void 0;
2171
+ if (!URL.canParse(value.url) || !/^https?:$/.test(new URL(value.url).protocol))
2172
+ return fail(name, "url must be an http or https URL", "url");
2173
+ if (value.headers !== void 0 && !isStringRecord(value.headers))
2174
+ return fail(name, "headers must map names to strings", "headers");
2175
+ const oauthErr = validateOAuth(name, value);
2176
+ if (oauthErr) return oauthErr;
2177
+ return value;
2178
+ }
2179
+ function validateStdioServer(name, value) {
2180
+ if (typeof value.command !== "string" || value.type !== void 0 && value.type !== "stdio")
2181
+ return void 0;
2182
+ if (value.args !== void 0 && !(Array.isArray(value.args) && value.args.every((a) => typeof a === "string")))
2183
+ return fail(name, "args must be an array of strings", "args");
2184
+ if (value.env !== void 0 && !isStringRecord(value.env))
2185
+ return fail(name, "env must map names to strings", "env");
2186
+ if (value.cwd !== void 0 && typeof value.cwd !== "string")
2187
+ return fail(name, "cwd must be a string", "cwd");
2188
+ return value;
2189
+ }
2190
+ function validateServer(name, raw) {
2191
+ if (!SERVER_NAME.test(name))
2192
+ return fail(name, `invalid server name "${name}" (use letters, digits, "_" and "-")`);
2193
+ if (!isRecord(raw)) return fail(name, "must be an object");
2194
+ const value = resolveAliases(raw);
2195
+ if (value.type === "sse")
2196
+ return fail(name, "legacy SSE transport is not supported; use the streamable HTTP URL", "type");
2197
+ const common = validateCommon(name, value);
2198
+ if (common) return common;
2199
+ const httpResult = validateHttpServer(name, value);
2200
+ if (httpResult) return httpResult;
2201
+ const stdioResult = validateStdioServer(name, value);
2202
+ if (stdioResult) return stdioResult;
2203
+ if (value.type !== void 0 && !["http", "streamable-http", "stdio"].includes(value.type))
2204
+ return fail(name, 'type must be "stdio", "http", or "streamable-http"', "type");
2205
+ return fail(name, 'needs a "url" or "command"');
2206
+ }
2207
+ function isOverride(value) {
2208
+ return value.command === void 0 && value.url === void 0 && value.type === void 0;
2209
+ }
2210
+ function canonical(name) {
2211
+ return name.replace(/-/g, "_");
2212
+ }
2213
+ function parseMcpJson(text) {
2214
+ const result = { servers: [], overrides: [], problems: [] };
2215
+ let parsed;
2216
+ try {
2217
+ parsed = JSON.parse(text);
2218
+ } catch (error) {
2219
+ result.problems.push({ message: `not valid JSON: ${error.message}` });
2220
+ return result;
2221
+ }
2222
+ if (!isRecord(parsed) || parsed.mcpServers !== void 0 && !isRecord(parsed.mcpServers)) {
2223
+ result.problems.push({ message: "mcpServers must be an object" });
2224
+ return result;
2225
+ }
2226
+ if (typeof parsed.autoEnableCodemode === "boolean")
2227
+ result.autoEnableCodemode = parsed.autoEnableCodemode;
2228
+ else if (parsed.autoEnableCodemode !== void 0)
2229
+ result.problems.push({ message: "autoEnableCodemode must be a boolean" });
2230
+ const seen = /* @__PURE__ */ new Set();
2231
+ for (const [name, raw] of Object.entries(parsed.mcpServers ?? {})) {
2232
+ if (!SERVER_NAME.test(name)) {
2233
+ result.problems.push(
2234
+ fail(name, `invalid server name "${name}" (use letters, digits, "_" and "-")`)
2235
+ );
2236
+ continue;
2237
+ }
2238
+ if (seen.has(canonical(name))) {
2239
+ result.problems.push(
2240
+ fail(name, 'names that differ only in "-" and "_" count as the same server')
2241
+ );
2242
+ continue;
2243
+ }
2244
+ seen.add(canonical(name));
2245
+ if (isRecord(raw) && isOverride(raw)) {
2246
+ result.overrides.push({ name, raw });
2247
+ continue;
2248
+ }
2249
+ const checked = validateServer(name, raw);
2250
+ if ("message" in checked) {
2251
+ result.problems.push(checked);
2252
+ continue;
2253
+ }
2254
+ result.servers.push({ name, config: checked });
2255
+ }
2256
+ return result;
2257
+ }
2258
+
2259
+ // src/config/merge.ts
2260
+ var OVERRIDE_KEYS = /* @__PURE__ */ new Set(["enabled", "exposure", "toolExposure"]);
2261
+ async function readFile(fs, path) {
2262
+ try {
2263
+ const data = await fs.readFile(path);
2264
+ return new TextDecoder().decode(data);
2265
+ } catch {
2266
+ return void 0;
2267
+ }
2268
+ }
2269
+ function applyOverride(servers, problems, name, override, projectPath) {
2270
+ const raw = override;
2271
+ const base = servers.get(name);
2272
+ const extra = Object.keys(raw).filter((k) => !OVERRIDE_KEYS.has(k));
2273
+ if (!base) {
2274
+ problems.push({
2275
+ server: name,
2276
+ message: 'needs "command" or "url", or a user server to override'
2277
+ });
2278
+ return;
2279
+ }
2280
+ if (extra.length > 0) {
2281
+ problems.push({
2282
+ server: name,
2283
+ message: "an override can only set enabled, exposure, toolExposure"
2284
+ });
2285
+ return;
2286
+ }
2287
+ const merged = { ...base.config, ...raw };
2288
+ servers.set(name, { ...base, config: merged, override: projectPath });
2289
+ }
2290
+ function applyProjectEntry(servers, problems, name, config, projectPath) {
2291
+ const clash = [...servers.keys()].find(
2292
+ (other) => other !== name && canonical(other) === canonical(name)
2293
+ );
2294
+ if (clash) {
2295
+ problems.push({
2296
+ server: name,
2297
+ message: `conflicts with "${clash}" (names differ only in "-"/"_")`
2298
+ });
2299
+ return;
2300
+ }
2301
+ servers.set(name, { name, config, source: projectPath, scope: "project" });
2302
+ }
2303
+ function loadUserServers(parsed, userPath, servers, problems) {
2304
+ for (const p of parsed.problems) problems.push(p);
2305
+ for (const { name, config } of parsed.servers) {
2306
+ servers.set(name, { name, config, source: userPath, scope: "user" });
2307
+ }
2308
+ }
2309
+ function loadProjectServers(parsed, projectPath, servers, problems) {
2310
+ for (const p of parsed.problems) problems.push(p);
2311
+ for (const { name, raw } of parsed.overrides) {
2312
+ applyOverride(servers, problems, name, raw, projectPath);
2313
+ }
2314
+ for (const { name, config } of parsed.servers) {
2315
+ applyProjectEntry(servers, problems, name, config, projectPath);
2316
+ }
2317
+ }
2318
+ async function loadConfig(input) {
2319
+ const servers = /* @__PURE__ */ new Map();
2320
+ const problems = [];
2321
+ let autoEnableCodemode;
2322
+ const userText = await readFile(input.fs, input.userPath);
2323
+ if (userText !== void 0) {
2324
+ const parsed = parseMcpJson(userText);
2325
+ if (parsed.autoEnableCodemode !== void 0) autoEnableCodemode = parsed.autoEnableCodemode;
2326
+ loadUserServers(parsed, input.userPath, servers, problems);
2327
+ }
2328
+ if (input.projectPath && input.trustProject) {
2329
+ const projectText = await readFile(input.fs, input.projectPath);
2330
+ if (projectText !== void 0) {
2331
+ const parsed = parseMcpJson(projectText);
2332
+ if (parsed.autoEnableCodemode !== void 0) autoEnableCodemode = parsed.autoEnableCodemode;
2333
+ loadProjectServers(parsed, input.projectPath, servers, problems);
2334
+ }
2335
+ }
2336
+ return {
2337
+ servers: [...servers.values()],
2338
+ problems,
2339
+ ...autoEnableCodemode !== void 0 ? { autoEnableCodemode } : {}
2340
+ };
2341
+ }
2342
+
2343
+ // src/config/paths.ts
2344
+ function userConfigDir(platform2) {
2345
+ const piDir = platform2.env.PI_CODING_AGENT_DIR;
2346
+ if (piDir) return piDir;
2347
+ const home = platform2.env.HOME ?? platform2.env.USERPROFILE ?? "/";
2348
+ return `${home}/.pi/agent`;
2349
+ }
2350
+ function userConfigPath(platform2) {
2351
+ return `${userConfigDir(platform2)}/mcp.json`;
2352
+ }
2353
+ function projectConfigPath(platform2) {
2354
+ return `${platform2.cwd}/.pi/mcp.json`;
2355
+ }
2356
+
2357
+ // src/config/index.ts
2358
+ var MergedConfigSource = class _MergedConfigSource {
2359
+ entries;
2360
+ config;
2361
+ constructor(config) {
2362
+ this.config = config;
2363
+ this.entries = /* @__PURE__ */ new Map();
2364
+ for (const entry of config.servers) {
2365
+ this.entries.set(entry.name, entry);
2366
+ }
2367
+ }
2368
+ async get(name) {
2369
+ const normalizedName = name.replace(/[-_]/g, "").toLowerCase();
2370
+ for (const [key, entry] of this.entries) {
2371
+ if (key.replace(/[-_]/g, "").toLowerCase() === normalizedName) {
2372
+ return entry.config;
2373
+ }
2374
+ }
2375
+ return void 0;
2376
+ }
2377
+ entry(name) {
2378
+ return this.entries.get(name);
2379
+ }
2380
+ list() {
2381
+ return [...this.entries.values()];
2382
+ }
2383
+ static async load(platform2, options) {
2384
+ const userPath = options?.configFile ?? userConfigPath(platform2);
2385
+ const projectPath = options?.configFile ? void 0 : projectConfigPath(platform2);
2386
+ const config = await loadConfig({
2387
+ userPath,
2388
+ projectPath,
2389
+ trustProject: options?.trustProject ?? false,
2390
+ fs: platform2.fs
2391
+ });
2392
+ return new _MergedConfigSource(config);
2393
+ }
2394
+ };
2395
+
2396
+ // src/config/lock.ts
2397
+ var LOCK_STALE_MS = 1e4;
2398
+ var LOCK_POLL_MS = 50;
2399
+ var LOCK_MAX_WAIT_MS = 5e3;
2400
+ function isEexist(err) {
2401
+ if (err instanceof Error && "code" in err)
2402
+ return err.code === "EEXIST";
2403
+ if (err instanceof Error) return err.message.includes("EEXIST");
2404
+ return false;
2405
+ }
2406
+ async function removeStaleLock(fs, lockPath) {
2407
+ try {
2408
+ const s = await fs.stat(lockPath);
2409
+ if (Date.now() - s.mtimeMs > LOCK_STALE_MS) {
2410
+ try {
2411
+ await fs.rmdir(lockPath);
2412
+ } catch {
2413
+ }
2414
+ return true;
2415
+ }
2416
+ } catch {
2417
+ }
2418
+ return false;
2419
+ }
2420
+ async function acquireLock(fs, configPath) {
2421
+ const lockPath = `${configPath}.lock`;
2422
+ const start = Date.now();
2423
+ while (true) {
2424
+ try {
2425
+ await fs.mkdirExclusive(lockPath);
2426
+ break;
2427
+ } catch (err) {
2428
+ if (!isEexist(err)) throw err;
2429
+ await removeStaleLock(fs, lockPath);
2430
+ }
2431
+ if (Date.now() - start > LOCK_MAX_WAIT_MS) {
2432
+ throw new Error(`mcpcat: could not acquire lock ${lockPath} within ${LOCK_MAX_WAIT_MS}ms`);
2433
+ }
2434
+ await new Promise((r) => setTimeout(r, LOCK_POLL_MS));
2435
+ }
2436
+ let released = false;
2437
+ return async () => {
2438
+ if (released) return;
2439
+ released = true;
2440
+ try {
2441
+ await fs.rmdir(lockPath);
2442
+ } catch {
2443
+ }
2444
+ };
2445
+ }
2446
+
2447
+ // src/config/edit.ts
2448
+ function isRecord2(value) {
2449
+ return typeof value === "object" && value !== null && !Array.isArray(value);
2450
+ }
2451
+ function detectIndent(text) {
2452
+ const match = /^([ \t]+)\S/m.exec(text);
2453
+ return match ? match[1] : " ";
2454
+ }
2455
+ async function readText(fs, path) {
2456
+ try {
2457
+ const data = await fs.readFile(path);
2458
+ return new TextDecoder().decode(data);
2459
+ } catch {
2460
+ return void 0;
2461
+ }
2462
+ }
2463
+ async function editMcpServers(fs, path, edit) {
2464
+ const release = await acquireLock(fs, path);
2465
+ try {
2466
+ const text = await readText(fs, path);
2467
+ const parsed = text === void 0 ? {} : JSON.parse(text);
2468
+ if (!isRecord2(parsed) || parsed.mcpServers !== void 0 && !isRecord2(parsed.mcpServers))
2469
+ throw new Error(`${path}: expected an object with an "mcpServers" object`);
2470
+ const servers = isRecord2(parsed.mcpServers) ? parsed.mcpServers : void 0;
2471
+ if (!edit(servers, parsed)) return { changed: false };
2472
+ const indent = text ? detectIndent(text) : " ";
2473
+ const output = `${JSON.stringify(parsed, null, indent)}
2474
+ `;
2475
+ const dir = path.slice(0, path.lastIndexOf("/"));
2476
+ if (dir) {
2477
+ try {
2478
+ await fs.mkdir(dir);
2479
+ } catch {
2480
+ }
2481
+ }
2482
+ const tmpPath = `${path}.tmp.${Date.now()}`;
2483
+ await fs.writeFile(tmpPath, output);
2484
+ await fs.rename(tmpPath, path);
2485
+ return { changed: true };
2486
+ } finally {
2487
+ await release();
2488
+ }
2489
+ }
2490
+ async function addServer(fs, path, name, config) {
2491
+ let replaced = false;
2492
+ await editMcpServers(fs, path, (servers, parsed) => {
2493
+ const target = servers ?? {};
2494
+ replaced = target[name] !== void 0;
2495
+ target[name] = config;
2496
+ parsed.mcpServers = target;
2497
+ return true;
2498
+ });
2499
+ return { replaced };
2500
+ }
2501
+ async function removeServer(fs, path, name) {
2502
+ let removed = false;
2503
+ await editMcpServers(fs, path, (servers) => {
2504
+ if (!servers || servers[name] === void 0) return false;
2505
+ delete servers[name];
2506
+ removed = true;
2507
+ return true;
2508
+ });
2509
+ return removed;
2510
+ }
2511
+ async function updateServer(fs, path, name, patch) {
2512
+ let updated = false;
2513
+ await editMcpServers(fs, path, (servers) => {
2514
+ if (!servers || !isRecord2(servers[name])) return false;
2515
+ const entry = servers[name];
2516
+ if (patch.enabled !== void 0) entry.enabled = patch.enabled;
2517
+ if (patch.exposure !== void 0) entry.exposure = patch.exposure;
2518
+ if (patch.toolExposure !== void 0) entry.toolExposure = patch.toolExposure;
2519
+ updated = true;
2520
+ return true;
2521
+ });
2522
+ return updated;
2523
+ }
2524
+ async function setExposure(fs, path, name, exposure, toolGlob) {
2525
+ let updated = false;
2526
+ await editMcpServers(fs, path, (servers) => {
2527
+ if (!servers || !isRecord2(servers[name])) return false;
2528
+ const entry = servers[name];
2529
+ if (toolGlob) {
2530
+ const te = isRecord2(entry.toolExposure) ? { ...entry.toolExposure } : {};
2531
+ te[toolGlob] = exposure;
2532
+ entry.toolExposure = te;
2533
+ } else {
2534
+ entry.exposure = exposure;
2535
+ }
2536
+ updated = true;
2537
+ return true;
2538
+ });
2539
+ return updated;
2540
+ }
2541
+
2542
+ // src/config/import.ts
2543
+ function isRecord3(value) {
2544
+ return typeof value === "object" && value !== null && !Array.isArray(value);
2545
+ }
2546
+ function isStringRecord2(value) {
2547
+ return isRecord3(value) && Object.values(value).every((v) => typeof v === "string");
2548
+ }
2549
+ function tryParse(text) {
2550
+ try {
2551
+ return { parsed: JSON.parse(text) };
2552
+ } catch (e) {
2553
+ return { parsed: void 0, error: `not valid JSON: ${e.message}` };
2554
+ }
2555
+ }
2556
+ function filterNames(entries, names) {
2557
+ const result = [];
2558
+ for (const [name, value] of entries) {
2559
+ if (names && names.length > 0 && !names.includes(name)) continue;
2560
+ result.push([name, value]);
2561
+ }
2562
+ return result;
2563
+ }
2564
+ function replaceInputVars(value) {
2565
+ return value.replace(
2566
+ /\$\{input:([^}]+)\}/g,
2567
+ (_, name) => `\${${name.toUpperCase().replace(/[^A-Z0-9]/g, "_")}}`
2568
+ );
2569
+ }
2570
+ function importClaude(text, names) {
2571
+ const result = { servers: [], errors: [] };
2572
+ const { parsed, error } = tryParse(text);
2573
+ if (error) {
2574
+ result.errors.push(error);
2575
+ return result;
2576
+ }
2577
+ if (!isRecord3(parsed)) {
2578
+ result.errors.push("expected an object");
2579
+ return result;
2580
+ }
2581
+ const mcpServers = parsed.mcpServers;
2582
+ if (!isRecord3(mcpServers)) {
2583
+ result.errors.push('no "mcpServers" object');
2584
+ return result;
2585
+ }
2586
+ for (const [name, entry] of filterNames(Object.entries(mcpServers), names)) {
2587
+ if (!isRecord3(entry)) {
2588
+ result.errors.push(`${name}: not an object`);
2589
+ continue;
2590
+ }
2591
+ result.servers.push({ name, config: entry });
2592
+ }
2593
+ return result;
2594
+ }
2595
+ function importVSCode(text, names) {
2596
+ const result = { servers: [], errors: [] };
2597
+ const { parsed, error } = tryParse(text);
2598
+ if (error) {
2599
+ result.errors.push(error);
2600
+ return result;
2601
+ }
2602
+ if (!isRecord3(parsed)) {
2603
+ result.errors.push("expected an object");
2604
+ return result;
2605
+ }
2606
+ const servers = parsed.servers ?? parsed.mcpServers;
2607
+ if (!isRecord3(servers)) {
2608
+ result.errors.push('no "servers" or "mcpServers" object');
2609
+ return result;
2610
+ }
2611
+ for (const [name, entry] of filterNames(Object.entries(servers), names)) {
2612
+ if (!isRecord3(entry)) {
2613
+ result.errors.push(`${name}: not an object`);
2614
+ continue;
2615
+ }
2616
+ result.servers.push({ name, config: convertVSCodeEntry(entry) });
2617
+ }
2618
+ return result;
2619
+ }
2620
+ function convertVSCodeEntry(entry) {
2621
+ const config = {};
2622
+ for (const [key, value] of Object.entries(entry)) {
2623
+ if (key === "type" || key === "command" || key === "args" || key === "cwd" || key === "url") {
2624
+ config[key] = value;
2625
+ } else if (key === "headers" && isStringRecord2(value)) {
2626
+ const headers = {};
2627
+ for (const [h, v] of Object.entries(value)) headers[h] = replaceInputVars(v);
2628
+ config.headers = headers;
2629
+ } else if (key === "env" && isStringRecord2(value)) {
2630
+ const env = {};
2631
+ for (const [k, v] of Object.entries(value)) env[k] = replaceInputVars(v);
2632
+ config.env = env;
2633
+ } else {
2634
+ config[key] = value;
2635
+ }
2636
+ }
2637
+ return config;
2638
+ }
2639
+ function importSixServers(text, names) {
2640
+ const result = { servers: [], errors: [] };
2641
+ const { parsed, error } = tryParse(text);
2642
+ if (error) {
2643
+ result.errors.push(error);
2644
+ return result;
2645
+ }
2646
+ if (!isRecord3(parsed)) {
2647
+ result.errors.push("expected an object");
2648
+ return result;
2649
+ }
2650
+ const servers = parsed.servers;
2651
+ if (!isRecord3(servers)) {
2652
+ result.errors.push('no "servers" object');
2653
+ return result;
2654
+ }
2655
+ for (const [name, entry] of filterNames(Object.entries(servers), names)) {
2656
+ if (!isRecord3(entry)) {
2657
+ result.errors.push(`${name}: not an object`);
2658
+ continue;
2659
+ }
2660
+ const config = {};
2661
+ if (typeof entry.url === "string") config.url = entry.url;
2662
+ if (isStringRecord2(entry.headers)) config.headers = entry.headers;
2663
+ if (typeof entry.exposure === "string") config.exposure = entry.exposure;
2664
+ if (isRecord3(entry.toolExposure)) config.toolExposure = entry.toolExposure;
2665
+ result.servers.push({ name, config });
2666
+ }
2667
+ return result;
2668
+ }
2669
+ function importCodexToml(text, names) {
2670
+ const result = { servers: [], errors: [] };
2671
+ const sections = parseTomlSections(text);
2672
+ for (const [sectionName, fields] of sections) {
2673
+ const prefix = "mcp_servers.";
2674
+ if (!sectionName.startsWith(prefix)) continue;
2675
+ const name = sectionName.slice(prefix.length);
2676
+ if (names && names.length > 0 && !names.includes(name)) continue;
2677
+ const config = {};
2678
+ if (fields.command) config.command = fields.command;
2679
+ if (fields.args) config.args = fields.args;
2680
+ if (fields.url) config.url = fields.url;
2681
+ if (fields.env && isRecord3(fields.env)) config.env = fields.env;
2682
+ result.servers.push({ name, config });
2683
+ }
2684
+ return result;
2685
+ }
2686
+ function parseTomlSections(text) {
2687
+ const sections = [];
2688
+ let current;
2689
+ let currentName = "";
2690
+ for (const line of text.split("\n")) {
2691
+ const trimmed = line.trim();
2692
+ if (trimmed.startsWith("#") || trimmed === "") continue;
2693
+ const sectionMatch = /^\[([^\]]+)\]/.exec(trimmed);
2694
+ if (sectionMatch) {
2695
+ if (current) sections.push([currentName, current]);
2696
+ currentName = sectionMatch[1].trim();
2697
+ current = {};
2698
+ continue;
2699
+ }
2700
+ if (!current) continue;
2701
+ const kvMatch = /^(\w+)\s*=\s*(.+)/.exec(trimmed);
2702
+ if (kvMatch) {
2703
+ current[kvMatch[1]] = parseTomlValue(kvMatch[2].trim());
2704
+ }
2705
+ }
2706
+ if (current) sections.push([currentName, current]);
2707
+ return sections;
2708
+ }
2709
+ function parseTomlValue(raw) {
2710
+ if (raw.startsWith('"') && raw.endsWith('"')) return raw.slice(1, -1);
2711
+ if (raw.startsWith("'") && raw.endsWith("'")) return raw.slice(1, -1);
2712
+ if (raw.startsWith("[")) {
2713
+ try {
2714
+ return JSON.parse(raw.replace(/'/g, '"'));
2715
+ } catch {
2716
+ return raw;
2717
+ }
2718
+ }
2719
+ if (raw === "true") return true;
2720
+ if (raw === "false") return false;
2721
+ const num = Number(raw);
2722
+ if (!Number.isNaN(num)) return num;
2723
+ return raw;
2724
+ }
2725
+ function convertOpenCodeLocal(entry) {
2726
+ const config = {};
2727
+ if (Array.isArray(entry.command) && entry.command.length > 0) {
2728
+ config.command = entry.command[0];
2729
+ if (entry.command.length > 1) config.args = entry.command.slice(1);
2730
+ } else if (typeof entry.command === "string") {
2731
+ config.command = entry.command;
2732
+ }
2733
+ if (isRecord3(entry.environment)) {
2734
+ const env = {};
2735
+ for (const [k, v] of Object.entries(entry.environment)) {
2736
+ if (typeof v === "string")
2737
+ env[k] = v.replace(/\{env:([^}]+)\}/g, (_, n) => `\${${n}}`);
2738
+ }
2739
+ config.env = env;
2740
+ }
2741
+ return config;
2742
+ }
2743
+ function convertOpenCodeRemote(entry) {
2744
+ const config = {};
2745
+ config.url = entry.url;
2746
+ if (isStringRecord2(entry.headers)) config.headers = entry.headers;
2747
+ return config;
2748
+ }
2749
+ function importOpenCode(text, names) {
2750
+ const result = { servers: [], errors: [] };
2751
+ const { parsed, error } = tryParse(text);
2752
+ if (error) {
2753
+ result.errors.push(error);
2754
+ return result;
2755
+ }
2756
+ if (!isRecord3(parsed)) {
2757
+ result.errors.push("expected an object");
2758
+ return result;
2759
+ }
2760
+ const mcpServers = parsed.mcpServers ?? parsed.mcp;
2761
+ if (!isRecord3(mcpServers)) {
2762
+ result.errors.push("no mcp servers found");
2763
+ return result;
2764
+ }
2765
+ for (const [name, entry] of filterNames(Object.entries(mcpServers), names)) {
2766
+ if (!isRecord3(entry)) {
2767
+ result.errors.push(`${name}: not an object`);
2768
+ continue;
2769
+ }
2770
+ const isLocal = entry.type === "local" || entry.command;
2771
+ const config = isLocal ? convertOpenCodeLocal(entry) : convertOpenCodeRemote(entry);
2772
+ result.servers.push({ name, config });
2773
+ }
2774
+ return result;
2775
+ }
2776
+ function detectFormat(text, filename) {
2777
+ if (filename.endsWith(".toml")) return "codex";
2778
+ if (filename.includes("servers.json")) return "six";
2779
+ if (filename.includes(".vscode")) return "vscode";
2780
+ try {
2781
+ const parsed = JSON.parse(text);
2782
+ if (isRecord3(parsed)) {
2783
+ if (parsed.servers && isRecord3(parsed.servers) && parsed.version !== void 0) return "six";
2784
+ if (parsed.servers && isRecord3(parsed.servers)) return "vscode";
2785
+ if (parsed.mcpServers) return "claude";
2786
+ if (parsed.mcp) return "opencode";
2787
+ }
2788
+ } catch {
2789
+ }
2790
+ if (text.includes("[mcp_servers.")) return "codex";
2791
+ return "claude";
2792
+ }
2793
+ function importFile(text, filename, names) {
2794
+ const format = detectFormat(text, filename);
2795
+ switch (format) {
2796
+ case "claude":
2797
+ return importClaude(text, names);
2798
+ case "vscode":
2799
+ return importVSCode(text, names);
2800
+ case "six":
2801
+ return importSixServers(text, names);
2802
+ case "codex":
2803
+ return importCodexToml(text, names);
2804
+ case "opencode":
2805
+ return importOpenCode(text, names);
2806
+ default:
2807
+ return importClaude(text, names);
2808
+ }
2809
+ }
2810
+
2811
+ // src/config/warnings.ts
2812
+ var SECRET_HEADER = /authorization|cookie|token|key|secret|password|session/i;
2813
+ var PLACEHOLDER = /\$\{([A-Za-z_][A-Za-z0-9_]*)\}/g;
2814
+ var SCHEME = /^\s*(?:bearer|basic|token|apikey|api-key)\s+/i;
2815
+ var SECRET_PARAM = /token|key|secret|password|signature|sig|auth|code/i;
2816
+ function literalSecret(header, value) {
2817
+ if (!SECRET_HEADER.test(header)) return false;
2818
+ return value.replace(PLACEHOLDER, "").replace(SCHEME, "").trim() !== "";
2819
+ }
2820
+ function urlHasSecret(url) {
2821
+ if (!URL.canParse(url)) return void 0;
2822
+ const parsed = new URL(url);
2823
+ if (parsed.username || parsed.password) return "url";
2824
+ for (const name of parsed.searchParams.keys()) if (SECRET_PARAM.test(name)) return `url ?${name}`;
2825
+ return void 0;
2826
+ }
2827
+ var SLICC_OAUTH_REFUSED = {
2828
+ clientSecret: "a client secret would have to live in mcp.json; SLICC signs in as a public client",
2829
+ callbackPort: "SLICC signs in through its own page, not a loopback port",
2830
+ callbackUrl: "SLICC signs in through its own page, not a loopback URL",
2831
+ clientRegistration: "SLICC registers dynamically or uses oauth.clientId"
2832
+ };
2833
+ function checkAgentWarnings(name, config) {
2834
+ const warnings = [];
2835
+ if ("command" in config && config.command) {
2836
+ warnings.push({
2837
+ server: name,
2838
+ message: "stdio servers need slicc-kernel#68, so the agent will skip this entry",
2839
+ advice: "use mcpcat serve --proxy to bridge it as HTTP"
2840
+ });
2841
+ }
2842
+ if ("headers" in config && config.headers) {
2843
+ for (const [h, v] of Object.entries(config.headers)) {
2844
+ if (v.trim().startsWith("!")) {
2845
+ warnings.push({
2846
+ server: name,
2847
+ message: `headers.${h} runs a command, which the agent can't do`,
2848
+ advice: "use ${NAME} and set the value in Settings > MCP servers"
2849
+ });
2850
+ }
2851
+ if (literalSecret(h, v)) {
2852
+ warnings.push({
2853
+ server: name,
2854
+ message: `headers.${h} holds a literal secret`,
2855
+ advice: "write ${NAME} instead, and add the value in Settings > MCP servers"
2856
+ });
2857
+ }
2858
+ }
2859
+ }
2860
+ if ("url" in config && config.url) {
2861
+ const secret = urlHasSecret(config.url);
2862
+ if (secret) {
2863
+ warnings.push({
2864
+ server: name,
2865
+ message: `${secret} holds a literal secret`,
2866
+ advice: "put it in a header as ${NAME}"
2867
+ });
2868
+ }
2869
+ }
2870
+ if ("oauth" in config && config.oauth) {
2871
+ for (const key of Object.keys(config.oauth)) {
2872
+ const reason = SLICC_OAUTH_REFUSED[key];
2873
+ if (reason) {
2874
+ warnings.push({
2875
+ server: name,
2876
+ message: `oauth.${key} is not supported by the agent: ${reason}`,
2877
+ advice: `remove oauth.${key}`
2878
+ });
2879
+ }
2880
+ }
2881
+ }
2882
+ return warnings;
2883
+ }
2884
+
2885
+ // src/cli/config-verbs.ts
2886
+ function parseHeader(hdr) {
2887
+ const colonIdx = hdr.indexOf(":");
2888
+ if (colonIdx < 1) return `invalid header '${hdr}'`;
2889
+ return [hdr.slice(0, colonIdx).trim(), hdr.slice(colonIdx + 1).trim()];
2890
+ }
2891
+ function parseEnvPair(pair) {
2892
+ const eqIdx = pair.indexOf("=");
2893
+ if (eqIdx < 1) return `invalid --env '${pair}'`;
2894
+ return [pair.slice(0, eqIdx), pair.slice(eqIdx + 1)];
2895
+ }
2896
+ function parseAddFlagAt(flagArgs, i, flags) {
2897
+ const arg = flagArgs[i];
2898
+ if (arg === "-l" || arg === "--local") {
2899
+ flags.local = true;
2900
+ return { next: i + 1 };
2901
+ }
2902
+ if (arg === "--no-probe") {
2903
+ flags.noProbe = true;
2904
+ return { next: i + 1 };
2905
+ }
2906
+ if (arg === "--config" && i + 1 < flagArgs.length) {
2907
+ flags.configFile = flagArgs[i + 1];
2908
+ return { next: i + 2 };
2909
+ }
2910
+ if (arg === "--description" && i + 1 < flagArgs.length) {
2911
+ flags.description = flagArgs[i + 1];
2912
+ return { next: i + 2 };
2913
+ }
2914
+ if (arg === "--exposure" && i + 1 < flagArgs.length) {
2915
+ flags.exposure = flagArgs[i + 1];
2916
+ return { next: i + 2 };
2917
+ }
2918
+ if ((arg === "-H" || arg === "--header") && i + 1 < flagArgs.length) {
2919
+ const result = parseHeader(flagArgs[i + 1]);
2920
+ if (typeof result === "string") return { next: i + 2, error: result };
2921
+ flags.headers.push(result);
2922
+ return { next: i + 2 };
2923
+ }
2924
+ if (arg === "--env" && i + 1 < flagArgs.length) {
2925
+ const result = parseEnvPair(flagArgs[i + 1]);
2926
+ if (typeof result === "string") return { next: i + 2, error: result };
2927
+ flags.envPairs.push(result);
2928
+ return { next: i + 2 };
2929
+ }
2930
+ if (arg === "--cwd" && i + 1 < flagArgs.length) {
2931
+ flags.cwd = flagArgs[i + 1];
2932
+ return { next: i + 2 };
2933
+ }
2934
+ if (arg.startsWith("-")) return { next: i + 1, error: `unknown option '${arg}'` };
2935
+ flags.positional.push(arg);
2936
+ return { next: i + 1 };
2937
+ }
2938
+ function parseAddFlags(args2) {
2939
+ const flags = {
2940
+ local: false,
2941
+ noProbe: false,
2942
+ headers: [],
2943
+ envPairs: [],
2944
+ positional: [],
2945
+ cmdArgs: []
2946
+ };
2947
+ const dashDashIdx = args2.indexOf("--");
2948
+ const flagArgs = dashDashIdx >= 0 ? args2.slice(0, dashDashIdx) : args2;
2949
+ flags.cmdArgs = dashDashIdx >= 0 ? args2.slice(dashDashIdx + 1) : [];
2950
+ let i = 0;
2951
+ while (i < flagArgs.length) {
2952
+ const step = parseAddFlagAt(flagArgs, i, flags);
2953
+ if (step.error) return step.error;
2954
+ i = step.next;
2955
+ }
2956
+ return flags;
2957
+ }
2958
+ function buildStdioConfig(flags) {
2959
+ const config = {
2960
+ command: flags.cmdArgs[0],
2961
+ ...flags.cmdArgs.length > 1 ? { args: flags.cmdArgs.slice(1) } : {}
2962
+ };
2963
+ if (flags.envPairs.length > 0) {
2964
+ const env = {};
2965
+ for (const [k, v] of flags.envPairs) env[k] = v;
2966
+ config.env = env;
2967
+ }
2968
+ if (flags.cwd) config.cwd = flags.cwd;
2969
+ if (flags.description) config.description = flags.description;
2970
+ if (flags.exposure) config.exposure = flags.exposure;
2971
+ return config;
2972
+ }
2973
+ function buildHttpConfig(urlStr, flags) {
2974
+ const config = { url: urlStr };
2975
+ if (flags.headers.length > 0) {
2976
+ const hdrs = {};
2977
+ for (const [k, v] of flags.headers) {
2978
+ if (literalSecret(k, v)) return `header '${k}' holds a literal secret; use \${NAME} instead`;
2979
+ hdrs[k] = v;
2980
+ }
2981
+ config.headers = hdrs;
2982
+ }
2983
+ if (flags.description) config.description = flags.description;
2984
+ if (flags.exposure) config.exposure = flags.exposure;
2985
+ return config;
2986
+ }
2987
+ async function runAdd(platform2, args2, probe) {
2988
+ const flags = parseAddFlags(args2);
2989
+ if (typeof flags === "string") {
2990
+ await platform2.write(2, `mcpcat add: ${flags}
2991
+ `);
2992
+ return 2;
2993
+ }
2994
+ if (flags.positional.length < 1) {
2995
+ await platform2.write(2, "mcpcat add: requires a server name\n");
2996
+ return 2;
2997
+ }
2998
+ let name;
2999
+ let config;
3000
+ if (flags.cmdArgs.length > 0) {
3001
+ name = flags.positional[0];
3002
+ config = buildStdioConfig(flags);
3003
+ } else if (flags.positional.length >= 2) {
3004
+ let urlStr;
3005
+ if (URL.canParse(flags.positional[0]) && !URL.canParse(flags.positional[1])) {
3006
+ urlStr = flags.positional[0];
3007
+ name = flags.positional[1];
3008
+ } else {
3009
+ name = flags.positional[0];
3010
+ urlStr = flags.positional[1];
3011
+ }
3012
+ if (!URL.canParse(urlStr)) {
3013
+ await platform2.write(2, `mcpcat add: '${urlStr}' is not a valid URL
3014
+ `);
3015
+ return 2;
3016
+ }
3017
+ const secret = urlHasSecret(urlStr);
3018
+ if (secret) {
3019
+ await platform2.write(
3020
+ 2,
3021
+ `mcpcat add: ${secret} holds a literal secret; use \${NAME} in a header instead
3022
+ `
3023
+ );
3024
+ return 2;
3025
+ }
3026
+ const httpResult = buildHttpConfig(urlStr, flags);
3027
+ if (typeof httpResult === "string") {
3028
+ await platform2.write(2, `mcpcat add: ${httpResult}
3029
+ `);
3030
+ return 2;
3031
+ }
3032
+ config = httpResult;
3033
+ } else {
3034
+ await platform2.write(2, "mcpcat add: requires a URL or -- <command>\n");
3035
+ return 2;
3036
+ }
3037
+ const validation = validateServer(name, config);
3038
+ if ("message" in validation) {
3039
+ await platform2.write(2, `mcpcat add: ${validation.message}
3040
+ `);
3041
+ return 2;
3042
+ }
3043
+ const path = flags.configFile ?? (flags.local ? projectConfigPath(platform2) : userConfigPath(platform2));
3044
+ const { replaced } = await addServer(platform2.fs, path, name, config);
3045
+ const warnings = checkAgentWarnings(name, config);
3046
+ for (const w of warnings) {
3047
+ await platform2.write(2, `mcpcat add: warning: ${w.message}; ${w.advice}
3048
+ `);
3049
+ }
3050
+ await platform2.write(1, `${replaced ? "replaced" : "added"} ${name} in ${path}
3051
+ `);
3052
+ if (!flags.noProbe && probe) {
3053
+ const reason = await probe(name, config, platform2);
3054
+ if (reason) {
3055
+ await platform2.write(2, `mcpcat add: probe failed: ${reason}
3056
+ `);
3057
+ return 3;
3058
+ }
3059
+ }
3060
+ return 0;
3061
+ }
3062
+ async function runRm(platform2, args2) {
3063
+ let configFile;
3064
+ let local = false;
3065
+ const names = [];
3066
+ for (let i = 0; i < args2.length; i++) {
3067
+ const arg = args2[i];
3068
+ if (arg === "--config" && i + 1 < args2.length) {
3069
+ configFile = args2[++i];
3070
+ continue;
3071
+ }
3072
+ if (arg === "-l" || arg === "--local") {
3073
+ local = true;
3074
+ continue;
3075
+ }
3076
+ if (arg === "--logout") continue;
3077
+ if (arg.startsWith("-")) {
3078
+ await platform2.write(2, `mcpcat rm: unknown option '${arg}'
3079
+ `);
3080
+ return 2;
3081
+ }
3082
+ names.push(arg);
3083
+ }
3084
+ if (names.length === 0) {
3085
+ await platform2.write(2, "mcpcat rm: requires a server name\n");
3086
+ return 2;
3087
+ }
3088
+ const path = configFile ?? (local ? projectConfigPath(platform2) : userConfigPath(platform2));
3089
+ for (const name of names) {
3090
+ const removed = await removeServer(platform2.fs, path, name);
3091
+ if (!removed) {
3092
+ await platform2.write(2, `mcpcat rm: '${name}' not found in ${path}
3093
+ `);
3094
+ return 1;
3095
+ }
3096
+ await platform2.write(1, `removed ${name} from ${path}
3097
+ `);
3098
+ }
3099
+ return 0;
3100
+ }
3101
+ function formatListEntry(entry) {
3102
+ const c = entry.config;
3103
+ const transport = "url" in c ? "http" : "stdio";
3104
+ const target = "url" in c ? c.url : c.command;
3105
+ const enabled = "enabled" in c && c.enabled === false ? " (disabled)" : "";
3106
+ const exp = c.exposure ? ` [${c.exposure}]` : "";
3107
+ return `${entry.name} ${transport} ${target}${enabled}${exp} (${entry.source})`;
3108
+ }
3109
+ async function printListJson(platform2, config) {
3110
+ const entries = {};
3111
+ for (const entry of config.servers) {
3112
+ entries[entry.name] = {
3113
+ ...entry.config,
3114
+ source: entry.source,
3115
+ ...entry.scope ? { scope: entry.scope } : {},
3116
+ ...entry.override ? { override: entry.override } : {}
3117
+ };
3118
+ }
3119
+ await platform2.write(1, `${JSON.stringify(entries, null, 2)}
3120
+ `);
3121
+ }
3122
+ async function printListText(platform2, config) {
3123
+ if (config.servers.length === 0 && config.problems.length === 0) {
3124
+ await platform2.write(1, "no servers configured\n");
3125
+ return;
3126
+ }
3127
+ for (const entry of config.servers) {
3128
+ await platform2.write(1, `${formatListEntry(entry)}
3129
+ `);
3130
+ }
3131
+ }
3132
+ async function runList(platform2, args2) {
3133
+ let json = false;
3134
+ let configFile;
3135
+ let trustProject = false;
3136
+ for (let i = 0; i < args2.length; i++) {
3137
+ const arg = args2[i];
3138
+ if (arg === "--json") {
3139
+ json = true;
3140
+ continue;
3141
+ }
3142
+ if (arg === "--config" && i + 1 < args2.length) {
3143
+ configFile = args2[++i];
3144
+ continue;
3145
+ }
3146
+ if (arg === "--trust-project") {
3147
+ trustProject = true;
3148
+ }
3149
+ }
3150
+ const source = await MergedConfigSource.load(platform2, { trustProject, configFile });
3151
+ const { config } = source;
3152
+ if (json) {
3153
+ await printListJson(platform2, config);
3154
+ } else {
3155
+ await printListText(platform2, config);
3156
+ }
3157
+ let hasProblems = false;
3158
+ for (const p of config.problems) {
3159
+ hasProblems = true;
3160
+ const prefix = p.server ? `${p.server}: ` : "";
3161
+ await platform2.write(2, `mcpcat list: ${prefix}${p.message}
3162
+ `);
3163
+ }
3164
+ return hasProblems ? 1 : 0;
3165
+ }
3166
+ async function runEnable(platform2, args2, enable) {
3167
+ let configFile;
3168
+ let local = false;
3169
+ const names = [];
3170
+ for (let i = 0; i < args2.length; i++) {
3171
+ const arg = args2[i];
3172
+ if (arg === "--config" && i + 1 < args2.length) {
3173
+ configFile = args2[++i];
3174
+ continue;
3175
+ }
3176
+ if (arg === "-l" || arg === "--local") {
3177
+ local = true;
3178
+ continue;
3179
+ }
3180
+ if (arg.startsWith("-")) continue;
3181
+ names.push(arg);
3182
+ }
3183
+ if (names.length === 0) {
3184
+ await platform2.write(2, `mcpcat ${enable ? "enable" : "disable"}: requires a server name
3185
+ `);
3186
+ return 2;
3187
+ }
3188
+ const path = configFile ?? (local ? projectConfigPath(platform2) : userConfigPath(platform2));
3189
+ for (const name of names) {
3190
+ const ok = await updateServer(platform2.fs, path, name, { enabled: enable });
3191
+ if (!ok) {
3192
+ await platform2.write(
3193
+ 2,
3194
+ `mcpcat ${enable ? "enable" : "disable"}: '${name}' not found in ${path}
3195
+ `
3196
+ );
3197
+ return 1;
3198
+ }
3199
+ await platform2.write(1, `${enable ? "enabled" : "disabled"} ${name} in ${path}
3200
+ `);
3201
+ }
3202
+ return 0;
3203
+ }
3204
+ async function runExposure(platform2, args2) {
3205
+ let configFile;
3206
+ let local = false;
3207
+ let toolGlob;
3208
+ const positional = [];
3209
+ for (let i = 0; i < args2.length; i++) {
3210
+ const arg = args2[i];
3211
+ if (arg === "--config" && i + 1 < args2.length) {
3212
+ configFile = args2[++i];
3213
+ continue;
3214
+ }
3215
+ if (arg === "-l" || arg === "--local") {
3216
+ local = true;
3217
+ continue;
3218
+ }
3219
+ if (arg === "--tool" && i + 1 < args2.length) {
3220
+ toolGlob = args2[++i];
3221
+ continue;
3222
+ }
3223
+ if (arg.startsWith("-")) continue;
3224
+ positional.push(arg);
3225
+ }
3226
+ if (positional.length < 2) {
3227
+ await platform2.write(2, "mcpcat exposure: requires <name> <mode>\n");
3228
+ return 2;
3229
+ }
3230
+ const [name, mode] = positional;
3231
+ const validModes = ["codemode", "deferred", "direct", "hidden"];
3232
+ if (!validModes.includes(mode)) {
3233
+ await platform2.write(2, `mcpcat exposure: mode must be one of ${validModes.join(", ")}
3234
+ `);
3235
+ return 2;
3236
+ }
3237
+ const path = configFile ?? (local ? projectConfigPath(platform2) : userConfigPath(platform2));
3238
+ const ok = await setExposure(platform2.fs, path, name, mode, toolGlob);
3239
+ if (!ok) {
3240
+ await platform2.write(2, `mcpcat exposure: '${name}' not found in ${path}
3241
+ `);
3242
+ return 1;
3243
+ }
3244
+ const what = toolGlob ? `${name} tool ${toolGlob}` : name;
3245
+ await platform2.write(1, `set ${what} to ${mode} in ${path}
3246
+ `);
3247
+ return 0;
3248
+ }
3249
+ async function runImport(platform2, args2) {
3250
+ let configFile;
3251
+ let local = false;
3252
+ const positional = [];
3253
+ for (let i = 0; i < args2.length; i++) {
3254
+ const arg = args2[i];
3255
+ if (arg === "--config" && i + 1 < args2.length) {
3256
+ configFile = args2[++i];
3257
+ continue;
3258
+ }
3259
+ if (arg === "-l" || arg === "--local") {
3260
+ local = true;
3261
+ continue;
3262
+ }
3263
+ if (arg.startsWith("-")) continue;
3264
+ positional.push(arg);
3265
+ }
3266
+ if (positional.length < 1) {
3267
+ await platform2.write(2, "mcpcat import: requires a file path\n");
3268
+ return 2;
3269
+ }
3270
+ const filePath = positional[0];
3271
+ const names = positional.slice(1);
3272
+ let text;
3273
+ try {
3274
+ const data = await platform2.fs.readFile(filePath);
3275
+ text = new TextDecoder().decode(data);
3276
+ } catch {
3277
+ await platform2.write(2, `mcpcat import: cannot read '${filePath}'
3278
+ `);
3279
+ return 2;
3280
+ }
3281
+ const result = importFile(text, filePath, names.length > 0 ? names : void 0);
3282
+ for (const err of result.errors) {
3283
+ await platform2.write(2, `mcpcat import: ${err}
3284
+ `);
3285
+ }
3286
+ if (result.servers.length === 0) {
3287
+ await platform2.write(2, "mcpcat import: no servers found\n");
3288
+ return 1;
3289
+ }
3290
+ const destPath = configFile ?? (local ? projectConfigPath(platform2) : userConfigPath(platform2));
3291
+ for (const { name, config } of result.servers) {
3292
+ const validation = validateServer(name, config);
3293
+ if ("message" in validation) {
3294
+ await platform2.write(2, `mcpcat import: ${name}: ${validation.message}
3295
+ `);
3296
+ continue;
3297
+ }
3298
+ const { replaced } = await addServer(platform2.fs, destPath, name, config);
3299
+ await platform2.write(1, `${replaced ? "replaced" : "imported"} ${name}
3300
+ `);
3301
+ }
3302
+ return 0;
3303
+ }
2
3304
 
3
3305
  // src/cli/main.ts
4
3306
  var VERBS = [
5
3307
  "info",
6
3308
  "tools",
3309
+ "ls",
7
3310
  "call",
8
3311
  "describe",
9
3312
  "resources",
@@ -28,6 +3331,31 @@ var VERBS = [
28
3331
  "inbox",
29
3332
  "help"
30
3333
  ];
3334
+ var CONFIG_VERBS = {
3335
+ add: true,
3336
+ rm: true,
3337
+ list: true,
3338
+ enable: true,
3339
+ disable: true,
3340
+ exposure: true,
3341
+ import: true
3342
+ };
3343
+ var UNIMPLEMENTED_VERBS = /* @__PURE__ */ new Set([
3344
+ "resources",
3345
+ "read",
3346
+ "prompts",
3347
+ "prompt",
3348
+ "grep",
3349
+ "complete",
3350
+ "task",
3351
+ "login",
3352
+ "logout",
3353
+ "serve",
3354
+ "code",
3355
+ "discover",
3356
+ "inbox"
3357
+ ]);
3358
+ var CLIENT_VERBS = /* @__PURE__ */ new Set(["info", "tools", "call", "describe"]);
31
3359
  function helpText(version) {
32
3360
  return `mcpcat ${version} \u2013 a curl for the Model Context Protocol
33
3361
 
@@ -35,25 +3363,18 @@ Usage: mcpcat [options] <target> [<tool> [args\u2026]]
35
3363
  mcpcat [options] <verb> \u2026
36
3364
 
37
3365
  Client verbs:
38
- info <target> Server identity and capabilities (not implemented yet)
39
- tools <target> List tools (not implemented yet)
40
- call <target> <tool> Call a tool (not implemented yet)
41
- describe <target> [tool] Tool schema and help (not implemented yet)
42
- resources <target> List resources (not implemented yet)
43
- read <target> <uri> Read a resource (not implemented yet)
44
- prompts <target> List prompts (not implemented yet)
45
- prompt <target> <name> Render a prompt (not implemented yet)
46
- grep <pattern> Search across servers (not implemented yet)
47
- complete <target> \u2026 Completion (not implemented yet)
48
- task get|cancel \u2026 Task extension (not implemented yet)
3366
+ info <target> Server identity and capabilities
3367
+ tools <target> List tools
3368
+ call <target> <tool> Call a tool
3369
+ describe <target> [tool] Tool schema and help
49
3370
 
50
3371
  Configuration verbs:
51
- add <name> <url> Add a server to mcp.json (not implemented yet)
52
- rm <name> Remove a server (not implemented yet)
53
- list List configured servers (not implemented yet)
54
- enable|disable <name> Toggle a server (not implemented yet)
55
- exposure <name> <mode> Set exposure mode (not implemented yet)
56
- import <file> Import from other configs (not implemented yet)
3372
+ add <name> <url> Add a server to mcp.json
3373
+ rm <name> Remove a server
3374
+ list List configured servers
3375
+ enable|disable <name> Toggle a server
3376
+ exposure <name> <mode> Set exposure mode
3377
+ import <file> Import from other configs
57
3378
  login <target> OAuth sign-in (not implemented yet)
58
3379
  logout <target> Remove tokens (not implemented yet)
59
3380
 
@@ -67,7 +3388,10 @@ Server and code verbs:
67
3388
  Options:
68
3389
  --json JSON output
69
3390
  -H, --header 'N: V' Extra header
3391
+ -d <json> Tool arguments as JSON (or @file, @- for stdin)
70
3392
  -m, --timeout <s> Request timeout (default 60)
3393
+ --protocol-version <v> Pin protocol version
3394
+ --schema Print raw JSON Schema (describe)
71
3395
  -v, --verbose Trace on stderr
72
3396
  -s, --silent Suppress progress
73
3397
  -o, --output <path> Write result to file
@@ -75,6 +3399,289 @@ Options:
75
3399
  -h, --help Show this help
76
3400
  `;
77
3401
  }
3402
+ function parseFlags(args2) {
3403
+ const headers = {};
3404
+ let dataArg;
3405
+ let protocolVersion;
3406
+ let showSchema = false;
3407
+ const positional = [];
3408
+ let trailingCommand;
3409
+ for (let i = 0; i < args2.length; i++) {
3410
+ const arg = args2[i];
3411
+ if (arg === "--") {
3412
+ trailingCommand = args2.slice(i + 1);
3413
+ break;
3414
+ }
3415
+ if ((arg === "-H" || arg === "--header") && args2[i + 1]) {
3416
+ const val = args2[++i];
3417
+ const colonIdx = val.indexOf(":");
3418
+ if (colonIdx > 0) {
3419
+ headers[val.slice(0, colonIdx).trim()] = val.slice(colonIdx + 1).trim();
3420
+ }
3421
+ } else if (arg === "-d" && args2[i + 1]) {
3422
+ dataArg = args2[++i];
3423
+ } else if (arg === "--protocol-version" && args2[i + 1]) {
3424
+ protocolVersion = args2[++i];
3425
+ } else if (arg === "--schema") {
3426
+ showSchema = true;
3427
+ } else if (!arg.startsWith("-")) {
3428
+ positional.push(arg);
3429
+ }
3430
+ }
3431
+ return { headers, dataArg, protocolVersion, showSchema, positional, trailingCommand };
3432
+ }
3433
+ function resolveVerb(positional, trailingCommand) {
3434
+ let verb;
3435
+ let target;
3436
+ let toolName;
3437
+ if (positional.length >= 1 && VERBS.includes(positional[0])) {
3438
+ verb = positional[0];
3439
+ if (verb === "ls") verb = "tools";
3440
+ target = positional[1];
3441
+ toolName = positional[2];
3442
+ } else if (positional.length >= 2) {
3443
+ target = positional[0];
3444
+ toolName = positional[1];
3445
+ verb = "call";
3446
+ } else if (positional.length === 1) {
3447
+ target = positional[0];
3448
+ verb = "info";
3449
+ }
3450
+ if (!target && trailingCommand && trailingCommand.length > 0) {
3451
+ target = "__trailing__";
3452
+ }
3453
+ return {
3454
+ verb,
3455
+ target,
3456
+ toolName,
3457
+ dataArg: void 0,
3458
+ headers: {},
3459
+ protocolVersion: void 0,
3460
+ showSchema: false,
3461
+ trailingCommand
3462
+ };
3463
+ }
3464
+ function parseArgs(args2) {
3465
+ const flags = parseFlags(args2);
3466
+ const resolved = resolveVerb(flags.positional, flags.trailingCommand);
3467
+ return {
3468
+ ...resolved,
3469
+ headers: flags.headers,
3470
+ dataArg: flags.dataArg,
3471
+ protocolVersion: flags.protocolVersion,
3472
+ showSchema: flags.showSchema
3473
+ };
3474
+ }
3475
+ function formatToolLine(tool) {
3476
+ const desc = tool.description ? ` ${tool.description}` : "";
3477
+ return `${tool.name}${desc}`;
3478
+ }
3479
+ function formatToolSchema(tool) {
3480
+ const lines = [];
3481
+ lines.push(`${tool.name}`);
3482
+ if (tool.description) lines.push(` ${tool.description}`);
3483
+ if (tool.inputSchema) {
3484
+ lines.push(" Input schema:");
3485
+ lines.push(` ${JSON.stringify(tool.inputSchema, null, 2).split("\n").join("\n ")}`);
3486
+ }
3487
+ const annotations = tool.annotations;
3488
+ if (annotations) {
3489
+ lines.push(" Annotations:");
3490
+ for (const [k, v] of Object.entries(annotations)) {
3491
+ lines.push(` ${k}: ${JSON.stringify(v)}`);
3492
+ }
3493
+ }
3494
+ return lines.join("\n");
3495
+ }
3496
+ function renderCallResult(result) {
3497
+ const parts = [];
3498
+ if (result.content) {
3499
+ for (const block of result.content) {
3500
+ if (block.type === "text") parts.push(block.text);
3501
+ else if (block.type === "image")
3502
+ parts.push(`[image: ${block.mimeType}, ${Math.ceil(block.data.length * 3 / 4)} bytes]`);
3503
+ else if (block.type === "resource_link") parts.push(`[resource: ${block.uri}]`);
3504
+ else if (block.type === "resource") {
3505
+ parts.push(
3506
+ "text" in block.resource ? block.resource.text : `[blob: ${block.resource.uri}]`
3507
+ );
3508
+ }
3509
+ }
3510
+ }
3511
+ if (parts.length === 0 && result.structuredContent !== void 0) {
3512
+ parts.push(JSON.stringify(result.structuredContent, null, 2));
3513
+ }
3514
+ return parts.join("\n");
3515
+ }
3516
+ async function readDataArg(platform2, dataArg) {
3517
+ let raw;
3518
+ if (dataArg.startsWith("@-")) {
3519
+ const reader = platform2.stdin.getReader();
3520
+ const chunks = [];
3521
+ for (; ; ) {
3522
+ const { done, value } = await reader.read();
3523
+ if (done) break;
3524
+ chunks.push(value);
3525
+ }
3526
+ reader.releaseLock();
3527
+ const total = chunks.reduce((s, c) => s + c.length, 0);
3528
+ const merged = new Uint8Array(total);
3529
+ let offset = 0;
3530
+ for (const c of chunks) {
3531
+ merged.set(c, offset);
3532
+ offset += c.length;
3533
+ }
3534
+ raw = new TextDecoder().decode(merged);
3535
+ } else if (dataArg.startsWith("@")) {
3536
+ const data = await platform2.fs.readFile(dataArg.slice(1));
3537
+ raw = new TextDecoder().decode(data);
3538
+ } else {
3539
+ raw = dataArg;
3540
+ }
3541
+ return JSON.parse(raw);
3542
+ }
3543
+ async function runInfo(platform2, conn) {
3544
+ const info = await conn.info();
3545
+ const lines = [];
3546
+ if (info.name) lines.push(`Server: ${info.name}${info.version ? ` ${info.version}` : ""}`);
3547
+ if (info.title) lines.push(`Title: ${info.title}`);
3548
+ lines.push(`Protocol: ${info.protocolVersion}`);
3549
+ if (info.instructions) lines.push(`Instructions: ${info.instructions}`);
3550
+ const caps = Object.keys(info.capabilities).filter(
3551
+ (k) => info.capabilities[k] != null && info.capabilities[k] !== false
3552
+ );
3553
+ if (caps.length > 0) lines.push(`Capabilities: ${caps.join(", ")}`);
3554
+ try {
3555
+ const tools = await conn.listTools();
3556
+ lines.push(`Tools: ${tools.length}`);
3557
+ } catch {
3558
+ }
3559
+ await platform2.write(1, `${lines.join("\n")}
3560
+ `);
3561
+ return 0;
3562
+ }
3563
+ async function runTools(platform2, conn) {
3564
+ const tools = await conn.listTools();
3565
+ if (tools.length === 0) {
3566
+ await platform2.write(1, "(no tools)\n");
3567
+ } else {
3568
+ await platform2.write(1, `${tools.map(formatToolLine).join("\n")}
3569
+ `);
3570
+ }
3571
+ return 0;
3572
+ }
3573
+ async function runCall(platform2, conn, toolName, dataArg) {
3574
+ let toolArgs = {};
3575
+ if (dataArg) {
3576
+ try {
3577
+ toolArgs = await readDataArg(platform2, dataArg);
3578
+ } catch (err) {
3579
+ await platform2.write(2, `mcpcat: invalid -d argument: ${err.message}
3580
+ `);
3581
+ return 2;
3582
+ }
3583
+ }
3584
+ const result = await conn.callTool(toolName, toolArgs);
3585
+ const output = renderCallResult(result);
3586
+ if (result.isError) {
3587
+ await platform2.write(2, `${output}
3588
+ `);
3589
+ return 1;
3590
+ }
3591
+ await platform2.write(1, `${output}
3592
+ `);
3593
+ return 0;
3594
+ }
3595
+ async function runDescribe(platform2, conn, toolName, showSchema) {
3596
+ const tools = await conn.listTools();
3597
+ if (toolName) {
3598
+ const tool = tools.find((t) => t.name === toolName);
3599
+ if (!tool) {
3600
+ await platform2.write(2, `mcpcat: tool '${toolName}' not found
3601
+ `);
3602
+ return 2;
3603
+ }
3604
+ if (showSchema && tool.inputSchema) {
3605
+ await platform2.write(1, `${JSON.stringify(tool.inputSchema, null, 2)}
3606
+ `);
3607
+ } else {
3608
+ await platform2.write(1, `${formatToolSchema(tool)}
3609
+ `);
3610
+ }
3611
+ } else {
3612
+ for (const tool of tools) {
3613
+ await platform2.write(1, `${formatToolSchema(tool)}
3614
+
3615
+ `);
3616
+ }
3617
+ }
3618
+ return 0;
3619
+ }
3620
+ async function runVerb(platform2, parsed, conn) {
3621
+ switch (parsed.verb) {
3622
+ case "info":
3623
+ return runInfo(platform2, conn);
3624
+ case "tools":
3625
+ return runTools(platform2, conn);
3626
+ case "call":
3627
+ return runCall(platform2, conn, parsed.toolName, parsed.dataArg);
3628
+ case "describe":
3629
+ return runDescribe(platform2, conn, parsed.toolName, parsed.showSchema);
3630
+ default:
3631
+ await platform2.write(2, `mcpcat: unknown command '${parsed.verb}'
3632
+ `);
3633
+ return 2;
3634
+ }
3635
+ }
3636
+ async function probeServer(_name, config, platform2) {
3637
+ try {
3638
+ const opts = { version: "probe" };
3639
+ if ("url" in config && config.url) {
3640
+ opts.url = config.url;
3641
+ if ("headers" in config) opts.headers = config.headers;
3642
+ } else if ("command" in config && config.command) {
3643
+ opts.command = config.command;
3644
+ if ("args" in config) opts.args = config.args;
3645
+ if ("env" in config) opts.env = config.env;
3646
+ if ("cwd" in config) opts.cwd = config.cwd;
3647
+ } else {
3648
+ return void 0;
3649
+ }
3650
+ const conn = await connect(platform2, opts);
3651
+ try {
3652
+ await conn.info();
3653
+ } finally {
3654
+ try {
3655
+ await conn.close();
3656
+ } catch {
3657
+ }
3658
+ }
3659
+ return void 0;
3660
+ } catch (err) {
3661
+ return err.message;
3662
+ }
3663
+ }
3664
+ async function dispatchConfigVerb(platform2, verb, args2) {
3665
+ const rest = args2.slice(1);
3666
+ switch (verb) {
3667
+ case "add":
3668
+ return runAdd(platform2, rest, probeServer);
3669
+ case "rm":
3670
+ return runRm(platform2, rest);
3671
+ case "list":
3672
+ return runList(platform2, rest);
3673
+ case "enable":
3674
+ return runEnable(platform2, rest, true);
3675
+ case "disable":
3676
+ return runEnable(platform2, rest, false);
3677
+ case "exposure":
3678
+ return runExposure(platform2, rest);
3679
+ case "import":
3680
+ return runImport(platform2, rest);
3681
+ default:
3682
+ return 1;
3683
+ }
3684
+ }
78
3685
  async function run(platform2, version, args2) {
79
3686
  if (args2.includes("--version") || args2.includes("-V")) {
80
3687
  await platform2.write(1, `${version}
@@ -89,21 +3696,82 @@ async function run(platform2, version, args2) {
89
3696
  await platform2.write(1, helpText(version));
90
3697
  return 0;
91
3698
  }
92
- const verb = args2[0];
93
- if (VERBS.includes(verb)) {
94
- await platform2.write(2, `mcpcat: '${verb}' is not implemented yet
3699
+ const parsed = parseArgs(args2);
3700
+ if (!parsed.verb) {
3701
+ await platform2.write(2, "mcpcat: no verb or target given\n");
3702
+ return 2;
3703
+ }
3704
+ if (parsed.verb in CONFIG_VERBS) {
3705
+ return dispatchConfigVerb(platform2, parsed.verb, args2);
3706
+ }
3707
+ if (UNIMPLEMENTED_VERBS.has(parsed.verb)) {
3708
+ await platform2.write(2, `mcpcat: '${parsed.verb}' is not implemented yet
95
3709
  `);
96
3710
  return 1;
97
3711
  }
98
- await platform2.write(2, `mcpcat: unknown command '${verb}'
3712
+ if (!CLIENT_VERBS.has(parsed.verb)) {
3713
+ await platform2.write(2, `mcpcat: unknown command '${parsed.verb}'
3714
+ `);
3715
+ return 2;
3716
+ }
3717
+ if (parsed.verb === "call" && !parsed.toolName) {
3718
+ await platform2.write(2, "mcpcat: call requires a tool name\n");
3719
+ return 2;
3720
+ }
3721
+ if (!parsed.target) {
3722
+ await platform2.write(2, `mcpcat: ${parsed.verb} requires a target
3723
+ `);
3724
+ return 2;
3725
+ }
3726
+ const config = await MergedConfigSource.load(platform2);
3727
+ let resolved;
3728
+ try {
3729
+ resolved = await resolveTarget(
3730
+ parsed.target === "__trailing__" ? "" : parsed.target,
3731
+ parsed.trailingCommand,
3732
+ config
3733
+ );
3734
+ } catch (err) {
3735
+ await platform2.write(2, `mcpcat: ${err.message}
3736
+ `);
3737
+ return 2;
3738
+ }
3739
+ let conn;
3740
+ try {
3741
+ conn = await connect(platform2, {
3742
+ ...resolved,
3743
+ headers: { ...resolved.headers, ...parsed.headers },
3744
+ protocolVersion: parsed.protocolVersion,
3745
+ version
3746
+ });
3747
+ } catch (err) {
3748
+ await platform2.write(2, `mcpcat: connection failed: ${err.message}
3749
+ `);
3750
+ return 3;
3751
+ }
3752
+ try {
3753
+ return await runVerb(platform2, parsed, conn);
3754
+ } catch (err) {
3755
+ if (err instanceof InputRequiredError) {
3756
+ await platform2.write(2, `${err.requestJson}
99
3757
  `);
100
- return 2;
3758
+ return 6;
3759
+ }
3760
+ await platform2.write(2, `mcpcat: ${err.message}
3761
+ `);
3762
+ return 5;
3763
+ } finally {
3764
+ try {
3765
+ await conn.close();
3766
+ } catch {
3767
+ }
3768
+ }
101
3769
  }
102
3770
 
103
3771
  // src/platform/host.ts
104
3772
  import { spawn as cpSpawn } from "node:child_process";
105
3773
  import { constants } from "node:fs";
106
- import { access, mkdir, readFile, rename, stat, writeFile } from "node:fs/promises";
3774
+ import { access, mkdir, readFile as readFile2, rename, rmdir, stat, writeFile } from "node:fs/promises";
107
3775
  import { Readable, Writable } from "node:stream";
108
3776
  import * as tty from "node:tty";
109
3777
  function nodeSpawn(options) {
@@ -146,7 +3814,7 @@ function signalNumber(sig) {
146
3814
  }
147
3815
  var hostFs = {
148
3816
  async readFile(path) {
149
- return new Uint8Array(await readFile(path));
3817
+ return new Uint8Array(await readFile2(path));
150
3818
  },
151
3819
  async writeFile(path, data) {
152
3820
  await writeFile(path, data);
@@ -168,6 +3836,12 @@ var hostFs = {
168
3836
  } catch {
169
3837
  return false;
170
3838
  }
3839
+ },
3840
+ async mkdirExclusive(path) {
3841
+ await mkdir(path, { recursive: false });
3842
+ },
3843
+ async rmdir(path) {
3844
+ await rmdir(path);
171
3845
  }
172
3846
  };
173
3847
  function createStdinStream() {
@@ -209,5 +3883,5 @@ function createHostPlatform() {
209
3883
  // src/host.ts
210
3884
  var platform = createHostPlatform();
211
3885
  var args = process.argv.slice(2);
212
- var code = await run(platform, "0.1.0", args);
3886
+ var code = await run(platform, "0.3.0", args);
213
3887
  process.exit(code);