run-mcp 1.6.2 → 1.7.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.
Files changed (3) hide show
  1. package/README.md +75 -0
  2. package/dist/index.js +2133 -860
  3. package/package.json +9 -3
package/dist/index.js CHANGED
@@ -3,10 +3,10 @@
3
3
  // src/index.ts
4
4
  import { program } from "commander";
5
5
  import { createConnection, createServer } from "net";
6
- import { existsSync as existsSync2 } from "fs";
7
- import { mkdir as mkdir2, readFile as readFile3, rm, writeFile as writeFile2 } from "fs/promises";
8
- import { join as join2, resolve } from "path";
9
- import { tmpdir as tmpdir2 } from "os";
6
+ import { existsSync as existsSync6 } from "fs";
7
+ import { mkdir as mkdir3, readFile as readFile5, rm, writeFile as writeFile3 } from "fs/promises";
8
+ import { join as join6, resolve as resolve2 } from "path";
9
+ import { tmpdir as tmpdir3 } from "os";
10
10
  import { spawn } from "child_process";
11
11
 
12
12
  // src/config-scanner.ts
@@ -143,6 +143,7 @@ async function pickDiscoveredServer() {
143
143
  import { mkdir, writeFile } from "fs/promises";
144
144
  import { tmpdir } from "os";
145
145
  import { join } from "path";
146
+ import { exec } from "child_process";
146
147
  var BASE64_PATTERN = /^[A-Za-z0-9+/]{1000,}={0,2}$/;
147
148
  var DEFAULT_TIMEOUT_MS = 3e5;
148
149
  var DEFAULT_MAX_TEXT_LENGTH = 5e4;
@@ -151,12 +152,14 @@ var ResponseInterceptor = class {
151
152
  defaultTimeoutMs;
152
153
  maxTextLength;
153
154
  mediaThresholdKb;
155
+ openMedia;
154
156
  fileCounter = 0;
155
157
  constructor(opts = {}) {
156
158
  this.outDir = opts.outDir ?? join(tmpdir(), "run-mcp");
157
159
  this.defaultTimeoutMs = opts.defaultTimeoutMs ?? DEFAULT_TIMEOUT_MS;
158
160
  this.maxTextLength = opts.maxTextLength ?? DEFAULT_MAX_TEXT_LENGTH;
159
161
  this.mediaThresholdKb = opts.mediaThresholdKb ?? 0;
162
+ this.openMedia = opts.openMedia ?? false;
160
163
  }
161
164
  /**
162
165
  * Call a tool on the target, applying timeout, media extraction, and truncation.
@@ -373,6 +376,13 @@ var ResponseInterceptor = class {
373
376
  const filepath = join(this.outDir, filename);
374
377
  const buffer = Buffer.from(base64Data, "base64");
375
378
  await writeFile(filepath, buffer);
379
+ if (this.openMedia) {
380
+ const isMac = process.platform === "darwin";
381
+ const isWin = process.platform === "win32";
382
+ const cmd = isMac ? "open" : isWin ? "start" : "xdg-open";
383
+ exec(`${cmd} "${filepath}"`, () => {
384
+ });
385
+ }
376
386
  const sizeKB = (buffer.length / 1024).toFixed(1);
377
387
  const label = mediaType === "audio" ? "Audio" : "Image";
378
388
  return {
@@ -423,8 +433,41 @@ var ResponseInterceptor = class {
423
433
  }
424
434
  };
425
435
 
426
- // src/parsing.ts
436
+ // src/colors.ts
427
437
  import pc from "picocolors";
438
+ function shouldEnableColor() {
439
+ const dashDashIdx = process.argv.indexOf("--");
440
+ const argsToCheck = dashDashIdx === -1 ? process.argv : process.argv.slice(0, dashDashIdx);
441
+ for (let i = 0; i < argsToCheck.length; i++) {
442
+ const arg = argsToCheck[i];
443
+ if (arg.startsWith("--color=")) {
444
+ const val = arg.split("=")[1];
445
+ if (val === "always") return true;
446
+ if (val === "never") return false;
447
+ } else if (arg === "--color") {
448
+ const val = argsToCheck[i + 1];
449
+ if (val === "always") return true;
450
+ if (val === "never") return false;
451
+ }
452
+ }
453
+ if (process.env.CLICOLOR_FORCE !== void 0 && process.env.CLICOLOR_FORCE !== "0") {
454
+ return true;
455
+ }
456
+ if (process.env.NO_COLOR) {
457
+ return false;
458
+ }
459
+ if (process.env.CLICOLOR === "0") {
460
+ return false;
461
+ }
462
+ const isatty = !!(process.stdout?.isTTY || process.stderr?.isTTY);
463
+ if (!isatty) {
464
+ return false;
465
+ }
466
+ return true;
467
+ }
468
+ var colors = pc.createColors(shouldEnableColor());
469
+
470
+ // src/parsing.ts
428
471
  function parseCommandLine(input3) {
429
472
  const spaceIdx = input3.indexOf(" ");
430
473
  if (spaceIdx === -1) {
@@ -466,10 +509,10 @@ function colorizeJson(json) {
466
509
  if (ch === '"') {
467
510
  const str = consumeString(json, i);
468
511
  if (expectingValue) {
469
- result.push(pc.green(str));
512
+ result.push(colors.green(str));
470
513
  expectingValue = false;
471
514
  } else {
472
- result.push(pc.cyan(str));
515
+ result.push(colors.cyan(str));
473
516
  }
474
517
  i += str.length;
475
518
  continue;
@@ -493,19 +536,19 @@ function colorizeJson(json) {
493
536
  continue;
494
537
  }
495
538
  if (json.startsWith("true", i)) {
496
- result.push(pc.magenta("true"));
539
+ result.push(colors.magenta("true"));
497
540
  expectingValue = false;
498
541
  i += 4;
499
542
  continue;
500
543
  }
501
544
  if (json.startsWith("false", i)) {
502
- result.push(pc.magenta("false"));
545
+ result.push(colors.magenta("false"));
503
546
  expectingValue = false;
504
547
  i += 5;
505
548
  continue;
506
549
  }
507
550
  if (json.startsWith("null", i)) {
508
- result.push(pc.dim("null"));
551
+ result.push(colors.dim("null"));
509
552
  expectingValue = false;
510
553
  i += 4;
511
554
  continue;
@@ -516,7 +559,7 @@ function colorizeJson(json) {
516
559
  num += json[i];
517
560
  i++;
518
561
  }
519
- result.push(pc.yellow(num));
562
+ result.push(colors.yellow(num));
520
563
  expectingValue = false;
521
564
  continue;
522
565
  }
@@ -834,7 +877,425 @@ function interpolateString(input3, context) {
834
877
 
835
878
  // src/target-manager.ts
836
879
  import { EventEmitter } from "events";
880
+ import { execSync } from "child_process";
881
+ import { writeFileSync, rmSync, existsSync as existsSync3 } from "fs";
882
+ import { join as join3 } from "path";
883
+ import { tmpdir as tmpdir2 } from "os";
837
884
  import treeKill from "tree-kill";
885
+
886
+ // src/settings.ts
887
+ import { existsSync as existsSync2, readFileSync } from "fs";
888
+ import { dirname, join as join2, resolve, isAbsolute } from "path";
889
+ import { homedir as homedir2 } from "os";
890
+ function resolvePath(p, settingsFileDir) {
891
+ if (p.startsWith("~")) {
892
+ return resolve(homedir2(), p.slice(1).replace(/^[/\\]/, ""));
893
+ }
894
+ if (p.startsWith("$HOME")) {
895
+ return resolve(homedir2(), p.slice(5).replace(/^[/\\]/, ""));
896
+ }
897
+ if (isAbsolute(p)) {
898
+ return resolve(p);
899
+ }
900
+ return resolve(settingsFileDir, p);
901
+ }
902
+ function matchDomain(host, pattern) {
903
+ if (pattern === "*") return true;
904
+ const [hostName, hostPort] = host.split(":");
905
+ const [patternName, patternPort] = pattern.split(":");
906
+ if (patternPort && patternPort !== "*") {
907
+ if (hostPort !== patternPort) return false;
908
+ }
909
+ const regexPattern = patternName.replace(/\./g, "\\.").replace(/\*/g, ".*");
910
+ const regex = new RegExp(`^${regexPattern}$`, "i");
911
+ return regex.test(hostName);
912
+ }
913
+ var SandboxPolicy = class {
914
+ // Allowlists (Absolute paths or patterns)
915
+ fileReadAllow = /* @__PURE__ */ new Set();
916
+ fileWriteAllow = /* @__PURE__ */ new Set();
917
+ networkAllow = /* @__PURE__ */ new Set();
918
+ // Denylists (Absolute paths or patterns)
919
+ fileReadDeny = /* @__PURE__ */ new Set();
920
+ fileWriteDeny = /* @__PURE__ */ new Set();
921
+ networkDeny = /* @__PURE__ */ new Set();
922
+ constructor() {
923
+ }
924
+ /**
925
+ * Merge a SandboxConfig block into the policy.
926
+ * Path rules are resolved relative to the settingsFileDir.
927
+ */
928
+ mergeConfig(config, settingsFileDir) {
929
+ if (config.file?.allow?.read) {
930
+ for (const p of config.file.allow.read) {
931
+ this.fileReadAllow.add(resolvePath(p, settingsFileDir));
932
+ }
933
+ }
934
+ if (config.file?.allow?.write) {
935
+ for (const p of config.file.allow.write) {
936
+ this.fileWriteAllow.add(resolvePath(p, settingsFileDir));
937
+ }
938
+ }
939
+ if (config.file?.deny?.read) {
940
+ for (const p of config.file.deny.read) {
941
+ this.fileReadDeny.add(resolvePath(p, settingsFileDir));
942
+ }
943
+ }
944
+ if (config.file?.deny?.write) {
945
+ for (const p of config.file.deny.write) {
946
+ this.fileWriteDeny.add(resolvePath(p, settingsFileDir));
947
+ }
948
+ }
949
+ if (config.network?.allow) {
950
+ for (const host of config.network.allow) {
951
+ this.networkAllow.add(host);
952
+ }
953
+ }
954
+ if (config.network?.deny) {
955
+ for (const host of config.network.deny) {
956
+ this.networkDeny.add(host);
957
+ }
958
+ }
959
+ }
960
+ /**
961
+ * Merge raw command line overrides.
962
+ * CLI overrides are resolved relative to the process CWD.
963
+ */
964
+ mergeCliOverrides(options) {
965
+ const cwd = process.cwd();
966
+ if (options.allowRead) {
967
+ for (const p of options.allowRead) this.fileReadAllow.add(resolvePath(p, cwd));
968
+ }
969
+ if (options.allowWrite) {
970
+ for (const p of options.allowWrite) this.fileWriteAllow.add(resolvePath(p, cwd));
971
+ }
972
+ if (options.allowNet) {
973
+ for (const host of options.allowNet) this.networkAllow.add(host);
974
+ }
975
+ if (options.denyRead) {
976
+ for (const p of options.denyRead) this.fileReadDeny.add(resolvePath(p, cwd));
977
+ }
978
+ if (options.denyWrite) {
979
+ for (const p of options.denyWrite) this.fileWriteDeny.add(resolvePath(p, cwd));
980
+ }
981
+ if (options.denyNet) {
982
+ for (const host of options.denyNet) this.networkDeny.add(host);
983
+ }
984
+ }
985
+ /**
986
+ * Automatically deny reading sensitive credential directories if network outbound is allowed.
987
+ */
988
+ applyCredentialProtections() {
989
+ if (this.networkAllow.size > 0 && !this.networkDeny.has("*")) {
990
+ const sensitivePatterns = [
991
+ "~/.ssh",
992
+ "~/.aws",
993
+ "~/.kube",
994
+ "~/.config/gcloud",
995
+ "~/.netrc",
996
+ "~/.npmrc"
997
+ ];
998
+ const cwd = process.cwd();
999
+ for (const pattern of sensitivePatterns) {
1000
+ const resolved = resolvePath(pattern, cwd);
1001
+ if (!this.fileReadAllow.has(resolved)) {
1002
+ this.fileReadDeny.add(resolved);
1003
+ }
1004
+ }
1005
+ }
1006
+ }
1007
+ /**
1008
+ * Evaluates if a file read path is allowed, under Deny-Wins precedence.
1009
+ */
1010
+ isFileReadAllowed(p) {
1011
+ const absPath = resolve(p);
1012
+ for (const denyPath of this.fileReadDeny) {
1013
+ if (absPath === denyPath || absPath.startsWith(denyPath + "/")) {
1014
+ return false;
1015
+ }
1016
+ }
1017
+ for (const allowPath of this.fileReadAllow) {
1018
+ if (absPath === allowPath || absPath.startsWith(allowPath + "/")) {
1019
+ return true;
1020
+ }
1021
+ }
1022
+ return false;
1023
+ }
1024
+ /**
1025
+ * Evaluates if a file write path is allowed, under Deny-Wins precedence.
1026
+ */
1027
+ isFileWriteAllowed(p) {
1028
+ const absPath = resolve(p);
1029
+ for (const denyPath of this.fileWriteDeny) {
1030
+ if (absPath === denyPath || absPath.startsWith(denyPath + "/")) {
1031
+ return false;
1032
+ }
1033
+ }
1034
+ for (const allowPath of this.fileWriteAllow) {
1035
+ if (absPath === allowPath || absPath.startsWith(allowPath + "/")) {
1036
+ return true;
1037
+ }
1038
+ }
1039
+ return false;
1040
+ }
1041
+ /**
1042
+ * Evaluates if network access to a host (hostname:port or hostname) is allowed.
1043
+ */
1044
+ isNetworkAllowed(host) {
1045
+ for (const denyPattern of this.networkDeny) {
1046
+ if (matchDomain(host, denyPattern)) {
1047
+ return false;
1048
+ }
1049
+ }
1050
+ for (const allowPattern of this.networkAllow) {
1051
+ if (matchDomain(host, allowPattern)) {
1052
+ return true;
1053
+ }
1054
+ }
1055
+ return false;
1056
+ }
1057
+ /**
1058
+ * Generate Scheme-based macOS Seatbelt profile.
1059
+ */
1060
+ getSeatbeltProfile(options) {
1061
+ if (options.audit) {
1062
+ return `(version 1)
1063
+ (allow default)
1064
+ (deny network*)
1065
+ (deny file-write*)
1066
+ (deny process-fork)
1067
+ (deny process-exec*)
1068
+ (allow process-exec*
1069
+ (subpath "/System")
1070
+ (subpath "/usr")
1071
+ (subpath "/bin")
1072
+ (subpath "${options.nodeBinDir}")
1073
+ )
1074
+ (deny file-read*)
1075
+ (allow file-read*
1076
+ (literal "/")
1077
+ (subpath "/System")
1078
+ (subpath "/usr")
1079
+ (subpath "/dev")
1080
+ (subpath "/private/var")
1081
+ (subpath "/var")
1082
+ (subpath "/private/etc")
1083
+ (subpath "/etc")
1084
+ (subpath "${options.nodeBinDir}")
1085
+ (subpath "${options.nodeInstallDir}")
1086
+ (subpath "${options.cwd}")
1087
+ (path-ancestors "${options.cwd}")
1088
+ (path-ancestors "${options.nodeBinDir}")
1089
+ )
1090
+ `;
1091
+ }
1092
+ const readDirectives = Array.from(this.fileReadAllow).map((p) => ` (subpath "${p}")`).join("\n");
1093
+ const writeDirectives = Array.from(this.fileWriteAllow).map((p) => ` (subpath "${p}")`).join("\n");
1094
+ const readDenyDirectives = Array.from(this.fileReadDeny).map((p) => ` (subpath "${p}")`).join("\n");
1095
+ const writeDenyDirectives = Array.from(this.fileWriteDeny).map((p) => ` (subpath "${p}")`).join("\n");
1096
+ let netRules;
1097
+ if (this.networkAllow.size > 0 && !this.networkDeny.has("*")) {
1098
+ netRules = `(allow network-outbound)
1099
+ `;
1100
+ if (this.networkDeny.size > 0) {
1101
+ for (const pattern of this.networkDeny) {
1102
+ if (pattern !== "*") {
1103
+ netRules += `(deny network-outbound (remote ip "${pattern}"))
1104
+ `;
1105
+ }
1106
+ }
1107
+ }
1108
+ } else {
1109
+ netRules = `(deny network*)
1110
+ `;
1111
+ }
1112
+ return `(version 1)
1113
+ (allow default)
1114
+ ${netRules}
1115
+ (deny file-write*)
1116
+ (allow file-write*
1117
+ (subpath "${options.tmp}")
1118
+ (subpath "/private/tmp")
1119
+ (subpath "/tmp")
1120
+ ${writeDirectives}
1121
+ )
1122
+ ${writeDenyDirectives ? `(deny file-write*
1123
+ ${writeDenyDirectives}
1124
+ )
1125
+ ` : ""}
1126
+ (deny file-read*)
1127
+ (allow file-read*
1128
+ (literal "/")
1129
+ (subpath "/System")
1130
+ (subpath "/usr")
1131
+ (subpath "/dev")
1132
+ (subpath "/private/var")
1133
+ (subpath "/var")
1134
+ (subpath "/private/etc")
1135
+ (subpath "/etc")
1136
+ (subpath "/private/tmp")
1137
+ (subpath "/tmp")
1138
+ (subpath "${options.nodeBinDir}")
1139
+ (subpath "${options.nodeInstallDir}")
1140
+ (subpath "${options.cwd}")
1141
+ (subpath "${options.tmp}")
1142
+ (path-ancestors "${options.cwd}")
1143
+ (path-ancestors "${options.nodeBinDir}")
1144
+ ${readDirectives}
1145
+ )
1146
+ ${readDenyDirectives ? `(deny file-read*
1147
+ ${readDenyDirectives}
1148
+ )
1149
+ ` : ""}
1150
+ `;
1151
+ }
1152
+ };
1153
+ function loadSettings(cwd = process.cwd()) {
1154
+ const settings = { sandbox: {} };
1155
+ const loadedConfigs = [];
1156
+ let managedPath;
1157
+ if (process.platform === "darwin") {
1158
+ managedPath = "/Library/Application Support/run-mcp/settings.json";
1159
+ } else if (process.platform === "win32") {
1160
+ managedPath = join2(process.env.ProgramFiles || "C:\\Program Files", "run-mcp", "settings.json");
1161
+ } else {
1162
+ managedPath = "/etc/run-mcp/settings.json";
1163
+ }
1164
+ if (existsSync2(managedPath)) {
1165
+ try {
1166
+ const config = JSON.parse(readFileSync(managedPath, "utf8"));
1167
+ if (config.sandbox) loadedConfigs.push({ config: config.sandbox, dir: dirname(managedPath) });
1168
+ } catch {
1169
+ }
1170
+ }
1171
+ const userPath = join2(homedir2(), ".run-mcp", "settings.json");
1172
+ if (existsSync2(userPath)) {
1173
+ try {
1174
+ const config = JSON.parse(readFileSync(userPath, "utf8"));
1175
+ if (config.sandbox) loadedConfigs.push({ config: config.sandbox, dir: dirname(userPath) });
1176
+ } catch {
1177
+ }
1178
+ }
1179
+ const projectPath = join2(cwd, ".run-mcp", "settings.json");
1180
+ if (existsSync2(projectPath)) {
1181
+ try {
1182
+ const config = JSON.parse(readFileSync(projectPath, "utf8"));
1183
+ if (config.sandbox) loadedConfigs.push({ config: config.sandbox, dir: dirname(projectPath) });
1184
+ } catch {
1185
+ }
1186
+ }
1187
+ const localPath = join2(cwd, ".run-mcp", "settings.local.json");
1188
+ if (existsSync2(localPath)) {
1189
+ try {
1190
+ const config = JSON.parse(readFileSync(localPath, "utf8"));
1191
+ if (config.sandbox) loadedConfigs.push({ config: config.sandbox, dir: dirname(localPath) });
1192
+ } catch {
1193
+ }
1194
+ }
1195
+ const policy = new SandboxPolicy();
1196
+ for (const item of loadedConfigs) {
1197
+ policy.mergeConfig(item.config, item.dir);
1198
+ }
1199
+ settings.sandbox = {
1200
+ file: {
1201
+ allow: {
1202
+ read: Array.from(policy.fileReadAllow),
1203
+ write: Array.from(policy.fileWriteAllow)
1204
+ },
1205
+ deny: {
1206
+ read: Array.from(policy.fileReadDeny),
1207
+ write: Array.from(policy.fileWriteDeny)
1208
+ }
1209
+ },
1210
+ network: {
1211
+ allow: Array.from(policy.networkAllow),
1212
+ deny: Array.from(policy.networkDeny)
1213
+ }
1214
+ };
1215
+ return settings;
1216
+ }
1217
+
1218
+ // src/proxy-audit.ts
1219
+ import http from "http";
1220
+ import net from "net";
1221
+ var NetworkAuditProxy = class {
1222
+ server;
1223
+ port = 0;
1224
+ constructor() {
1225
+ this.server = http.createServer((req, res) => {
1226
+ const url = req.url || "";
1227
+ console.error(`\x1B[36m\u{1F310} [NETWORK AUDIT] HTTP request to: ${url}\x1B[0m`);
1228
+ try {
1229
+ const parsedUrl = new URL(url);
1230
+ const options = {
1231
+ hostname: parsedUrl.hostname,
1232
+ port: parsedUrl.port || 80,
1233
+ path: parsedUrl.pathname + parsedUrl.search,
1234
+ method: req.method,
1235
+ headers: req.headers
1236
+ };
1237
+ const proxyReq = http.request(options, (proxyRes) => {
1238
+ res.writeHead(proxyRes.statusCode || 200, proxyRes.headers);
1239
+ proxyRes.pipe(res);
1240
+ });
1241
+ proxyReq.on("error", (err) => {
1242
+ res.writeHead(502);
1243
+ res.end(`Proxy Error: ${err.message}`);
1244
+ });
1245
+ req.pipe(proxyReq);
1246
+ } catch (err) {
1247
+ res.writeHead(400);
1248
+ res.end(`Invalid URL: ${err.message}`);
1249
+ }
1250
+ });
1251
+ this.server.on("connect", (req, clientSocket, head) => {
1252
+ const parts = req.url ? req.url.split(":") : [];
1253
+ const hostname = parts[0];
1254
+ const port = parts[1] ? Number.parseInt(parts[1], 10) : 443;
1255
+ console.error(
1256
+ `\x1B[36m\u{1F310} [NETWORK AUDIT] HTTPS connection established to: ${hostname}:${port}\x1B[0m`
1257
+ );
1258
+ const serverSocket = net.connect(port, hostname, () => {
1259
+ clientSocket.write("HTTP/1.1 200 Connection Established\r\n\r\n");
1260
+ serverSocket.write(head);
1261
+ serverSocket.pipe(clientSocket);
1262
+ clientSocket.pipe(serverSocket);
1263
+ });
1264
+ serverSocket.on("error", (err) => {
1265
+ clientSocket.end(`HTTP/1.1 502 Bad Gateway\r
1266
+ \r
1267
+ Proxy Connection Error: ${err.message}`);
1268
+ });
1269
+ clientSocket.on("error", () => {
1270
+ serverSocket.end();
1271
+ });
1272
+ });
1273
+ }
1274
+ async start() {
1275
+ return new Promise((resolve3, reject) => {
1276
+ this.server.on("error", (err) => {
1277
+ reject(err);
1278
+ });
1279
+ this.server.listen(0, "127.0.0.1", () => {
1280
+ const addr = this.server.address();
1281
+ if (addr && typeof addr === "object") {
1282
+ this.port = addr.port;
1283
+ }
1284
+ resolve3(this.port);
1285
+ });
1286
+ });
1287
+ }
1288
+ async close() {
1289
+ return new Promise((resolve3) => {
1290
+ this.server.close(() => resolve3());
1291
+ });
1292
+ }
1293
+ getPort() {
1294
+ return this.port;
1295
+ }
1296
+ };
1297
+
1298
+ // src/target-manager.ts
838
1299
  import { Client } from "@modelcontextprotocol/sdk/client/index.js";
839
1300
  import { SSEClientTransport } from "@modelcontextprotocol/sdk/client/sse.js";
840
1301
  import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
@@ -852,11 +1313,29 @@ var MIN_UPTIME_FOR_RESTART_MS = 5e3;
852
1313
  var MAX_RECONNECT_ATTEMPTS = 3;
853
1314
  var STABLE_CONNECTION_RESET_MS = 6e4;
854
1315
  var MAX_HISTORY = 100;
1316
+ function isCommandAvailable(cmd) {
1317
+ try {
1318
+ const checkCmd = process.platform === "win32" ? `where ${cmd}` : `command -v ${cmd}`;
1319
+ execSync(checkCmd, { stdio: "ignore" });
1320
+ return true;
1321
+ } catch {
1322
+ return false;
1323
+ }
1324
+ }
855
1325
  var TargetManager = class _TargetManager extends EventEmitter {
856
- constructor(command, args) {
1326
+ constructor(command, args, options = {}) {
857
1327
  super();
858
1328
  this.command = command;
859
1329
  this.args = args;
1330
+ this.sandboxMode = options.sandbox ?? "none";
1331
+ this.sandboxOptions = {
1332
+ allowRead: options.allowRead,
1333
+ allowWrite: options.allowWrite,
1334
+ allowNet: options.allowNet,
1335
+ denyRead: options.denyRead,
1336
+ denyWrite: options.denyWrite,
1337
+ denyNet: options.denyNet
1338
+ };
860
1339
  }
861
1340
  client = null;
862
1341
  transport = null;
@@ -883,6 +1362,12 @@ var TargetManager = class _TargetManager extends EventEmitter {
883
1362
  static MAX_NOTIFICATIONS = 200;
884
1363
  // Roots
885
1364
  _roots = [];
1365
+ _proxy = null;
1366
+ _proxyPort = 0;
1367
+ sandboxMode;
1368
+ _tempSbPath = null;
1369
+ _useWindowsMxc = false;
1370
+ sandboxOptions;
886
1371
  /**
887
1372
  * Enable auto-reconnect behavior.
888
1373
  * Only applies to interactive REPL mode — proxy mode manages its own lifecycle.
@@ -901,11 +1386,27 @@ var TargetManager = class _TargetManager extends EventEmitter {
901
1386
  if (this.command.startsWith("http://") || this.command.startsWith("https://")) {
902
1387
  this.transport = new SSEClientTransport(new URL(this.command));
903
1388
  } else {
1389
+ const policy = new SandboxPolicy();
1390
+ const fileSettings = loadSettings();
1391
+ if (fileSettings.sandbox) {
1392
+ policy.mergeConfig(fileSettings.sandbox, process.cwd());
1393
+ }
1394
+ policy.mergeCliOverrides(this.sandboxOptions);
1395
+ policy.applyCredentialProtections();
1396
+ if (this.sandboxMode !== "none" && this.sandboxMode !== "audit" && policy.networkAllow.size > 0 && !policy.networkDeny.has("*")) {
1397
+ this._proxy = new NetworkAuditProxy();
1398
+ this._proxyPort = await this._proxy.start();
1399
+ }
1400
+ const { command: finalCommand, args: finalArgs } = await this._maybeWrapCommand(policy);
904
1401
  const stdioTransport = new StdioClientTransport({
905
- command: this.command,
906
- args: this.args,
907
- stderr: "pipe"
1402
+ command: finalCommand,
1403
+ args: finalArgs,
1404
+ stderr: "pipe",
1405
+ env: this._getDefaultEnvironment()
908
1406
  });
1407
+ if (this._useWindowsMxc) {
1408
+ await this._applyWindowsMxcOverride(stdioTransport, finalCommand, finalArgs, policy);
1409
+ }
909
1410
  stdioTransport.stderr?.on("data", (chunk) => {
910
1411
  const text = chunk.toString().trimEnd();
911
1412
  if (text) {
@@ -915,13 +1416,21 @@ var TargetManager = class _TargetManager extends EventEmitter {
915
1416
  if (this._stderrLines.length > _TargetManager.MAX_STDERR_LINES) {
916
1417
  this._stderrLines = this._stderrLines.slice(-_TargetManager.MAX_STDERR_LINES);
917
1418
  }
1419
+ if (this.sandboxMode === "audit") {
1420
+ const lowerText = text.toLowerCase();
1421
+ if (lowerText.includes("eperm") || lowerText.includes("eacces") || lowerText.includes("permission denied") || lowerText.includes("operation not permitted") || lowerText.includes("enotfound") || lowerText.includes("command not found") || lowerText.includes("cannot execute")) {
1422
+ console.error(
1423
+ `\x1B[31m\u26A0\uFE0F [SANDBOX AUDIT] Blocked unauthorized side-effect: ${text}\x1B[0m`
1424
+ );
1425
+ }
1426
+ }
918
1427
  this.emit("stderr", text);
919
1428
  }
920
1429
  });
921
1430
  this.transport = stdioTransport;
922
1431
  }
923
1432
  this.client = new Client(
924
- { name: "run-mcp", version: "1.6.2" },
1433
+ { name: "run-mcp", version: "1.7.0" },
925
1434
  {
926
1435
  capabilities: {
927
1436
  roots: { listChanged: true },
@@ -979,7 +1488,7 @@ var TargetManager = class _TargetManager extends EventEmitter {
979
1488
  this.emit("notification", record);
980
1489
  });
981
1490
  this.client.setRequestHandler(CreateMessageRequestSchema, async (request) => {
982
- return new Promise((resolve2, reject) => {
1491
+ return new Promise((resolve3, reject) => {
983
1492
  const timeout = setTimeout(() => {
984
1493
  reject(new Error("Sampling request timed out (no response from user in 5 minutes)"));
985
1494
  }, 3e5);
@@ -987,7 +1496,7 @@ var TargetManager = class _TargetManager extends EventEmitter {
987
1496
  request: request.params,
988
1497
  respond: (result) => {
989
1498
  clearTimeout(timeout);
990
- resolve2(result);
1499
+ resolve3(result);
991
1500
  },
992
1501
  reject: (err) => {
993
1502
  clearTimeout(timeout);
@@ -997,7 +1506,7 @@ var TargetManager = class _TargetManager extends EventEmitter {
997
1506
  });
998
1507
  });
999
1508
  this.client.setRequestHandler(ElicitRequestSchema, async (request) => {
1000
- return new Promise((resolve2, reject) => {
1509
+ return new Promise((resolve3, reject) => {
1001
1510
  const timeout = setTimeout(() => {
1002
1511
  reject(new Error("Elicitation request timed out (no response from user in 5 minutes)"));
1003
1512
  }, 3e5);
@@ -1005,7 +1514,7 @@ var TargetManager = class _TargetManager extends EventEmitter {
1005
1514
  request: request.params,
1006
1515
  respond: (result) => {
1007
1516
  clearTimeout(timeout);
1008
- resolve2(result);
1517
+ resolve3(result);
1009
1518
  },
1010
1519
  reject: (err) => {
1011
1520
  clearTimeout(timeout);
@@ -1343,7 +1852,8 @@ var TargetManager = class _TargetManager extends EventEmitter {
1343
1852
  lastResponseTime: this._lastResponseTime,
1344
1853
  stderrLineCount: this._stderrLineCount,
1345
1854
  reconnectAttempts: this._reconnectAttempts,
1346
- maxReconnectAttempts: MAX_RECONNECT_ATTEMPTS
1855
+ maxReconnectAttempts: MAX_RECONNECT_ATTEMPTS,
1856
+ sandbox: this.sandboxMode
1347
1857
  };
1348
1858
  }
1349
1859
  /**
@@ -1368,10 +1878,25 @@ var TargetManager = class _TargetManager extends EventEmitter {
1368
1878
  this.transport = null;
1369
1879
  }
1370
1880
  if (pidToKill) {
1371
- await new Promise((resolve2) => {
1372
- treeKill(pidToKill, "SIGKILL", () => resolve2());
1881
+ await new Promise((resolve3) => {
1882
+ treeKill(pidToKill, "SIGKILL", () => resolve3());
1373
1883
  });
1374
1884
  }
1885
+ if (this._tempSbPath && existsSync3(this._tempSbPath)) {
1886
+ try {
1887
+ rmSync(this._tempSbPath, { force: true });
1888
+ } catch {
1889
+ }
1890
+ this._tempSbPath = null;
1891
+ }
1892
+ if (this._proxy) {
1893
+ try {
1894
+ await this._proxy.close();
1895
+ } catch {
1896
+ }
1897
+ this._proxy = null;
1898
+ this._proxyPort = 0;
1899
+ }
1375
1900
  this._connected = false;
1376
1901
  this.childPid = null;
1377
1902
  }
@@ -1443,6 +1968,264 @@ var TargetManager = class _TargetManager extends EventEmitter {
1443
1968
  }
1444
1969
  }
1445
1970
  // ─── Internal helpers ──────────────────────────────────────────────────────
1971
+ async _maybeWrapCommand(policy) {
1972
+ let command = this.command;
1973
+ let args = [...this.args];
1974
+ let modeToUse = this.sandboxMode;
1975
+ if (modeToUse === "auto") {
1976
+ if (process.platform === "darwin") {
1977
+ if (isCommandAvailable("sandbox-exec")) {
1978
+ modeToUse = "native";
1979
+ } else {
1980
+ process.stderr.write("Warning: sandbox-exec is not available. Sandboxing disabled.\n");
1981
+ modeToUse = "none";
1982
+ }
1983
+ } else if (process.platform === "linux") {
1984
+ if (isCommandAvailable("bwrap")) {
1985
+ modeToUse = "native";
1986
+ } else {
1987
+ process.stderr.write("Warning: bwrap is not available. Sandboxing disabled.\n");
1988
+ modeToUse = "none";
1989
+ }
1990
+ } else if (process.platform === "win32") {
1991
+ let hasMxc = false;
1992
+ try {
1993
+ const mxcModule = "@microsoft/mxc-sdk";
1994
+ await import(mxcModule);
1995
+ hasMxc = true;
1996
+ } catch {
1997
+ }
1998
+ if (hasMxc) {
1999
+ modeToUse = "native";
2000
+ } else {
2001
+ process.stderr.write(
2002
+ "Warning: @microsoft/mxc-sdk is not available. Sandboxing disabled.\n"
2003
+ );
2004
+ modeToUse = "none";
2005
+ }
2006
+ } else {
2007
+ process.stderr.write(
2008
+ `Warning: Sandboxing not supported on platform "${process.platform}". Sandboxing disabled.
2009
+ `
2010
+ );
2011
+ modeToUse = "none";
2012
+ }
2013
+ }
2014
+ if (modeToUse === "docker") {
2015
+ let image = "node:20";
2016
+ if (command.includes("python")) {
2017
+ image = "python:3";
2018
+ }
2019
+ const cwd = process.cwd();
2020
+ args = [
2021
+ "run",
2022
+ "-i",
2023
+ "--rm",
2024
+ "--net=none",
2025
+ "-v",
2026
+ `${cwd}:/workspace`,
2027
+ "-w",
2028
+ "/workspace",
2029
+ image,
2030
+ command,
2031
+ ...args
2032
+ ];
2033
+ command = "docker";
2034
+ } else if (modeToUse === "native" || modeToUse === "audit") {
2035
+ const isAudit = modeToUse === "audit";
2036
+ if (process.platform === "darwin") {
2037
+ if (!isCommandAvailable("sandbox-exec")) {
2038
+ throw new Error(
2039
+ "sandbox-exec not found. Native sandboxing is not available on this macOS host."
2040
+ );
2041
+ }
2042
+ const cwd = process.cwd();
2043
+ const tmp = tmpdir2();
2044
+ const nodeBinDir = join3(process.execPath, "..");
2045
+ const nodeInstallDir = join3(nodeBinDir, "..");
2046
+ const profile = policy.getSeatbeltProfile({
2047
+ tmp,
2048
+ cwd,
2049
+ nodeBinDir,
2050
+ nodeInstallDir,
2051
+ audit: isAudit
2052
+ });
2053
+ const tempSbPath = join3(
2054
+ tmp,
2055
+ `run-mcp-sandbox-${Date.now()}-${Math.random().toString(36).slice(2)}.sb`
2056
+ );
2057
+ writeFileSync(tempSbPath, profile, "utf8");
2058
+ this._tempSbPath = tempSbPath;
2059
+ args = ["-f", tempSbPath, command, ...args];
2060
+ command = "sandbox-exec";
2061
+ } else if (process.platform === "linux") {
2062
+ if (!isCommandAvailable("bwrap")) {
2063
+ throw new Error(
2064
+ "bwrap not found. Native sandboxing is not available on this Linux host."
2065
+ );
2066
+ }
2067
+ const cwd = process.cwd();
2068
+ const tmp = tmpdir2();
2069
+ const bwrapArgs = [
2070
+ "--ro-bind",
2071
+ "/usr",
2072
+ "/usr",
2073
+ "--ro-bind",
2074
+ "/lib",
2075
+ "/lib",
2076
+ "--ro-bind-try",
2077
+ "/lib64",
2078
+ "/lib64",
2079
+ "--ro-bind-try",
2080
+ "/bin",
2081
+ "/bin",
2082
+ "--ro-bind-try",
2083
+ "/sbin",
2084
+ "/sbin",
2085
+ "--ro-bind-try",
2086
+ "/etc",
2087
+ "/etc",
2088
+ "--ro-bind",
2089
+ cwd,
2090
+ cwd,
2091
+ "--dev",
2092
+ "/dev",
2093
+ "--proc",
2094
+ "/proc",
2095
+ "--unshare-pid",
2096
+ "--unshare-user",
2097
+ "--unshare-ipc"
2098
+ ];
2099
+ if (isAudit) {
2100
+ bwrapArgs.push("--unshare-net");
2101
+ bwrapArgs.push("--ro-bind", tmp, tmp);
2102
+ } else {
2103
+ bwrapArgs.push("--bind", tmp, tmp);
2104
+ if (policy.networkAllow.size === 0 || policy.networkDeny.has("*")) {
2105
+ bwrapArgs.push("--unshare-net");
2106
+ }
2107
+ for (const p of policy.fileReadAllow) {
2108
+ if (existsSync3(p)) {
2109
+ bwrapArgs.push("--ro-bind", p, p);
2110
+ }
2111
+ }
2112
+ for (const p of policy.fileWriteAllow) {
2113
+ if (existsSync3(p)) {
2114
+ bwrapArgs.push("--bind", p, p);
2115
+ }
2116
+ }
2117
+ }
2118
+ args = [...bwrapArgs, command, ...args];
2119
+ command = "bwrap";
2120
+ } else if (process.platform === "win32") {
2121
+ let hasMxc = false;
2122
+ try {
2123
+ const mxcModule = "@microsoft/mxc-sdk";
2124
+ await import(mxcModule);
2125
+ hasMxc = true;
2126
+ } catch {
2127
+ }
2128
+ if (!hasMxc) {
2129
+ throw new Error(
2130
+ "@microsoft/mxc-sdk not found. Native sandboxing is not available on this Windows host."
2131
+ );
2132
+ }
2133
+ this._useWindowsMxc = true;
2134
+ } else {
2135
+ throw new Error(`Native sandboxing not supported on platform "${process.platform}"`);
2136
+ }
2137
+ }
2138
+ return { command, args };
2139
+ }
2140
+ async _applyWindowsMxcOverride(transport, command, args, policyObj) {
2141
+ const mxcModule = "@microsoft/mxc-sdk";
2142
+ const mxcSdk = await import(mxcModule);
2143
+ const isAudit = this.sandboxMode === "audit";
2144
+ const readPaths = isAudit ? [process.cwd()] : [process.cwd(), tmpdir2(), ...Array.from(policyObj.fileReadAllow)];
2145
+ const writePaths = isAudit ? [] : [tmpdir2(), ...Array.from(policyObj.fileWriteAllow)];
2146
+ const policy = {
2147
+ filesystem: {
2148
+ read: readPaths,
2149
+ write: writePaths
2150
+ },
2151
+ network: {
2152
+ outbound: isAudit ? "block" : policyObj.networkAllow.size > 0 ? "allow" : "block"
2153
+ }
2154
+ };
2155
+ const config = mxcSdk.createConfigFromPolicy(policy);
2156
+ transport.start = async () => {
2157
+ if (transport._process) {
2158
+ throw new Error("StdioClientTransport already started!");
2159
+ }
2160
+ return new Promise((resolve3, reject) => {
2161
+ try {
2162
+ const env = {
2163
+ ...this._getDefaultEnvironment(),
2164
+ ...transport._serverParams.env
2165
+ };
2166
+ transport._process = mxcSdk.spawnSandboxFromConfig(config, command, args, {
2167
+ env,
2168
+ stdio: ["pipe", "pipe", transport._serverParams.stderr ?? "inherit"],
2169
+ cwd: transport._serverParams.cwd
2170
+ });
2171
+ transport._process.on("error", (error) => {
2172
+ reject(error);
2173
+ transport.onerror?.(error);
2174
+ });
2175
+ transport._process.on("spawn", () => {
2176
+ resolve3();
2177
+ });
2178
+ transport._process.on("close", () => {
2179
+ transport._process = void 0;
2180
+ transport.onclose?.();
2181
+ });
2182
+ transport._process.stdin?.on("error", (error) => {
2183
+ transport.onerror?.(error);
2184
+ });
2185
+ transport._process.stdout?.on("data", (chunk) => {
2186
+ transport._readBuffer.append(chunk);
2187
+ transport.processReadBuffer();
2188
+ });
2189
+ transport._process.stdout?.on("error", (error) => {
2190
+ transport.onerror?.(error);
2191
+ });
2192
+ if (transport._stderrStream && transport._process.stderr) {
2193
+ transport._process.stderr.pipe(transport._stderrStream);
2194
+ }
2195
+ } catch (err) {
2196
+ reject(err);
2197
+ }
2198
+ });
2199
+ };
2200
+ }
2201
+ _getDefaultEnvironment() {
2202
+ const env = {};
2203
+ const safeVars = process.platform === "win32" ? [
2204
+ "APPDATA",
2205
+ "HOMEDRIVE",
2206
+ "HOMEPATH",
2207
+ "LOCALAPPDATA",
2208
+ "PATH",
2209
+ "TEMP",
2210
+ "USERPROFILE",
2211
+ "SYSTEMROOT"
2212
+ ] : ["HOME", "PATH", "SHELL", "USER"];
2213
+ for (const key of safeVars) {
2214
+ if (process.env[key]) {
2215
+ env[key] = process.env[key];
2216
+ }
2217
+ }
2218
+ if (this._proxyPort) {
2219
+ const proxyUrl = `http://127.0.0.1:${this._proxyPort}`;
2220
+ env["http_proxy"] = proxyUrl;
2221
+ env["https_proxy"] = proxyUrl;
2222
+ env["HTTP_PROXY"] = proxyUrl;
2223
+ env["HTTPS_PROXY"] = proxyUrl;
2224
+ env["all_proxy"] = proxyUrl;
2225
+ env["ALL_PROXY"] = proxyUrl;
2226
+ }
2227
+ return env;
2228
+ }
1446
2229
  _assertConnected() {
1447
2230
  if (!this._connected || !this.client) {
1448
2231
  throw new Error("Not connected to target MCP server");
@@ -1476,7 +2259,15 @@ var TargetManager = class _TargetManager extends EventEmitter {
1476
2259
  var DEFAULT_HEADLESS_TIMEOUT_MS = 3e4;
1477
2260
  async function runHeadless(targetCommand, operation, opts = {}) {
1478
2261
  const [command, ...args] = targetCommand;
1479
- const target = new TargetManager(command, args);
2262
+ const target = new TargetManager(command, args, {
2263
+ sandbox: opts.sandbox,
2264
+ allowRead: opts.allowRead,
2265
+ allowWrite: opts.allowWrite,
2266
+ allowNet: opts.allowNet,
2267
+ denyRead: opts.denyRead,
2268
+ denyWrite: opts.denyWrite,
2269
+ denyNet: opts.denyNet
2270
+ });
1480
2271
  const interceptor = new ResponseInterceptor({
1481
2272
  outDir: opts.outDir,
1482
2273
  defaultTimeoutMs: opts.timeoutMs ?? DEFAULT_HEADLESS_TIMEOUT_MS
@@ -1504,21 +2295,21 @@ async function runHeadless(targetCommand, operation, opts = {}) {
1504
2295
  process.exit(hasError ? 1 : 0);
1505
2296
  } catch (err) {
1506
2297
  const msg = err.message ?? String(err);
2298
+ let exitCode;
1507
2299
  if (msg.includes("ENOENT") || msg.includes("spawn")) {
1508
2300
  process.stderr.write(
1509
2301
  `Error: command "${command}" not found. Check that it is installed and in your PATH.
1510
2302
  `
1511
2303
  );
1512
- } else if (msg.includes("timed out")) {
1513
- process.stderr.write(`Error: ${msg}
1514
- `);
2304
+ exitCode = 66;
1515
2305
  } else {
1516
2306
  process.stderr.write(`Error: ${msg}
1517
2307
  `);
2308
+ exitCode = 69;
1518
2309
  }
1519
2310
  await target.close().catch(() => {
1520
2311
  });
1521
- process.exit(1);
2312
+ process.exit(exitCode);
1522
2313
  }
1523
2314
  }
1524
2315
  async function executeOperation(target, interceptor, operation, opts) {
@@ -1535,7 +2326,7 @@ async function executeOperation(target, interceptor, operation, opts) {
1535
2326
  `);
1536
2327
  process.stderr.write(` Received: ${operation.args}
1537
2328
  `);
1538
- process.exit(2);
2329
+ process.exit(65);
1539
2330
  }
1540
2331
  } else {
1541
2332
  parsedArgs = parseHttpieArgs(trimmed);
@@ -1583,7 +2374,7 @@ async function executeOperation(target, interceptor, operation, opts) {
1583
2374
  Available tools: ${available}
1584
2375
  `
1585
2376
  );
1586
- process.exit(1);
2377
+ process.exit(64);
1587
2378
  }
1588
2379
  return { result: tool, hasError: false };
1589
2380
  }
@@ -1599,7 +2390,7 @@ Available tools: ${available}
1599
2390
  `);
1600
2391
  process.stderr.write(` Received: ${operation.args}
1601
2392
  `);
1602
- process.exit(2);
2393
+ process.exit(65);
1603
2394
  }
1604
2395
  } else {
1605
2396
  parsedArgs = parseHttpieArgs(trimmed);
@@ -1614,14 +2405,16 @@ Available tools: ${available}
1614
2405
  }
1615
2406
  }
1616
2407
 
1617
- // src/repl.ts
1618
- import { readFile as readFile2 } from "fs/promises";
2408
+ // src/repl/index.ts
2409
+ import { readFile as readFile4 } from "fs/promises";
1619
2410
  import { createInterface } from "readline";
1620
- import { checkbox, confirm, input as input2, search } from "@inquirer/prompts";
1621
- import pc2 from "picocolors";
2411
+
2412
+ // src/repl/state.ts
1622
2413
  var KNOWN_COMMANDS = [
2414
+ "menu",
1623
2415
  "explore",
1624
2416
  "interactive",
2417
+ "view",
1625
2418
  "tools/list",
1626
2419
  "tools/describe",
1627
2420
  "tools/call",
@@ -1663,48 +2456,259 @@ var KNOWN_COMMANDS = [
1663
2456
  "pl",
1664
2457
  "pg"
1665
2458
  ];
2459
+ var activeRl = null;
2460
+ function setActiveRl(rl) {
2461
+ activeRl = rl;
2462
+ }
2463
+ var closed = false;
2464
+ function setClosed(val) {
2465
+ closed = val;
2466
+ }
2467
+ var isScriptMode = false;
2468
+ function setIsScriptMode(val) {
2469
+ isScriptMode = val;
2470
+ }
2471
+ var globalPauseReadlineClose = false;
2472
+ function setGlobalPauseReadlineClose(val) {
2473
+ globalPauseReadlineClose = val;
2474
+ }
2475
+ var deferNextPrompt = false;
2476
+ function setDeferNextPrompt(val) {
2477
+ deferNextPrompt = val;
2478
+ }
1666
2479
  var cachedToolNames = [];
2480
+ function setCachedToolNames(names) {
2481
+ cachedToolNames = names;
2482
+ }
1667
2483
  var cachedResourceUris = [];
2484
+ function setCachedResourceUris(uris) {
2485
+ cachedResourceUris = uris;
2486
+ }
1668
2487
  var cachedPromptNames = [];
1669
- var activeCapabilities = null;
1670
- function getActiveCommands() {
1671
- let commands = [...KNOWN_COMMANDS];
1672
- if (!activeCapabilities?.resources) {
1673
- commands = commands.filter(
1674
- (c) => !c.startsWith("resources/") && !["rl", "rr", "rt", "rs", "ru"].includes(c)
1675
- );
1676
- }
1677
- if (!activeCapabilities?.prompts) {
1678
- commands = commands.filter((c) => !c.startsWith("prompts/") && !["pl", "pg"].includes(c));
1679
- }
1680
- return commands;
2488
+ function setCachedPromptNames(names) {
2489
+ cachedPromptNames = names;
1681
2490
  }
1682
- async function refreshCaches(target) {
1683
- const caps = target.getServerCapabilities() ?? {};
2491
+ var activeCapabilities = null;
2492
+ function setActiveCapabilities(caps) {
1684
2493
  activeCapabilities = caps;
1685
- try {
1686
- const { tools } = await target.listTools();
1687
- cachedToolNames = tools.map((t) => t.name);
1688
- } catch {
1689
- }
1690
- if (caps.resources) {
2494
+ }
2495
+ var tabCycleState = null;
2496
+ function setTabCycleState(state) {
2497
+ tabCycleState = state;
2498
+ }
2499
+ var callHistory = [];
2500
+ var lastToolArgsMap = /* @__PURE__ */ new Map();
2501
+
2502
+ // src/repl/history.ts
2503
+ import { readFile as readFile2, mkdir as mkdir2, appendFile } from "fs/promises";
2504
+ import { existsSync as existsSync4 } from "fs";
2505
+ import { join as join4 } from "path";
2506
+ import { homedir as homedir3 } from "os";
2507
+ var RUN_MCP_DIR = join4(homedir3(), ".run-mcp");
2508
+ var HISTORY_FILE = join4(RUN_MCP_DIR, "history");
2509
+ var replHistory = [];
2510
+ async function ensureRunMcpDir() {
2511
+ try {
2512
+ await mkdir2(RUN_MCP_DIR, { recursive: true });
2513
+ } catch {
2514
+ }
2515
+ }
2516
+ async function loadHistory() {
2517
+ await ensureRunMcpDir();
2518
+ if (existsSync4(HISTORY_FILE)) {
1691
2519
  try {
1692
- const { resources } = await target.listResources();
1693
- cachedResourceUris = resources.map((r) => r.uri);
2520
+ const content = await readFile2(HISTORY_FILE, "utf8");
2521
+ const lines = content.split("\n").map((l) => l.trim()).filter(Boolean);
2522
+ replHistory.length = 0;
2523
+ replHistory.push(...lines);
1694
2524
  } catch {
1695
2525
  }
1696
2526
  }
1697
- if (caps.prompts) {
2527
+ }
2528
+ async function appendToHistoryFile(line) {
2529
+ await ensureRunMcpDir();
2530
+ try {
2531
+ await appendFile(HISTORY_FILE, line + "\n", "utf8");
2532
+ } catch {
2533
+ }
2534
+ }
2535
+
2536
+ // src/repl/wizard.ts
2537
+ import { readFile as readFile3, writeFile as writeFile2 } from "fs/promises";
2538
+ import { existsSync as existsSync5 } from "fs";
2539
+ import { join as join5 } from "path";
2540
+ var WIZARD_DEFAULTS_FILE = join5(RUN_MCP_DIR, "wizard_defaults.json");
2541
+ async function loadWizardDefaults() {
2542
+ await ensureRunMcpDir();
2543
+ if (existsSync5(WIZARD_DEFAULTS_FILE)) {
1698
2544
  try {
1699
- const { prompts } = await target.listPrompts();
1700
- cachedPromptNames = prompts.map((p) => p.name);
2545
+ const content = await readFile3(WIZARD_DEFAULTS_FILE, "utf8");
2546
+ const parsed = JSON.parse(content);
2547
+ lastToolArgsMap.clear();
2548
+ for (const [key, val] of Object.entries(parsed)) {
2549
+ lastToolArgsMap.set(key, val);
2550
+ }
1701
2551
  } catch {
1702
2552
  }
1703
2553
  }
1704
2554
  }
1705
- var tabCycleState = null;
2555
+ async function saveWizardDefaults() {
2556
+ await ensureRunMcpDir();
2557
+ try {
2558
+ const obj = Object.fromEntries(lastToolArgsMap.entries());
2559
+ await writeFile2(WIZARD_DEFAULTS_FILE, JSON.stringify(obj, null, 2), "utf8");
2560
+ } catch {
2561
+ }
2562
+ }
2563
+
2564
+ // src/repl/ui.ts
2565
+ function printResultBlock(opts) {
2566
+ const colorFn = colors[opts.labelColor];
2567
+ const elapsedStr = opts.elapsed < 1e3 ? `${opts.elapsed}ms` : `${(opts.elapsed / 1e3).toFixed(1)}s`;
2568
+ const detail = opts.detail ?? opts.toolName ?? "";
2569
+ console.log(` ${colorFn(opts.label)} ${colors.dim(detail)} ${colors.dim(`(${elapsedStr})`)}`);
2570
+ console.log(colors.dim(` ${"\u2500".repeat(60)}`));
2571
+ }
2572
+ function printShortHelp() {
2573
+ const hasTools = !!activeCapabilities?.tools;
2574
+ const hasResources = !!activeCapabilities?.resources;
2575
+ const hasPrompts = !!activeCapabilities?.prompts;
2576
+ const tC = hasTools ? colors.green : colors.dim;
2577
+ const rC = hasResources ? colors.green : colors.dim;
2578
+ const pC = hasPrompts ? colors.green : colors.dim;
2579
+ console.log(`
2580
+ ${colors.bold("Quick Reference:")}
2581
+
2582
+ ${tC("tl")} ${tC("tools/list")} ${rC("rl")} ${rC("resources/list")} ${pC("pl")} ${pC("prompts/list")}
2583
+ ${tC("td")} ${tC("tools/describe")} ${rC("rr")} ${rC("resources/read")} ${pC("pg")} ${pC("prompts/get")}
2584
+ ${tC("tc")} ${tC("tools/call")} ${rC("rt")} ${rC("resources/templates")}
2585
+ ${tC("ts")} ${tC("tools/scaffold")} ${rC("rs")} ${rC("resources/subscribe")}
2586
+ ${rC("ru")} ${rC("resources/unsubscribe")}
2587
+
2588
+ ${colors.green("ping")} ${colors.green("status")} ${colors.green("timing")} ${colors.green("history")} ${colors.green("!!")} ${colors.green("explore")} ${colors.green("reconnect")}
2589
+
2590
+ ${colors.dim("Type 'help' for full command reference.")}
2591
+ `);
2592
+ }
2593
+ function printHelp() {
2594
+ const hasTools = !!activeCapabilities?.tools;
2595
+ const hasResources = !!activeCapabilities?.resources;
2596
+ const hasPrompts = !!activeCapabilities?.prompts;
2597
+ const hasLogging = !!activeCapabilities?.logging;
2598
+ const tC = hasTools ? colors.green : colors.dim;
2599
+ const rC = hasResources ? colors.green : colors.dim;
2600
+ const pC = hasPrompts ? colors.green : colors.dim;
2601
+ const lC = hasLogging ? colors.green : colors.dim;
2602
+ const tD = hasTools ? (s) => s : colors.dim;
2603
+ const rD = hasResources ? (s) => s : colors.dim;
2604
+ const pD = hasPrompts ? (s) => s : colors.dim;
2605
+ const lD = hasLogging ? (s) => s : colors.dim;
2606
+ const tH = hasTools ? colors.bold("Tool Commands:") : colors.dim(colors.bold("Tool Commands:")) + colors.dim(" (Unsupported)");
2607
+ const rH = hasResources ? colors.bold("Resource Commands:") : colors.dim(colors.bold("Resource Commands:")) + colors.dim(" (Unsupported)");
2608
+ const pH = hasPrompts ? colors.bold("Prompt Commands:") : colors.dim(colors.bold("Prompt Commands:")) + colors.dim(" (Unsupported)");
2609
+ console.log(`
2610
+ ${tH}
2611
+
2612
+ ${tC("tools/list")} ${tD("List all available tools")}
2613
+ ${tC("tools/describe")} <name> ${tD("Show a tool's input schema")}
2614
+ ${tC("tools/call")} <name> [json] [opts] ${tD("Call a tool (interactive if no json)")}
2615
+ ${tD("Options:")} ${colors.dim("--timeout <ms>")} ${tD("Override default timeout (60s)")}
2616
+ ${colors.dim("--clear")} ${tD("Ignore remembered argument defaults")}
2617
+ ${tC("tools/scaffold")} <name> ${tD("Generate a template for a tool's arguments")}
2618
+ ${tC("tools/forget")} [name] ${tD("Clear remembered interactive defaults")}
2619
+
2620
+ ${rH}
2621
+
2622
+ ${rC("resources/list")} ${rD("List all available resources")}
2623
+ ${rC("resources/read")} <uri> ${rD("Read a resource by URI")}
2624
+ ${rC("resources/templates")} ${rD("List resource templates")}
2625
+ ${rC("resources/subscribe")} <uri> ${rD("Subscribe to resource changes")}
2626
+ ${rC("resources/unsubscribe")} <uri> ${rD("Unsubscribe from resource changes")}
2627
+
2628
+ ${pH}
2629
+
2630
+ ${pC("prompts/list")} ${pD("List all available prompts")}
2631
+ ${pC("prompts/get")} <name> [json_args] ${pD("Get a prompt with arguments")}
2632
+
2633
+ ${colors.bold("Protocol Commands:")}
2634
+
2635
+ ${colors.green("ping")} Verify connection, show round-trip time
2636
+ ${lC("log-level")} <level> ${lD("Set server logging verbosity")}${hasLogging ? "" : colors.dim(" (Unsupported)")}
2637
+ ${colors.green("history")} [count|clear] Show request/response history
2638
+ ${colors.green("notifications")} [count|clear] Show server notifications
2639
+
2640
+ ${colors.bold("Roots Management:")}
2641
+
2642
+ ${colors.green("roots/list")} Show configured client roots
2643
+ ${colors.green("roots/add")} <uri> [name] Add a root directory
2644
+ ${colors.green("roots/remove")} <uri> Remove a root directory
2645
+
2646
+ ${colors.bold("Session Commands:")}
2647
+
2648
+ ${colors.green("!!")} / ${colors.green("last")} Re-run the last command
2649
+ ${colors.green("reconnect")} Disconnect and reconnect to the server
2650
+ ${colors.green("timing")} Show tool call performance stats
2651
+ ${colors.green("status")} Show target server status
2652
+ ${colors.green("help")} Show this help
2653
+ ${colors.green("exit")} / ${colors.green("quit")} Disconnect and exit
2654
+
2655
+ ${colors.bold("Shortcuts:")}
2656
+
2657
+ ${tC("tl")} ${tC("tools/list")} ${rC("rl")} ${rC("resources/list")} ${pC("pl")} ${pC("prompts/list")}
2658
+ ${tC("td")} ${tC("tools/describe")} ${rC("rr")} ${rC("resources/read")} ${pC("pg")} ${pC("prompts/get")}
2659
+ ${tC("tc")} ${tC("tools/call")} ${rC("rt")} ${rC("resources/templates")}
2660
+ ${tC("ts")} ${tC("tools/scaffold")} ${rC("rs")} ${rC("resources/subscribe")}
2661
+ ${rC("ru")} ${rC("resources/unsubscribe")}
2662
+
2663
+ ${colors.dim("Lines starting with # are treated as comments.")}
2664
+ ${colors.dim('JSON arguments can contain spaces: tools/call say {"message": "hello world"}')}
2665
+ ${colors.dim("Run tools/call <name> without JSON for interactive argument prompting.")}
2666
+ ${colors.dim("Use tools/call <name> --clear to ignore remembered defaults.")}
2667
+ `);
2668
+ }
2669
+ function stripAnsi(str) {
2670
+ return str.replace(/\x1B\[\d+m/g, "");
2671
+ }
2672
+ var BOX_WIDTH = 58;
2673
+ function padLine(content) {
2674
+ const clean = stripAnsi(content);
2675
+ const padding = Math.max(0, BOX_WIDTH - clean.length);
2676
+ return `${colors.cyan(" \u2502")}${content}${"".padEnd(padding)}${colors.cyan("\u2502")}`;
2677
+ }
2678
+ function printBanner(serverName, serverVersion, toolCount, resourceCount, promptCount, harnessMode) {
2679
+ const parts = [];
2680
+ parts.push(`${colors.bold(toolCount.toString())} tools`);
2681
+ if (resourceCount > 0) parts.push(`${colors.bold(resourceCount.toString())} resources`);
2682
+ if (promptCount > 0) parts.push(`${colors.bold(promptCount.toString())} prompts`);
2683
+ const baseTitle = serverVersion ? `${serverName} ${colors.dim(`v${serverVersion}`)}` : serverName;
2684
+ const title = harnessMode ? `${baseTitle} ${colors.bgBlue(colors.white(" AGENT HARNESS "))}` : baseTitle;
2685
+ console.log(colors.cyan(` \u250C${"\u2500".repeat(BOX_WIDTH)}\u2510`));
2686
+ console.log(padLine(` Connected to ${title}`));
2687
+ console.log(padLine(` Discovered ${parts.join(", ")}`));
2688
+ console.log(colors.cyan(` \u251C${"\u2500".repeat(BOX_WIDTH)}\u2524`));
2689
+ console.log(padLine(` ${colors.green("tools/list")} See all tools`));
2690
+ console.log(padLine(` ${colors.green("tools/call")} ${colors.dim("<name>")} Call a tool`));
2691
+ console.log(padLine(` ${colors.green("help")} All commands`));
2692
+ console.log(padLine(""));
2693
+ console.log(padLine(colors.dim(" Tab completion is active. Start typing to explore.")));
2694
+ console.log(colors.cyan(` \u2514${"\u2500".repeat(BOX_WIDTH)}\u2518`));
2695
+ }
2696
+
2697
+ // src/repl/completer.ts
1706
2698
  function resetTabCycle() {
1707
- tabCycleState = null;
2699
+ setTabCycleState(null);
2700
+ }
2701
+ function getActiveCommands() {
2702
+ let commands = [...KNOWN_COMMANDS];
2703
+ if (!activeCapabilities?.resources) {
2704
+ commands = commands.filter(
2705
+ (c) => !c.startsWith("resources/") && !["rl", "rr", "rt", "rs", "ru"].includes(c)
2706
+ );
2707
+ }
2708
+ if (!activeCapabilities?.prompts) {
2709
+ commands = commands.filter((c) => !c.startsWith("prompts/") && !["pl", "pg"].includes(c));
2710
+ }
2711
+ return commands;
1708
2712
  }
1709
2713
  function computeMatches(line) {
1710
2714
  const expanded = resolveAlias(line);
@@ -1733,8 +2737,12 @@ var completer = (line) => {
1733
2737
  if (tabCycleState) {
1734
2738
  const inCycle = line === tabCycleState.original || tabCycleState.matches.includes(line);
1735
2739
  if (inCycle) {
1736
- tabCycleState.index = (tabCycleState.index + 1) % tabCycleState.matches.length;
1737
- const next = tabCycleState.matches[tabCycleState.index];
2740
+ const nextIndex = (tabCycleState.index + 1) % tabCycleState.matches.length;
2741
+ setTabCycleState({
2742
+ ...tabCycleState,
2743
+ index: nextIndex
2744
+ });
2745
+ const next = tabCycleState.matches[nextIndex];
1738
2746
  setImmediate(() => {
1739
2747
  if (activeRl) {
1740
2748
  activeRl.line = next;
@@ -1744,17 +2752,41 @@ var completer = (line) => {
1744
2752
  });
1745
2753
  return [[], ""];
1746
2754
  }
1747
- tabCycleState = null;
2755
+ setTabCycleState(null);
1748
2756
  }
1749
2757
  const [matches, matchLine] = computeMatches(line);
1750
2758
  if (matches.length > 1) {
1751
- tabCycleState = { matches, index: -1, original: line };
2759
+ setTabCycleState({ matches, index: -1, original: line });
1752
2760
  }
1753
2761
  return [matches, matchLine];
1754
2762
  };
1755
- var callHistory = [];
1756
- var lastToolArgsMap = /* @__PURE__ */ new Map();
1757
- var replHistory = [];
2763
+ async function refreshCaches(target) {
2764
+ const caps = target.getServerCapabilities() ?? {};
2765
+ setActiveCapabilities(caps);
2766
+ try {
2767
+ const { tools } = await target.listTools();
2768
+ setCachedToolNames(tools.map((t) => t.name));
2769
+ } catch {
2770
+ }
2771
+ if (caps.resources) {
2772
+ try {
2773
+ const { resources } = await target.listResources();
2774
+ setCachedResourceUris(resources.map((r) => r.uri));
2775
+ } catch {
2776
+ }
2777
+ }
2778
+ if (caps.prompts) {
2779
+ try {
2780
+ const { prompts } = await target.listPrompts();
2781
+ setCachedPromptNames(prompts.map((p) => p.name));
2782
+ } catch {
2783
+ }
2784
+ }
2785
+ }
2786
+
2787
+ // src/repl/commands.ts
2788
+ import { exec as exec2 } from "child_process";
2789
+ import { checkbox, confirm, input as input2, search } from "@inquirer/prompts";
1758
2790
  var lastCommand = null;
1759
2791
  var AbortFlowError = class extends Error {
1760
2792
  constructor() {
@@ -1766,419 +2798,67 @@ function isAbortError(err) {
1766
2798
  if (!err) return false;
1767
2799
  return err.name === "ExitPromptError" || err.name === "AbortError" || err.message === "Prompt was aborted" || typeof err.message === "string" && err.message.includes("User force closed");
1768
2800
  }
1769
- var activeRl = null;
1770
- var isScriptMode = false;
1771
- var globalPauseReadlineClose = false;
1772
- var deferNextPrompt = false;
1773
2801
  async function withSuspendedReadline(target, interceptor, fn) {
1774
2802
  const wasActive = !!activeRl;
1775
2803
  if (wasActive) {
1776
- globalPauseReadlineClose = true;
2804
+ setGlobalPauseReadlineClose(true);
1777
2805
  activeRl.close();
1778
- activeRl = null;
2806
+ setActiveRl(null);
1779
2807
  }
1780
2808
  try {
1781
2809
  return await fn();
1782
2810
  } finally {
1783
2811
  if (wasActive) {
1784
- globalPauseReadlineClose = false;
2812
+ setGlobalPauseReadlineClose(false);
1785
2813
  if (!isScriptMode) {
1786
- deferNextPrompt = true;
2814
+ setDeferNextPrompt(true);
1787
2815
  startReadlineLoop(target, interceptor);
1788
2816
  }
1789
2817
  }
1790
2818
  }
1791
2819
  }
1792
- function getPrompt(target) {
1793
- if (target.connected) return `${pc2.green("\u2713")}${pc2.cyan("> ")}`;
1794
- return `${pc2.red("\u2717")}${pc2.cyan("> ")}`;
1795
- }
1796
- function printBanner(serverName, serverVersion, toolCount, resourceCount, promptCount) {
1797
- const parts = [];
1798
- parts.push(`${pc2.bold(toolCount.toString())} tools`);
1799
- if (resourceCount > 0) parts.push(`${pc2.bold(resourceCount.toString())} resources`);
1800
- if (promptCount > 0) parts.push(`${pc2.bold(promptCount.toString())} prompts`);
1801
- const baseTitle = serverVersion ? `${serverName} ${pc2.dim(`v${serverVersion}`)}` : serverName;
1802
- const isAgentHarness = serverName === "run-mcp";
1803
- const title = isAgentHarness ? `${baseTitle} ${pc2.bgBlue(pc2.white(" AGENT HARNESS "))}` : baseTitle;
1804
- const BOX_WIDTH = 53;
1805
- const padLine = (content) => {
1806
- const visible = stripAnsi(content).length;
1807
- const padding = Math.max(0, BOX_WIDTH - visible);
1808
- return `${pc2.cyan(" \u2502")}${content}${"".padEnd(padding)}${pc2.cyan("\u2502")}`;
1809
- };
1810
- const partsStr = ` ${parts.join(" \u2022 ")}`;
1811
- console.log();
1812
- console.log(pc2.cyan(` \u250C${"\u2500".repeat(BOX_WIDTH)}\u2510`));
1813
- console.log(padLine(` ${title}`));
1814
- console.log(padLine(partsStr));
1815
- console.log(pc2.cyan(` \u251C${"\u2500".repeat(BOX_WIDTH)}\u2524`));
1816
- console.log(padLine(" Quick start:"));
1817
- console.log(padLine(` ${pc2.green("tools/list")} See all tools`));
1818
- console.log(padLine(` ${pc2.green("tools/call")} ${pc2.dim("<name>")} Call a tool`));
1819
- console.log(padLine(` ${pc2.green("help")} All commands`));
1820
- console.log(padLine(""));
1821
- console.log(padLine(pc2.dim(" Tab completion is active. Start typing to explore.")));
1822
- console.log(pc2.cyan(` \u2514${"\u2500".repeat(BOX_WIDTH)}\u2518`));
1823
- console.log();
1824
- }
1825
- function stripAnsi(str) {
1826
- return str.replace(/\x1b\[[0-9;]*m/g, "");
1827
- }
1828
- async function startRepl(targetCommand, opts) {
1829
- const [command, ...args] = targetCommand;
1830
- const target = new TargetManager(command, args);
1831
- const interceptor = new ResponseInterceptor({
1832
- outDir: opts.outDir,
1833
- mediaThresholdKb: opts.mediaThresholdKb
1834
- });
1835
- isScriptMode = !!opts.script;
1836
- target.on("stderr", (text) => {
1837
- for (const line of text.split("\n")) {
1838
- console.error(pc2.dim(`[server] ${line}`));
1839
- }
1840
- });
1841
- console.log(pc2.cyan("\u27F3 Connecting to target MCP server..."));
1842
- console.log(pc2.dim(` Command: ${targetCommand.join(" ")}`));
1843
- try {
1844
- await target.connect();
1845
- } catch (err) {
1846
- const msg = err.message ?? String(err);
1847
- if (msg.includes("ENOENT") || msg.includes("spawn")) {
1848
- console.error(pc2.red(`\u2717 Failed to start server: command "${command}" not found.`));
1849
- console.error(pc2.dim(` Check that "${command}" is installed and in your PATH.`));
1850
- } else {
1851
- console.error(pc2.red(`\u2717 Failed to connect: ${msg}`));
1852
- console.error(pc2.dim(` Check that the target command starts a valid MCP server on stdio.`));
1853
- }
1854
- process.exit(1);
2820
+ async function handleCommand(input3, target, interceptor) {
2821
+ const expanded = resolveAlias(input3);
2822
+ const effective = expanded ?? input3;
2823
+ const { cmd, rest } = parseCommandLine(effective);
2824
+ if (cmd !== "!!" && cmd !== "last") {
2825
+ lastCommand = input3;
1855
2826
  }
1856
- const status = target.getStatus();
1857
- console.log(pc2.green(`\u2713 Connected (PID: ${status.pid})`));
1858
- if (!isScriptMode) {
1859
- target.enableAutoReconnect();
1860
- target.on(
1861
- "reconnecting",
1862
- ({ attempt, maxAttempts }) => {
1863
- console.log(
1864
- pc2.yellow(`
1865
- \u27F3 Server disconnected. Reconnecting (${attempt}/${maxAttempts})...`)
1866
- );
1867
- }
1868
- );
1869
- target.on("reconnected", async ({ attempt }) => {
1870
- const s = target.getStatus();
1871
- console.log(pc2.green(`\u2713 Reconnected (PID: ${s.pid}, attempt ${attempt})`));
1872
- await refreshCaches(target);
1873
- });
1874
- target.on("reconnect_failed", ({ reason, message }) => {
1875
- console.error(pc2.red(`\u2717 ${message}`));
1876
- if (reason === "max_retries") {
1877
- console.log(
1878
- pc2.dim(" Use 'exit' to quit or wait for the server to be fixed and restart manually.")
1879
- );
1880
- }
1881
- });
1882
- target.on("notification", (notification) => {
1883
- const method = notification.method;
1884
- if (method === "notifications/message") {
1885
- const lvl = notification.params?.level ?? "info";
1886
- const data = notification.params?.data ?? "";
1887
- const text = typeof data === "string" ? data : JSON.stringify(data);
1888
- console.log(pc2.dim(`
1889
- [${lvl}] ${text}`));
1890
- } else if (method === "notifications/tools/list_changed") {
1891
- console.log(pc2.yellow("\n \u27F3 Server tools changed. Run tools/list to see updates."));
1892
- refreshCaches(target).catch(() => {
1893
- });
1894
- } else if (method === "notifications/resources/list_changed") {
1895
- console.log(pc2.yellow("\n \u27F3 Server resources changed. Run resources/list to see."));
1896
- refreshCaches(target).catch(() => {
1897
- });
1898
- } else if (method === "notifications/resources/updated") {
1899
- const uri = notification.params?.uri ?? "unknown";
1900
- console.log(pc2.yellow(`
1901
- \u27F3 Resource updated: ${uri}`));
1902
- } else if (method === "notifications/prompts/list_changed") {
1903
- console.log(pc2.yellow("\n \u27F3 Server prompts changed. Run prompts/list to see."));
1904
- refreshCaches(target).catch(() => {
1905
- });
1906
- }
1907
- });
1908
- target.on("sampling_request", async ({ request, respond, reject: rejectFn }) => {
1909
- console.log(pc2.magenta("\n \u2554\u2550\u2550 Sampling Request \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550"));
1910
- const messages = request?.messages ?? [];
1911
- for (const msg of messages) {
1912
- const role = msg.role === "user" ? pc2.blue("user") : pc2.magenta("assistant");
1913
- const text = msg.content?.text ?? JSON.stringify(msg.content);
1914
- console.log(pc2.magenta(` \u2551 ${role}: ${text}`));
2827
+ switch (cmd) {
2828
+ case "help":
2829
+ printHelp();
2830
+ return;
2831
+ case "?":
2832
+ printShortHelp();
2833
+ return;
2834
+ case "view": {
2835
+ const filepath = rest.trim();
2836
+ if (!filepath) {
2837
+ console.error(colors.red("Error: Please specify a file path to view."));
2838
+ return;
1915
2839
  }
1916
- console.log(pc2.magenta(" \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550"));
1917
- if (activeRl) {
1918
- try {
1919
- const answer = await question(activeRl, ` ${pc2.bold("Approve? [y/N/text]:")} `);
1920
- const trimmed = answer.trim().toLowerCase();
1921
- if (trimmed === "y" || trimmed === "yes") {
1922
- respond({
1923
- model: "user-approved",
1924
- role: "assistant",
1925
- content: { type: "text", text: "Approved by user." }
1926
- });
1927
- } else if (trimmed === "n" || trimmed === "no" || trimmed === "") {
1928
- rejectFn(new Error("Sampling request rejected by user"));
1929
- } else {
1930
- respond({
1931
- model: "user-provided",
1932
- role: "assistant",
1933
- content: { type: "text", text: answer.trim() }
1934
- });
1935
- }
1936
- } catch (err) {
1937
- if (err instanceof AbortFlowError) {
1938
- rejectFn(new Error("Sampling request rejected by user"));
1939
- } else {
1940
- throw err;
1941
- }
2840
+ const isMac = process.platform === "darwin";
2841
+ const isWin = process.platform === "win32";
2842
+ const cmdToRun = isMac ? "open" : isWin ? "start" : "xdg-open";
2843
+ exec2(`${cmdToRun} "${filepath}"`, (err) => {
2844
+ if (err) {
2845
+ console.error(colors.red(`Error opening file: ${err.message}`));
2846
+ } else {
2847
+ console.log(colors.green(`Opened ${filepath}`));
1942
2848
  }
1943
- } else {
1944
- rejectFn(new Error("No interactive terminal available for sampling approval"));
1945
- }
1946
- });
1947
- target.on("elicitation_request", async ({ request, respond, reject: rejectFn }) => {
1948
- console.log(pc2.cyan("\n \u2554\u2550\u2550 Elicitation Request \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550"));
1949
- console.log(pc2.cyan(` \u2551 ${request?.message ?? "Server requests input"}`));
1950
- console.log(pc2.cyan(" \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550"));
1951
- if (activeRl) {
1952
- try {
1953
- const answer = await question(
1954
- activeRl,
1955
- ` ${pc2.bold("Your response (empty to decline):")} `
1956
- );
1957
- if (answer.trim() === "") {
1958
- respond({ action: "decline" });
1959
- } else {
1960
- try {
1961
- const parsed = JSON.parse(answer.trim());
1962
- respond({ action: "accept", content: parsed });
1963
- } catch {
1964
- respond({ action: "accept", content: { value: answer.trim() } });
1965
- }
1966
- }
1967
- } catch (err) {
1968
- if (err instanceof AbortFlowError) {
1969
- respond({ action: "decline" });
1970
- } else {
1971
- throw err;
1972
- }
1973
- }
1974
- } else {
1975
- rejectFn(new Error("No interactive terminal available for elicitation"));
1976
- }
1977
- });
1978
- }
1979
- let toolCount;
1980
- let resourceCount = 0;
1981
- let promptCount = 0;
1982
- try {
1983
- const { tools } = await target.listTools();
1984
- toolCount = tools.length;
1985
- const caps = target.getServerCapabilities() ?? {};
1986
- if (caps.resources) {
1987
- try {
1988
- const { resources } = await target.listResources();
1989
- resourceCount = resources.length;
1990
- } catch {
1991
- }
1992
- }
1993
- if (caps.prompts) {
1994
- try {
1995
- const { prompts } = await target.listPrompts();
1996
- promptCount = prompts.length;
1997
- } catch {
1998
- }
1999
- }
2000
- const serverInfo = target.getServerVersion();
2001
- const serverName = serverInfo?.name ?? "MCP Server";
2002
- const serverVersion = serverInfo?.version ?? "";
2003
- printBanner(serverName, serverVersion, toolCount, resourceCount, promptCount);
2004
- if (toolCount >= 10) {
2005
- const groups = groupToolsByPrefix(
2006
- cachedToolNames.length > 0 ? cachedToolNames : tools.map((t) => t.name)
2007
- );
2008
- if (!groups.has("All")) {
2009
- for (const [label, members] of groups) {
2010
- console.log(` ${pc2.bold(label.padEnd(16))} ${pc2.dim(members.join(", "))}`);
2011
- }
2012
- console.log();
2013
- }
2014
- }
2015
- } catch (err) {
2016
- console.log(pc2.yellow(` Warning: Could not list tools: ${err.message}
2017
- `));
2018
- }
2019
- await refreshCaches(target);
2020
- if (isScriptMode) {
2021
- const lines = await readScriptLines(opts.script);
2022
- const scriptContext = {};
2023
- let expectError = false;
2024
- for (const line of lines) {
2025
- let trimmed = line.trim();
2026
- if (!trimmed || trimmed.startsWith("#")) {
2027
- if (trimmed === "# @expect-error") {
2028
- expectError = true;
2029
- }
2030
- continue;
2031
- }
2032
- if (trimmed.endsWith("# @expect-error")) {
2033
- expectError = true;
2034
- trimmed = trimmed.replace(/\s*#\s*@expect-error$/, "");
2035
- }
2036
- const interpolated = interpolateString(trimmed, scriptContext);
2037
- try {
2038
- const res = await handleCommand(interpolated, target, interceptor);
2039
- if (res !== void 0) {
2040
- scriptContext.LAST = res;
2041
- }
2042
- const isErrorRes = res && typeof res === "object" && res.isError === true;
2043
- if (expectError && !isErrorRes) {
2044
- console.error(pc2.red(`\u2717 Expected an error but the command succeeded.`));
2045
- await target.close();
2046
- process.exit(1);
2047
- }
2048
- if (!expectError && isErrorRes) {
2049
- console.error(pc2.red(`\u2717 Command failed unexpectedly.`));
2050
- await target.close();
2051
- process.exit(1);
2052
- }
2053
- if (expectError && isErrorRes) {
2054
- console.log(pc2.yellow(` \u2713 Expected error caught: tool returned isError: true`));
2055
- }
2056
- } catch (err) {
2057
- if (expectError) {
2058
- console.log(pc2.yellow(` \u2713 Expected error caught: ${err.message}`));
2059
- } else {
2060
- if (err?.message?.includes("-32601") || err?.code === -32601) {
2061
- let msg = "Server does not support this feature (Method not found)";
2062
- if (trimmed.startsWith("prompts/")) msg = "This server does not have any prompts.";
2063
- else if (trimmed.startsWith("resources/"))
2064
- msg = "This server does not have any resources.";
2065
- else if (trimmed.startsWith("tools/")) msg = "This server does not have any tools.";
2066
- console.log(pc2.yellow(` ${msg}`));
2067
- } else {
2068
- console.error(pc2.red(`\u2717 Error: ${err.message}`));
2069
- }
2070
- console.log(pc2.dim("\nShutting down..."));
2071
- await target.close();
2072
- process.exit(1);
2073
- }
2074
- }
2075
- expectError = false;
2076
- }
2077
- console.log(pc2.dim("\nShutting down..."));
2078
- await target.close();
2079
- process.exit(0);
2080
- } else {
2081
- startReadlineLoop(target, interceptor);
2082
- }
2083
- }
2084
- function startReadlineLoop(target, interceptor) {
2085
- if (isScriptMode || activeRl) return;
2086
- const rl = createInterface({
2087
- input: process.stdin,
2088
- output: process.stdout,
2089
- prompt: getPrompt(target),
2090
- terminal: true,
2091
- completer,
2092
- history: [...replHistory].reverse()
2093
- // Node's readline history expects newest first
2094
- });
2095
- activeRl = rl;
2096
- if (process.stdin.isTTY) {
2097
- process.stdin.on("keypress", (_str, key) => {
2098
- if (!key || key.name !== "tab") {
2099
- resetTabCycle();
2100
- }
2101
- });
2102
- }
2103
- if (!deferNextPrompt) {
2104
- rl.prompt();
2105
- }
2106
- deferNextPrompt = false;
2107
- let processing = false;
2108
- let closed = false;
2109
- const queue = [];
2110
- const processQueue = async () => {
2111
- if (processing) return;
2112
- processing = true;
2113
- while (queue.length > 0) {
2114
- const trimmed = queue.shift();
2115
- try {
2116
- await handleCommand(trimmed, target, interceptor);
2117
- } catch (err) {
2118
- if (err instanceof AbortFlowError) {
2119
- console.log(pc2.yellow(" Aborted."));
2120
- } else if (err?.message?.includes("-32601") || err?.code === -32601) {
2121
- let msg = "Server does not support this feature (Method not found)";
2122
- if (trimmed.startsWith("prompts/")) msg = "This server does not have any prompts.";
2123
- else if (trimmed.startsWith("resources/"))
2124
- msg = "This server does not have any resources.";
2125
- else if (trimmed.startsWith("tools/")) msg = "This server does not have any tools.";
2126
- console.log(pc2.yellow(` ${msg}`));
2127
- } else {
2128
- console.error(pc2.red(`\u2717 Error: ${err.message}`));
2129
- }
2130
- }
2131
- if (activeRl) {
2132
- setImmediate(() => {
2133
- if (activeRl) {
2134
- console.log();
2135
- activeRl.setPrompt(getPrompt(target));
2136
- activeRl.prompt();
2137
- }
2138
- });
2139
- }
2140
- }
2141
- processing = false;
2142
- };
2143
- rl.on("line", (line) => {
2144
- const trimmed = line.trim();
2145
- if (!trimmed || trimmed.startsWith("#")) {
2146
- if (!closed && activeRl) activeRl.prompt();
2849
+ });
2147
2850
  return;
2148
2851
  }
2149
- replHistory.push(trimmed);
2150
- queue.push(trimmed);
2151
- processQueue();
2152
- });
2153
- rl.on("close", async () => {
2154
- closed = true;
2155
- activeRl = null;
2156
- if (!globalPauseReadlineClose) {
2157
- console.log(pc2.dim("\nShutting down..."));
2158
- await target.close();
2159
- process.exit(0);
2160
- }
2161
- });
2162
- }
2163
- async function handleCommand(input3, target, interceptor) {
2164
- const expanded = resolveAlias(input3);
2165
- const effective = expanded ?? input3;
2166
- const { cmd, rest } = parseCommandLine(effective);
2167
- if (cmd !== "!!" && cmd !== "last") {
2168
- lastCommand = input3;
2169
- }
2170
- switch (cmd) {
2171
- case "help":
2172
- printHelp();
2173
- return;
2174
- case "?":
2175
- printShortHelp();
2176
- return;
2852
+ case "menu":
2177
2853
  case "explore":
2178
2854
  case "interactive":
2179
- await withSuspendedReadline(target, interceptor, async () => {
2180
- await cmdExplore(target, interceptor);
2181
- });
2855
+ if (activeRl) {
2856
+ setGlobalPauseReadlineClose(true);
2857
+ activeRl.close();
2858
+ setActiveRl(null);
2859
+ setGlobalPauseReadlineClose(false);
2860
+ }
2861
+ mainMenuLoop(target, interceptor);
2182
2862
  return;
2183
2863
  case "tools/list":
2184
2864
  await cmdToolsList(target);
@@ -2243,10 +2923,10 @@ async function handleCommand(input3, target, interceptor) {
2243
2923
  case "!!":
2244
2924
  case "last":
2245
2925
  if (lastCommand) {
2246
- console.log(pc2.dim(` Re-running: ${lastCommand}`));
2926
+ console.log(colors.dim(` Re-running: ${lastCommand}`));
2247
2927
  return await handleCommand(lastCommand, target, interceptor);
2248
2928
  } else {
2249
- console.log(pc2.yellow("No previous command to re-run."));
2929
+ console.log(colors.yellow("No previous command to re-run."));
2250
2930
  }
2251
2931
  return;
2252
2932
  case "status":
@@ -2254,7 +2934,7 @@ async function handleCommand(input3, target, interceptor) {
2254
2934
  return;
2255
2935
  case "exit":
2256
2936
  case "quit": {
2257
- console.log(pc2.dim("Shutting down..."));
2937
+ console.log(colors.dim("Shutting down..."));
2258
2938
  await target.close();
2259
2939
  process.exit(0);
2260
2940
  return;
@@ -2265,11 +2945,11 @@ async function handleCommand(input3, target, interceptor) {
2265
2945
  }
2266
2946
  const suggestion = suggestCommand(cmd, getActiveCommands());
2267
2947
  if (suggestion) {
2268
- console.log(pc2.yellow(`Unknown command: ${cmd}.`));
2948
+ console.log(colors.yellow(`Unknown command: ${cmd}.`));
2269
2949
  try {
2270
2950
  await withSuspendedReadline(target, interceptor, async () => {
2271
2951
  const runIt = await confirm({
2272
- message: `Did you mean ${pc2.bold(suggestion)}?`,
2952
+ message: `Did you mean ${colors.bold(suggestion)}?`,
2273
2953
  default: true
2274
2954
  });
2275
2955
  if (runIt) {
@@ -2282,7 +2962,7 @@ async function handleCommand(input3, target, interceptor) {
2282
2962
  throw new AbortFlowError();
2283
2963
  }
2284
2964
  } else {
2285
- console.log(pc2.yellow(`Unknown command: ${cmd}. Type ${pc2.bold("help")} for usage.`));
2965
+ console.log(colors.yellow(`Unknown command: ${cmd}. Type ${colors.bold("help")} for usage.`));
2286
2966
  }
2287
2967
  }
2288
2968
  }
@@ -2290,25 +2970,25 @@ async function handleCommand(input3, target, interceptor) {
2290
2970
  async function cmdToolsList(target) {
2291
2971
  const { tools } = await target.listTools();
2292
2972
  if (tools.length === 0) {
2293
- console.log(pc2.dim(" No tools available."));
2973
+ console.log(colors.dim(" No tools available."));
2294
2974
  return;
2295
2975
  }
2296
2976
  const nameWidth = Math.max(8, ...tools.map((t) => t.name.length));
2297
- console.log(pc2.bold(` ${"Name".padEnd(nameWidth)} Description`));
2298
- console.log(pc2.dim(` ${"\u2500".repeat(nameWidth)} ${"\u2500".repeat(50)}`));
2977
+ console.log(colors.bold(` ${"Name".padEnd(nameWidth)} Description`));
2978
+ console.log(colors.dim(` ${"\u2500".repeat(nameWidth)} ${"\u2500".repeat(50)}`));
2299
2979
  for (const tool of tools) {
2300
- const desc = tool.description ? tool.description.length > 60 ? `${tool.description.slice(0, 57)}...` : tool.description : pc2.dim("(no description)");
2301
- console.log(` ${pc2.green(tool.name.padEnd(nameWidth))} ${desc}`);
2980
+ const desc = tool.description ? tool.description.length > 60 ? `${tool.description.slice(0, 57)}...` : tool.description : colors.dim("(no description)");
2981
+ console.log(` ${colors.green(tool.name.padEnd(nameWidth))} ${desc}`);
2302
2982
  }
2303
- console.log(pc2.dim(`
2983
+ console.log(colors.dim(`
2304
2984
  ${tools.length} tool(s) total.`));
2305
2985
  if (tools.length >= 10) {
2306
2986
  const groups = groupToolsByPrefix(tools.map((t) => t.name));
2307
2987
  if (!groups.has("All")) {
2308
2988
  console.log();
2309
- console.log(pc2.bold(" Groups:"));
2989
+ console.log(colors.bold(" Groups:"));
2310
2990
  for (const [label, members] of groups) {
2311
- console.log(` ${pc2.cyan(label.padEnd(16))} ${pc2.dim(members.join(", "))}`);
2991
+ console.log(` ${colors.cyan(label.padEnd(16))} ${colors.dim(members.join(", "))}`);
2312
2992
  }
2313
2993
  }
2314
2994
  }
@@ -2316,28 +2996,28 @@ async function cmdToolsList(target) {
2316
2996
  async function cmdToolsDescribe(target, rest) {
2317
2997
  const name = rest.trim();
2318
2998
  if (!name) {
2319
- console.log(pc2.yellow(" Usage: tools/describe <name>"));
2999
+ console.log(colors.yellow(" Usage: tools/describe <name>"));
2320
3000
  if (cachedToolNames.length > 0) {
2321
3001
  const preview = cachedToolNames.slice(0, 6);
2322
3002
  const more = cachedToolNames.length > 6 ? `, ... (${cachedToolNames.length} total)` : "";
2323
- console.log(pc2.dim(`
3003
+ console.log(colors.dim(`
2324
3004
  Available tools: ${preview.join(", ")}${more}`));
2325
- console.log(pc2.dim(` Type ${pc2.bold("tools/list")} for all.`));
3005
+ console.log(colors.dim(` Type ${colors.bold("tools/list")} for all.`));
2326
3006
  }
2327
3007
  return;
2328
3008
  }
2329
3009
  const { tools } = await target.listTools();
2330
3010
  const tool = tools.find((t) => t.name === name);
2331
3011
  if (!tool) {
2332
- console.log(pc2.red(`Tool "${name}" not found.`));
3012
+ console.log(colors.red(`Tool "${name}" not found.`));
2333
3013
  const suggestion = suggestCommand(
2334
3014
  name,
2335
3015
  tools.map((t) => t.name)
2336
3016
  );
2337
3017
  if (suggestion) {
2338
- console.log(pc2.yellow(`Did you mean ${pc2.bold(suggestion)}?`));
3018
+ console.log(colors.yellow(`Did you mean ${colors.bold(suggestion)}?`));
2339
3019
  } else {
2340
- console.log(pc2.dim(`Available: ${tools.map((t) => t.name).join(", ")}`));
3020
+ console.log(colors.dim(`Available: ${tools.map((t) => t.name).join(", ")}`));
2341
3021
  }
2342
3022
  return;
2343
3023
  }
@@ -2368,14 +3048,14 @@ async function cmdToolsCall(target, interceptor, rest) {
2368
3048
  if (!picked) return;
2369
3049
  return cmdToolsCall(target, interceptor, picked);
2370
3050
  }
2371
- console.log(pc2.yellow(" Usage: tools/call <name> [json_args] [--timeout <ms>]"));
3051
+ console.log(colors.yellow(" Usage: tools/call <name> [json_args] [--timeout <ms>]"));
2372
3052
  if (cachedToolNames.length > 0) {
2373
3053
  const preview = cachedToolNames.slice(0, 6);
2374
3054
  const more = cachedToolNames.length > 6 ? `, ... (${cachedToolNames.length} total)` : "";
2375
- console.log(pc2.dim(`
3055
+ console.log(colors.dim(`
2376
3056
  Available tools: ${preview.join(", ")}${more}`));
2377
3057
  console.log(
2378
- pc2.dim(` Run without args for ${pc2.bold("interactive mode")}: tools/call <name>`)
3058
+ colors.dim(` Run without args for ${colors.bold("interactive mode")}: tools/call <name>`)
2379
3059
  );
2380
3060
  }
2381
3061
  return;
@@ -2387,31 +3067,31 @@ async function cmdToolsCall(target, interceptor, rest) {
2387
3067
  try {
2388
3068
  args = JSON.parse(trimmed);
2389
3069
  } catch (err) {
2390
- console.error(pc2.red(`Invalid JSON: ${err.message}`));
2391
- console.log(pc2.dim(` Received: ${jsonArgs}`));
3070
+ console.error(colors.red(`Invalid JSON: ${err.message}`));
3071
+ console.log(colors.dim(` Received: ${jsonArgs}`));
2392
3072
  return;
2393
3073
  }
2394
3074
  } else {
2395
3075
  try {
2396
3076
  args = parseHttpieArgs(trimmed);
2397
3077
  } catch (err) {
2398
- console.error(pc2.red(`Invalid shorthand arguments: ${err.message}`));
3078
+ console.error(colors.red(`Invalid shorthand arguments: ${err.message}`));
2399
3079
  return;
2400
3080
  }
2401
3081
  }
2402
3082
  const { tools } = await target.listTools();
2403
3083
  const tool = tools.find((t) => t.name === toolName);
2404
3084
  if (!tool) {
2405
- console.log(pc2.red(`
3085
+ console.log(colors.red(`
2406
3086
  \u2717 Tool "${toolName}" not found.`));
2407
3087
  const toolNames = tools.map((t) => t.name);
2408
3088
  const suggestion = suggestCommand(toolName, toolNames);
2409
3089
  if (suggestion) {
2410
- console.log(pc2.yellow(` \u{1F4A1} Did you mean "${suggestion}"?`));
3090
+ console.log(colors.yellow(` \u{1F4A1} Did you mean "${suggestion}"?`));
2411
3091
  } else {
2412
3092
  const preview = toolNames.slice(0, 6);
2413
3093
  const more = toolNames.length > 6 ? `, ... (${toolNames.length} total)` : "";
2414
- console.log(pc2.dim(` Available tools: ${preview.join(", ")}${more}`));
3094
+ console.log(colors.dim(` Available tools: ${preview.join(", ")}${more}`));
2415
3095
  }
2416
3096
  return { isError: true, content: [{ type: "text", text: `Tool not found: ${toolName}` }] };
2417
3097
  }
@@ -2419,14 +3099,14 @@ async function cmdToolsCall(target, interceptor, rest) {
2419
3099
  const required = schema.required ?? [];
2420
3100
  const missing = required.filter((r) => !(r in args));
2421
3101
  if (missing.length > 0) {
2422
- console.log(pc2.yellow(`
3102
+ console.log(colors.yellow(`
2423
3103
  Missing required arguments: ${missing.join(", ")}`));
2424
3104
  console.log();
2425
3105
  const scaffolded = scaffoldArgs(schema);
2426
- console.log(pc2.dim(" Try:"));
3106
+ console.log(colors.dim(" Try:"));
2427
3107
  console.log(` tools/call ${toolName} ${scaffolded}`);
2428
3108
  console.log();
2429
- console.log(pc2.dim(" Or run without args for interactive mode:"));
3109
+ console.log(colors.dim(" Or run without args for interactive mode:"));
2430
3110
  console.log(` tools/call ${toolName}`);
2431
3111
  console.log();
2432
3112
  return;
@@ -2438,17 +3118,21 @@ async function cmdToolsCall(target, interceptor, rest) {
2438
3118
  if (!isScriptMode) {
2439
3119
  const fullCmd = `tools/call ${toolName} ${JSON.stringify(args)}`;
2440
3120
  replHistory.push(fullCmd);
3121
+ appendToHistoryFile(fullCmd).catch(() => {
3122
+ });
2441
3123
  if (activeRl) {
2442
3124
  activeRl.history.unshift(fullCmd);
2443
3125
  }
2444
3126
  }
2445
3127
  }
2446
- console.log(pc2.dim(` Calling ${toolName}...`));
3128
+ console.log(colors.dim(` Calling ${toolName}...`));
2447
3129
  const startTime = Date.now();
2448
3130
  const result = await interceptor.callTool(target, toolName, args, timeoutMs);
2449
3131
  const elapsed = Date.now() - startTime;
2450
3132
  callHistory.push({ toolName, durationMs: elapsed, timestamp: startTime });
2451
3133
  lastToolArgsMap.set(toolName, { ...args });
3134
+ saveWizardDefaults().catch(() => {
3135
+ });
2452
3136
  const isError = result.isError === true;
2453
3137
  console.log();
2454
3138
  printResultBlock({
@@ -2462,17 +3146,17 @@ async function cmdToolsCall(target, interceptor, rest) {
2462
3146
  for (const item of content) {
2463
3147
  if (item.type === "text") {
2464
3148
  if (isError) {
2465
- console.log(pc2.red(` \u2717 ${item.text}`));
3149
+ console.log(colors.red(` \u2717 ${item.text}`));
2466
3150
  } else {
2467
3151
  try {
2468
3152
  const parsed = JSON.parse(item.text);
2469
3153
  if (typeof parsed === "object" && parsed !== null) {
2470
3154
  console.log(formatJson(parsed, 2, true));
2471
3155
  } else {
2472
- console.log(pc2.yellow(` ${item.text}`));
3156
+ console.log(colors.yellow(` ${item.text}`));
2473
3157
  }
2474
3158
  } catch {
2475
- console.log(pc2.yellow(` ${item.text}`));
3159
+ console.log(colors.yellow(` ${item.text}`));
2476
3160
  }
2477
3161
  }
2478
3162
  } else {
@@ -2486,7 +3170,7 @@ async function cmdToolsCall(target, interceptor, rest) {
2486
3170
  const errText = Array.isArray(content) ? content.map((c) => c.text || "").join(" ").toLowerCase() : typeof content === "object" ? (content.text || "").toLowerCase() : "";
2487
3171
  if (errText.includes("argument") || errText.includes("validation") || errText.includes("schema") || errText.includes("missing") || errText.includes("invalid")) {
2488
3172
  console.log(
2489
- pc2.yellow(
3173
+ colors.yellow(
2490
3174
  ` \u{1F4A1} Tip: Check the tool arguments via 'tools/describe ${toolName}'
2491
3175
  or view the raw server stderr above.`
2492
3176
  )
@@ -2500,15 +3184,15 @@ async function interactiveArgPrompt(target, interceptor, toolName, clearPrevious
2500
3184
  const { tools } = await target.listTools();
2501
3185
  const tool = tools.find((t) => t.name === toolName);
2502
3186
  if (!tool) {
2503
- console.log(pc2.red(`Tool "${toolName}" not found.`));
3187
+ console.log(colors.red(`Tool "${toolName}" not found.`));
2504
3188
  const suggestion = suggestCommand(
2505
3189
  toolName,
2506
3190
  tools.map((t) => t.name)
2507
3191
  );
2508
3192
  if (suggestion) {
2509
- console.log(pc2.yellow(`Did you mean ${pc2.bold(suggestion)}?`));
3193
+ console.log(colors.yellow(`Did you mean ${colors.bold(suggestion)}?`));
2510
3194
  } else {
2511
- console.log(pc2.dim(`Available: ${tools.map((t) => t.name).join(", ")}`));
3195
+ console.log(colors.dim(`Available: ${tools.map((t) => t.name).join(", ")}`));
2512
3196
  }
2513
3197
  return null;
2514
3198
  }
@@ -2518,9 +3202,9 @@ async function interactiveArgPrompt(target, interceptor, toolName, clearPrevious
2518
3202
  return {};
2519
3203
  }
2520
3204
  if (isScriptMode) {
2521
- console.log(pc2.yellow(` Tool "${toolName}" requires arguments.`));
3205
+ console.log(colors.yellow(` Tool "${toolName}" requires arguments.`));
2522
3206
  const scaffolded = scaffoldArgs(schema);
2523
- console.log(pc2.dim(` Usage: tools/call ${toolName} ${scaffolded}`));
3207
+ console.log(colors.dim(` Usage: tools/call ${toolName} ${scaffolded}`));
2524
3208
  return null;
2525
3209
  }
2526
3210
  const required = schema.required ?? [];
@@ -2529,10 +3213,10 @@ async function interactiveArgPrompt(target, interceptor, toolName, clearPrevious
2529
3213
  const optionalProps = allProps.filter(([name]) => !required.includes(name));
2530
3214
  const previousArgs = clearPrevious ? void 0 : lastToolArgsMap.get(toolName);
2531
3215
  console.log();
2532
- console.log(` ${pc2.bold(tool.name)}${tool.description ? pc2.dim(` \u2014 ${tool.description}`) : ""}`);
3216
+ console.log(` ${colors.bold(tool.name)}${tool.description ? colors.dim(` \u2014 ${tool.description}`) : ""}`);
2533
3217
  if (previousArgs) {
2534
- console.log(pc2.dim(` Previous: ${JSON.stringify(previousArgs)}`));
2535
- console.log(pc2.dim(" Press Enter to reuse values, or type to override."));
3218
+ console.log(colors.dim(` Previous: ${JSON.stringify(previousArgs)}`));
3219
+ console.log(colors.dim(" Press Enter to reuse values, or type to override."));
2536
3220
  }
2537
3221
  console.log();
2538
3222
  const collectedArgs = {};
@@ -2550,7 +3234,7 @@ async function interactiveArgPrompt(target, interceptor, toolName, clearPrevious
2550
3234
  const typeStr = prop.type ?? "any";
2551
3235
  const desc = prop.description ?? "";
2552
3236
  const prevVal = previousArgs?.[name];
2553
- const label = desc ? `${name} ${pc2.dim(`(${typeStr})`)} ${pc2.dim(desc)}` : `${name} ${pc2.dim(`(${typeStr})`)}`;
3237
+ const label = desc ? `${name} ${colors.dim(`(${typeStr})`)} ${colors.dim(desc)}` : `${name} ${colors.dim(`(${typeStr})`)}`;
2554
3238
  const answerStr = await input2(
2555
3239
  {
2556
3240
  message: label,
@@ -2592,7 +3276,7 @@ async function interactiveArgPrompt(target, interceptor, toolName, clearPrevious
2592
3276
  const typeStr = prop.type ?? "any";
2593
3277
  const desc = prop.description ?? "";
2594
3278
  const prevVal = previousArgs?.[name];
2595
- const label = desc ? `${name} ${pc2.dim(`(${typeStr})`)} ${pc2.dim(desc)}` : `${name} ${pc2.dim(`(${typeStr})`)}`;
3279
+ const label = desc ? `${name} ${colors.dim(`(${typeStr})`)} ${colors.dim(desc)}` : `${name} ${colors.dim(`(${typeStr})`)}`;
2596
3280
  const answerStr = await input2(
2597
3281
  {
2598
3282
  message: label,
@@ -2631,14 +3315,14 @@ function printJsonTemplate(filled, allProps, currentProp) {
2631
3315
  const parts = [];
2632
3316
  for (const [name] of allProps) {
2633
3317
  if (name in filled) {
2634
- parts.push(`"${name}": ${pc2.green(JSON.stringify(filled[name]))}`);
3318
+ parts.push(`"${name}": ${colors.green(JSON.stringify(filled[name]))}`);
2635
3319
  } else if (name === currentProp) {
2636
- parts.push(`"${name}": ${pc2.yellow("\u2592")}`);
3320
+ parts.push(`"${name}": ${colors.yellow("\u2592")}`);
2637
3321
  } else {
2638
- parts.push(`"${name}": ${pc2.dim("\u2592")}`);
3322
+ parts.push(`"${name}": ${colors.dim("\u2592")}`);
2639
3323
  }
2640
3324
  }
2641
- console.log(pc2.dim(" { ") + parts.join(pc2.dim(", ")) + pc2.dim(" }"));
3325
+ console.log(colors.dim(" { ") + parts.join(colors.dim(", ")) + colors.dim(" }"));
2642
3326
  console.log();
2643
3327
  }
2644
3328
  function coerceValue(input3, type) {
@@ -2655,7 +3339,7 @@ function coerceValue(input3, type) {
2655
3339
  }
2656
3340
  }
2657
3341
  function question(rl, prompt) {
2658
- return new Promise((resolve2, reject) => {
3342
+ return new Promise((resolve3, reject) => {
2659
3343
  let aborted = false;
2660
3344
  const onKeypress = (_str, key) => {
2661
3345
  if (key && key.name === "escape") {
@@ -2679,7 +3363,7 @@ function question(rl, prompt) {
2679
3363
  if (aborted) {
2680
3364
  reject(new AbortFlowError());
2681
3365
  } else {
2682
- resolve2(answer);
3366
+ resolve3(answer);
2683
3367
  }
2684
3368
  });
2685
3369
  });
@@ -2687,11 +3371,11 @@ function question(rl, prompt) {
2687
3371
  async function cmdToolsScaffold(target, rest) {
2688
3372
  const name = rest.trim();
2689
3373
  if (!name) {
2690
- console.log(pc2.yellow(" Usage: tools/scaffold <name>"));
3374
+ console.log(colors.yellow(" Usage: tools/scaffold <name>"));
2691
3375
  if (cachedToolNames.length > 0) {
2692
3376
  const preview = cachedToolNames.slice(0, 6);
2693
3377
  const more = cachedToolNames.length > 6 ? `, ... (${cachedToolNames.length} total)` : "";
2694
- console.log(pc2.dim(`
3378
+ console.log(colors.dim(`
2695
3379
  Available tools: ${preview.join(", ")}${more}`));
2696
3380
  }
2697
3381
  return;
@@ -2699,38 +3383,38 @@ async function cmdToolsScaffold(target, rest) {
2699
3383
  const { tools } = await target.listTools();
2700
3384
  const tool = tools.find((t) => t.name === name);
2701
3385
  if (!tool) {
2702
- console.log(pc2.red(`Tool "${name}" not found.`));
3386
+ console.log(colors.red(`Tool "${name}" not found.`));
2703
3387
  const suggestion = suggestCommand(
2704
3388
  name,
2705
3389
  tools.map((t) => t.name)
2706
3390
  );
2707
3391
  if (suggestion) {
2708
- console.log(pc2.yellow(`Did you mean ${pc2.bold(suggestion)}?`));
3392
+ console.log(colors.yellow(`Did you mean ${colors.bold(suggestion)}?`));
2709
3393
  } else {
2710
- console.log(pc2.dim(`Available: ${tools.map((t) => t.name).join(", ")}`));
3394
+ console.log(colors.dim(`Available: ${tools.map((t) => t.name).join(", ")}`));
2711
3395
  }
2712
3396
  return;
2713
3397
  }
2714
3398
  const scaffolded = scaffoldArgs(tool.inputSchema);
2715
- console.log(pc2.cyan("\n Ready-to-paste command:"));
3399
+ console.log(colors.cyan("\n Ready-to-paste command:"));
2716
3400
  console.log(` tools/call ${name} ${scaffolded}
2717
3401
  `);
2718
3402
  }
2719
3403
  async function cmdResourcesList(target) {
2720
3404
  const { resources } = await target.listResources();
2721
3405
  if (resources.length === 0) {
2722
- console.log(pc2.dim(" No resources available."));
3406
+ console.log(colors.dim(" No resources available."));
2723
3407
  return;
2724
3408
  }
2725
3409
  const uriWidth = Math.max(6, ...resources.map((r) => r.uri.length));
2726
- console.log(pc2.bold(` ${"URI".padEnd(uriWidth)} Name`));
2727
- console.log(pc2.dim(` ${"\u2500".repeat(uriWidth)} ${"\u2500".repeat(40)}`));
3410
+ console.log(colors.bold(` ${"URI".padEnd(uriWidth)} Name`));
3411
+ console.log(colors.dim(` ${"\u2500".repeat(uriWidth)} ${"\u2500".repeat(40)}`));
2728
3412
  for (const r of resources) {
2729
3413
  const uri = r.uri;
2730
- const name = r.name ?? pc2.dim("(unnamed)");
2731
- console.log(` ${pc2.green(uri.padEnd(uriWidth))} ${name}`);
3414
+ const name = r.name ?? colors.dim("(unnamed)");
3415
+ console.log(` ${colors.green(uri.padEnd(uriWidth))} ${name}`);
2732
3416
  }
2733
- console.log(pc2.dim(`
3417
+ console.log(colors.dim(`
2734
3418
  ${resources.length} resource(s) total.`));
2735
3419
  }
2736
3420
  async function cmdResourcesRead(target, rest, interceptor) {
@@ -2750,11 +3434,11 @@ async function cmdResourcesRead(target, rest, interceptor) {
2750
3434
  if (!picked) return;
2751
3435
  return cmdResourcesRead(target, picked, interceptor);
2752
3436
  }
2753
- console.log(pc2.yellow(" Usage: resources/read <uri>"));
3437
+ console.log(colors.yellow(" Usage: resources/read <uri>"));
2754
3438
  if (cachedResourceUris.length > 0) {
2755
3439
  const preview = cachedResourceUris.slice(0, 5);
2756
3440
  const more = cachedResourceUris.length > 5 ? `, ... (${cachedResourceUris.length} total)` : "";
2757
- console.log(pc2.dim(`
3441
+ console.log(colors.dim(`
2758
3442
  Available resources: ${preview.join(", ")}${more}`));
2759
3443
  }
2760
3444
  return;
@@ -2772,15 +3456,15 @@ async function cmdResourcesRead(target, rest, interceptor) {
2772
3456
  if (typeof parsed === "object" && parsed !== null) {
2773
3457
  console.log(formatJson(parsed, 2, true));
2774
3458
  } else {
2775
- console.log(pc2.yellow(` ${text}`));
3459
+ console.log(colors.yellow(` ${text}`));
2776
3460
  }
2777
3461
  } catch {
2778
- console.log(pc2.yellow(` ${text}`));
3462
+ console.log(colors.yellow(` ${text}`));
2779
3463
  }
2780
3464
  } else if (item.blob !== void 0) {
2781
3465
  const mimeType = item.mimeType ?? "application/octet-stream";
2782
3466
  const sizeBytes = Buffer.from(item.blob, "base64").length;
2783
- console.log(pc2.dim(` [Binary: ${mimeType}, ${sizeBytes} bytes]`));
3467
+ console.log(colors.dim(` [Binary: ${mimeType}, ${sizeBytes} bytes]`));
2784
3468
  } else {
2785
3469
  console.log(formatJson(item, 2, true));
2786
3470
  }
@@ -2791,37 +3475,37 @@ async function cmdResourcesRead(target, rest, interceptor) {
2791
3475
  async function cmdResourcesTemplates(target) {
2792
3476
  const { resourceTemplates } = await target.listResourceTemplates();
2793
3477
  if (resourceTemplates.length === 0) {
2794
- console.log(pc2.dim(" No resource templates available."));
3478
+ console.log(colors.dim(" No resource templates available."));
2795
3479
  return;
2796
3480
  }
2797
3481
  const uriWidth = Math.max(
2798
3482
  12,
2799
3483
  ...resourceTemplates.map((t) => t.uriTemplate.length)
2800
3484
  );
2801
- console.log(pc2.bold(` ${"URI Template".padEnd(uriWidth)} Name`));
2802
- console.log(pc2.dim(` ${"\u2500".repeat(uriWidth)} ${"\u2500".repeat(40)}`));
3485
+ console.log(colors.bold(` ${"URI Template".padEnd(uriWidth)} Name`));
3486
+ console.log(colors.dim(` ${"\u2500".repeat(uriWidth)} ${"\u2500".repeat(40)}`));
2803
3487
  for (const t of resourceTemplates) {
2804
3488
  const uriTemplate = t.uriTemplate;
2805
- const name = t.name ?? pc2.dim("(unnamed)");
2806
- console.log(` ${pc2.green(uriTemplate.padEnd(uriWidth))} ${name}`);
3489
+ const name = t.name ?? colors.dim("(unnamed)");
3490
+ console.log(` ${colors.green(uriTemplate.padEnd(uriWidth))} ${name}`);
2807
3491
  }
2808
- console.log(pc2.dim(`
3492
+ console.log(colors.dim(`
2809
3493
  ${resourceTemplates.length} template(s) total.`));
2810
3494
  }
2811
3495
  async function cmdPromptsList(target) {
2812
3496
  const { prompts } = await target.listPrompts();
2813
3497
  if (prompts.length === 0) {
2814
- console.log(pc2.dim(" No prompts available."));
3498
+ console.log(colors.dim(" No prompts available."));
2815
3499
  return;
2816
3500
  }
2817
3501
  const nameWidth = Math.max(8, ...prompts.map((p) => p.name.length));
2818
- console.log(pc2.bold(` ${"Name".padEnd(nameWidth)} Description`));
2819
- console.log(pc2.dim(` ${"\u2500".repeat(nameWidth)} ${"\u2500".repeat(50)}`));
3502
+ console.log(colors.bold(` ${"Name".padEnd(nameWidth)} Description`));
3503
+ console.log(colors.dim(` ${"\u2500".repeat(nameWidth)} ${"\u2500".repeat(50)}`));
2820
3504
  for (const p of prompts) {
2821
- const desc = p.description ? p.description.length > 60 ? `${p.description.slice(0, 57)}...` : p.description : pc2.dim("(no description)");
2822
- console.log(` ${pc2.green(p.name.padEnd(nameWidth))} ${desc}`);
3505
+ const desc = p.description ? p.description.length > 60 ? `${p.description.slice(0, 57)}...` : p.description : colors.dim("(no description)");
3506
+ console.log(` ${colors.green(p.name.padEnd(nameWidth))} ${desc}`);
2823
3507
  }
2824
- console.log(pc2.dim(`
3508
+ console.log(colors.dim(`
2825
3509
  ${prompts.length} prompt(s) total.`));
2826
3510
  }
2827
3511
  async function cmdPromptsGet(target, rest, interceptor) {
@@ -2838,9 +3522,9 @@ async function cmdPromptsGet(target, rest, interceptor) {
2838
3522
  if (!picked) return;
2839
3523
  return cmdPromptsGet(target, picked);
2840
3524
  }
2841
- console.log(pc2.yellow(" Usage: prompts/get <name> [json_args]"));
3525
+ console.log(colors.yellow(" Usage: prompts/get <name> [json_args]"));
2842
3526
  if (cachedPromptNames.length > 0) {
2843
- console.log(pc2.dim(`
3527
+ console.log(colors.dim(`
2844
3528
  Available prompts: ${cachedPromptNames.join(", ")}`));
2845
3529
  }
2846
3530
  return;
@@ -2852,15 +3536,15 @@ async function cmdPromptsGet(target, rest, interceptor) {
2852
3536
  try {
2853
3537
  promptArgs = JSON.parse(trimmed);
2854
3538
  } catch (err) {
2855
- console.error(pc2.red(`Invalid JSON: ${err.message}`));
2856
- console.log(pc2.dim(` Received: ${jsonArgs}`));
3539
+ console.error(colors.red(`Invalid JSON: ${err.message}`));
3540
+ console.log(colors.dim(` Received: ${jsonArgs}`));
2857
3541
  return;
2858
3542
  }
2859
3543
  } else {
2860
3544
  try {
2861
3545
  promptArgs = parseHttpieArgs(trimmed);
2862
3546
  } catch (err) {
2863
- console.error(pc2.red(`Invalid shorthand arguments: ${err.message}`));
3547
+ console.error(colors.red(`Invalid shorthand arguments: ${err.message}`));
2864
3548
  return;
2865
3549
  }
2866
3550
  }
@@ -2868,14 +3552,14 @@ async function cmdPromptsGet(target, rest, interceptor) {
2868
3552
  const { prompts } = await target.listPrompts();
2869
3553
  const prompt = prompts.find((p) => p.name === promptName);
2870
3554
  if (!prompt) {
2871
- console.log(pc2.red(`
3555
+ console.log(colors.red(`
2872
3556
  \u2717 Prompt "${promptName}" not found.`));
2873
3557
  const promptNames = prompts.map((p) => p.name);
2874
3558
  const suggestion = suggestCommand(promptName, promptNames);
2875
3559
  if (suggestion) {
2876
- console.log(pc2.yellow(` \u{1F4A1} Did you mean "${suggestion}"?`));
3560
+ console.log(colors.yellow(` \u{1F4A1} Did you mean "${suggestion}"?`));
2877
3561
  } else {
2878
- console.log(pc2.dim(` Available prompts: ${promptNames.join(", ")}`));
3562
+ console.log(colors.dim(` Available prompts: ${promptNames.join(", ")}`));
2879
3563
  }
2880
3564
  return { isError: true, content: [{ type: "text", text: `Prompt not found: ${promptName}` }] };
2881
3565
  }
@@ -2883,24 +3567,24 @@ async function cmdPromptsGet(target, rest, interceptor) {
2883
3567
  const result = await target.getPrompt({ name: promptName, arguments: promptArgs });
2884
3568
  const elapsed = Date.now() - startTime;
2885
3569
  if (result.messages.length === 0) {
2886
- console.log(pc2.dim(" No messages returned."));
3570
+ console.log(colors.dim(" No messages returned."));
2887
3571
  return;
2888
3572
  }
2889
3573
  console.log();
2890
3574
  printResultBlock({ label: "Prompt", labelColor: "blue", elapsed, detail: promptName });
2891
3575
  for (const msg of result.messages) {
2892
- const role = msg.role === "user" ? pc2.blue("user") : pc2.magenta("assistant");
3576
+ const role = msg.role === "user" ? colors.blue("user") : colors.magenta("assistant");
2893
3577
  const text = msg.content.text ?? JSON.stringify(msg.content);
2894
3578
  try {
2895
3579
  const parsed = JSON.parse(text);
2896
3580
  if (typeof parsed === "object" && parsed !== null) {
2897
- console.log(` ${pc2.bold(role)}:`);
3581
+ console.log(` ${colors.bold(role)}:`);
2898
3582
  console.log(formatJson(parsed, 4, true));
2899
3583
  } else {
2900
- console.log(` ${pc2.bold(role)}: ${pc2.yellow(text)}`);
3584
+ console.log(` ${colors.bold(role)}: ${colors.yellow(text)}`);
2901
3585
  }
2902
3586
  } catch {
2903
- console.log(` ${pc2.bold(role)}: ${pc2.yellow(text)}`);
3587
+ console.log(` ${colors.bold(role)}: ${colors.yellow(text)}`);
2904
3588
  }
2905
3589
  }
2906
3590
  console.log();
@@ -2910,36 +3594,36 @@ async function cmdPing(target) {
2910
3594
  try {
2911
3595
  const elapsed = await target.ping();
2912
3596
  console.log();
2913
- console.log(pc2.green(` \u2713 Pong! Round-trip: ${elapsed}ms`));
3597
+ console.log(colors.green(` \u2713 Pong! Round-trip: ${elapsed}ms`));
2914
3598
  console.log();
2915
3599
  } catch (err) {
2916
3600
  console.log();
2917
- console.error(pc2.red(` \u2717 Ping failed: ${err.message}`));
3601
+ console.error(colors.red(` \u2717 Ping failed: ${err.message}`));
2918
3602
  console.log();
2919
3603
  }
2920
3604
  }
2921
3605
  async function cmdLogLevel(target, rest) {
2922
3606
  const level = rest.trim().toLowerCase();
2923
3607
  if (!level) {
2924
- console.log(pc2.yellow(" Usage: log-level <level>"));
2925
- console.log(pc2.dim(` Valid levels: ${LOG_LEVELS.join(", ")}`));
3608
+ console.log(colors.yellow(" Usage: log-level <level>"));
3609
+ console.log(colors.dim(` Valid levels: ${LOG_LEVELS.join(", ")}`));
2926
3610
  return;
2927
3611
  }
2928
3612
  if (!LOG_LEVELS.includes(level)) {
2929
3613
  const suggestion = suggestCommand(level, [...LOG_LEVELS]);
2930
3614
  const hint = suggestion ? ` Did you mean "${suggestion}"?` : "";
2931
- console.log(pc2.red(` Unknown log level: "${level}".${hint}`));
2932
- console.log(pc2.dim(` Valid levels: ${LOG_LEVELS.join(", ")}`));
3615
+ console.log(colors.red(` Unknown log level: "${level}".${hint}`));
3616
+ console.log(colors.dim(` Valid levels: ${LOG_LEVELS.join(", ")}`));
2933
3617
  return;
2934
3618
  }
2935
3619
  try {
2936
3620
  await target.setLoggingLevel(level);
2937
- console.log(pc2.green(` \u2713 Logging level set to: ${level}`));
3621
+ console.log(colors.green(` \u2713 Logging level set to: ${level}`));
2938
3622
  } catch (err) {
2939
- console.error(pc2.red(` \u2717 Failed to set log level: ${err.message}`));
3623
+ console.error(colors.red(` \u2717 Failed to set log level: ${err.message}`));
2940
3624
  const caps = target.getServerCapabilities();
2941
3625
  if (!caps?.logging) {
2942
- console.log(pc2.dim(" The server does not advertise logging support."));
3626
+ console.log(colors.dim(" The server does not advertise logging support."));
2943
3627
  }
2944
3628
  }
2945
3629
  }
@@ -2947,28 +3631,28 @@ function cmdHistory(target, rest) {
2947
3631
  const arg = rest.trim();
2948
3632
  if (arg === "clear") {
2949
3633
  target.clearHistory();
2950
- console.log(pc2.dim(" History cleared."));
3634
+ console.log(colors.dim(" History cleared."));
2951
3635
  return;
2952
3636
  }
2953
3637
  const count = arg ? Number.parseInt(arg, 10) : 20;
2954
3638
  const records = target.getHistory(Number.isNaN(count) ? 20 : count);
2955
3639
  if (records.length === 0) {
2956
- console.log(pc2.dim(" No request history yet."));
3640
+ console.log(colors.dim(" No request history yet."));
2957
3641
  return;
2958
3642
  }
2959
- console.log(pc2.bold("\n Request History"));
2960
- console.log(pc2.dim(` ${"\u2500".repeat(70)}`));
3643
+ console.log(colors.bold("\n Request History"));
3644
+ console.log(colors.dim(` ${"\u2500".repeat(70)}`));
2961
3645
  for (const rec of records) {
2962
3646
  const time = new Date(rec.timestamp).toLocaleTimeString();
2963
3647
  const dur = `${rec.durationMs}ms`;
2964
- const hasError = rec.error ? pc2.red(" \u2717") : "";
3648
+ const hasError = rec.error ? colors.red(" \u2717") : "";
2965
3649
  console.log(
2966
- ` ${pc2.dim(`#${rec.id}`)} ${pc2.dim(time)} ${pc2.green(rec.method.padEnd(30))} ${pc2.cyan(dur.padStart(8))}${hasError}`
3650
+ ` ${colors.dim(`#${rec.id}`)} ${colors.dim(time)} ${colors.green(rec.method.padEnd(30))} ${colors.cyan(dur.padStart(8))}${hasError}`
2967
3651
  );
2968
3652
  }
2969
3653
  const total = target.getHistory().length;
2970
3654
  console.log(
2971
- pc2.dim(`
3655
+ colors.dim(`
2972
3656
  Showing ${records.length} of ${total} total. Use "history clear" to reset.`)
2973
3657
  );
2974
3658
  console.log();
@@ -2977,26 +3661,26 @@ function cmdNotifications(target, rest) {
2977
3661
  const arg = rest.trim();
2978
3662
  if (arg === "clear") {
2979
3663
  target.clearNotifications();
2980
- console.log(pc2.dim(" Notifications cleared."));
3664
+ console.log(colors.dim(" Notifications cleared."));
2981
3665
  return;
2982
3666
  }
2983
3667
  const count = arg ? Number.parseInt(arg, 10) : 20;
2984
3668
  const records = target.getNotifications(Number.isNaN(count) ? 20 : count);
2985
3669
  if (records.length === 0) {
2986
- console.log(pc2.dim(" No notifications received yet."));
2987
- console.log(pc2.dim(" Notifications appear inline as they arrive from the server."));
3670
+ console.log(colors.dim(" No notifications received yet."));
3671
+ console.log(colors.dim(" Notifications appear inline as they arrive from the server."));
2988
3672
  return;
2989
3673
  }
2990
- console.log(pc2.bold("\n Server Notifications"));
2991
- console.log(pc2.dim(` ${"\u2500".repeat(70)}`));
3674
+ console.log(colors.bold("\n Server Notifications"));
3675
+ console.log(colors.dim(` ${"\u2500".repeat(70)}`));
2992
3676
  for (const n of records) {
2993
3677
  const time = new Date(n.timestamp).toLocaleTimeString();
2994
- const params = n.params ? ` ${pc2.dim(JSON.stringify(n.params))}` : "";
2995
- console.log(` ${pc2.dim(time)} ${pc2.yellow(n.method)}${params}`);
3678
+ const params = n.params ? ` ${colors.dim(JSON.stringify(n.params))}` : "";
3679
+ console.log(` ${colors.dim(time)} ${colors.yellow(n.method)}${params}`);
2996
3680
  }
2997
3681
  const total = target.getNotifications().length;
2998
3682
  console.log(
2999
- pc2.dim(`
3683
+ colors.dim(`
3000
3684
  Showing ${records.length} of ${total} total. Use "notifications clear" to reset.`)
3001
3685
  );
3002
3686
  console.log();
@@ -3004,44 +3688,44 @@ function cmdNotifications(target, rest) {
3004
3688
  async function cmdResourcesSubscribe(target, rest) {
3005
3689
  const uri = rest.trim();
3006
3690
  if (!uri) {
3007
- console.log(pc2.yellow(" Usage: resources/subscribe <uri>"));
3691
+ console.log(colors.yellow(" Usage: resources/subscribe <uri>"));
3008
3692
  if (cachedResourceUris.length > 0) {
3009
- console.log(pc2.dim(` Available: ${cachedResourceUris.join(", ")}`));
3693
+ console.log(colors.dim(` Available: ${cachedResourceUris.join(", ")}`));
3010
3694
  }
3011
3695
  return;
3012
3696
  }
3013
3697
  try {
3014
3698
  await target.subscribeResource({ uri });
3015
- console.log(pc2.green(` \u2713 Subscribed to: ${uri}`));
3016
- console.log(pc2.dim(" You'll see notifications when this resource changes."));
3699
+ console.log(colors.green(` \u2713 Subscribed to: ${uri}`));
3700
+ console.log(colors.dim(" You'll see notifications when this resource changes."));
3017
3701
  } catch (err) {
3018
- console.error(pc2.red(` \u2717 Subscribe failed: ${err.message}`));
3702
+ console.error(colors.red(` \u2717 Subscribe failed: ${err.message}`));
3019
3703
  }
3020
3704
  }
3021
3705
  async function cmdResourcesUnsubscribe(target, rest) {
3022
3706
  const uri = rest.trim();
3023
3707
  if (!uri) {
3024
- console.log(pc2.yellow(" Usage: resources/unsubscribe <uri>"));
3708
+ console.log(colors.yellow(" Usage: resources/unsubscribe <uri>"));
3025
3709
  return;
3026
3710
  }
3027
3711
  try {
3028
3712
  await target.unsubscribeResource({ uri });
3029
- console.log(pc2.green(` \u2713 Unsubscribed from: ${uri}`));
3713
+ console.log(colors.green(` \u2713 Unsubscribed from: ${uri}`));
3030
3714
  } catch (err) {
3031
- console.error(pc2.red(` \u2717 Unsubscribe failed: ${err.message}`));
3715
+ console.error(colors.red(` \u2717 Unsubscribe failed: ${err.message}`));
3032
3716
  }
3033
3717
  }
3034
3718
  function cmdRootsList(target) {
3035
3719
  const roots = target.getRoots();
3036
3720
  if (roots.length === 0) {
3037
- console.log(pc2.dim(" No roots configured."));
3038
- console.log(pc2.dim(" Use roots/add <uri> [name] to add one."));
3721
+ console.log(colors.dim(" No roots configured."));
3722
+ console.log(colors.dim(" Use roots/add <uri> [name] to add one."));
3039
3723
  return;
3040
3724
  }
3041
- console.log(pc2.bold("\n Client Roots"));
3725
+ console.log(colors.bold("\n Client Roots"));
3042
3726
  for (const r of roots) {
3043
3727
  const name = r.name ? ` (${r.name})` : "";
3044
- console.log(` ${pc2.green(r.uri)}${pc2.dim(name)}`);
3728
+ console.log(` ${colors.green(r.uri)}${colors.dim(name)}`);
3045
3729
  }
3046
3730
  console.log();
3047
3731
  }
@@ -3050,53 +3734,53 @@ async function cmdRootsAdd(target, rest) {
3050
3734
  const uri = parts[0];
3051
3735
  const name = parts.slice(1).join(" ") || void 0;
3052
3736
  if (!uri) {
3053
- console.log(pc2.yellow(" Usage: roots/add <uri> [name]"));
3054
- console.log(pc2.dim(' Example: roots/add file:///Users/me/project "My Project"'));
3737
+ console.log(colors.yellow(" Usage: roots/add <uri> [name]"));
3738
+ console.log(colors.dim(' Example: roots/add file:///Users/me/project "My Project"'));
3055
3739
  return;
3056
3740
  }
3057
3741
  await target.addRoot({ uri, name });
3058
- console.log(pc2.green(` \u2713 Root added: ${uri}`));
3059
- console.log(pc2.dim(" Server has been notified of the change."));
3742
+ console.log(colors.green(` \u2713 Root added: ${uri}`));
3743
+ console.log(colors.dim(" Server has been notified of the change."));
3060
3744
  }
3061
3745
  async function cmdRootsRemove(target, rest) {
3062
3746
  const uri = rest.trim();
3063
3747
  if (!uri) {
3064
- console.log(pc2.yellow(" Usage: roots/remove <uri>"));
3748
+ console.log(colors.yellow(" Usage: roots/remove <uri>"));
3065
3749
  const roots = target.getRoots();
3066
3750
  if (roots.length > 0) {
3067
- console.log(pc2.dim(` Current roots: ${roots.map((r) => r.uri).join(", ")}`));
3751
+ console.log(colors.dim(` Current roots: ${roots.map((r) => r.uri).join(", ")}`));
3068
3752
  }
3069
3753
  return;
3070
3754
  }
3071
3755
  const removed = await target.removeRoot(uri);
3072
3756
  if (removed) {
3073
- console.log(pc2.green(` \u2713 Root removed: ${uri}`));
3757
+ console.log(colors.green(` \u2713 Root removed: ${uri}`));
3074
3758
  } else {
3075
- console.log(pc2.yellow(` Root not found: ${uri}`));
3759
+ console.log(colors.yellow(` Root not found: ${uri}`));
3076
3760
  }
3077
3761
  }
3078
3762
  async function cmdReconnect(target) {
3079
- console.log(pc2.cyan("\u27F3 Disconnecting..."));
3763
+ console.log(colors.cyan("\u27F3 Disconnecting..."));
3080
3764
  await target.close();
3081
- await new Promise((resolve2) => setTimeout(resolve2, 200));
3082
- console.log(pc2.cyan("\u27F3 Reconnecting..."));
3765
+ await new Promise((resolve3) => setTimeout(resolve3, 200));
3766
+ console.log(colors.cyan("\u27F3 Reconnecting..."));
3083
3767
  const { command, args } = target.getStatus();
3084
- console.log(pc2.dim(` Command: ${command} ${args.join(" ")}`));
3768
+ console.log(colors.dim(` Command: ${command} ${args.join(" ")}`));
3085
3769
  try {
3086
3770
  await target.connect();
3087
3771
  const s = target.getStatus();
3088
- console.log(pc2.green(`\u2713 Reconnected (PID: ${s.pid})`));
3772
+ console.log(colors.green(`\u2713 Reconnected (PID: ${s.pid})`));
3089
3773
  const { tools } = await target.listTools();
3090
- console.log(pc2.cyan(` ${tools.length} tool(s) available.
3774
+ console.log(colors.cyan(` ${tools.length} tool(s) available.
3091
3775
  `));
3092
3776
  await refreshCaches(target);
3093
3777
  } catch (err) {
3094
- console.error(pc2.red(`\u2717 Failed to reconnect: ${err.message}`));
3778
+ console.error(colors.red(`\u2717 Failed to reconnect: ${err.message}`));
3095
3779
  }
3096
3780
  }
3097
3781
  function cmdTiming() {
3098
3782
  if (callHistory.length === 0) {
3099
- console.log(pc2.dim(" No tool calls recorded yet."));
3783
+ console.log(colors.dim(" No tool calls recorded yet."));
3100
3784
  return;
3101
3785
  }
3102
3786
  const groups = /* @__PURE__ */ new Map();
@@ -3106,8 +3790,8 @@ function cmdTiming() {
3106
3790
  groups.set(record.toolName, list);
3107
3791
  }
3108
3792
  const nameWidth = Math.max(8, ...[...groups.keys()].map((n) => n.length));
3109
- console.log(pc2.bold("\n Tool Call Performance"));
3110
- console.log(pc2.dim(` ${"\u2500".repeat(nameWidth + 50)}`));
3793
+ console.log(colors.bold("\n Tool Call Performance"));
3794
+ console.log(colors.dim(` ${"\u2500".repeat(nameWidth + 50)}`));
3111
3795
  let totalCalls = 0;
3112
3796
  let totalMs = 0;
3113
3797
  let slowestName = "";
@@ -3125,12 +3809,12 @@ function cmdTiming() {
3125
3809
  slowestName = name;
3126
3810
  }
3127
3811
  console.log(
3128
- ` ${pc2.green(name.padEnd(nameWidth))} \xD7 ${count} avg: ${avg}ms p95: ${p95}ms max: ${max}ms`
3812
+ ` ${colors.green(name.padEnd(nameWidth))} \xD7 ${count} avg: ${avg}ms p95: ${p95}ms max: ${max}ms`
3129
3813
  );
3130
3814
  }
3131
3815
  const overallAvg = Math.round(totalMs / totalCalls);
3132
3816
  console.log(
3133
- pc2.dim(
3817
+ colors.dim(
3134
3818
  `
3135
3819
  Total: ${totalCalls} call(s), avg ${overallAvg}ms, slowest: ${slowestName} (${slowestMs}ms)`
3136
3820
  )
@@ -3141,138 +3825,34 @@ function cmdStatus(target) {
3141
3825
  const s = target.getStatus();
3142
3826
  const uptimeStr = s.uptime >= 60 ? `${Math.floor(s.uptime / 60)}m ${(s.uptime % 60).toFixed(0)}s` : `${s.uptime.toFixed(1)}s`;
3143
3827
  const lastRespStr = s.lastResponseTime ? `${((Date.now() - s.lastResponseTime) / 1e3).toFixed(1)}s ago` : "never";
3144
- console.log(pc2.bold("\n Target Server Status"));
3145
- console.log(` ${pc2.dim("Connected:")} ${s.connected ? pc2.green("yes") : pc2.red("no")}`);
3146
- console.log(` ${pc2.dim("PID:")} ${s.pid ?? "N/A"}`);
3147
- console.log(` ${pc2.dim("Uptime:")} ${uptimeStr}`);
3148
- console.log(` ${pc2.dim("Last response:")} ${lastRespStr}`);
3149
- console.log(` ${pc2.dim("Stderr lines:")} ${s.stderrLineCount.toLocaleString()}`);
3150
- console.log(` ${pc2.dim("Reconnects:")} ${s.reconnectAttempts}/${s.maxReconnectAttempts}`);
3151
- console.log(` ${pc2.dim("Command:")} ${s.command} ${s.args.join(" ")}`);
3828
+ console.log(colors.bold("\n Target Server Status"));
3829
+ console.log(` ${colors.dim("Connected:")} ${s.connected ? colors.green("yes") : colors.red("no")}`);
3830
+ console.log(` ${colors.dim("PID:")} ${s.pid ?? "N/A"}`);
3831
+ console.log(` ${colors.dim("Uptime:")} ${uptimeStr}`);
3832
+ console.log(` ${colors.dim("Last response:")} ${lastRespStr}`);
3833
+ console.log(` ${colors.dim("Stderr lines:")} ${s.stderrLineCount.toLocaleString()}`);
3834
+ console.log(` ${colors.dim("Reconnects:")} ${s.reconnectAttempts}/${s.maxReconnectAttempts}`);
3835
+ console.log(` ${colors.dim("Command:")} ${s.command} ${s.args.join(" ")}`);
3152
3836
  console.log();
3153
3837
  }
3154
- function printResultBlock(opts) {
3155
- const colorFn = pc2[opts.labelColor];
3156
- const elapsedStr = opts.elapsed < 1e3 ? `${opts.elapsed}ms` : `${(opts.elapsed / 1e3).toFixed(1)}s`;
3157
- const detail = opts.detail ?? opts.toolName ?? "";
3158
- console.log(` ${colorFn(opts.label)} ${pc2.dim(detail)} ${pc2.dim(`(${elapsedStr})`)}`);
3159
- console.log(pc2.dim(` ${"\u2500".repeat(60)}`));
3160
- }
3161
- function printShortHelp() {
3162
- const hasTools = !!activeCapabilities?.tools;
3163
- const hasResources = !!activeCapabilities?.resources;
3164
- const hasPrompts = !!activeCapabilities?.prompts;
3165
- const tC = hasTools ? pc2.green : pc2.dim;
3166
- const rC = hasResources ? pc2.green : pc2.dim;
3167
- const pC = hasPrompts ? pc2.green : pc2.dim;
3168
- console.log(`
3169
- ${pc2.bold("Quick Reference:")}
3170
-
3171
- ${tC("tl")} ${tC("tools/list")} ${rC("rl")} ${rC("resources/list")} ${pC("pl")} ${pC("prompts/list")}
3172
- ${tC("td")} ${tC("tools/describe")} ${rC("rr")} ${rC("resources/read")} ${pC("pg")} ${pC("prompts/get")}
3173
- ${tC("tc")} ${tC("tools/call")} ${rC("rt")} ${rC("resources/templates")}
3174
- ${tC("ts")} ${tC("tools/scaffold")} ${rC("rs")} ${rC("resources/subscribe")}
3175
- ${rC("ru")} ${rC("resources/unsubscribe")}
3176
-
3177
- ${pc2.green("ping")} ${pc2.green("status")} ${pc2.green("timing")} ${pc2.green("history")} ${pc2.green("!!")} ${pc2.green("explore")} ${pc2.green("reconnect")}
3178
-
3179
- ${pc2.dim("Type 'help' for full command reference.")}
3180
- `);
3181
- }
3182
- function printHelp() {
3183
- const hasTools = !!activeCapabilities?.tools;
3184
- const hasResources = !!activeCapabilities?.resources;
3185
- const hasPrompts = !!activeCapabilities?.prompts;
3186
- const hasLogging = !!activeCapabilities?.logging;
3187
- const tC = hasTools ? pc2.green : pc2.dim;
3188
- const rC = hasResources ? pc2.green : pc2.dim;
3189
- const pC = hasPrompts ? pc2.green : pc2.dim;
3190
- const lC = hasLogging ? pc2.green : pc2.dim;
3191
- const tD = hasTools ? (s) => s : pc2.dim;
3192
- const rD = hasResources ? (s) => s : pc2.dim;
3193
- const pD = hasPrompts ? (s) => s : pc2.dim;
3194
- const lD = hasLogging ? (s) => s : pc2.dim;
3195
- const tH = hasTools ? pc2.bold("Tool Commands:") : pc2.dim(pc2.bold("Tool Commands:")) + pc2.dim(" (Unsupported)");
3196
- const rH = hasResources ? pc2.bold("Resource Commands:") : pc2.dim(pc2.bold("Resource Commands:")) + pc2.dim(" (Unsupported)");
3197
- const pH = hasPrompts ? pc2.bold("Prompt Commands:") : pc2.dim(pc2.bold("Prompt Commands:")) + pc2.dim(" (Unsupported)");
3198
- console.log(`
3199
- ${tH}
3200
-
3201
- ${tC("tools/list")} ${tD("List all available tools")}
3202
- ${tC("tools/describe")} <name> ${tD("Show a tool's input schema")}
3203
- ${tC("tools/call")} <name> [json] [opts] ${tD("Call a tool (interactive if no json)")}
3204
- ${tD("Options:")} ${pc2.dim("--timeout <ms>")} ${tD("Override default timeout (60s)")}
3205
- ${pc2.dim("--clear")} ${tD("Ignore remembered argument defaults")}
3206
- ${tC("tools/scaffold")} <name> ${tD("Generate a template for a tool's arguments")}
3207
- ${tC("tools/forget")} [name] ${tD("Clear remembered interactive defaults")}
3208
-
3209
- ${rH}
3210
-
3211
- ${rC("resources/list")} ${rD("List all available resources")}
3212
- ${rC("resources/read")} <uri> ${rD("Read a resource by URI")}
3213
- ${rC("resources/templates")} ${rD("List resource templates")}
3214
- ${rC("resources/subscribe")} <uri> ${rD("Subscribe to resource changes")}
3215
- ${rC("resources/unsubscribe")} <uri> ${rD("Unsubscribe from resource changes")}
3216
-
3217
- ${pH}
3218
-
3219
- ${pC("prompts/list")} ${pD("List all available prompts")}
3220
- ${pC("prompts/get")} <name> [json_args] ${pD("Get a prompt with arguments")}
3221
-
3222
- ${pc2.bold("Protocol Commands:")}
3223
-
3224
- ${pc2.green("ping")} Verify connection, show round-trip time
3225
- ${lC("log-level")} <level> ${lD("Set server logging verbosity")}${hasLogging ? "" : pc2.dim(" (Unsupported)")}
3226
- ${pc2.green("history")} [count|clear] Show request/response history
3227
- ${pc2.green("notifications")} [count|clear] Show server notifications
3228
-
3229
- ${pc2.bold("Roots Management:")}
3230
-
3231
- ${pc2.green("roots/list")} Show configured client roots
3232
- ${pc2.green("roots/add")} <uri> [name] Add a root directory
3233
- ${pc2.green("roots/remove")} <uri> Remove a root directory
3234
-
3235
- ${pc2.bold("Session Commands:")}
3236
-
3237
- ${pc2.green("!!")} / ${pc2.green("last")} Re-run the last command
3238
- ${pc2.green("reconnect")} Disconnect and reconnect to the server
3239
- ${pc2.green("timing")} Show tool call performance stats
3240
- ${pc2.green("status")} Show target server status
3241
- ${pc2.green("help")} Show this help
3242
- ${pc2.green("exit")} / ${pc2.green("quit")} Disconnect and exit
3243
-
3244
- ${pc2.bold("Shortcuts:")}
3245
-
3246
- ${tC("tl")} ${tC("tools/list")} ${rC("rl")} ${rC("resources/list")} ${pC("pl")} ${pC("prompts/list")}
3247
- ${tC("td")} ${tC("tools/describe")} ${rC("rr")} ${rC("resources/read")} ${pC("pg")} ${pC("prompts/get")}
3248
- ${tC("tc")} ${tC("tools/call")} ${rC("rt")} ${rC("resources/templates")}
3249
- ${tC("ts")} ${tC("tools/scaffold")} ${rC("rs")} ${rC("resources/subscribe")}
3250
- ${rC("ru")} ${rC("resources/unsubscribe")}
3251
-
3252
- ${pc2.dim("Lines starting with # are treated as comments.")}
3253
- ${pc2.dim('JSON arguments can contain spaces: tools/call say {"message": "hello world"}')}
3254
- ${pc2.dim("Run tools/call <name> without JSON for interactive argument prompting.")}
3255
- ${pc2.dim("Use tools/call <name> --clear to ignore remembered defaults.")}
3256
- `);
3257
- }
3258
- async function readScriptLines(filepath) {
3259
- const content = await readFile2(filepath, "utf-8");
3260
- return content.split("\n");
3261
- }
3262
- function cmdToolsForget(rest) {
3263
- const toolName = rest.trim();
3264
- if (toolName) {
3265
- if (lastToolArgsMap.has(toolName)) {
3266
- lastToolArgsMap.delete(toolName);
3267
- console.log(pc2.green(` Cleared remembered args for ${pc2.bold(toolName)}.`));
3268
- } else {
3269
- console.log(pc2.yellow(` No remembered args for "${toolName}".`));
3270
- }
3271
- } else {
3272
- const count = lastToolArgsMap.size;
3273
- lastToolArgsMap.clear();
3274
- console.log(pc2.green(` Cleared remembered args for ${count} tool${count === 1 ? "" : "s"}.`));
3275
- }
3838
+ function cmdToolsForget(rest) {
3839
+ const toolName = rest.trim();
3840
+ if (toolName) {
3841
+ if (lastToolArgsMap.has(toolName)) {
3842
+ lastToolArgsMap.delete(toolName);
3843
+ saveWizardDefaults().catch(() => {
3844
+ });
3845
+ console.log(colors.green(` Cleared remembered args for ${colors.bold(toolName)}.`));
3846
+ } else {
3847
+ console.log(colors.yellow(` No remembered args for "${toolName}".`));
3848
+ }
3849
+ } else {
3850
+ const count = lastToolArgsMap.size;
3851
+ lastToolArgsMap.clear();
3852
+ saveWizardDefaults().catch(() => {
3853
+ });
3854
+ console.log(colors.green(` Cleared remembered args for ${count} tool${count === 1 ? "" : "s"}.`));
3855
+ }
3276
3856
  }
3277
3857
  async function pickInteractive(items, message) {
3278
3858
  if (items.length === 0) return null;
@@ -3298,88 +3878,489 @@ async function pickInteractive(items, message) {
3298
3878
  if (isAbortError(err)) return null;
3299
3879
  throw err;
3300
3880
  }
3301
- }
3302
- async function cmdExplore(target, interceptor) {
3303
- const choices = [];
3304
- const caps = target.getServerCapabilities() ?? {};
3881
+ }
3882
+ async function showMainMenu(target, interceptor) {
3883
+ const choices = [];
3884
+ const caps = target.getServerCapabilities() ?? {};
3885
+ try {
3886
+ const { tools } = await target.listTools();
3887
+ for (const t of tools) {
3888
+ choices.push({
3889
+ name: `\u{1F6E0}\uFE0F Tool: ${t.name}`,
3890
+ value: { type: "tool", name: t.name },
3891
+ description: t.description || `Call the ${t.name} tool`
3892
+ });
3893
+ }
3894
+ } catch {
3895
+ }
3896
+ if (caps.resources) {
3897
+ try {
3898
+ const { resources } = await target.listResources();
3899
+ for (const r of resources) {
3900
+ choices.push({
3901
+ name: `\u{1F4C4} Resource: ${r.name || r.uri}`,
3902
+ value: { type: "resource", uri: r.uri },
3903
+ description: r.description || `Read resource ${r.uri}`
3904
+ });
3905
+ }
3906
+ } catch {
3907
+ }
3908
+ }
3909
+ if (caps.prompts) {
3910
+ try {
3911
+ const { prompts } = await target.listPrompts();
3912
+ for (const p of prompts) {
3913
+ choices.push({
3914
+ name: `\u{1F4AC} Prompt: ${p.name}`,
3915
+ value: { type: "prompt", name: p.name },
3916
+ description: p.description || `Get the ${p.name} prompt`
3917
+ });
3918
+ }
3919
+ } catch {
3920
+ }
3921
+ }
3922
+ choices.push({
3923
+ name: "\u2699\uFE0F Check Server Status",
3924
+ value: { type: "command", name: "status" },
3925
+ description: "View connection status and server info"
3926
+ });
3927
+ choices.push({
3928
+ name: "\u{1F504} Reconnect Server",
3929
+ value: { type: "command", name: "reconnect" },
3930
+ description: "Restart the target MCP server"
3931
+ });
3932
+ choices.push({
3933
+ name: "\u2328\uFE0F Type a raw command",
3934
+ value: { type: "raw" },
3935
+ description: "Drop to the traditional REPL prompt (use 'menu' to return)"
3936
+ });
3937
+ choices.push({
3938
+ name: "\u{1F6AA} Exit",
3939
+ value: { type: "command", name: "exit" },
3940
+ description: "Close connection and exit"
3941
+ });
3942
+ if (!process.stdin.isTTY) return false;
3943
+ try {
3944
+ const answer = await search({
3945
+ message: "Select an action (start typing to search):",
3946
+ source: async (term) => {
3947
+ if (!term) return choices;
3948
+ const lower = term.toLowerCase();
3949
+ return choices.filter(
3950
+ (c) => c.name.toLowerCase().includes(lower) || c.description.toLowerCase().includes(lower)
3951
+ );
3952
+ }
3953
+ });
3954
+ if (answer.type === "tool") {
3955
+ await cmdToolsCall(target, interceptor, answer.name);
3956
+ } else if (answer.type === "resource") {
3957
+ await cmdResourcesRead(target, answer.uri);
3958
+ } else if (answer.type === "prompt") {
3959
+ await cmdPromptsGet(target, answer.name);
3960
+ } else if (answer.type === "command") {
3961
+ if (answer.name === "status") {
3962
+ cmdStatus(target);
3963
+ } else if (answer.name === "reconnect") {
3964
+ await cmdReconnect(target);
3965
+ } else if (answer.name === "exit") {
3966
+ console.log(colors.dim("\nShutting down..."));
3967
+ await target.close();
3968
+ process.exit(0);
3969
+ }
3970
+ } else if (answer.type === "raw") {
3971
+ return true;
3972
+ }
3973
+ return false;
3974
+ } catch (err) {
3975
+ if (isAbortError(err)) {
3976
+ console.log(colors.dim("\nShutting down..."));
3977
+ await target.close();
3978
+ process.exit(0);
3979
+ }
3980
+ throw err;
3981
+ }
3982
+ }
3983
+ async function mainMenuLoop(target, interceptor) {
3984
+ if (isScriptMode || !process.stdin.isTTY) {
3985
+ startReadlineLoop(target, interceptor);
3986
+ return;
3987
+ }
3988
+ while (true) {
3989
+ try {
3990
+ const dropToRaw = await showMainMenu(target, interceptor);
3991
+ if (dropToRaw) {
3992
+ console.log(colors.dim(" Entering raw command mode. Type 'menu' to return."));
3993
+ startReadlineLoop(target, interceptor);
3994
+ break;
3995
+ }
3996
+ } catch (err) {
3997
+ console.error(colors.red(`Error in menu: ${err.message}`));
3998
+ startReadlineLoop(target, interceptor);
3999
+ break;
4000
+ }
4001
+ }
4002
+ }
4003
+
4004
+ // src/repl/index.ts
4005
+ function getPrompt(target) {
4006
+ if (target.connected) return `${colors.green("\u2713")}${colors.cyan("> ")}`;
4007
+ return `${colors.red("\u2717")}${colors.cyan("> ")}`;
4008
+ }
4009
+ function startReadlineLoop(target, interceptor) {
4010
+ if (isScriptMode || activeRl) return;
4011
+ const rl = createInterface({
4012
+ input: process.stdin,
4013
+ output: process.stdout,
4014
+ prompt: getPrompt(target),
4015
+ terminal: true,
4016
+ completer,
4017
+ history: [...replHistory].reverse()
4018
+ // Node's readline history expects newest first
4019
+ });
4020
+ setActiveRl(rl);
4021
+ setClosed(false);
4022
+ if (process.stdin.isTTY) {
4023
+ process.stdin.on("keypress", (_str, key) => {
4024
+ if (!key || key.name !== "tab") {
4025
+ resetTabCycle();
4026
+ }
4027
+ });
4028
+ }
4029
+ if (!deferNextPrompt) {
4030
+ rl.prompt();
4031
+ }
4032
+ setDeferNextPrompt(false);
4033
+ let processing = false;
4034
+ const queue = [];
4035
+ const processQueue = async () => {
4036
+ if (processing) return;
4037
+ processing = true;
4038
+ while (queue.length > 0) {
4039
+ const trimmed = queue.shift();
4040
+ try {
4041
+ await handleCommand(trimmed, target, interceptor);
4042
+ } catch (err) {
4043
+ if (err instanceof AbortFlowError) {
4044
+ console.log(colors.yellow(" Aborted."));
4045
+ } else if (err?.message?.includes("-32601") || err?.code === -32601) {
4046
+ let msg = "Server does not support this feature (Method not found)";
4047
+ if (trimmed.startsWith("prompts/")) msg = "This server does not have any prompts.";
4048
+ else if (trimmed.startsWith("resources/"))
4049
+ msg = "This server does not have any resources.";
4050
+ else if (trimmed.startsWith("tools/")) msg = "This server does not have any tools.";
4051
+ console.log(colors.yellow(` ${msg}`));
4052
+ } else {
4053
+ console.error(colors.red(`\u2717 Error: ${err.message}`));
4054
+ }
4055
+ }
4056
+ if (activeRl) {
4057
+ setImmediate(() => {
4058
+ if (activeRl) {
4059
+ console.log();
4060
+ activeRl.setPrompt(getPrompt(target));
4061
+ activeRl.prompt();
4062
+ }
4063
+ });
4064
+ }
4065
+ }
4066
+ processing = false;
4067
+ };
4068
+ rl.on("line", (line) => {
4069
+ const trimmed = line.trim();
4070
+ if (!trimmed || trimmed.startsWith("#")) {
4071
+ if (!closed && activeRl) activeRl.prompt();
4072
+ return;
4073
+ }
4074
+ replHistory.push(trimmed);
4075
+ appendToHistoryFile(trimmed).catch(() => {
4076
+ });
4077
+ queue.push(trimmed);
4078
+ processQueue();
4079
+ });
4080
+ rl.on("close", async () => {
4081
+ setClosed(true);
4082
+ setActiveRl(null);
4083
+ if (!globalPauseReadlineClose) {
4084
+ console.log(colors.dim("\nShutting down..."));
4085
+ await target.close();
4086
+ process.exit(0);
4087
+ }
4088
+ });
4089
+ }
4090
+ async function startRepl(targetCommand, opts) {
4091
+ const [command, ...args] = targetCommand;
4092
+ const target = new TargetManager(command, args, {
4093
+ sandbox: opts.sandbox,
4094
+ allowRead: opts.allowRead,
4095
+ allowWrite: opts.allowWrite,
4096
+ allowNet: opts.allowNet,
4097
+ denyRead: opts.denyRead,
4098
+ denyWrite: opts.denyWrite,
4099
+ denyNet: opts.denyNet
4100
+ });
4101
+ const interceptor = new ResponseInterceptor({
4102
+ outDir: opts.outDir,
4103
+ mediaThresholdKb: opts.mediaThresholdKb,
4104
+ openMedia: opts.openMedia
4105
+ });
4106
+ setIsScriptMode(!!opts.script);
4107
+ if (!isScriptMode) {
4108
+ await loadHistory();
4109
+ await loadWizardDefaults();
4110
+ }
4111
+ target.on("stderr", (text) => {
4112
+ for (const line of text.split("\n")) {
4113
+ console.error(colors.dim(`[server] ${line}`));
4114
+ }
4115
+ });
4116
+ console.log(colors.cyan("\u27F3 Connecting to target MCP server..."));
4117
+ console.log(colors.dim(` Command: ${targetCommand.join(" ")}`));
4118
+ try {
4119
+ await target.connect();
4120
+ } catch (err) {
4121
+ const msg = err.message ?? String(err);
4122
+ if (msg.includes("ENOENT") || msg.includes("spawn")) {
4123
+ console.error(colors.red(`\u2717 Failed to start server: command "${command}" not found.`));
4124
+ console.error(colors.dim(` Check that "${command}" is installed and in your PATH.`));
4125
+ } else {
4126
+ console.error(colors.red(`\u2717 Failed to connect: ${msg}`));
4127
+ console.error(colors.dim(` Check that the target command starts a valid MCP server on stdio.`));
4128
+ }
4129
+ process.exit(1);
4130
+ }
4131
+ const status = target.getStatus();
4132
+ console.log(colors.green(`\u2713 Connected (PID: ${status.pid})`));
4133
+ if (!isScriptMode) {
4134
+ target.enableAutoReconnect();
4135
+ target.on(
4136
+ "reconnecting",
4137
+ ({ attempt, maxAttempts }) => {
4138
+ console.log(
4139
+ colors.yellow(`
4140
+ \u27F3 Server disconnected. Reconnecting (${attempt}/${maxAttempts})...`)
4141
+ );
4142
+ }
4143
+ );
4144
+ target.on("reconnected", async ({ attempt }) => {
4145
+ const s = target.getStatus();
4146
+ console.log(colors.green(`\u2713 Reconnected (PID: ${s.pid}, attempt ${attempt})`));
4147
+ await refreshCaches(target);
4148
+ });
4149
+ target.on("reconnect_failed", ({ reason, message }) => {
4150
+ console.error(colors.red(`\u2717 ${message}`));
4151
+ if (reason === "max_retries") {
4152
+ console.log(
4153
+ colors.dim(" Use 'exit' to quit or wait for the server to be fixed and restart manually.")
4154
+ );
4155
+ }
4156
+ });
4157
+ target.on("notification", (notification) => {
4158
+ const method = notification.method;
4159
+ if (method === "notifications/message") {
4160
+ const lvl = notification.params?.level ?? "info";
4161
+ const data = notification.params?.data ?? "";
4162
+ const text = typeof data === "string" ? data : JSON.stringify(data);
4163
+ console.log(colors.dim(`
4164
+ [${lvl}] ${text}`));
4165
+ } else if (method === "notifications/tools/list_changed") {
4166
+ console.log(colors.yellow("\n \u27F3 Server tools changed. Run tools/list to see updates."));
4167
+ refreshCaches(target).catch(() => {
4168
+ });
4169
+ } else if (method === "notifications/resources/list_changed") {
4170
+ console.log(colors.yellow("\n \u27F3 Server resources changed. Run resources/list to see."));
4171
+ refreshCaches(target).catch(() => {
4172
+ });
4173
+ } else if (method === "notifications/resources/updated") {
4174
+ const uri = notification.params?.uri ?? "unknown";
4175
+ console.log(colors.yellow(`
4176
+ \u27F3 Resource updated: ${uri}`));
4177
+ } else if (method === "notifications/prompts/list_changed") {
4178
+ console.log(colors.yellow("\n \u27F3 Server prompts changed. Run prompts/list to see."));
4179
+ refreshCaches(target).catch(() => {
4180
+ });
4181
+ }
4182
+ });
4183
+ target.on("sampling_request", async ({ request, respond, reject: rejectFn }) => {
4184
+ console.log(colors.magenta("\n \u2554\u2550\u2550 Sampling Request \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550"));
4185
+ const messages = request?.messages ?? [];
4186
+ for (const msg of messages) {
4187
+ const role = msg.role === "user" ? colors.blue("user") : colors.magenta("assistant");
4188
+ const text = msg.content?.text ?? JSON.stringify(msg.content);
4189
+ console.log(colors.magenta(` \u2551 ${role}: ${text}`));
4190
+ }
4191
+ console.log(colors.magenta(" \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550"));
4192
+ if (activeRl) {
4193
+ try {
4194
+ const answer = await question(activeRl, ` ${colors.bold("Approve? [y/N/text]:")} `);
4195
+ const trimmed = answer.trim().toLowerCase();
4196
+ if (trimmed === "y" || trimmed === "yes") {
4197
+ respond({
4198
+ model: "user-approved",
4199
+ role: "assistant",
4200
+ content: { type: "text", text: "Approved by user." }
4201
+ });
4202
+ } else if (trimmed === "n" || trimmed === "no" || trimmed === "") {
4203
+ rejectFn(new Error("Sampling request rejected by user"));
4204
+ } else {
4205
+ respond({
4206
+ model: "user-provided",
4207
+ role: "assistant",
4208
+ content: { type: "text", text: answer.trim() }
4209
+ });
4210
+ }
4211
+ } catch (err) {
4212
+ if (err instanceof AbortFlowError) {
4213
+ rejectFn(new Error("Sampling request rejected by user"));
4214
+ } else {
4215
+ throw err;
4216
+ }
4217
+ }
4218
+ } else {
4219
+ rejectFn(new Error("No interactive terminal available for sampling approval"));
4220
+ }
4221
+ });
4222
+ target.on("elicitation_request", async ({ request, respond, reject: rejectFn }) => {
4223
+ console.log(colors.cyan("\n \u2554\u2550\u2550 Elicitation Request \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550"));
4224
+ console.log(colors.cyan(` \u2551 ${request?.message ?? "Server requests input"}`));
4225
+ console.log(colors.cyan(" \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550"));
4226
+ if (activeRl) {
4227
+ try {
4228
+ const answer = await question(
4229
+ activeRl,
4230
+ ` ${colors.bold("Your response (empty to decline):")} `
4231
+ );
4232
+ if (answer.trim() === "") {
4233
+ respond({ action: "decline" });
4234
+ } else {
4235
+ try {
4236
+ const parsed = JSON.parse(answer.trim());
4237
+ respond({ action: "accept", content: parsed });
4238
+ } catch {
4239
+ respond({ action: "accept", content: { value: answer.trim() } });
4240
+ }
4241
+ }
4242
+ } catch (err) {
4243
+ if (err instanceof AbortFlowError) {
4244
+ respond({ action: "decline" });
4245
+ } else {
4246
+ throw err;
4247
+ }
4248
+ }
4249
+ } else {
4250
+ rejectFn(new Error("No interactive terminal available for elicitation"));
4251
+ }
4252
+ });
4253
+ }
4254
+ let toolCount;
4255
+ let resourceCount = 0;
4256
+ let promptCount = 0;
3305
4257
  try {
3306
4258
  const { tools } = await target.listTools();
3307
- for (const t of tools) {
3308
- choices.push({
3309
- name: `\u{1F6E0}\uFE0F Tool: ${t.name}`,
3310
- value: { type: "tool", name: t.name },
3311
- description: t.description || `Call the ${t.name} tool`
3312
- });
3313
- }
3314
- } catch {
3315
- }
3316
- if (caps.resources) {
3317
- try {
3318
- const { resources } = await target.listResources();
3319
- for (const r of resources) {
3320
- choices.push({
3321
- name: `\u{1F4C4} Resource: ${r.name || r.uri}`,
3322
- value: { type: "resource", uri: r.uri },
3323
- description: r.description || `Read resource ${r.uri}`
3324
- });
4259
+ toolCount = tools.length;
4260
+ const caps = target.getServerCapabilities() ?? {};
4261
+ if (caps.resources) {
4262
+ try {
4263
+ const { resources } = await target.listResources();
4264
+ resourceCount = resources.length;
4265
+ } catch {
3325
4266
  }
3326
- } catch {
3327
4267
  }
3328
- }
3329
- if (caps.prompts) {
3330
- try {
3331
- const { prompts } = await target.listPrompts();
3332
- for (const p of prompts) {
3333
- choices.push({
3334
- name: `\u{1F4AC} Prompt: ${p.name}`,
3335
- value: { type: "prompt", name: p.name },
3336
- description: p.description || `Get the ${p.name} prompt`
3337
- });
4268
+ if (caps.prompts) {
4269
+ try {
4270
+ const { prompts } = await target.listPrompts();
4271
+ promptCount = prompts.length;
4272
+ } catch {
3338
4273
  }
3339
- } catch {
3340
4274
  }
3341
- }
3342
- if (choices.length === 0) {
3343
- console.log(pc2.yellow("No tools, resources, or prompts found."));
3344
- return;
3345
- }
3346
- if (!process.stdin.isTTY) {
3347
- console.log(pc2.bold("\n Server Capabilities\n"));
3348
- for (let i = 0; i < choices.length; i++) {
3349
- console.log(
3350
- ` ${pc2.cyan(String(i + 1).padStart(2))}. ${choices[i].name} ${pc2.dim(choices[i].description)}`
4275
+ const serverInfo = target.getServerVersion();
4276
+ const serverName = serverInfo?.name ?? "MCP Server";
4277
+ const serverVersion = serverInfo?.version ?? "";
4278
+ const isHarness = serverName === "run-mcp";
4279
+ printBanner(serverName, serverVersion, toolCount, resourceCount, promptCount, isHarness);
4280
+ if (toolCount >= 10) {
4281
+ const groups = groupToolsByPrefix(
4282
+ cachedToolNames.length > 0 ? cachedToolNames : tools.map((t) => t.name)
3351
4283
  );
3352
- }
3353
- console.log();
3354
- console.log(
3355
- pc2.dim(" Non-interactive mode \u2014 use specific commands instead (e.g., tools/call <name>).")
3356
- );
3357
- return;
3358
- }
3359
- try {
3360
- const answer = await search({
3361
- message: "Explore server capabilities:",
3362
- source: async (term) => {
3363
- if (!term) return choices;
3364
- const lower = term.toLowerCase();
3365
- return choices.filter(
3366
- (c) => c.name.toLowerCase().includes(lower) || c.description.toLowerCase().includes(lower)
3367
- );
4284
+ if (!groups.has("All")) {
4285
+ for (const [label, members] of groups) {
4286
+ console.log(` ${colors.bold(label.padEnd(16))} ${colors.dim(members.join(", "))}`);
4287
+ }
4288
+ console.log();
3368
4289
  }
3369
- });
3370
- if (answer.type === "tool") {
3371
- await cmdToolsCall(target, interceptor, answer.name);
3372
- } else if (answer.type === "resource") {
3373
- await cmdResourcesRead(target, answer.uri);
3374
- } else if (answer.type === "prompt") {
3375
- await cmdPromptsGet(target, answer.name);
3376
4290
  }
3377
4291
  } catch (err) {
3378
- if (!isAbortError(err)) {
3379
- throw err;
4292
+ console.log(colors.yellow(` Warning: Could not list tools: ${err.message}
4293
+ `));
4294
+ }
4295
+ await refreshCaches(target);
4296
+ if (isScriptMode) {
4297
+ const lines = await readScriptLines(opts.script);
4298
+ const scriptContext = {};
4299
+ let expectError = false;
4300
+ for (const line of lines) {
4301
+ let trimmed = line.trim();
4302
+ if (!trimmed || trimmed.startsWith("#")) {
4303
+ if (trimmed === "# @expect-error") {
4304
+ expectError = true;
4305
+ }
4306
+ continue;
4307
+ }
4308
+ if (trimmed.endsWith("# @expect-error")) {
4309
+ expectError = true;
4310
+ trimmed = trimmed.replace(/\s*#\s*@expect-error$/, "");
4311
+ }
4312
+ const interpolated = interpolateString(trimmed, scriptContext);
4313
+ try {
4314
+ const res = await handleCommand(interpolated, target, interceptor);
4315
+ if (res !== void 0) {
4316
+ scriptContext.LAST = res;
4317
+ }
4318
+ const isErrorRes = res && typeof res === "object" && res.isError === true;
4319
+ if (expectError && !isErrorRes) {
4320
+ console.error(colors.red(`\u2717 Expected an error but the command succeeded.`));
4321
+ await target.close();
4322
+ process.exit(1);
4323
+ }
4324
+ if (!expectError && isErrorRes) {
4325
+ console.error(colors.red(`\u2717 Command failed unexpectedly.`));
4326
+ await target.close();
4327
+ process.exit(1);
4328
+ }
4329
+ if (expectError && isErrorRes) {
4330
+ console.log(colors.yellow(` \u2713 Expected error caught: tool returned isError: true`));
4331
+ }
4332
+ } catch (err) {
4333
+ if (expectError) {
4334
+ console.log(colors.yellow(` \u2713 Expected error caught: ${err.message}`));
4335
+ } else {
4336
+ if (err?.message?.includes("-32601") || err?.code === -32601) {
4337
+ let msg = "Server does not support this feature (Method not found)";
4338
+ if (trimmed.startsWith("prompts/")) msg = "This server does not have any prompts.";
4339
+ else if (trimmed.startsWith("resources/"))
4340
+ msg = "This server does not have any resources.";
4341
+ else if (trimmed.startsWith("tools/")) msg = "This server does not have any tools.";
4342
+ console.log(colors.yellow(` ${msg}`));
4343
+ } else {
4344
+ console.error(colors.red(`\u2717 Error: ${err.message}`));
4345
+ }
4346
+ console.log(colors.dim("\nShutting down..."));
4347
+ await target.close();
4348
+ process.exit(1);
4349
+ }
4350
+ }
4351
+ expectError = false;
3380
4352
  }
4353
+ console.log(colors.dim("\nShutting down..."));
4354
+ await target.close();
4355
+ process.exit(0);
4356
+ } else {
4357
+ mainMenuLoop(target, interceptor);
3381
4358
  }
3382
4359
  }
4360
+ async function readScriptLines(filepath) {
4361
+ const content = await readFile4(filepath, "utf8");
4362
+ return content.split("\n");
4363
+ }
3383
4364
 
3384
4365
  // src/server.ts
3385
4366
  import { createHash } from "crypto";
@@ -3444,7 +4425,7 @@ async function startServer(opts) {
3444
4425
  mediaThresholdKb: opts.mediaThresholdKb
3445
4426
  });
3446
4427
  const mcpServer = new McpServer(
3447
- { name: "run-mcp", version: "1.6.2" },
4428
+ { name: "run-mcp", version: "1.7.0" },
3448
4429
  {
3449
4430
  capabilities: {
3450
4431
  tools: {},
@@ -3605,7 +4586,15 @@ async function startServer(opts) {
3605
4586
  process.env[key] = value;
3606
4587
  }
3607
4588
  }
3608
- target = new TargetManager(cmdToUse, argsToUse ?? []);
4589
+ target = new TargetManager(cmdToUse, argsToUse ?? [], {
4590
+ sandbox: opts.sandbox,
4591
+ allowRead: opts.allowRead,
4592
+ allowWrite: opts.allowWrite,
4593
+ allowNet: opts.allowNet,
4594
+ denyRead: opts.denyRead,
4595
+ denyWrite: opts.denyWrite,
4596
+ denyNet: opts.denyNet
4597
+ });
3609
4598
  setupTargetListeners(target);
3610
4599
  try {
3611
4600
  await target.connect();
@@ -3709,10 +4698,11 @@ async function startServer(opts) {
3709
4698
  ),
3710
4699
  summary: z.boolean().optional().describe(
3711
4700
  "If true, returns only the name and description of each primitive (omitting full schemas) when included to save tokens."
3712
- )
4701
+ ),
4702
+ sandbox: z.enum(["auto", "docker", "native", "audit", "none"]).optional().describe("Sandbox mode to use for this server")
3713
4703
  }
3714
4704
  },
3715
- async ({ command, args, env, include, summary }) => {
4705
+ async ({ command, args, env, include, summary, sandbox }) => {
3716
4706
  if (target?.connected) {
3717
4707
  return {
3718
4708
  content: [
@@ -3734,7 +4724,15 @@ async function startServer(opts) {
3734
4724
  process.env[key] = value;
3735
4725
  }
3736
4726
  }
3737
- target = new TargetManager(command, args ?? []);
4727
+ target = new TargetManager(command, args ?? [], {
4728
+ sandbox: sandbox ?? opts.sandbox,
4729
+ allowRead: opts.allowRead,
4730
+ allowWrite: opts.allowWrite,
4731
+ allowNet: opts.allowNet,
4732
+ denyRead: opts.denyRead,
4733
+ denyWrite: opts.denyWrite,
4734
+ denyNet: opts.denyNet
4735
+ });
3738
4736
  setupTargetListeners(target);
3739
4737
  try {
3740
4738
  await target.connect();
@@ -4069,6 +5067,7 @@ Available: ${available}`
4069
5067
  type: z.enum(["tool", "resource", "prompt"]).describe("The MCP primitive type to invoke"),
4070
5068
  name: z.string().describe("Tool name, resource URI, or prompt name"),
4071
5069
  arguments: z.record(z.unknown()).optional().describe("Arguments for the tool or prompt (not used for resources)"),
5070
+ args: z.record(z.unknown()).optional().describe("Arguments for the tool or prompt (alias for 'arguments')"),
4072
5071
  // Auto-connect params (only needed if not already connected)
4073
5072
  auto_connect: z.object({
4074
5073
  command: z.string().describe("Command to spawn the server (e.g. 'node')."),
@@ -4092,12 +5091,14 @@ Available: ${available}`
4092
5091
  type: primitiveType,
4093
5092
  name,
4094
5093
  arguments: callArgs,
5094
+ args: callArgsAlias,
4095
5095
  auto_connect,
4096
5096
  disconnect_after,
4097
5097
  timeout_ms,
4098
5098
  include_metadata,
4099
5099
  max_text_length
4100
5100
  }) => {
5101
+ const finalArgs = callArgs ?? callArgsAlias;
4101
5102
  try {
4102
5103
  const connectError = await ensureConnected(
4103
5104
  auto_connect?.command,
@@ -4147,14 +5148,14 @@ Available tools: ${toolNames.join(", ")}`
4147
5148
  }
4148
5149
  const schema = matchedTool.inputSchema;
4149
5150
  const requiredProps = schema?.required ?? [];
4150
- const providedKeys = Object.keys(callArgs ?? {});
5151
+ const providedKeys = Object.keys(finalArgs ?? {});
4151
5152
  const missingProps = requiredProps.filter((p) => !providedKeys.includes(p));
4152
5153
  if (missingProps.length > 0) {
4153
5154
  return {
4154
5155
  content: [
4155
5156
  {
4156
5157
  type: "text",
4157
- text: `Tool "${name}" requires: ${missingProps.join(", ")}. Received: ${JSON.stringify(callArgs ?? {})}`
5158
+ text: `Tool "${name}" requires: ${missingProps.join(", ")}. Received: ${JSON.stringify(finalArgs ?? {})}`
4158
5159
  }
4159
5160
  ],
4160
5161
  isError: true
@@ -4168,7 +5169,7 @@ Available tools: ${toolNames.join(", ")}`
4168
5169
  const { result: toolResult, metadata } = await interceptor.callToolWithMetadata(
4169
5170
  target,
4170
5171
  name,
4171
- callArgs ?? {},
5172
+ finalArgs ?? {},
4172
5173
  timeout_ms,
4173
5174
  max_text_length
4174
5175
  );
@@ -4178,7 +5179,7 @@ Available tools: ${toolNames.join(", ")}`
4178
5179
  result = await interceptor.callTool(
4179
5180
  target,
4180
5181
  name,
4181
- callArgs ?? {},
5182
+ finalArgs ?? {},
4182
5183
  timeout_ms,
4183
5184
  max_text_length
4184
5185
  );
@@ -4202,6 +5203,7 @@ Available tools: ${toolNames.join(", ")}`
4202
5203
  text: `--- metadata ---
4203
5204
  ${JSON.stringify(meta)}`
4204
5205
  });
5206
+ result.meta = meta;
4205
5207
  }
4206
5208
  break;
4207
5209
  }
@@ -4233,6 +5235,7 @@ ${JSON.stringify(meta)}`
4233
5235
  text: `--- metadata ---
4234
5236
  ${JSON.stringify(meta)}`
4235
5237
  });
5238
+ result.meta = meta;
4236
5239
  }
4237
5240
  break;
4238
5241
  }
@@ -4262,7 +5265,7 @@ Available prompts: ${promptNames.join(", ")}`
4262
5265
  target,
4263
5266
  {
4264
5267
  name,
4265
- arguments: callArgs ?? {}
5268
+ arguments: finalArgs ?? {}
4266
5269
  },
4267
5270
  timeout_ms,
4268
5271
  max_text_length
@@ -4301,6 +5304,7 @@ ${item.text}` });
4301
5304
  text: `--- metadata ---
4302
5305
  ${JSON.stringify(meta)}`
4303
5306
  });
5307
+ result.meta = meta;
4304
5308
  }
4305
5309
  break;
4306
5310
  }
@@ -4350,6 +5354,230 @@ ${JSON.stringify(meta)}`
4350
5354
  };
4351
5355
  }
4352
5356
  );
5357
+ mcpServer.registerTool(
5358
+ "validate_mcp_server",
5359
+ {
5360
+ title: "Validate MCP Server",
5361
+ description: "Attempts to spawn the target MCP server, connect to it, check its tools, collect any stderr/errors, and shut it down cleanly. Returns pass/fail status and captured diagnostics.",
5362
+ inputSchema: {
5363
+ command: z.string().describe("Command to run (e.g. 'node', 'python')"),
5364
+ args: z.array(z.string()).optional().describe("Arguments to pass"),
5365
+ env: z.record(z.string()).optional().describe("Extra environment variables")
5366
+ }
5367
+ },
5368
+ async ({ command, args, env }) => {
5369
+ const originalEnv = {};
5370
+ if (env) {
5371
+ for (const [key, value] of Object.entries(env)) {
5372
+ originalEnv[key] = process.env[key];
5373
+ process.env[key] = value;
5374
+ }
5375
+ }
5376
+ const tempTarget = new TargetManager(command, args ?? [], {
5377
+ sandbox: opts.sandbox,
5378
+ allowRead: opts.allowRead,
5379
+ allowWrite: opts.allowWrite,
5380
+ allowNet: opts.allowNet,
5381
+ denyRead: opts.denyRead,
5382
+ denyWrite: opts.denyWrite,
5383
+ denyNet: opts.denyNet
5384
+ });
5385
+ const stderrLines = [];
5386
+ tempTarget.on("stderr", (text) => {
5387
+ stderrLines.push(text);
5388
+ });
5389
+ try {
5390
+ const connectPromise = tempTarget.connect();
5391
+ const timeoutPromise = new Promise(
5392
+ (_, reject) => setTimeout(() => reject(new Error("Connection timed out after 5000ms")), 5e3)
5393
+ );
5394
+ await Promise.race([connectPromise, timeoutPromise]);
5395
+ const toolsResult = await tempTarget.listTools();
5396
+ const caps = tempTarget.getServerCapabilities() ?? {};
5397
+ const ver = tempTarget.getServerVersion();
5398
+ await tempTarget.close();
5399
+ return {
5400
+ content: [
5401
+ {
5402
+ type: "text",
5403
+ text: `Validation Result: SUCCESS
5404
+ Server Name: ${ver?.name ?? "unknown"}
5405
+ Server Version: ${ver?.version ?? "unknown"}
5406
+ Tools Count: ${toolsResult.tools.length}
5407
+ Capabilities: ${Object.keys(caps).join(", ") || "none"}
5408
+
5409
+ Captured Stderr:
5410
+ ${stderrLines.join("\n") || "(none)"}`
5411
+ }
5412
+ ]
5413
+ };
5414
+ } catch (err) {
5415
+ await tempTarget.close().catch(() => {
5416
+ });
5417
+ return {
5418
+ content: [
5419
+ {
5420
+ type: "text",
5421
+ text: `Validation Result: FAILED
5422
+ Error: ${err.message}
5423
+
5424
+ Captured Stderr:
5425
+ ${stderrLines.join("\n") || "(none)"}`
5426
+ }
5427
+ ],
5428
+ isError: true
5429
+ };
5430
+ } finally {
5431
+ if (env) {
5432
+ for (const key of Object.keys(env)) {
5433
+ if (originalEnv[key] === void 0) {
5434
+ delete process.env[key];
5435
+ } else {
5436
+ process.env[key] = originalEnv[key];
5437
+ }
5438
+ }
5439
+ }
5440
+ }
5441
+ }
5442
+ );
5443
+ mcpServer.registerTool(
5444
+ "search_all_local_mcp_servers",
5445
+ {
5446
+ title: "Search All Local MCP Servers",
5447
+ description: "Scans all configured/discovered local MCP servers, connects to them, and searches their tool names/descriptions or resource names/URIs for a query string.",
5448
+ inputSchema: {
5449
+ query: z.string().describe("Search query (case-insensitive substring match)"),
5450
+ type: z.array(z.enum(["tools", "resources", "prompts"])).optional().describe("Primitives to search. Defaults to ['tools'].")
5451
+ }
5452
+ },
5453
+ async ({ query, type }) => {
5454
+ const searchTypes = type ?? ["tools"];
5455
+ const lowerQuery = query.toLowerCase();
5456
+ try {
5457
+ const servers = await discoverServers();
5458
+ if (servers.length === 0) {
5459
+ return {
5460
+ content: [{ type: "text", text: "No local MCP servers found to search." }]
5461
+ };
5462
+ }
5463
+ const uniqueServers = /* @__PURE__ */ new Map();
5464
+ for (const s of servers) {
5465
+ const key = `${s.name}::${s.config.command}::${(s.config.args || []).join(" ")}`;
5466
+ if (!uniqueServers.has(key)) {
5467
+ uniqueServers.set(key, s);
5468
+ } else if (s.source.includes("Project")) {
5469
+ uniqueServers.set(key, s);
5470
+ }
5471
+ }
5472
+ const matchResults = [];
5473
+ for (const s of uniqueServers.values()) {
5474
+ const originalEnv = {};
5475
+ if (s.config.env) {
5476
+ for (const [key, value] of Object.entries(s.config.env)) {
5477
+ originalEnv[key] = process.env[key];
5478
+ process.env[key] = value;
5479
+ }
5480
+ }
5481
+ const tempTarget = new TargetManager(s.config.command, s.config.args || [], {
5482
+ sandbox: opts.sandbox,
5483
+ allowRead: opts.allowRead,
5484
+ allowWrite: opts.allowWrite,
5485
+ allowNet: opts.allowNet,
5486
+ denyRead: opts.denyRead,
5487
+ denyWrite: opts.denyWrite,
5488
+ denyNet: opts.denyNet
5489
+ });
5490
+ try {
5491
+ await Promise.race([
5492
+ tempTarget.connect(),
5493
+ new Promise(
5494
+ (_, reject) => setTimeout(() => reject(new Error("Timeout")), 3e3)
5495
+ )
5496
+ ]);
5497
+ const caps = tempTarget.getServerCapabilities() ?? {};
5498
+ if (searchTypes.includes("tools") && caps.tools) {
5499
+ const { tools } = await tempTarget.listTools();
5500
+ for (const t of tools) {
5501
+ if (t.name.toLowerCase().includes(lowerQuery) || t.description && t.description.toLowerCase().includes(lowerQuery)) {
5502
+ matchResults.push({
5503
+ server: s.name,
5504
+ primitive: "tool",
5505
+ name: t.name,
5506
+ description: t.description
5507
+ });
5508
+ }
5509
+ }
5510
+ }
5511
+ if (searchTypes.includes("resources") && caps.resources) {
5512
+ const { resources } = await tempTarget.listResources();
5513
+ for (const r of resources) {
5514
+ if (r.name && r.name.toLowerCase().includes(lowerQuery) || r.uri.toLowerCase().includes(lowerQuery) || r.description && r.description.toLowerCase().includes(lowerQuery)) {
5515
+ matchResults.push({
5516
+ server: s.name,
5517
+ primitive: "resource",
5518
+ name: r.name || r.uri,
5519
+ uri: r.uri,
5520
+ description: r.description
5521
+ });
5522
+ }
5523
+ }
5524
+ }
5525
+ if (searchTypes.includes("prompts") && caps.prompts) {
5526
+ const { prompts } = await tempTarget.listPrompts();
5527
+ for (const p of prompts) {
5528
+ if (p.name.toLowerCase().includes(lowerQuery) || p.description && p.description.toLowerCase().includes(lowerQuery)) {
5529
+ matchResults.push({
5530
+ server: s.name,
5531
+ primitive: "prompt",
5532
+ name: p.name,
5533
+ description: p.description
5534
+ });
5535
+ }
5536
+ }
5537
+ }
5538
+ } catch {
5539
+ } finally {
5540
+ await tempTarget.close().catch(() => {
5541
+ });
5542
+ if (s.config.env) {
5543
+ for (const key of Object.keys(s.config.env)) {
5544
+ if (originalEnv[key] === void 0) {
5545
+ delete process.env[key];
5546
+ } else {
5547
+ process.env[key] = originalEnv[key];
5548
+ }
5549
+ }
5550
+ }
5551
+ }
5552
+ }
5553
+ if (matchResults.length === 0) {
5554
+ return {
5555
+ content: [
5556
+ {
5557
+ type: "text",
5558
+ text: `No matches found for query "${query}".`
5559
+ }
5560
+ ]
5561
+ };
5562
+ }
5563
+ return {
5564
+ content: [
5565
+ {
5566
+ type: "text",
5567
+ text: `Search results for "${query}":
5568
+
5569
+ ${JSON.stringify(matchResults, null, 2)}`
5570
+ }
5571
+ ]
5572
+ };
5573
+ } catch (err) {
5574
+ return {
5575
+ content: [{ type: "text", text: `Error searching servers: ${err.message}` }],
5576
+ isError: true
5577
+ };
5578
+ }
5579
+ }
5580
+ );
4353
5581
  const transport = new StdioServerTransport();
4354
5582
  mcpServer.server.onclose = async () => {
4355
5583
  if (target) {
@@ -4364,7 +5592,8 @@ ${JSON.stringify(meta)}`
4364
5592
 
4365
5593
  // src/index.ts
4366
5594
  function requireTargetCommand(targetCommand, subcommandUsage) {
4367
- if (!activeTargetCommand) {
5595
+ const target = activeTargetCommand ?? targetCommand;
5596
+ if (!target || target.length === 0) {
4368
5597
  process.stderr.write(`Error: Target server command must be separated by '--'.
4369
5598
  `);
4370
5599
  process.stderr.write(`This avoids option parsing conflicts.
@@ -4372,19 +5601,19 @@ function requireTargetCommand(targetCommand, subcommandUsage) {
4372
5601
  `);
4373
5602
  process.stderr.write(`Usage: ${subcommandUsage}
4374
5603
  `);
4375
- process.exit(2);
5604
+ process.exit(64);
4376
5605
  }
4377
- return activeTargetCommand;
5606
+ return target;
4378
5607
  }
4379
- var SESSION_DIR = join2(tmpdir2(), "run-mcp", "sessions");
5608
+ var SESSION_DIR = join6(tmpdir3(), "run-mcp", "sessions");
4380
5609
  function getSessionPath(name) {
4381
- return join2(SESSION_DIR, `${name}.json`);
5610
+ return join6(SESSION_DIR, `${name}.json`);
4382
5611
  }
4383
5612
  async function getSession(name) {
4384
5613
  const path2 = getSessionPath(name);
4385
- if (!existsSync2(path2)) return null;
5614
+ if (!existsSync6(path2)) return null;
4386
5615
  try {
4387
- const data = await readFile3(path2, "utf8");
5616
+ const data = await readFile5(path2, "utf8");
4388
5617
  const parsed = JSON.parse(data);
4389
5618
  try {
4390
5619
  process.kill(parsed.pid, 0);
@@ -4399,7 +5628,7 @@ async function getSession(name) {
4399
5628
  }
4400
5629
  }
4401
5630
  function sendDaemonRequest(port, request) {
4402
- return new Promise((resolve2, reject) => {
5631
+ return new Promise((resolve3, reject) => {
4403
5632
  const socket = createConnection({ port });
4404
5633
  let buffer = "";
4405
5634
  socket.on("connect", () => {
@@ -4414,7 +5643,7 @@ function sendDaemonRequest(port, request) {
4414
5643
  if (parsed.error) {
4415
5644
  reject(new Error(parsed.error.message));
4416
5645
  } else {
4417
- resolve2(parsed.result);
5646
+ resolve3(parsed.result);
4418
5647
  }
4419
5648
  } catch (err) {
4420
5649
  reject(new Error(`Failed to parse daemon response: ${err}`));
@@ -4436,11 +5665,16 @@ async function handleHeadlessSession(sessionName, targetCommand, operation, opts
4436
5665
  `);
4437
5666
  process.stderr.write(`Usage: ${subcommandUsage}
4438
5667
  `);
4439
- process.exit(2);
5668
+ process.exit(64);
4440
5669
  }
4441
5670
  const target = activeTargetCommand;
4442
- const binPath = resolve(import.meta.dirname, "./index.js");
4443
- const daemonProcess = spawn("node", [binPath, "daemon", sessionName, ...target], {
5671
+ const binPath = resolve2(import.meta.dirname, "./index.js");
5672
+ const daemonArgs = ["daemon", sessionName];
5673
+ if (opts.sandbox) {
5674
+ daemonArgs.push("--sandbox", opts.sandbox);
5675
+ }
5676
+ daemonArgs.push(...target);
5677
+ const daemonProcess = spawn("node", [binPath, ...daemonArgs], {
4444
5678
  detached: true,
4445
5679
  stdio: "ignore"
4446
5680
  });
@@ -4449,7 +5683,7 @@ async function handleHeadlessSession(sessionName, targetCommand, operation, opts
4449
5683
  while (attempts < 50) {
4450
5684
  session = await getSession(sessionName);
4451
5685
  if (session) break;
4452
- await new Promise((resolve2) => setTimeout(resolve2, 100));
5686
+ await new Promise((resolve3) => setTimeout(resolve3, 100));
4453
5687
  attempts++;
4454
5688
  }
4455
5689
  if (!session) {
@@ -4482,7 +5716,14 @@ function parseHeadlessOpts(opts) {
4482
5716
  timeoutMs: opts.timeout ? Number.parseInt(opts.timeout, 10) : void 0,
4483
5717
  raw: opts.raw,
4484
5718
  showStderr: opts.showStderr,
4485
- mediaThresholdKb: opts.mediaThreshold ? Number.parseInt(opts.mediaThreshold, 10) : void 0
5719
+ mediaThresholdKb: opts.mediaThreshold ? Number.parseInt(opts.mediaThreshold, 10) : void 0,
5720
+ sandbox: opts.sandbox,
5721
+ allowRead: opts.allowRead,
5722
+ allowWrite: opts.allowWrite,
5723
+ allowNet: opts.allowNet,
5724
+ denyRead: opts.denyRead,
5725
+ denyWrite: opts.denyWrite,
5726
+ denyNet: opts.denyNet
4486
5727
  };
4487
5728
  }
4488
5729
  var activeTargetCommand;
@@ -4496,7 +5737,7 @@ program.enablePositionalOptions();
4496
5737
  program.command("call").argument("<tool>", "Tool name to call").argument("[json_args]", "JSON arguments for the tool").argument("[target_command...]", "Target server command (after --)").description("Call a tool on a target MCP server and print the result as JSON").option("-o, --out-dir <path>", "Output directory for saved media").option("-t, --timeout <ms>", "Timeout in milliseconds (default: 30000)").option(
4497
5738
  "-m, --media-threshold <kb>",
4498
5739
  "Media size threshold in KB to save to disk (0 to always save, -1 to keep inline)"
4499
- ).option("--raw", "Print the full result object including metadata").option("--show-stderr", "Stream target server stderr to process stderr").option("--session <name>", "Persistent session name").allowUnknownOption().action(
5740
+ ).option("--raw", "Print the full result object including metadata").option("--show-stderr", "Stream target server stderr to process stderr").option("--session <name>", "Persistent session name").option("--sandbox <mode>", "Sandbox execution mode: auto, docker, native, audit, none", "none").allowUnknownOption().action(
4500
5741
  async (tool, jsonArgs, targetCommand, opts) => {
4501
5742
  const operation = { type: "call", tool, args: jsonArgs };
4502
5743
  const parsedOpts = parseHeadlessOpts(opts);
@@ -4517,7 +5758,7 @@ program.command("call").argument("<tool>", "Tool name to call").argument("[json_
4517
5758
  }
4518
5759
  }
4519
5760
  );
4520
- program.command("list-tools").argument("[target_command...]", "Target server command (after --)").description("List all tools on a target MCP server as JSON").option("--show-stderr", "Stream target server stderr to process stderr").option("--session <name>", "Persistent session name").allowUnknownOption().action(async (targetCommand, opts) => {
5761
+ program.command("list-tools").argument("[target_command...]", "Target server command (after --)").description("List all tools on a target MCP server as JSON").option("--show-stderr", "Stream target server stderr to process stderr").option("--session <name>", "Persistent session name").option("--sandbox <mode>", "Sandbox execution mode: auto, docker, native, audit, none", "none").allowUnknownOption().action(async (targetCommand, opts) => {
4521
5762
  const operation = { type: "list-tools" };
4522
5763
  const parsedOpts = parseHeadlessOpts(opts);
4523
5764
  if (opts.session) {
@@ -4536,7 +5777,7 @@ program.command("list-tools").argument("[target_command...]", "Target server com
4536
5777
  await runHeadless(target, operation, parsedOpts);
4537
5778
  }
4538
5779
  });
4539
- program.command("list-resources").argument("[target_command...]", "Target server command (after --)").description("List all resources on a target MCP server as JSON").option("--show-stderr", "Stream target server stderr to process stderr").option("--session <name>", "Persistent session name").allowUnknownOption().action(async (targetCommand, opts) => {
5780
+ program.command("list-resources").argument("[target_command...]", "Target server command (after --)").description("List all resources on a target MCP server as JSON").option("--show-stderr", "Stream target server stderr to process stderr").option("--session <name>", "Persistent session name").option("--sandbox <mode>", "Sandbox execution mode: auto, docker, native, audit, none", "none").allowUnknownOption().action(async (targetCommand, opts) => {
4540
5781
  const operation = { type: "list-resources" };
4541
5782
  const parsedOpts = parseHeadlessOpts(opts);
4542
5783
  if (opts.session) {
@@ -4555,7 +5796,7 @@ program.command("list-resources").argument("[target_command...]", "Target server
4555
5796
  await runHeadless(target, operation, parsedOpts);
4556
5797
  }
4557
5798
  });
4558
- program.command("list-prompts").argument("[target_command...]", "Target server command (after --)").description("List all prompts on a target MCP server as JSON").option("--show-stderr", "Stream target server stderr to process stderr").option("--session <name>", "Persistent session name").allowUnknownOption().action(async (targetCommand, opts) => {
5799
+ program.command("list-prompts").argument("[target_command...]", "Target server command (after --)").description("List all prompts on a target MCP server as JSON").option("--show-stderr", "Stream target server stderr to process stderr").option("--session <name>", "Persistent session name").option("--sandbox <mode>", "Sandbox execution mode: auto, docker, native, audit, none", "none").allowUnknownOption().action(async (targetCommand, opts) => {
4559
5800
  const operation = { type: "list-prompts" };
4560
5801
  const parsedOpts = parseHeadlessOpts(opts);
4561
5802
  if (opts.session) {
@@ -4577,7 +5818,7 @@ program.command("list-prompts").argument("[target_command...]", "Target server c
4577
5818
  program.command("read").argument("<uri>", "Resource URI to read").argument("[target_command...]", "Target server command (after --)").description("Read a resource by URI from a target MCP server").option(
4578
5819
  "-m, --media-threshold <kb>",
4579
5820
  "Media size threshold in KB to save to disk (0 to always save, -1 to keep inline)"
4580
- ).option("--show-stderr", "Stream target server stderr to process stderr").option("--session <name>", "Persistent session name").allowUnknownOption().action(async (uri, targetCommand, opts) => {
5821
+ ).option("--show-stderr", "Stream target server stderr to process stderr").option("--session <name>", "Persistent session name").option("--sandbox <mode>", "Sandbox execution mode: auto, docker, native, audit, none", "none").allowUnknownOption().action(async (uri, targetCommand, opts) => {
4581
5822
  const operation = { type: "read", uri };
4582
5823
  const parsedOpts = parseHeadlessOpts(opts);
4583
5824
  if (opts.session) {
@@ -4596,7 +5837,7 @@ program.command("read").argument("<uri>", "Resource URI to read").argument("[tar
4596
5837
  await runHeadless(target, operation, parsedOpts);
4597
5838
  }
4598
5839
  });
4599
- program.command("describe").argument("<tool>", "Tool name to describe").argument("[target_command...]", "Target server command (after --)").description("Print a tool's full schema as JSON").option("--show-stderr", "Stream target server stderr to process stderr").option("--session <name>", "Persistent session name").allowUnknownOption().action(async (tool, targetCommand, opts) => {
5840
+ program.command("describe").argument("<tool>", "Tool name to describe").argument("[target_command...]", "Target server command (after --)").description("Print a tool's full schema as JSON").option("--show-stderr", "Stream target server stderr to process stderr").option("--session <name>", "Persistent session name").option("--sandbox <mode>", "Sandbox execution mode: auto, docker, native, audit, none", "none").allowUnknownOption().action(async (tool, targetCommand, opts) => {
4600
5841
  const operation = { type: "describe", tool };
4601
5842
  const parsedOpts = parseHeadlessOpts(opts);
4602
5843
  if (opts.session) {
@@ -4618,7 +5859,7 @@ program.command("describe").argument("<tool>", "Tool name to describe").argument
4618
5859
  program.command("get-prompt").argument("<name>", "Prompt name").argument("[json_args]", "JSON arguments for the prompt").argument("[target_command...]", "Target server command (after --)").description("Get a prompt with optional arguments from a target MCP server").option(
4619
5860
  "-m, --media-threshold <kb>",
4620
5861
  "Media size threshold in KB to save to disk (0 to always save, -1 to keep inline)"
4621
- ).option("--show-stderr", "Stream target server stderr to process stderr").option("--session <name>", "Persistent session name").allowUnknownOption().action(
5862
+ ).option("--show-stderr", "Stream target server stderr to process stderr").option("--session <name>", "Persistent session name").option("--sandbox <mode>", "Sandbox execution mode: auto, docker, native, audit, none", "none").allowUnknownOption().action(
4622
5863
  async (name, jsonArgs, targetCommand, opts) => {
4623
5864
  const operation = { type: "get-prompt", name, args: jsonArgs };
4624
5865
  const parsedOpts = parseHeadlessOpts(opts);
@@ -4639,17 +5880,19 @@ program.command("get-prompt").argument("<name>", "Prompt name").argument("[json_
4639
5880
  }
4640
5881
  }
4641
5882
  );
4642
- program.command("daemon").argument("<session_name>", "Session name").argument("[target_command...]", "Target server command").description("Start run-mcp in background session daemon mode").allowUnknownOption().action(async (sessionName, targetCommand) => {
5883
+ program.command("daemon").argument("<session_name>", "Session name").argument("[target_command...]", "Target server command").description("Start run-mcp in background session daemon mode").option("--sandbox <mode>", "Sandbox execution mode: auto, docker, native, audit, none", "none").allowUnknownOption().action(async (sessionName, targetCommand, opts) => {
4643
5884
  const targetCmd = activeTargetCommand ?? targetCommand;
4644
5885
  if (!targetCmd || targetCmd.length === 0) {
4645
5886
  process.stderr.write("Error: No target command provided for daemon.\n");
4646
- process.exit(1);
5887
+ process.exit(64);
4647
5888
  }
4648
5889
  const server = createServer();
4649
5890
  server.listen(0, "127.0.0.1", async () => {
4650
5891
  const addr = server.address();
4651
5892
  const port = addr.port;
4652
- const target = new TargetManager(targetCmd[0], targetCmd.slice(1));
5893
+ const target = new TargetManager(targetCmd[0], targetCmd.slice(1), {
5894
+ sandbox: opts.sandbox
5895
+ });
4653
5896
  const interceptor = new ResponseInterceptor();
4654
5897
  try {
4655
5898
  await target.connect();
@@ -4658,8 +5901,8 @@ program.command("daemon").argument("<session_name>", "Session name").argument("[
4658
5901
  `);
4659
5902
  process.exit(1);
4660
5903
  }
4661
- await mkdir2(SESSION_DIR, { recursive: true });
4662
- await writeFile2(
5904
+ await mkdir3(SESSION_DIR, { recursive: true });
5905
+ await writeFile3(
4663
5906
  getSessionPath(sessionName),
4664
5907
  JSON.stringify({ port, pid: process.pid }),
4665
5908
  "utf8"
@@ -4676,12 +5919,12 @@ program.command("daemon").argument("<session_name>", "Session name").argument("[
4676
5919
  try {
4677
5920
  const req = JSON.parse(trimmed);
4678
5921
  if (req.method === "execute") {
4679
- const { operation, opts } = req.params;
5922
+ const { operation, opts: opts2 } = req.params;
4680
5923
  const { result, hasError } = await executeOperation(
4681
5924
  target,
4682
5925
  interceptor,
4683
5926
  operation,
4684
- opts
5927
+ opts2
4685
5928
  );
4686
5929
  socket.write(
4687
5930
  JSON.stringify({ jsonrpc: "2.0", result: { result, hasError }, id: req.id }) + "\n"
@@ -4732,7 +5975,7 @@ program.command("close-session").argument("<session_name>", "Session name").desc
4732
5975
  }
4733
5976
  }
4734
5977
  });
4735
- program.name("run-mcp").description("A smart interactive REPL and live test harness for MCP servers").version("1.6.2").passThroughOptions().allowUnknownOption().argument(
5978
+ program.name("run-mcp").description("A smart interactive REPL and live test harness for MCP servers").version("1.7.0").passThroughOptions().allowUnknownOption().argument(
4736
5979
  "[target_command...]",
4737
5980
  "Command to spawn the target MCP server (starts REPL if provided, Agent server otherwise)"
4738
5981
  ).option("-o, --out-dir <path>", "Directory to save intercepted images and audio").option(
@@ -4744,7 +5987,10 @@ program.name("run-mcp").description("A smart interactive REPL and live test harn
4744
5987
  ).option(
4745
5988
  "-m, --media-threshold <kb>",
4746
5989
  "Media size threshold in KB to save to disk (0 to always save, -1 to keep inline)"
4747
- ).option("--mcp", "Force start Agent Server mode even if run interactively without arguments").option("-s, --script <file>", "Read commands from a file instead of stdin (REPL Mode only)").addHelpText(
5990
+ ).option("--mcp", "Force start Agent Server mode even if run interactively without arguments").option("-s, --script <file>", "Read commands from a file instead of stdin (REPL Mode only)").option("--color <mode>", "Color output mode: always, never, auto (default: auto)").option(
5991
+ "--open-media",
5992
+ "Automatically open intercepted images and audio files using the host OS viewer"
5993
+ ).option("--sandbox <mode>", "Sandbox execution mode: auto, docker, native, audit, none", "none").addHelpText(
4748
5994
  "after",
4749
5995
  `
4750
5996
  Examples:
@@ -4781,6 +6027,8 @@ Agent Mode Tools:
4781
6027
  disconnect_from_mcp \u2192 Tear down and reconnect after changes
4782
6028
  mcp_server_status \u2192 Check connection status
4783
6029
  get_mcp_server_stderr \u2192 View target server stderr output
6030
+ validate_mcp_server \u2192 Validate an MCP server command and collect diagnostics
6031
+ search_all_local_mcp_servers \u2192 Scan and search all local MCP servers for a query
4784
6032
 
4785
6033
  REPL Mode Commands (once connected):
4786
6034
  tools/list List all available tools
@@ -4809,18 +6057,20 @@ REPL Mode Commands (once connected):
4809
6057
  Shortcuts: tl td tc ts rl rr rt rs ru pl pg (see help for details)`
4810
6058
  ).action(
4811
6059
  async (targetCommand, opts) => {
4812
- if (targetCommand && targetCommand.length > 0 && !activeTargetCommand) {
4813
- process.stderr.write(
4814
- "Error: Target server command must be separated by '--'.\nThis avoids argument parsing ambiguity.\n\nExample:\n run-mcp -- node my-server.js\n run-mcp -s script.txt -- node my-server.js\n"
4815
- );
4816
- process.exit(1);
4817
- }
4818
- const target = activeTargetCommand ?? [];
6060
+ const target = activeTargetCommand ?? targetCommand ?? [];
4819
6061
  if (target && target.length > 0) {
4820
6062
  await startRepl(target, {
4821
6063
  script: opts.script,
4822
6064
  outDir: opts.outDir,
4823
- mediaThresholdKb: opts.mediaThreshold ? Number.parseInt(opts.mediaThreshold, 10) : void 0
6065
+ mediaThresholdKb: opts.mediaThreshold ? Number.parseInt(opts.mediaThreshold, 10) : void 0,
6066
+ openMedia: opts.openMedia,
6067
+ sandbox: opts.sandbox,
6068
+ allowRead: opts.allowRead,
6069
+ allowWrite: opts.allowWrite,
6070
+ allowNet: opts.allowNet,
6071
+ denyRead: opts.denyRead,
6072
+ denyWrite: opts.denyWrite,
6073
+ denyNet: opts.denyNet
4824
6074
  });
4825
6075
  } else {
4826
6076
  if (opts.mcp || !process.stdin.isTTY) {
@@ -4828,7 +6078,14 @@ Shortcuts: tl td tc ts rl rr rt rs ru pl pg (see help for details)`
4828
6078
  outDir: opts.outDir,
4829
6079
  timeoutMs: opts.timeout ? Number.parseInt(opts.timeout, 10) : void 0,
4830
6080
  maxTextLength: opts.maxText ? Number.parseInt(opts.maxText, 10) : void 0,
4831
- mediaThresholdKb: opts.mediaThreshold ? Number.parseInt(opts.mediaThreshold, 10) : void 0
6081
+ mediaThresholdKb: opts.mediaThreshold ? Number.parseInt(opts.mediaThreshold, 10) : void 0,
6082
+ sandbox: opts.sandbox,
6083
+ allowRead: opts.allowRead,
6084
+ allowWrite: opts.allowWrite,
6085
+ allowNet: opts.allowNet,
6086
+ denyRead: opts.denyRead,
6087
+ denyWrite: opts.denyWrite,
6088
+ denyNet: opts.denyNet
4832
6089
  });
4833
6090
  } else {
4834
6091
  const selected = await pickDiscoveredServer();
@@ -4842,10 +6099,26 @@ Shortcuts: tl td tc ts rl rr rt rs ru pl pg (see help for details)`
4842
6099
  await startRepl([selected.config.command, ...selected.config.args || []], {
4843
6100
  script: opts.script,
4844
6101
  outDir: opts.outDir,
4845
- mediaThresholdKb: opts.mediaThreshold ? Number.parseInt(opts.mediaThreshold, 10) : void 0
6102
+ mediaThresholdKb: opts.mediaThreshold ? Number.parseInt(opts.mediaThreshold, 10) : void 0,
6103
+ openMedia: opts.openMedia,
6104
+ sandbox: opts.sandbox,
6105
+ allowRead: opts.allowRead,
6106
+ allowWrite: opts.allowWrite,
6107
+ allowNet: opts.allowNet,
6108
+ denyRead: opts.denyRead,
6109
+ denyWrite: opts.denyWrite,
6110
+ denyNet: opts.denyNet
4846
6111
  });
4847
6112
  }
4848
6113
  }
4849
6114
  }
4850
6115
  );
6116
+ for (const cmd of [program, ...program.commands]) {
6117
+ if (cmd.options.some((o) => o.long === "--sandbox")) {
6118
+ cmd.option("--allow-read <paths...>", "Paths to allow reading under the sandbox").option("--allow-write <paths...>", "Paths to allow writing under the sandbox").option(
6119
+ "--allow-net <domains...>",
6120
+ "Network domains to allow connecting to under the sandbox"
6121
+ ).option("--deny-read <paths...>", "Paths to deny reading under the sandbox").option("--deny-write <paths...>", "Paths to deny writing under the sandbox").option("--deny-net <domains...>", "Network domains to deny connecting to under the sandbox");
6122
+ }
6123
+ }
4851
6124
  program.parse(argvToParse);