run-mcp 1.6.3 → 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 +2081 -854
  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.3" },
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,15 +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 = [
1623
2414
  "menu",
1624
2415
  "explore",
1625
2416
  "interactive",
2417
+ "view",
1626
2418
  "tools/list",
1627
2419
  "tools/describe",
1628
2420
  "tools/call",
@@ -1664,48 +2456,259 @@ var KNOWN_COMMANDS = [
1664
2456
  "pl",
1665
2457
  "pg"
1666
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
+ }
1667
2479
  var cachedToolNames = [];
2480
+ function setCachedToolNames(names) {
2481
+ cachedToolNames = names;
2482
+ }
1668
2483
  var cachedResourceUris = [];
2484
+ function setCachedResourceUris(uris) {
2485
+ cachedResourceUris = uris;
2486
+ }
1669
2487
  var cachedPromptNames = [];
1670
- var activeCapabilities = null;
1671
- function getActiveCommands() {
1672
- let commands = [...KNOWN_COMMANDS];
1673
- if (!activeCapabilities?.resources) {
1674
- commands = commands.filter(
1675
- (c) => !c.startsWith("resources/") && !["rl", "rr", "rt", "rs", "ru"].includes(c)
1676
- );
1677
- }
1678
- if (!activeCapabilities?.prompts) {
1679
- commands = commands.filter((c) => !c.startsWith("prompts/") && !["pl", "pg"].includes(c));
1680
- }
1681
- return commands;
2488
+ function setCachedPromptNames(names) {
2489
+ cachedPromptNames = names;
1682
2490
  }
1683
- async function refreshCaches(target) {
1684
- const caps = target.getServerCapabilities() ?? {};
2491
+ var activeCapabilities = null;
2492
+ function setActiveCapabilities(caps) {
1685
2493
  activeCapabilities = caps;
1686
- try {
1687
- const { tools } = await target.listTools();
1688
- cachedToolNames = tools.map((t) => t.name);
1689
- } catch {
1690
- }
1691
- 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)) {
1692
2519
  try {
1693
- const { resources } = await target.listResources();
1694
- 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);
1695
2524
  } catch {
1696
2525
  }
1697
2526
  }
1698
- 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)) {
1699
2544
  try {
1700
- const { prompts } = await target.listPrompts();
1701
- 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
+ }
1702
2551
  } catch {
1703
2552
  }
1704
2553
  }
1705
2554
  }
1706
- 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
1707
2698
  function resetTabCycle() {
1708
- 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;
1709
2712
  }
1710
2713
  function computeMatches(line) {
1711
2714
  const expanded = resolveAlias(line);
@@ -1734,8 +2737,12 @@ var completer = (line) => {
1734
2737
  if (tabCycleState) {
1735
2738
  const inCycle = line === tabCycleState.original || tabCycleState.matches.includes(line);
1736
2739
  if (inCycle) {
1737
- tabCycleState.index = (tabCycleState.index + 1) % tabCycleState.matches.length;
1738
- 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];
1739
2746
  setImmediate(() => {
1740
2747
  if (activeRl) {
1741
2748
  activeRl.line = next;
@@ -1745,421 +2752,70 @@ var completer = (line) => {
1745
2752
  });
1746
2753
  return [[], ""];
1747
2754
  }
1748
- tabCycleState = null;
2755
+ setTabCycleState(null);
1749
2756
  }
1750
2757
  const [matches, matchLine] = computeMatches(line);
1751
2758
  if (matches.length > 1) {
1752
- tabCycleState = { matches, index: -1, original: line };
2759
+ setTabCycleState({ matches, index: -1, original: line });
1753
2760
  }
1754
- return [matches, matchLine];
1755
- };
1756
- var callHistory = [];
1757
- var lastToolArgsMap = /* @__PURE__ */ new Map();
1758
- var replHistory = [];
1759
- var lastCommand = null;
1760
- var AbortFlowError = class extends Error {
1761
- constructor() {
1762
- super("Aborted by user.");
1763
- this.name = "AbortFlowError";
1764
- }
1765
- };
1766
- function isAbortError(err) {
1767
- if (!err) return false;
1768
- return err.name === "ExitPromptError" || err.name === "AbortError" || err.message === "Prompt was aborted" || typeof err.message === "string" && err.message.includes("User force closed");
1769
- }
1770
- var activeRl = null;
1771
- var isScriptMode = false;
1772
- var globalPauseReadlineClose = false;
1773
- var deferNextPrompt = false;
1774
- async function withSuspendedReadline(target, interceptor, fn) {
1775
- const wasActive = !!activeRl;
1776
- if (wasActive) {
1777
- globalPauseReadlineClose = true;
1778
- activeRl.close();
1779
- activeRl = null;
1780
- }
1781
- try {
1782
- return await fn();
1783
- } finally {
1784
- if (wasActive) {
1785
- globalPauseReadlineClose = false;
1786
- if (!isScriptMode) {
1787
- deferNextPrompt = true;
1788
- startReadlineLoop(target, interceptor);
1789
- }
1790
- }
1791
- }
1792
- }
1793
- function getPrompt(target) {
1794
- if (target.connected) return `${pc2.green("\u2713")}${pc2.cyan("> ")}`;
1795
- return `${pc2.red("\u2717")}${pc2.cyan("> ")}`;
1796
- }
1797
- function printBanner(serverName, serverVersion, toolCount, resourceCount, promptCount) {
1798
- const parts = [];
1799
- parts.push(`${pc2.bold(toolCount.toString())} tools`);
1800
- if (resourceCount > 0) parts.push(`${pc2.bold(resourceCount.toString())} resources`);
1801
- if (promptCount > 0) parts.push(`${pc2.bold(promptCount.toString())} prompts`);
1802
- const baseTitle = serverVersion ? `${serverName} ${pc2.dim(`v${serverVersion}`)}` : serverName;
1803
- const isAgentHarness = serverName === "run-mcp";
1804
- const title = isAgentHarness ? `${baseTitle} ${pc2.bgBlue(pc2.white(" AGENT HARNESS "))}` : baseTitle;
1805
- const BOX_WIDTH = 53;
1806
- const padLine = (content) => {
1807
- const visible = stripAnsi(content).length;
1808
- const padding = Math.max(0, BOX_WIDTH - visible);
1809
- return `${pc2.cyan(" \u2502")}${content}${"".padEnd(padding)}${pc2.cyan("\u2502")}`;
1810
- };
1811
- const partsStr = ` ${parts.join(" \u2022 ")}`;
1812
- console.log();
1813
- console.log(pc2.cyan(` \u250C${"\u2500".repeat(BOX_WIDTH)}\u2510`));
1814
- console.log(padLine(` ${title}`));
1815
- console.log(padLine(partsStr));
1816
- console.log(pc2.cyan(` \u251C${"\u2500".repeat(BOX_WIDTH)}\u2524`));
1817
- console.log(padLine(" Quick start:"));
1818
- console.log(padLine(` ${pc2.green("tools/list")} See all tools`));
1819
- console.log(padLine(` ${pc2.green("tools/call")} ${pc2.dim("<name>")} Call a tool`));
1820
- console.log(padLine(` ${pc2.green("help")} All commands`));
1821
- console.log(padLine(""));
1822
- console.log(padLine(pc2.dim(" Tab completion is active. Start typing to explore.")));
1823
- console.log(pc2.cyan(` \u2514${"\u2500".repeat(BOX_WIDTH)}\u2518`));
1824
- console.log();
1825
- }
1826
- function stripAnsi(str) {
1827
- return str.replace(/\x1b\[[0-9;]*m/g, "");
1828
- }
1829
- async function startRepl(targetCommand, opts) {
1830
- const [command, ...args] = targetCommand;
1831
- const target = new TargetManager(command, args);
1832
- const interceptor = new ResponseInterceptor({
1833
- outDir: opts.outDir,
1834
- mediaThresholdKb: opts.mediaThresholdKb
1835
- });
1836
- isScriptMode = !!opts.script;
1837
- target.on("stderr", (text) => {
1838
- for (const line of text.split("\n")) {
1839
- console.error(pc2.dim(`[server] ${line}`));
1840
- }
1841
- });
1842
- console.log(pc2.cyan("\u27F3 Connecting to target MCP server..."));
1843
- console.log(pc2.dim(` Command: ${targetCommand.join(" ")}`));
1844
- try {
1845
- await target.connect();
1846
- } catch (err) {
1847
- const msg = err.message ?? String(err);
1848
- if (msg.includes("ENOENT") || msg.includes("spawn")) {
1849
- console.error(pc2.red(`\u2717 Failed to start server: command "${command}" not found.`));
1850
- console.error(pc2.dim(` Check that "${command}" is installed and in your PATH.`));
1851
- } else {
1852
- console.error(pc2.red(`\u2717 Failed to connect: ${msg}`));
1853
- console.error(pc2.dim(` Check that the target command starts a valid MCP server on stdio.`));
1854
- }
1855
- process.exit(1);
1856
- }
1857
- const status = target.getStatus();
1858
- console.log(pc2.green(`\u2713 Connected (PID: ${status.pid})`));
1859
- if (!isScriptMode) {
1860
- target.enableAutoReconnect();
1861
- target.on(
1862
- "reconnecting",
1863
- ({ attempt, maxAttempts }) => {
1864
- console.log(
1865
- pc2.yellow(`
1866
- \u27F3 Server disconnected. Reconnecting (${attempt}/${maxAttempts})...`)
1867
- );
1868
- }
1869
- );
1870
- target.on("reconnected", async ({ attempt }) => {
1871
- const s = target.getStatus();
1872
- console.log(pc2.green(`\u2713 Reconnected (PID: ${s.pid}, attempt ${attempt})`));
1873
- await refreshCaches(target);
1874
- });
1875
- target.on("reconnect_failed", ({ reason, message }) => {
1876
- console.error(pc2.red(`\u2717 ${message}`));
1877
- if (reason === "max_retries") {
1878
- console.log(
1879
- pc2.dim(" Use 'exit' to quit or wait for the server to be fixed and restart manually.")
1880
- );
1881
- }
1882
- });
1883
- target.on("notification", (notification) => {
1884
- const method = notification.method;
1885
- if (method === "notifications/message") {
1886
- const lvl = notification.params?.level ?? "info";
1887
- const data = notification.params?.data ?? "";
1888
- const text = typeof data === "string" ? data : JSON.stringify(data);
1889
- console.log(pc2.dim(`
1890
- [${lvl}] ${text}`));
1891
- } else if (method === "notifications/tools/list_changed") {
1892
- console.log(pc2.yellow("\n \u27F3 Server tools changed. Run tools/list to see updates."));
1893
- refreshCaches(target).catch(() => {
1894
- });
1895
- } else if (method === "notifications/resources/list_changed") {
1896
- console.log(pc2.yellow("\n \u27F3 Server resources changed. Run resources/list to see."));
1897
- refreshCaches(target).catch(() => {
1898
- });
1899
- } else if (method === "notifications/resources/updated") {
1900
- const uri = notification.params?.uri ?? "unknown";
1901
- console.log(pc2.yellow(`
1902
- \u27F3 Resource updated: ${uri}`));
1903
- } else if (method === "notifications/prompts/list_changed") {
1904
- console.log(pc2.yellow("\n \u27F3 Server prompts changed. Run prompts/list to see."));
1905
- refreshCaches(target).catch(() => {
1906
- });
1907
- }
1908
- });
1909
- target.on("sampling_request", async ({ request, respond, reject: rejectFn }) => {
1910
- 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"));
1911
- const messages = request?.messages ?? [];
1912
- for (const msg of messages) {
1913
- const role = msg.role === "user" ? pc2.blue("user") : pc2.magenta("assistant");
1914
- const text = msg.content?.text ?? JSON.stringify(msg.content);
1915
- console.log(pc2.magenta(` \u2551 ${role}: ${text}`));
1916
- }
1917
- 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"));
1918
- if (activeRl) {
1919
- try {
1920
- const answer = await question(activeRl, ` ${pc2.bold("Approve? [y/N/text]:")} `);
1921
- const trimmed = answer.trim().toLowerCase();
1922
- if (trimmed === "y" || trimmed === "yes") {
1923
- respond({
1924
- model: "user-approved",
1925
- role: "assistant",
1926
- content: { type: "text", text: "Approved by user." }
1927
- });
1928
- } else if (trimmed === "n" || trimmed === "no" || trimmed === "") {
1929
- rejectFn(new Error("Sampling request rejected by user"));
1930
- } else {
1931
- respond({
1932
- model: "user-provided",
1933
- role: "assistant",
1934
- content: { type: "text", text: answer.trim() }
1935
- });
1936
- }
1937
- } catch (err) {
1938
- if (err instanceof AbortFlowError) {
1939
- rejectFn(new Error("Sampling request rejected by user"));
1940
- } else {
1941
- throw err;
1942
- }
1943
- }
1944
- } else {
1945
- rejectFn(new Error("No interactive terminal available for sampling approval"));
1946
- }
1947
- });
1948
- target.on("elicitation_request", async ({ request, respond, reject: rejectFn }) => {
1949
- 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"));
1950
- console.log(pc2.cyan(` \u2551 ${request?.message ?? "Server requests input"}`));
1951
- 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"));
1952
- if (activeRl) {
1953
- try {
1954
- const answer = await question(
1955
- activeRl,
1956
- ` ${pc2.bold("Your response (empty to decline):")} `
1957
- );
1958
- if (answer.trim() === "") {
1959
- respond({ action: "decline" });
1960
- } else {
1961
- try {
1962
- const parsed = JSON.parse(answer.trim());
1963
- respond({ action: "accept", content: parsed });
1964
- } catch {
1965
- respond({ action: "accept", content: { value: answer.trim() } });
1966
- }
1967
- }
1968
- } catch (err) {
1969
- if (err instanceof AbortFlowError) {
1970
- respond({ action: "decline" });
1971
- } else {
1972
- throw err;
1973
- }
1974
- }
1975
- } else {
1976
- rejectFn(new Error("No interactive terminal available for elicitation"));
1977
- }
1978
- });
1979
- }
1980
- let toolCount;
1981
- let resourceCount = 0;
1982
- let promptCount = 0;
1983
- try {
1984
- const { tools } = await target.listTools();
1985
- toolCount = tools.length;
1986
- const caps = target.getServerCapabilities() ?? {};
1987
- if (caps.resources) {
1988
- try {
1989
- const { resources } = await target.listResources();
1990
- resourceCount = resources.length;
1991
- } catch {
1992
- }
1993
- }
1994
- if (caps.prompts) {
1995
- try {
1996
- const { prompts } = await target.listPrompts();
1997
- promptCount = prompts.length;
1998
- } catch {
1999
- }
2000
- }
2001
- const serverInfo = target.getServerVersion();
2002
- const serverName = serverInfo?.name ?? "MCP Server";
2003
- const serverVersion = serverInfo?.version ?? "";
2004
- printBanner(serverName, serverVersion, toolCount, resourceCount, promptCount);
2005
- if (toolCount >= 10) {
2006
- const groups = groupToolsByPrefix(
2007
- cachedToolNames.length > 0 ? cachedToolNames : tools.map((t) => t.name)
2008
- );
2009
- if (!groups.has("All")) {
2010
- for (const [label, members] of groups) {
2011
- console.log(` ${pc2.bold(label.padEnd(16))} ${pc2.dim(members.join(", "))}`);
2012
- }
2013
- console.log();
2014
- }
2761
+ return [matches, matchLine];
2762
+ };
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 {
2015
2776
  }
2016
- } catch (err) {
2017
- console.log(pc2.yellow(` Warning: Could not list tools: ${err.message}
2018
- `));
2019
2777
  }
2020
- await refreshCaches(target);
2021
- if (isScriptMode) {
2022
- const lines = await readScriptLines(opts.script);
2023
- const scriptContext = {};
2024
- let expectError = false;
2025
- for (const line of lines) {
2026
- let trimmed = line.trim();
2027
- if (!trimmed || trimmed.startsWith("#")) {
2028
- if (trimmed === "# @expect-error") {
2029
- expectError = true;
2030
- }
2031
- continue;
2032
- }
2033
- if (trimmed.endsWith("# @expect-error")) {
2034
- expectError = true;
2035
- trimmed = trimmed.replace(/\s*#\s*@expect-error$/, "");
2036
- }
2037
- const interpolated = interpolateString(trimmed, scriptContext);
2038
- try {
2039
- const res = await handleCommand(interpolated, target, interceptor);
2040
- if (res !== void 0) {
2041
- scriptContext.LAST = res;
2042
- }
2043
- const isErrorRes = res && typeof res === "object" && res.isError === true;
2044
- if (expectError && !isErrorRes) {
2045
- console.error(pc2.red(`\u2717 Expected an error but the command succeeded.`));
2046
- await target.close();
2047
- process.exit(1);
2048
- }
2049
- if (!expectError && isErrorRes) {
2050
- console.error(pc2.red(`\u2717 Command failed unexpectedly.`));
2051
- await target.close();
2052
- process.exit(1);
2053
- }
2054
- if (expectError && isErrorRes) {
2055
- console.log(pc2.yellow(` \u2713 Expected error caught: tool returned isError: true`));
2056
- }
2057
- } catch (err) {
2058
- if (expectError) {
2059
- console.log(pc2.yellow(` \u2713 Expected error caught: ${err.message}`));
2060
- } else {
2061
- if (err?.message?.includes("-32601") || err?.code === -32601) {
2062
- let msg = "Server does not support this feature (Method not found)";
2063
- if (trimmed.startsWith("prompts/")) msg = "This server does not have any prompts.";
2064
- else if (trimmed.startsWith("resources/"))
2065
- msg = "This server does not have any resources.";
2066
- else if (trimmed.startsWith("tools/")) msg = "This server does not have any tools.";
2067
- console.log(pc2.yellow(` ${msg}`));
2068
- } else {
2069
- console.error(pc2.red(`\u2717 Error: ${err.message}`));
2070
- }
2071
- console.log(pc2.dim("\nShutting down..."));
2072
- await target.close();
2073
- process.exit(1);
2074
- }
2075
- }
2076
- expectError = false;
2778
+ if (caps.prompts) {
2779
+ try {
2780
+ const { prompts } = await target.listPrompts();
2781
+ setCachedPromptNames(prompts.map((p) => p.name));
2782
+ } catch {
2077
2783
  }
2078
- console.log(pc2.dim("\nShutting down..."));
2079
- await target.close();
2080
- process.exit(0);
2081
- } else {
2082
- mainMenuLoop(target, interceptor);
2083
2784
  }
2084
2785
  }
2085
- function startReadlineLoop(target, interceptor) {
2086
- if (isScriptMode || activeRl) return;
2087
- const rl = createInterface({
2088
- input: process.stdin,
2089
- output: process.stdout,
2090
- prompt: getPrompt(target),
2091
- terminal: true,
2092
- completer,
2093
- history: [...replHistory].reverse()
2094
- // Node's readline history expects newest first
2095
- });
2096
- activeRl = rl;
2097
- if (process.stdin.isTTY) {
2098
- process.stdin.on("keypress", (_str, key) => {
2099
- if (!key || key.name !== "tab") {
2100
- resetTabCycle();
2101
- }
2102
- });
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";
2790
+ var lastCommand = null;
2791
+ var AbortFlowError = class extends Error {
2792
+ constructor() {
2793
+ super("Aborted by user.");
2794
+ this.name = "AbortFlowError";
2103
2795
  }
2104
- if (!deferNextPrompt) {
2105
- rl.prompt();
2796
+ };
2797
+ function isAbortError(err) {
2798
+ if (!err) return false;
2799
+ return err.name === "ExitPromptError" || err.name === "AbortError" || err.message === "Prompt was aborted" || typeof err.message === "string" && err.message.includes("User force closed");
2800
+ }
2801
+ async function withSuspendedReadline(target, interceptor, fn) {
2802
+ const wasActive = !!activeRl;
2803
+ if (wasActive) {
2804
+ setGlobalPauseReadlineClose(true);
2805
+ activeRl.close();
2806
+ setActiveRl(null);
2106
2807
  }
2107
- deferNextPrompt = false;
2108
- let processing = false;
2109
- let closed = false;
2110
- const queue = [];
2111
- const processQueue = async () => {
2112
- if (processing) return;
2113
- processing = true;
2114
- while (queue.length > 0) {
2115
- const trimmed = queue.shift();
2116
- try {
2117
- await handleCommand(trimmed, target, interceptor);
2118
- } catch (err) {
2119
- if (err instanceof AbortFlowError) {
2120
- console.log(pc2.yellow(" Aborted."));
2121
- } else if (err?.message?.includes("-32601") || err?.code === -32601) {
2122
- let msg = "Server does not support this feature (Method not found)";
2123
- if (trimmed.startsWith("prompts/")) msg = "This server does not have any prompts.";
2124
- else if (trimmed.startsWith("resources/"))
2125
- msg = "This server does not have any resources.";
2126
- else if (trimmed.startsWith("tools/")) msg = "This server does not have any tools.";
2127
- console.log(pc2.yellow(` ${msg}`));
2128
- } else {
2129
- console.error(pc2.red(`\u2717 Error: ${err.message}`));
2130
- }
2131
- }
2132
- if (activeRl) {
2133
- setImmediate(() => {
2134
- if (activeRl) {
2135
- console.log();
2136
- activeRl.setPrompt(getPrompt(target));
2137
- activeRl.prompt();
2138
- }
2139
- });
2808
+ try {
2809
+ return await fn();
2810
+ } finally {
2811
+ if (wasActive) {
2812
+ setGlobalPauseReadlineClose(false);
2813
+ if (!isScriptMode) {
2814
+ setDeferNextPrompt(true);
2815
+ startReadlineLoop(target, interceptor);
2140
2816
  }
2141
2817
  }
2142
- processing = false;
2143
- };
2144
- rl.on("line", (line) => {
2145
- const trimmed = line.trim();
2146
- if (!trimmed || trimmed.startsWith("#")) {
2147
- if (!closed && activeRl) activeRl.prompt();
2148
- return;
2149
- }
2150
- replHistory.push(trimmed);
2151
- queue.push(trimmed);
2152
- processQueue();
2153
- });
2154
- rl.on("close", async () => {
2155
- closed = true;
2156
- activeRl = null;
2157
- if (!globalPauseReadlineClose) {
2158
- console.log(pc2.dim("\nShutting down..."));
2159
- await target.close();
2160
- process.exit(0);
2161
- }
2162
- });
2818
+ }
2163
2819
  }
2164
2820
  async function handleCommand(input3, target, interceptor) {
2165
2821
  const expanded = resolveAlias(input3);
@@ -2175,14 +2831,32 @@ async function handleCommand(input3, target, interceptor) {
2175
2831
  case "?":
2176
2832
  printShortHelp();
2177
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;
2839
+ }
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}`));
2848
+ }
2849
+ });
2850
+ return;
2851
+ }
2178
2852
  case "menu":
2179
2853
  case "explore":
2180
2854
  case "interactive":
2181
2855
  if (activeRl) {
2182
- globalPauseReadlineClose = true;
2856
+ setGlobalPauseReadlineClose(true);
2183
2857
  activeRl.close();
2184
- activeRl = null;
2185
- globalPauseReadlineClose = false;
2858
+ setActiveRl(null);
2859
+ setGlobalPauseReadlineClose(false);
2186
2860
  }
2187
2861
  mainMenuLoop(target, interceptor);
2188
2862
  return;
@@ -2249,10 +2923,10 @@ async function handleCommand(input3, target, interceptor) {
2249
2923
  case "!!":
2250
2924
  case "last":
2251
2925
  if (lastCommand) {
2252
- console.log(pc2.dim(` Re-running: ${lastCommand}`));
2926
+ console.log(colors.dim(` Re-running: ${lastCommand}`));
2253
2927
  return await handleCommand(lastCommand, target, interceptor);
2254
2928
  } else {
2255
- console.log(pc2.yellow("No previous command to re-run."));
2929
+ console.log(colors.yellow("No previous command to re-run."));
2256
2930
  }
2257
2931
  return;
2258
2932
  case "status":
@@ -2260,7 +2934,7 @@ async function handleCommand(input3, target, interceptor) {
2260
2934
  return;
2261
2935
  case "exit":
2262
2936
  case "quit": {
2263
- console.log(pc2.dim("Shutting down..."));
2937
+ console.log(colors.dim("Shutting down..."));
2264
2938
  await target.close();
2265
2939
  process.exit(0);
2266
2940
  return;
@@ -2271,11 +2945,11 @@ async function handleCommand(input3, target, interceptor) {
2271
2945
  }
2272
2946
  const suggestion = suggestCommand(cmd, getActiveCommands());
2273
2947
  if (suggestion) {
2274
- console.log(pc2.yellow(`Unknown command: ${cmd}.`));
2948
+ console.log(colors.yellow(`Unknown command: ${cmd}.`));
2275
2949
  try {
2276
2950
  await withSuspendedReadline(target, interceptor, async () => {
2277
2951
  const runIt = await confirm({
2278
- message: `Did you mean ${pc2.bold(suggestion)}?`,
2952
+ message: `Did you mean ${colors.bold(suggestion)}?`,
2279
2953
  default: true
2280
2954
  });
2281
2955
  if (runIt) {
@@ -2288,7 +2962,7 @@ async function handleCommand(input3, target, interceptor) {
2288
2962
  throw new AbortFlowError();
2289
2963
  }
2290
2964
  } else {
2291
- 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.`));
2292
2966
  }
2293
2967
  }
2294
2968
  }
@@ -2296,25 +2970,25 @@ async function handleCommand(input3, target, interceptor) {
2296
2970
  async function cmdToolsList(target) {
2297
2971
  const { tools } = await target.listTools();
2298
2972
  if (tools.length === 0) {
2299
- console.log(pc2.dim(" No tools available."));
2973
+ console.log(colors.dim(" No tools available."));
2300
2974
  return;
2301
2975
  }
2302
2976
  const nameWidth = Math.max(8, ...tools.map((t) => t.name.length));
2303
- console.log(pc2.bold(` ${"Name".padEnd(nameWidth)} Description`));
2304
- 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)}`));
2305
2979
  for (const tool of tools) {
2306
- const desc = tool.description ? tool.description.length > 60 ? `${tool.description.slice(0, 57)}...` : tool.description : pc2.dim("(no description)");
2307
- 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}`);
2308
2982
  }
2309
- console.log(pc2.dim(`
2983
+ console.log(colors.dim(`
2310
2984
  ${tools.length} tool(s) total.`));
2311
2985
  if (tools.length >= 10) {
2312
2986
  const groups = groupToolsByPrefix(tools.map((t) => t.name));
2313
2987
  if (!groups.has("All")) {
2314
2988
  console.log();
2315
- console.log(pc2.bold(" Groups:"));
2989
+ console.log(colors.bold(" Groups:"));
2316
2990
  for (const [label, members] of groups) {
2317
- console.log(` ${pc2.cyan(label.padEnd(16))} ${pc2.dim(members.join(", "))}`);
2991
+ console.log(` ${colors.cyan(label.padEnd(16))} ${colors.dim(members.join(", "))}`);
2318
2992
  }
2319
2993
  }
2320
2994
  }
@@ -2322,28 +2996,28 @@ async function cmdToolsList(target) {
2322
2996
  async function cmdToolsDescribe(target, rest) {
2323
2997
  const name = rest.trim();
2324
2998
  if (!name) {
2325
- console.log(pc2.yellow(" Usage: tools/describe <name>"));
2999
+ console.log(colors.yellow(" Usage: tools/describe <name>"));
2326
3000
  if (cachedToolNames.length > 0) {
2327
3001
  const preview = cachedToolNames.slice(0, 6);
2328
3002
  const more = cachedToolNames.length > 6 ? `, ... (${cachedToolNames.length} total)` : "";
2329
- console.log(pc2.dim(`
3003
+ console.log(colors.dim(`
2330
3004
  Available tools: ${preview.join(", ")}${more}`));
2331
- console.log(pc2.dim(` Type ${pc2.bold("tools/list")} for all.`));
3005
+ console.log(colors.dim(` Type ${colors.bold("tools/list")} for all.`));
2332
3006
  }
2333
3007
  return;
2334
3008
  }
2335
3009
  const { tools } = await target.listTools();
2336
3010
  const tool = tools.find((t) => t.name === name);
2337
3011
  if (!tool) {
2338
- console.log(pc2.red(`Tool "${name}" not found.`));
3012
+ console.log(colors.red(`Tool "${name}" not found.`));
2339
3013
  const suggestion = suggestCommand(
2340
3014
  name,
2341
3015
  tools.map((t) => t.name)
2342
3016
  );
2343
3017
  if (suggestion) {
2344
- console.log(pc2.yellow(`Did you mean ${pc2.bold(suggestion)}?`));
3018
+ console.log(colors.yellow(`Did you mean ${colors.bold(suggestion)}?`));
2345
3019
  } else {
2346
- console.log(pc2.dim(`Available: ${tools.map((t) => t.name).join(", ")}`));
3020
+ console.log(colors.dim(`Available: ${tools.map((t) => t.name).join(", ")}`));
2347
3021
  }
2348
3022
  return;
2349
3023
  }
@@ -2374,14 +3048,14 @@ async function cmdToolsCall(target, interceptor, rest) {
2374
3048
  if (!picked) return;
2375
3049
  return cmdToolsCall(target, interceptor, picked);
2376
3050
  }
2377
- 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>]"));
2378
3052
  if (cachedToolNames.length > 0) {
2379
3053
  const preview = cachedToolNames.slice(0, 6);
2380
3054
  const more = cachedToolNames.length > 6 ? `, ... (${cachedToolNames.length} total)` : "";
2381
- console.log(pc2.dim(`
3055
+ console.log(colors.dim(`
2382
3056
  Available tools: ${preview.join(", ")}${more}`));
2383
3057
  console.log(
2384
- 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>`)
2385
3059
  );
2386
3060
  }
2387
3061
  return;
@@ -2393,31 +3067,31 @@ async function cmdToolsCall(target, interceptor, rest) {
2393
3067
  try {
2394
3068
  args = JSON.parse(trimmed);
2395
3069
  } catch (err) {
2396
- console.error(pc2.red(`Invalid JSON: ${err.message}`));
2397
- console.log(pc2.dim(` Received: ${jsonArgs}`));
3070
+ console.error(colors.red(`Invalid JSON: ${err.message}`));
3071
+ console.log(colors.dim(` Received: ${jsonArgs}`));
2398
3072
  return;
2399
3073
  }
2400
3074
  } else {
2401
3075
  try {
2402
3076
  args = parseHttpieArgs(trimmed);
2403
3077
  } catch (err) {
2404
- console.error(pc2.red(`Invalid shorthand arguments: ${err.message}`));
3078
+ console.error(colors.red(`Invalid shorthand arguments: ${err.message}`));
2405
3079
  return;
2406
3080
  }
2407
3081
  }
2408
3082
  const { tools } = await target.listTools();
2409
3083
  const tool = tools.find((t) => t.name === toolName);
2410
3084
  if (!tool) {
2411
- console.log(pc2.red(`
3085
+ console.log(colors.red(`
2412
3086
  \u2717 Tool "${toolName}" not found.`));
2413
3087
  const toolNames = tools.map((t) => t.name);
2414
3088
  const suggestion = suggestCommand(toolName, toolNames);
2415
3089
  if (suggestion) {
2416
- console.log(pc2.yellow(` \u{1F4A1} Did you mean "${suggestion}"?`));
3090
+ console.log(colors.yellow(` \u{1F4A1} Did you mean "${suggestion}"?`));
2417
3091
  } else {
2418
3092
  const preview = toolNames.slice(0, 6);
2419
3093
  const more = toolNames.length > 6 ? `, ... (${toolNames.length} total)` : "";
2420
- console.log(pc2.dim(` Available tools: ${preview.join(", ")}${more}`));
3094
+ console.log(colors.dim(` Available tools: ${preview.join(", ")}${more}`));
2421
3095
  }
2422
3096
  return { isError: true, content: [{ type: "text", text: `Tool not found: ${toolName}` }] };
2423
3097
  }
@@ -2425,14 +3099,14 @@ async function cmdToolsCall(target, interceptor, rest) {
2425
3099
  const required = schema.required ?? [];
2426
3100
  const missing = required.filter((r) => !(r in args));
2427
3101
  if (missing.length > 0) {
2428
- console.log(pc2.yellow(`
3102
+ console.log(colors.yellow(`
2429
3103
  Missing required arguments: ${missing.join(", ")}`));
2430
3104
  console.log();
2431
3105
  const scaffolded = scaffoldArgs(schema);
2432
- console.log(pc2.dim(" Try:"));
3106
+ console.log(colors.dim(" Try:"));
2433
3107
  console.log(` tools/call ${toolName} ${scaffolded}`);
2434
3108
  console.log();
2435
- console.log(pc2.dim(" Or run without args for interactive mode:"));
3109
+ console.log(colors.dim(" Or run without args for interactive mode:"));
2436
3110
  console.log(` tools/call ${toolName}`);
2437
3111
  console.log();
2438
3112
  return;
@@ -2444,17 +3118,21 @@ async function cmdToolsCall(target, interceptor, rest) {
2444
3118
  if (!isScriptMode) {
2445
3119
  const fullCmd = `tools/call ${toolName} ${JSON.stringify(args)}`;
2446
3120
  replHistory.push(fullCmd);
3121
+ appendToHistoryFile(fullCmd).catch(() => {
3122
+ });
2447
3123
  if (activeRl) {
2448
3124
  activeRl.history.unshift(fullCmd);
2449
3125
  }
2450
3126
  }
2451
3127
  }
2452
- console.log(pc2.dim(` Calling ${toolName}...`));
3128
+ console.log(colors.dim(` Calling ${toolName}...`));
2453
3129
  const startTime = Date.now();
2454
3130
  const result = await interceptor.callTool(target, toolName, args, timeoutMs);
2455
3131
  const elapsed = Date.now() - startTime;
2456
3132
  callHistory.push({ toolName, durationMs: elapsed, timestamp: startTime });
2457
3133
  lastToolArgsMap.set(toolName, { ...args });
3134
+ saveWizardDefaults().catch(() => {
3135
+ });
2458
3136
  const isError = result.isError === true;
2459
3137
  console.log();
2460
3138
  printResultBlock({
@@ -2468,17 +3146,17 @@ async function cmdToolsCall(target, interceptor, rest) {
2468
3146
  for (const item of content) {
2469
3147
  if (item.type === "text") {
2470
3148
  if (isError) {
2471
- console.log(pc2.red(` \u2717 ${item.text}`));
3149
+ console.log(colors.red(` \u2717 ${item.text}`));
2472
3150
  } else {
2473
3151
  try {
2474
3152
  const parsed = JSON.parse(item.text);
2475
3153
  if (typeof parsed === "object" && parsed !== null) {
2476
3154
  console.log(formatJson(parsed, 2, true));
2477
3155
  } else {
2478
- console.log(pc2.yellow(` ${item.text}`));
3156
+ console.log(colors.yellow(` ${item.text}`));
2479
3157
  }
2480
3158
  } catch {
2481
- console.log(pc2.yellow(` ${item.text}`));
3159
+ console.log(colors.yellow(` ${item.text}`));
2482
3160
  }
2483
3161
  }
2484
3162
  } else {
@@ -2492,7 +3170,7 @@ async function cmdToolsCall(target, interceptor, rest) {
2492
3170
  const errText = Array.isArray(content) ? content.map((c) => c.text || "").join(" ").toLowerCase() : typeof content === "object" ? (content.text || "").toLowerCase() : "";
2493
3171
  if (errText.includes("argument") || errText.includes("validation") || errText.includes("schema") || errText.includes("missing") || errText.includes("invalid")) {
2494
3172
  console.log(
2495
- pc2.yellow(
3173
+ colors.yellow(
2496
3174
  ` \u{1F4A1} Tip: Check the tool arguments via 'tools/describe ${toolName}'
2497
3175
  or view the raw server stderr above.`
2498
3176
  )
@@ -2506,15 +3184,15 @@ async function interactiveArgPrompt(target, interceptor, toolName, clearPrevious
2506
3184
  const { tools } = await target.listTools();
2507
3185
  const tool = tools.find((t) => t.name === toolName);
2508
3186
  if (!tool) {
2509
- console.log(pc2.red(`Tool "${toolName}" not found.`));
3187
+ console.log(colors.red(`Tool "${toolName}" not found.`));
2510
3188
  const suggestion = suggestCommand(
2511
3189
  toolName,
2512
3190
  tools.map((t) => t.name)
2513
3191
  );
2514
3192
  if (suggestion) {
2515
- console.log(pc2.yellow(`Did you mean ${pc2.bold(suggestion)}?`));
3193
+ console.log(colors.yellow(`Did you mean ${colors.bold(suggestion)}?`));
2516
3194
  } else {
2517
- console.log(pc2.dim(`Available: ${tools.map((t) => t.name).join(", ")}`));
3195
+ console.log(colors.dim(`Available: ${tools.map((t) => t.name).join(", ")}`));
2518
3196
  }
2519
3197
  return null;
2520
3198
  }
@@ -2524,9 +3202,9 @@ async function interactiveArgPrompt(target, interceptor, toolName, clearPrevious
2524
3202
  return {};
2525
3203
  }
2526
3204
  if (isScriptMode) {
2527
- console.log(pc2.yellow(` Tool "${toolName}" requires arguments.`));
3205
+ console.log(colors.yellow(` Tool "${toolName}" requires arguments.`));
2528
3206
  const scaffolded = scaffoldArgs(schema);
2529
- console.log(pc2.dim(` Usage: tools/call ${toolName} ${scaffolded}`));
3207
+ console.log(colors.dim(` Usage: tools/call ${toolName} ${scaffolded}`));
2530
3208
  return null;
2531
3209
  }
2532
3210
  const required = schema.required ?? [];
@@ -2535,10 +3213,10 @@ async function interactiveArgPrompt(target, interceptor, toolName, clearPrevious
2535
3213
  const optionalProps = allProps.filter(([name]) => !required.includes(name));
2536
3214
  const previousArgs = clearPrevious ? void 0 : lastToolArgsMap.get(toolName);
2537
3215
  console.log();
2538
- 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}`) : ""}`);
2539
3217
  if (previousArgs) {
2540
- console.log(pc2.dim(` Previous: ${JSON.stringify(previousArgs)}`));
2541
- 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."));
2542
3220
  }
2543
3221
  console.log();
2544
3222
  const collectedArgs = {};
@@ -2556,7 +3234,7 @@ async function interactiveArgPrompt(target, interceptor, toolName, clearPrevious
2556
3234
  const typeStr = prop.type ?? "any";
2557
3235
  const desc = prop.description ?? "";
2558
3236
  const prevVal = previousArgs?.[name];
2559
- 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})`)}`;
2560
3238
  const answerStr = await input2(
2561
3239
  {
2562
3240
  message: label,
@@ -2598,7 +3276,7 @@ async function interactiveArgPrompt(target, interceptor, toolName, clearPrevious
2598
3276
  const typeStr = prop.type ?? "any";
2599
3277
  const desc = prop.description ?? "";
2600
3278
  const prevVal = previousArgs?.[name];
2601
- 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})`)}`;
2602
3280
  const answerStr = await input2(
2603
3281
  {
2604
3282
  message: label,
@@ -2637,14 +3315,14 @@ function printJsonTemplate(filled, allProps, currentProp) {
2637
3315
  const parts = [];
2638
3316
  for (const [name] of allProps) {
2639
3317
  if (name in filled) {
2640
- parts.push(`"${name}": ${pc2.green(JSON.stringify(filled[name]))}`);
3318
+ parts.push(`"${name}": ${colors.green(JSON.stringify(filled[name]))}`);
2641
3319
  } else if (name === currentProp) {
2642
- parts.push(`"${name}": ${pc2.yellow("\u2592")}`);
3320
+ parts.push(`"${name}": ${colors.yellow("\u2592")}`);
2643
3321
  } else {
2644
- parts.push(`"${name}": ${pc2.dim("\u2592")}`);
3322
+ parts.push(`"${name}": ${colors.dim("\u2592")}`);
2645
3323
  }
2646
3324
  }
2647
- console.log(pc2.dim(" { ") + parts.join(pc2.dim(", ")) + pc2.dim(" }"));
3325
+ console.log(colors.dim(" { ") + parts.join(colors.dim(", ")) + colors.dim(" }"));
2648
3326
  console.log();
2649
3327
  }
2650
3328
  function coerceValue(input3, type) {
@@ -2661,7 +3339,7 @@ function coerceValue(input3, type) {
2661
3339
  }
2662
3340
  }
2663
3341
  function question(rl, prompt) {
2664
- return new Promise((resolve2, reject) => {
3342
+ return new Promise((resolve3, reject) => {
2665
3343
  let aborted = false;
2666
3344
  const onKeypress = (_str, key) => {
2667
3345
  if (key && key.name === "escape") {
@@ -2685,7 +3363,7 @@ function question(rl, prompt) {
2685
3363
  if (aborted) {
2686
3364
  reject(new AbortFlowError());
2687
3365
  } else {
2688
- resolve2(answer);
3366
+ resolve3(answer);
2689
3367
  }
2690
3368
  });
2691
3369
  });
@@ -2693,11 +3371,11 @@ function question(rl, prompt) {
2693
3371
  async function cmdToolsScaffold(target, rest) {
2694
3372
  const name = rest.trim();
2695
3373
  if (!name) {
2696
- console.log(pc2.yellow(" Usage: tools/scaffold <name>"));
3374
+ console.log(colors.yellow(" Usage: tools/scaffold <name>"));
2697
3375
  if (cachedToolNames.length > 0) {
2698
3376
  const preview = cachedToolNames.slice(0, 6);
2699
3377
  const more = cachedToolNames.length > 6 ? `, ... (${cachedToolNames.length} total)` : "";
2700
- console.log(pc2.dim(`
3378
+ console.log(colors.dim(`
2701
3379
  Available tools: ${preview.join(", ")}${more}`));
2702
3380
  }
2703
3381
  return;
@@ -2705,38 +3383,38 @@ async function cmdToolsScaffold(target, rest) {
2705
3383
  const { tools } = await target.listTools();
2706
3384
  const tool = tools.find((t) => t.name === name);
2707
3385
  if (!tool) {
2708
- console.log(pc2.red(`Tool "${name}" not found.`));
3386
+ console.log(colors.red(`Tool "${name}" not found.`));
2709
3387
  const suggestion = suggestCommand(
2710
3388
  name,
2711
3389
  tools.map((t) => t.name)
2712
3390
  );
2713
3391
  if (suggestion) {
2714
- console.log(pc2.yellow(`Did you mean ${pc2.bold(suggestion)}?`));
3392
+ console.log(colors.yellow(`Did you mean ${colors.bold(suggestion)}?`));
2715
3393
  } else {
2716
- console.log(pc2.dim(`Available: ${tools.map((t) => t.name).join(", ")}`));
3394
+ console.log(colors.dim(`Available: ${tools.map((t) => t.name).join(", ")}`));
2717
3395
  }
2718
3396
  return;
2719
3397
  }
2720
3398
  const scaffolded = scaffoldArgs(tool.inputSchema);
2721
- console.log(pc2.cyan("\n Ready-to-paste command:"));
3399
+ console.log(colors.cyan("\n Ready-to-paste command:"));
2722
3400
  console.log(` tools/call ${name} ${scaffolded}
2723
3401
  `);
2724
3402
  }
2725
3403
  async function cmdResourcesList(target) {
2726
3404
  const { resources } = await target.listResources();
2727
3405
  if (resources.length === 0) {
2728
- console.log(pc2.dim(" No resources available."));
3406
+ console.log(colors.dim(" No resources available."));
2729
3407
  return;
2730
3408
  }
2731
3409
  const uriWidth = Math.max(6, ...resources.map((r) => r.uri.length));
2732
- console.log(pc2.bold(` ${"URI".padEnd(uriWidth)} Name`));
2733
- 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)}`));
2734
3412
  for (const r of resources) {
2735
3413
  const uri = r.uri;
2736
- const name = r.name ?? pc2.dim("(unnamed)");
2737
- 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}`);
2738
3416
  }
2739
- console.log(pc2.dim(`
3417
+ console.log(colors.dim(`
2740
3418
  ${resources.length} resource(s) total.`));
2741
3419
  }
2742
3420
  async function cmdResourcesRead(target, rest, interceptor) {
@@ -2756,11 +3434,11 @@ async function cmdResourcesRead(target, rest, interceptor) {
2756
3434
  if (!picked) return;
2757
3435
  return cmdResourcesRead(target, picked, interceptor);
2758
3436
  }
2759
- console.log(pc2.yellow(" Usage: resources/read <uri>"));
3437
+ console.log(colors.yellow(" Usage: resources/read <uri>"));
2760
3438
  if (cachedResourceUris.length > 0) {
2761
3439
  const preview = cachedResourceUris.slice(0, 5);
2762
3440
  const more = cachedResourceUris.length > 5 ? `, ... (${cachedResourceUris.length} total)` : "";
2763
- console.log(pc2.dim(`
3441
+ console.log(colors.dim(`
2764
3442
  Available resources: ${preview.join(", ")}${more}`));
2765
3443
  }
2766
3444
  return;
@@ -2778,15 +3456,15 @@ async function cmdResourcesRead(target, rest, interceptor) {
2778
3456
  if (typeof parsed === "object" && parsed !== null) {
2779
3457
  console.log(formatJson(parsed, 2, true));
2780
3458
  } else {
2781
- console.log(pc2.yellow(` ${text}`));
3459
+ console.log(colors.yellow(` ${text}`));
2782
3460
  }
2783
3461
  } catch {
2784
- console.log(pc2.yellow(` ${text}`));
3462
+ console.log(colors.yellow(` ${text}`));
2785
3463
  }
2786
3464
  } else if (item.blob !== void 0) {
2787
3465
  const mimeType = item.mimeType ?? "application/octet-stream";
2788
3466
  const sizeBytes = Buffer.from(item.blob, "base64").length;
2789
- console.log(pc2.dim(` [Binary: ${mimeType}, ${sizeBytes} bytes]`));
3467
+ console.log(colors.dim(` [Binary: ${mimeType}, ${sizeBytes} bytes]`));
2790
3468
  } else {
2791
3469
  console.log(formatJson(item, 2, true));
2792
3470
  }
@@ -2797,37 +3475,37 @@ async function cmdResourcesRead(target, rest, interceptor) {
2797
3475
  async function cmdResourcesTemplates(target) {
2798
3476
  const { resourceTemplates } = await target.listResourceTemplates();
2799
3477
  if (resourceTemplates.length === 0) {
2800
- console.log(pc2.dim(" No resource templates available."));
3478
+ console.log(colors.dim(" No resource templates available."));
2801
3479
  return;
2802
3480
  }
2803
3481
  const uriWidth = Math.max(
2804
3482
  12,
2805
3483
  ...resourceTemplates.map((t) => t.uriTemplate.length)
2806
3484
  );
2807
- console.log(pc2.bold(` ${"URI Template".padEnd(uriWidth)} Name`));
2808
- 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)}`));
2809
3487
  for (const t of resourceTemplates) {
2810
3488
  const uriTemplate = t.uriTemplate;
2811
- const name = t.name ?? pc2.dim("(unnamed)");
2812
- 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}`);
2813
3491
  }
2814
- console.log(pc2.dim(`
3492
+ console.log(colors.dim(`
2815
3493
  ${resourceTemplates.length} template(s) total.`));
2816
3494
  }
2817
3495
  async function cmdPromptsList(target) {
2818
3496
  const { prompts } = await target.listPrompts();
2819
3497
  if (prompts.length === 0) {
2820
- console.log(pc2.dim(" No prompts available."));
3498
+ console.log(colors.dim(" No prompts available."));
2821
3499
  return;
2822
3500
  }
2823
3501
  const nameWidth = Math.max(8, ...prompts.map((p) => p.name.length));
2824
- console.log(pc2.bold(` ${"Name".padEnd(nameWidth)} Description`));
2825
- 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)}`));
2826
3504
  for (const p of prompts) {
2827
- const desc = p.description ? p.description.length > 60 ? `${p.description.slice(0, 57)}...` : p.description : pc2.dim("(no description)");
2828
- 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}`);
2829
3507
  }
2830
- console.log(pc2.dim(`
3508
+ console.log(colors.dim(`
2831
3509
  ${prompts.length} prompt(s) total.`));
2832
3510
  }
2833
3511
  async function cmdPromptsGet(target, rest, interceptor) {
@@ -2844,9 +3522,9 @@ async function cmdPromptsGet(target, rest, interceptor) {
2844
3522
  if (!picked) return;
2845
3523
  return cmdPromptsGet(target, picked);
2846
3524
  }
2847
- console.log(pc2.yellow(" Usage: prompts/get <name> [json_args]"));
3525
+ console.log(colors.yellow(" Usage: prompts/get <name> [json_args]"));
2848
3526
  if (cachedPromptNames.length > 0) {
2849
- console.log(pc2.dim(`
3527
+ console.log(colors.dim(`
2850
3528
  Available prompts: ${cachedPromptNames.join(", ")}`));
2851
3529
  }
2852
3530
  return;
@@ -2858,15 +3536,15 @@ async function cmdPromptsGet(target, rest, interceptor) {
2858
3536
  try {
2859
3537
  promptArgs = JSON.parse(trimmed);
2860
3538
  } catch (err) {
2861
- console.error(pc2.red(`Invalid JSON: ${err.message}`));
2862
- console.log(pc2.dim(` Received: ${jsonArgs}`));
3539
+ console.error(colors.red(`Invalid JSON: ${err.message}`));
3540
+ console.log(colors.dim(` Received: ${jsonArgs}`));
2863
3541
  return;
2864
3542
  }
2865
3543
  } else {
2866
3544
  try {
2867
3545
  promptArgs = parseHttpieArgs(trimmed);
2868
3546
  } catch (err) {
2869
- console.error(pc2.red(`Invalid shorthand arguments: ${err.message}`));
3547
+ console.error(colors.red(`Invalid shorthand arguments: ${err.message}`));
2870
3548
  return;
2871
3549
  }
2872
3550
  }
@@ -2874,14 +3552,14 @@ async function cmdPromptsGet(target, rest, interceptor) {
2874
3552
  const { prompts } = await target.listPrompts();
2875
3553
  const prompt = prompts.find((p) => p.name === promptName);
2876
3554
  if (!prompt) {
2877
- console.log(pc2.red(`
3555
+ console.log(colors.red(`
2878
3556
  \u2717 Prompt "${promptName}" not found.`));
2879
3557
  const promptNames = prompts.map((p) => p.name);
2880
3558
  const suggestion = suggestCommand(promptName, promptNames);
2881
3559
  if (suggestion) {
2882
- console.log(pc2.yellow(` \u{1F4A1} Did you mean "${suggestion}"?`));
3560
+ console.log(colors.yellow(` \u{1F4A1} Did you mean "${suggestion}"?`));
2883
3561
  } else {
2884
- console.log(pc2.dim(` Available prompts: ${promptNames.join(", ")}`));
3562
+ console.log(colors.dim(` Available prompts: ${promptNames.join(", ")}`));
2885
3563
  }
2886
3564
  return { isError: true, content: [{ type: "text", text: `Prompt not found: ${promptName}` }] };
2887
3565
  }
@@ -2889,24 +3567,24 @@ async function cmdPromptsGet(target, rest, interceptor) {
2889
3567
  const result = await target.getPrompt({ name: promptName, arguments: promptArgs });
2890
3568
  const elapsed = Date.now() - startTime;
2891
3569
  if (result.messages.length === 0) {
2892
- console.log(pc2.dim(" No messages returned."));
3570
+ console.log(colors.dim(" No messages returned."));
2893
3571
  return;
2894
3572
  }
2895
3573
  console.log();
2896
3574
  printResultBlock({ label: "Prompt", labelColor: "blue", elapsed, detail: promptName });
2897
3575
  for (const msg of result.messages) {
2898
- const role = msg.role === "user" ? pc2.blue("user") : pc2.magenta("assistant");
3576
+ const role = msg.role === "user" ? colors.blue("user") : colors.magenta("assistant");
2899
3577
  const text = msg.content.text ?? JSON.stringify(msg.content);
2900
3578
  try {
2901
3579
  const parsed = JSON.parse(text);
2902
3580
  if (typeof parsed === "object" && parsed !== null) {
2903
- console.log(` ${pc2.bold(role)}:`);
3581
+ console.log(` ${colors.bold(role)}:`);
2904
3582
  console.log(formatJson(parsed, 4, true));
2905
3583
  } else {
2906
- console.log(` ${pc2.bold(role)}: ${pc2.yellow(text)}`);
3584
+ console.log(` ${colors.bold(role)}: ${colors.yellow(text)}`);
2907
3585
  }
2908
3586
  } catch {
2909
- console.log(` ${pc2.bold(role)}: ${pc2.yellow(text)}`);
3587
+ console.log(` ${colors.bold(role)}: ${colors.yellow(text)}`);
2910
3588
  }
2911
3589
  }
2912
3590
  console.log();
@@ -2916,36 +3594,36 @@ async function cmdPing(target) {
2916
3594
  try {
2917
3595
  const elapsed = await target.ping();
2918
3596
  console.log();
2919
- console.log(pc2.green(` \u2713 Pong! Round-trip: ${elapsed}ms`));
3597
+ console.log(colors.green(` \u2713 Pong! Round-trip: ${elapsed}ms`));
2920
3598
  console.log();
2921
3599
  } catch (err) {
2922
3600
  console.log();
2923
- console.error(pc2.red(` \u2717 Ping failed: ${err.message}`));
3601
+ console.error(colors.red(` \u2717 Ping failed: ${err.message}`));
2924
3602
  console.log();
2925
3603
  }
2926
3604
  }
2927
3605
  async function cmdLogLevel(target, rest) {
2928
3606
  const level = rest.trim().toLowerCase();
2929
3607
  if (!level) {
2930
- console.log(pc2.yellow(" Usage: log-level <level>"));
2931
- 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(", ")}`));
2932
3610
  return;
2933
3611
  }
2934
3612
  if (!LOG_LEVELS.includes(level)) {
2935
3613
  const suggestion = suggestCommand(level, [...LOG_LEVELS]);
2936
3614
  const hint = suggestion ? ` Did you mean "${suggestion}"?` : "";
2937
- console.log(pc2.red(` Unknown log level: "${level}".${hint}`));
2938
- 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(", ")}`));
2939
3617
  return;
2940
3618
  }
2941
3619
  try {
2942
3620
  await target.setLoggingLevel(level);
2943
- console.log(pc2.green(` \u2713 Logging level set to: ${level}`));
3621
+ console.log(colors.green(` \u2713 Logging level set to: ${level}`));
2944
3622
  } catch (err) {
2945
- 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}`));
2946
3624
  const caps = target.getServerCapabilities();
2947
3625
  if (!caps?.logging) {
2948
- console.log(pc2.dim(" The server does not advertise logging support."));
3626
+ console.log(colors.dim(" The server does not advertise logging support."));
2949
3627
  }
2950
3628
  }
2951
3629
  }
@@ -2953,28 +3631,28 @@ function cmdHistory(target, rest) {
2953
3631
  const arg = rest.trim();
2954
3632
  if (arg === "clear") {
2955
3633
  target.clearHistory();
2956
- console.log(pc2.dim(" History cleared."));
3634
+ console.log(colors.dim(" History cleared."));
2957
3635
  return;
2958
3636
  }
2959
3637
  const count = arg ? Number.parseInt(arg, 10) : 20;
2960
3638
  const records = target.getHistory(Number.isNaN(count) ? 20 : count);
2961
3639
  if (records.length === 0) {
2962
- console.log(pc2.dim(" No request history yet."));
3640
+ console.log(colors.dim(" No request history yet."));
2963
3641
  return;
2964
3642
  }
2965
- console.log(pc2.bold("\n Request History"));
2966
- console.log(pc2.dim(` ${"\u2500".repeat(70)}`));
3643
+ console.log(colors.bold("\n Request History"));
3644
+ console.log(colors.dim(` ${"\u2500".repeat(70)}`));
2967
3645
  for (const rec of records) {
2968
3646
  const time = new Date(rec.timestamp).toLocaleTimeString();
2969
3647
  const dur = `${rec.durationMs}ms`;
2970
- const hasError = rec.error ? pc2.red(" \u2717") : "";
3648
+ const hasError = rec.error ? colors.red(" \u2717") : "";
2971
3649
  console.log(
2972
- ` ${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}`
2973
3651
  );
2974
3652
  }
2975
3653
  const total = target.getHistory().length;
2976
3654
  console.log(
2977
- pc2.dim(`
3655
+ colors.dim(`
2978
3656
  Showing ${records.length} of ${total} total. Use "history clear" to reset.`)
2979
3657
  );
2980
3658
  console.log();
@@ -2983,26 +3661,26 @@ function cmdNotifications(target, rest) {
2983
3661
  const arg = rest.trim();
2984
3662
  if (arg === "clear") {
2985
3663
  target.clearNotifications();
2986
- console.log(pc2.dim(" Notifications cleared."));
3664
+ console.log(colors.dim(" Notifications cleared."));
2987
3665
  return;
2988
3666
  }
2989
3667
  const count = arg ? Number.parseInt(arg, 10) : 20;
2990
3668
  const records = target.getNotifications(Number.isNaN(count) ? 20 : count);
2991
3669
  if (records.length === 0) {
2992
- console.log(pc2.dim(" No notifications received yet."));
2993
- 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."));
2994
3672
  return;
2995
3673
  }
2996
- console.log(pc2.bold("\n Server Notifications"));
2997
- console.log(pc2.dim(` ${"\u2500".repeat(70)}`));
3674
+ console.log(colors.bold("\n Server Notifications"));
3675
+ console.log(colors.dim(` ${"\u2500".repeat(70)}`));
2998
3676
  for (const n of records) {
2999
3677
  const time = new Date(n.timestamp).toLocaleTimeString();
3000
- const params = n.params ? ` ${pc2.dim(JSON.stringify(n.params))}` : "";
3001
- 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}`);
3002
3680
  }
3003
3681
  const total = target.getNotifications().length;
3004
3682
  console.log(
3005
- pc2.dim(`
3683
+ colors.dim(`
3006
3684
  Showing ${records.length} of ${total} total. Use "notifications clear" to reset.`)
3007
3685
  );
3008
3686
  console.log();
@@ -3010,44 +3688,44 @@ function cmdNotifications(target, rest) {
3010
3688
  async function cmdResourcesSubscribe(target, rest) {
3011
3689
  const uri = rest.trim();
3012
3690
  if (!uri) {
3013
- console.log(pc2.yellow(" Usage: resources/subscribe <uri>"));
3691
+ console.log(colors.yellow(" Usage: resources/subscribe <uri>"));
3014
3692
  if (cachedResourceUris.length > 0) {
3015
- console.log(pc2.dim(` Available: ${cachedResourceUris.join(", ")}`));
3693
+ console.log(colors.dim(` Available: ${cachedResourceUris.join(", ")}`));
3016
3694
  }
3017
3695
  return;
3018
3696
  }
3019
3697
  try {
3020
3698
  await target.subscribeResource({ uri });
3021
- console.log(pc2.green(` \u2713 Subscribed to: ${uri}`));
3022
- 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."));
3023
3701
  } catch (err) {
3024
- console.error(pc2.red(` \u2717 Subscribe failed: ${err.message}`));
3702
+ console.error(colors.red(` \u2717 Subscribe failed: ${err.message}`));
3025
3703
  }
3026
3704
  }
3027
3705
  async function cmdResourcesUnsubscribe(target, rest) {
3028
3706
  const uri = rest.trim();
3029
3707
  if (!uri) {
3030
- console.log(pc2.yellow(" Usage: resources/unsubscribe <uri>"));
3708
+ console.log(colors.yellow(" Usage: resources/unsubscribe <uri>"));
3031
3709
  return;
3032
3710
  }
3033
3711
  try {
3034
3712
  await target.unsubscribeResource({ uri });
3035
- console.log(pc2.green(` \u2713 Unsubscribed from: ${uri}`));
3713
+ console.log(colors.green(` \u2713 Unsubscribed from: ${uri}`));
3036
3714
  } catch (err) {
3037
- console.error(pc2.red(` \u2717 Unsubscribe failed: ${err.message}`));
3715
+ console.error(colors.red(` \u2717 Unsubscribe failed: ${err.message}`));
3038
3716
  }
3039
3717
  }
3040
3718
  function cmdRootsList(target) {
3041
3719
  const roots = target.getRoots();
3042
3720
  if (roots.length === 0) {
3043
- console.log(pc2.dim(" No roots configured."));
3044
- 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."));
3045
3723
  return;
3046
3724
  }
3047
- console.log(pc2.bold("\n Client Roots"));
3725
+ console.log(colors.bold("\n Client Roots"));
3048
3726
  for (const r of roots) {
3049
3727
  const name = r.name ? ` (${r.name})` : "";
3050
- console.log(` ${pc2.green(r.uri)}${pc2.dim(name)}`);
3728
+ console.log(` ${colors.green(r.uri)}${colors.dim(name)}`);
3051
3729
  }
3052
3730
  console.log();
3053
3731
  }
@@ -3056,53 +3734,53 @@ async function cmdRootsAdd(target, rest) {
3056
3734
  const uri = parts[0];
3057
3735
  const name = parts.slice(1).join(" ") || void 0;
3058
3736
  if (!uri) {
3059
- console.log(pc2.yellow(" Usage: roots/add <uri> [name]"));
3060
- 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"'));
3061
3739
  return;
3062
3740
  }
3063
3741
  await target.addRoot({ uri, name });
3064
- console.log(pc2.green(` \u2713 Root added: ${uri}`));
3065
- 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."));
3066
3744
  }
3067
3745
  async function cmdRootsRemove(target, rest) {
3068
3746
  const uri = rest.trim();
3069
3747
  if (!uri) {
3070
- console.log(pc2.yellow(" Usage: roots/remove <uri>"));
3748
+ console.log(colors.yellow(" Usage: roots/remove <uri>"));
3071
3749
  const roots = target.getRoots();
3072
3750
  if (roots.length > 0) {
3073
- 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(", ")}`));
3074
3752
  }
3075
3753
  return;
3076
3754
  }
3077
3755
  const removed = await target.removeRoot(uri);
3078
3756
  if (removed) {
3079
- console.log(pc2.green(` \u2713 Root removed: ${uri}`));
3757
+ console.log(colors.green(` \u2713 Root removed: ${uri}`));
3080
3758
  } else {
3081
- console.log(pc2.yellow(` Root not found: ${uri}`));
3759
+ console.log(colors.yellow(` Root not found: ${uri}`));
3082
3760
  }
3083
3761
  }
3084
3762
  async function cmdReconnect(target) {
3085
- console.log(pc2.cyan("\u27F3 Disconnecting..."));
3763
+ console.log(colors.cyan("\u27F3 Disconnecting..."));
3086
3764
  await target.close();
3087
- await new Promise((resolve2) => setTimeout(resolve2, 200));
3088
- console.log(pc2.cyan("\u27F3 Reconnecting..."));
3765
+ await new Promise((resolve3) => setTimeout(resolve3, 200));
3766
+ console.log(colors.cyan("\u27F3 Reconnecting..."));
3089
3767
  const { command, args } = target.getStatus();
3090
- console.log(pc2.dim(` Command: ${command} ${args.join(" ")}`));
3768
+ console.log(colors.dim(` Command: ${command} ${args.join(" ")}`));
3091
3769
  try {
3092
3770
  await target.connect();
3093
3771
  const s = target.getStatus();
3094
- console.log(pc2.green(`\u2713 Reconnected (PID: ${s.pid})`));
3772
+ console.log(colors.green(`\u2713 Reconnected (PID: ${s.pid})`));
3095
3773
  const { tools } = await target.listTools();
3096
- console.log(pc2.cyan(` ${tools.length} tool(s) available.
3774
+ console.log(colors.cyan(` ${tools.length} tool(s) available.
3097
3775
  `));
3098
3776
  await refreshCaches(target);
3099
3777
  } catch (err) {
3100
- console.error(pc2.red(`\u2717 Failed to reconnect: ${err.message}`));
3778
+ console.error(colors.red(`\u2717 Failed to reconnect: ${err.message}`));
3101
3779
  }
3102
3780
  }
3103
3781
  function cmdTiming() {
3104
3782
  if (callHistory.length === 0) {
3105
- console.log(pc2.dim(" No tool calls recorded yet."));
3783
+ console.log(colors.dim(" No tool calls recorded yet."));
3106
3784
  return;
3107
3785
  }
3108
3786
  const groups = /* @__PURE__ */ new Map();
@@ -3112,8 +3790,8 @@ function cmdTiming() {
3112
3790
  groups.set(record.toolName, list);
3113
3791
  }
3114
3792
  const nameWidth = Math.max(8, ...[...groups.keys()].map((n) => n.length));
3115
- console.log(pc2.bold("\n Tool Call Performance"));
3116
- 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)}`));
3117
3795
  let totalCalls = 0;
3118
3796
  let totalMs = 0;
3119
3797
  let slowestName = "";
@@ -3121,163 +3799,59 @@ function cmdTiming() {
3121
3799
  for (const [name, durations] of groups) {
3122
3800
  const count = durations.length;
3123
3801
  totalCalls += count;
3124
- const sorted = [...durations].sort((a, b) => a - b);
3125
- const avg = Math.round(sorted.reduce((a, b) => a + b, 0) / count);
3126
- const p95 = sorted[Math.floor(count * 0.95)];
3127
- const max = sorted[sorted.length - 1];
3128
- totalMs += sorted.reduce((a, b) => a + b, 0);
3129
- if (max > slowestMs) {
3130
- slowestMs = max;
3131
- slowestName = name;
3132
- }
3133
- console.log(
3134
- ` ${pc2.green(name.padEnd(nameWidth))} \xD7 ${count} avg: ${avg}ms p95: ${p95}ms max: ${max}ms`
3135
- );
3136
- }
3137
- const overallAvg = Math.round(totalMs / totalCalls);
3138
- console.log(
3139
- pc2.dim(
3140
- `
3141
- Total: ${totalCalls} call(s), avg ${overallAvg}ms, slowest: ${slowestName} (${slowestMs}ms)`
3142
- )
3143
- );
3144
- console.log();
3145
- }
3146
- function cmdStatus(target) {
3147
- const s = target.getStatus();
3148
- const uptimeStr = s.uptime >= 60 ? `${Math.floor(s.uptime / 60)}m ${(s.uptime % 60).toFixed(0)}s` : `${s.uptime.toFixed(1)}s`;
3149
- const lastRespStr = s.lastResponseTime ? `${((Date.now() - s.lastResponseTime) / 1e3).toFixed(1)}s ago` : "never";
3150
- console.log(pc2.bold("\n Target Server Status"));
3151
- console.log(` ${pc2.dim("Connected:")} ${s.connected ? pc2.green("yes") : pc2.red("no")}`);
3152
- console.log(` ${pc2.dim("PID:")} ${s.pid ?? "N/A"}`);
3153
- console.log(` ${pc2.dim("Uptime:")} ${uptimeStr}`);
3154
- console.log(` ${pc2.dim("Last response:")} ${lastRespStr}`);
3155
- console.log(` ${pc2.dim("Stderr lines:")} ${s.stderrLineCount.toLocaleString()}`);
3156
- console.log(` ${pc2.dim("Reconnects:")} ${s.reconnectAttempts}/${s.maxReconnectAttempts}`);
3157
- console.log(` ${pc2.dim("Command:")} ${s.command} ${s.args.join(" ")}`);
3158
- console.log();
3159
- }
3160
- function printResultBlock(opts) {
3161
- const colorFn = pc2[opts.labelColor];
3162
- const elapsedStr = opts.elapsed < 1e3 ? `${opts.elapsed}ms` : `${(opts.elapsed / 1e3).toFixed(1)}s`;
3163
- const detail = opts.detail ?? opts.toolName ?? "";
3164
- console.log(` ${colorFn(opts.label)} ${pc2.dim(detail)} ${pc2.dim(`(${elapsedStr})`)}`);
3165
- console.log(pc2.dim(` ${"\u2500".repeat(60)}`));
3166
- }
3167
- function printShortHelp() {
3168
- const hasTools = !!activeCapabilities?.tools;
3169
- const hasResources = !!activeCapabilities?.resources;
3170
- const hasPrompts = !!activeCapabilities?.prompts;
3171
- const tC = hasTools ? pc2.green : pc2.dim;
3172
- const rC = hasResources ? pc2.green : pc2.dim;
3173
- const pC = hasPrompts ? pc2.green : pc2.dim;
3174
- console.log(`
3175
- ${pc2.bold("Quick Reference:")}
3176
-
3177
- ${tC("tl")} ${tC("tools/list")} ${rC("rl")} ${rC("resources/list")} ${pC("pl")} ${pC("prompts/list")}
3178
- ${tC("td")} ${tC("tools/describe")} ${rC("rr")} ${rC("resources/read")} ${pC("pg")} ${pC("prompts/get")}
3179
- ${tC("tc")} ${tC("tools/call")} ${rC("rt")} ${rC("resources/templates")}
3180
- ${tC("ts")} ${tC("tools/scaffold")} ${rC("rs")} ${rC("resources/subscribe")}
3181
- ${rC("ru")} ${rC("resources/unsubscribe")}
3182
-
3183
- ${pc2.green("ping")} ${pc2.green("status")} ${pc2.green("timing")} ${pc2.green("history")} ${pc2.green("!!")} ${pc2.green("explore")} ${pc2.green("reconnect")}
3184
-
3185
- ${pc2.dim("Type 'help' for full command reference.")}
3186
- `);
3187
- }
3188
- function printHelp() {
3189
- const hasTools = !!activeCapabilities?.tools;
3190
- const hasResources = !!activeCapabilities?.resources;
3191
- const hasPrompts = !!activeCapabilities?.prompts;
3192
- const hasLogging = !!activeCapabilities?.logging;
3193
- const tC = hasTools ? pc2.green : pc2.dim;
3194
- const rC = hasResources ? pc2.green : pc2.dim;
3195
- const pC = hasPrompts ? pc2.green : pc2.dim;
3196
- const lC = hasLogging ? pc2.green : pc2.dim;
3197
- const tD = hasTools ? (s) => s : pc2.dim;
3198
- const rD = hasResources ? (s) => s : pc2.dim;
3199
- const pD = hasPrompts ? (s) => s : pc2.dim;
3200
- const lD = hasLogging ? (s) => s : pc2.dim;
3201
- const tH = hasTools ? pc2.bold("Tool Commands:") : pc2.dim(pc2.bold("Tool Commands:")) + pc2.dim(" (Unsupported)");
3202
- const rH = hasResources ? pc2.bold("Resource Commands:") : pc2.dim(pc2.bold("Resource Commands:")) + pc2.dim(" (Unsupported)");
3203
- const pH = hasPrompts ? pc2.bold("Prompt Commands:") : pc2.dim(pc2.bold("Prompt Commands:")) + pc2.dim(" (Unsupported)");
3204
- console.log(`
3205
- ${tH}
3206
-
3207
- ${tC("tools/list")} ${tD("List all available tools")}
3208
- ${tC("tools/describe")} <name> ${tD("Show a tool's input schema")}
3209
- ${tC("tools/call")} <name> [json] [opts] ${tD("Call a tool (interactive if no json)")}
3210
- ${tD("Options:")} ${pc2.dim("--timeout <ms>")} ${tD("Override default timeout (60s)")}
3211
- ${pc2.dim("--clear")} ${tD("Ignore remembered argument defaults")}
3212
- ${tC("tools/scaffold")} <name> ${tD("Generate a template for a tool's arguments")}
3213
- ${tC("tools/forget")} [name] ${tD("Clear remembered interactive defaults")}
3214
-
3215
- ${rH}
3216
-
3217
- ${rC("resources/list")} ${rD("List all available resources")}
3218
- ${rC("resources/read")} <uri> ${rD("Read a resource by URI")}
3219
- ${rC("resources/templates")} ${rD("List resource templates")}
3220
- ${rC("resources/subscribe")} <uri> ${rD("Subscribe to resource changes")}
3221
- ${rC("resources/unsubscribe")} <uri> ${rD("Unsubscribe from resource changes")}
3222
-
3223
- ${pH}
3224
-
3225
- ${pC("prompts/list")} ${pD("List all available prompts")}
3226
- ${pC("prompts/get")} <name> [json_args] ${pD("Get a prompt with arguments")}
3227
-
3228
- ${pc2.bold("Protocol Commands:")}
3229
-
3230
- ${pc2.green("ping")} Verify connection, show round-trip time
3231
- ${lC("log-level")} <level> ${lD("Set server logging verbosity")}${hasLogging ? "" : pc2.dim(" (Unsupported)")}
3232
- ${pc2.green("history")} [count|clear] Show request/response history
3233
- ${pc2.green("notifications")} [count|clear] Show server notifications
3234
-
3235
- ${pc2.bold("Roots Management:")}
3236
-
3237
- ${pc2.green("roots/list")} Show configured client roots
3238
- ${pc2.green("roots/add")} <uri> [name] Add a root directory
3239
- ${pc2.green("roots/remove")} <uri> Remove a root directory
3240
-
3241
- ${pc2.bold("Session Commands:")}
3242
-
3243
- ${pc2.green("!!")} / ${pc2.green("last")} Re-run the last command
3244
- ${pc2.green("reconnect")} Disconnect and reconnect to the server
3245
- ${pc2.green("timing")} Show tool call performance stats
3246
- ${pc2.green("status")} Show target server status
3247
- ${pc2.green("help")} Show this help
3248
- ${pc2.green("exit")} / ${pc2.green("quit")} Disconnect and exit
3249
-
3250
- ${pc2.bold("Shortcuts:")}
3251
-
3252
- ${tC("tl")} ${tC("tools/list")} ${rC("rl")} ${rC("resources/list")} ${pC("pl")} ${pC("prompts/list")}
3253
- ${tC("td")} ${tC("tools/describe")} ${rC("rr")} ${rC("resources/read")} ${pC("pg")} ${pC("prompts/get")}
3254
- ${tC("tc")} ${tC("tools/call")} ${rC("rt")} ${rC("resources/templates")}
3255
- ${tC("ts")} ${tC("tools/scaffold")} ${rC("rs")} ${rC("resources/subscribe")}
3256
- ${rC("ru")} ${rC("resources/unsubscribe")}
3257
-
3258
- ${pc2.dim("Lines starting with # are treated as comments.")}
3259
- ${pc2.dim('JSON arguments can contain spaces: tools/call say {"message": "hello world"}')}
3260
- ${pc2.dim("Run tools/call <name> without JSON for interactive argument prompting.")}
3261
- ${pc2.dim("Use tools/call <name> --clear to ignore remembered defaults.")}
3262
- `);
3802
+ const sorted = [...durations].sort((a, b) => a - b);
3803
+ const avg = Math.round(sorted.reduce((a, b) => a + b, 0) / count);
3804
+ const p95 = sorted[Math.floor(count * 0.95)];
3805
+ const max = sorted[sorted.length - 1];
3806
+ totalMs += sorted.reduce((a, b) => a + b, 0);
3807
+ if (max > slowestMs) {
3808
+ slowestMs = max;
3809
+ slowestName = name;
3810
+ }
3811
+ console.log(
3812
+ ` ${colors.green(name.padEnd(nameWidth))} \xD7 ${count} avg: ${avg}ms p95: ${p95}ms max: ${max}ms`
3813
+ );
3814
+ }
3815
+ const overallAvg = Math.round(totalMs / totalCalls);
3816
+ console.log(
3817
+ colors.dim(
3818
+ `
3819
+ Total: ${totalCalls} call(s), avg ${overallAvg}ms, slowest: ${slowestName} (${slowestMs}ms)`
3820
+ )
3821
+ );
3822
+ console.log();
3263
3823
  }
3264
- async function readScriptLines(filepath) {
3265
- const content = await readFile2(filepath, "utf-8");
3266
- return content.split("\n");
3824
+ function cmdStatus(target) {
3825
+ const s = target.getStatus();
3826
+ const uptimeStr = s.uptime >= 60 ? `${Math.floor(s.uptime / 60)}m ${(s.uptime % 60).toFixed(0)}s` : `${s.uptime.toFixed(1)}s`;
3827
+ const lastRespStr = s.lastResponseTime ? `${((Date.now() - s.lastResponseTime) / 1e3).toFixed(1)}s ago` : "never";
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(" ")}`);
3836
+ console.log();
3267
3837
  }
3268
3838
  function cmdToolsForget(rest) {
3269
3839
  const toolName = rest.trim();
3270
3840
  if (toolName) {
3271
3841
  if (lastToolArgsMap.has(toolName)) {
3272
3842
  lastToolArgsMap.delete(toolName);
3273
- console.log(pc2.green(` Cleared remembered args for ${pc2.bold(toolName)}.`));
3843
+ saveWizardDefaults().catch(() => {
3844
+ });
3845
+ console.log(colors.green(` Cleared remembered args for ${colors.bold(toolName)}.`));
3274
3846
  } else {
3275
- console.log(pc2.yellow(` No remembered args for "${toolName}".`));
3847
+ console.log(colors.yellow(` No remembered args for "${toolName}".`));
3276
3848
  }
3277
3849
  } else {
3278
3850
  const count = lastToolArgsMap.size;
3279
3851
  lastToolArgsMap.clear();
3280
- console.log(pc2.green(` Cleared remembered args for ${count} tool${count === 1 ? "" : "s"}.`));
3852
+ saveWizardDefaults().catch(() => {
3853
+ });
3854
+ console.log(colors.green(` Cleared remembered args for ${count} tool${count === 1 ? "" : "s"}.`));
3281
3855
  }
3282
3856
  }
3283
3857
  async function pickInteractive(items, message) {
@@ -3377,55 +3951,416 @@ async function showMainMenu(target, interceptor) {
3377
3951
  );
3378
3952
  }
3379
3953
  });
3380
- if (answer.type === "tool") {
3381
- await cmdToolsCall(target, interceptor, answer.name);
3382
- } else if (answer.type === "resource") {
3383
- await cmdResourcesRead(target, answer.uri);
3384
- } else if (answer.type === "prompt") {
3385
- await cmdPromptsGet(target, answer.name);
3386
- } else if (answer.type === "command") {
3387
- if (answer.name === "status") {
3388
- cmdStatus(target);
3389
- } else if (answer.name === "reconnect") {
3390
- await cmdReconnect(target);
3391
- } else if (answer.name === "exit") {
3392
- console.log(pc2.dim("\nShutting down..."));
3393
- await target.close();
3394
- process.exit(0);
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;
4257
+ try {
4258
+ const { tools } = await target.listTools();
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 {
3395
4266
  }
3396
- } else if (answer.type === "raw") {
3397
- return true;
3398
4267
  }
3399
- return false;
3400
- } catch (err) {
3401
- if (isAbortError(err)) {
3402
- console.log(pc2.dim("\nShutting down..."));
3403
- await target.close();
3404
- process.exit(0);
4268
+ if (caps.prompts) {
4269
+ try {
4270
+ const { prompts } = await target.listPrompts();
4271
+ promptCount = prompts.length;
4272
+ } catch {
4273
+ }
3405
4274
  }
3406
- throw err;
3407
- }
3408
- }
3409
- async function mainMenuLoop(target, interceptor) {
3410
- if (isScriptMode || !process.stdin.isTTY) {
3411
- startReadlineLoop(target, interceptor);
3412
- return;
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)
4283
+ );
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();
4289
+ }
4290
+ }
4291
+ } catch (err) {
4292
+ console.log(colors.yellow(` Warning: Could not list tools: ${err.message}
4293
+ `));
3413
4294
  }
3414
- while (true) {
3415
- try {
3416
- const dropToRaw = await showMainMenu(target, interceptor);
3417
- if (dropToRaw) {
3418
- console.log(pc2.dim(" Entering raw command mode. Type 'menu' to return."));
3419
- startReadlineLoop(target, interceptor);
3420
- break;
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;
3421
4307
  }
3422
- } catch (err) {
3423
- console.error(pc2.red(`Error in menu: ${err.message}`));
3424
- startReadlineLoop(target, interceptor);
3425
- break;
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;
3426
4352
  }
4353
+ console.log(colors.dim("\nShutting down..."));
4354
+ await target.close();
4355
+ process.exit(0);
4356
+ } else {
4357
+ mainMenuLoop(target, interceptor);
3427
4358
  }
3428
4359
  }
4360
+ async function readScriptLines(filepath) {
4361
+ const content = await readFile4(filepath, "utf8");
4362
+ return content.split("\n");
4363
+ }
3429
4364
 
3430
4365
  // src/server.ts
3431
4366
  import { createHash } from "crypto";
@@ -3490,7 +4425,7 @@ async function startServer(opts) {
3490
4425
  mediaThresholdKb: opts.mediaThresholdKb
3491
4426
  });
3492
4427
  const mcpServer = new McpServer(
3493
- { name: "run-mcp", version: "1.6.3" },
4428
+ { name: "run-mcp", version: "1.7.0" },
3494
4429
  {
3495
4430
  capabilities: {
3496
4431
  tools: {},
@@ -3651,7 +4586,15 @@ async function startServer(opts) {
3651
4586
  process.env[key] = value;
3652
4587
  }
3653
4588
  }
3654
- 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
+ });
3655
4598
  setupTargetListeners(target);
3656
4599
  try {
3657
4600
  await target.connect();
@@ -3755,10 +4698,11 @@ async function startServer(opts) {
3755
4698
  ),
3756
4699
  summary: z.boolean().optional().describe(
3757
4700
  "If true, returns only the name and description of each primitive (omitting full schemas) when included to save tokens."
3758
- )
4701
+ ),
4702
+ sandbox: z.enum(["auto", "docker", "native", "audit", "none"]).optional().describe("Sandbox mode to use for this server")
3759
4703
  }
3760
4704
  },
3761
- async ({ command, args, env, include, summary }) => {
4705
+ async ({ command, args, env, include, summary, sandbox }) => {
3762
4706
  if (target?.connected) {
3763
4707
  return {
3764
4708
  content: [
@@ -3780,7 +4724,15 @@ async function startServer(opts) {
3780
4724
  process.env[key] = value;
3781
4725
  }
3782
4726
  }
3783
- 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
+ });
3784
4736
  setupTargetListeners(target);
3785
4737
  try {
3786
4738
  await target.connect();
@@ -4115,6 +5067,7 @@ Available: ${available}`
4115
5067
  type: z.enum(["tool", "resource", "prompt"]).describe("The MCP primitive type to invoke"),
4116
5068
  name: z.string().describe("Tool name, resource URI, or prompt name"),
4117
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')"),
4118
5071
  // Auto-connect params (only needed if not already connected)
4119
5072
  auto_connect: z.object({
4120
5073
  command: z.string().describe("Command to spawn the server (e.g. 'node')."),
@@ -4138,12 +5091,14 @@ Available: ${available}`
4138
5091
  type: primitiveType,
4139
5092
  name,
4140
5093
  arguments: callArgs,
5094
+ args: callArgsAlias,
4141
5095
  auto_connect,
4142
5096
  disconnect_after,
4143
5097
  timeout_ms,
4144
5098
  include_metadata,
4145
5099
  max_text_length
4146
5100
  }) => {
5101
+ const finalArgs = callArgs ?? callArgsAlias;
4147
5102
  try {
4148
5103
  const connectError = await ensureConnected(
4149
5104
  auto_connect?.command,
@@ -4193,14 +5148,14 @@ Available tools: ${toolNames.join(", ")}`
4193
5148
  }
4194
5149
  const schema = matchedTool.inputSchema;
4195
5150
  const requiredProps = schema?.required ?? [];
4196
- const providedKeys = Object.keys(callArgs ?? {});
5151
+ const providedKeys = Object.keys(finalArgs ?? {});
4197
5152
  const missingProps = requiredProps.filter((p) => !providedKeys.includes(p));
4198
5153
  if (missingProps.length > 0) {
4199
5154
  return {
4200
5155
  content: [
4201
5156
  {
4202
5157
  type: "text",
4203
- text: `Tool "${name}" requires: ${missingProps.join(", ")}. Received: ${JSON.stringify(callArgs ?? {})}`
5158
+ text: `Tool "${name}" requires: ${missingProps.join(", ")}. Received: ${JSON.stringify(finalArgs ?? {})}`
4204
5159
  }
4205
5160
  ],
4206
5161
  isError: true
@@ -4214,7 +5169,7 @@ Available tools: ${toolNames.join(", ")}`
4214
5169
  const { result: toolResult, metadata } = await interceptor.callToolWithMetadata(
4215
5170
  target,
4216
5171
  name,
4217
- callArgs ?? {},
5172
+ finalArgs ?? {},
4218
5173
  timeout_ms,
4219
5174
  max_text_length
4220
5175
  );
@@ -4224,7 +5179,7 @@ Available tools: ${toolNames.join(", ")}`
4224
5179
  result = await interceptor.callTool(
4225
5180
  target,
4226
5181
  name,
4227
- callArgs ?? {},
5182
+ finalArgs ?? {},
4228
5183
  timeout_ms,
4229
5184
  max_text_length
4230
5185
  );
@@ -4248,6 +5203,7 @@ Available tools: ${toolNames.join(", ")}`
4248
5203
  text: `--- metadata ---
4249
5204
  ${JSON.stringify(meta)}`
4250
5205
  });
5206
+ result.meta = meta;
4251
5207
  }
4252
5208
  break;
4253
5209
  }
@@ -4279,6 +5235,7 @@ ${JSON.stringify(meta)}`
4279
5235
  text: `--- metadata ---
4280
5236
  ${JSON.stringify(meta)}`
4281
5237
  });
5238
+ result.meta = meta;
4282
5239
  }
4283
5240
  break;
4284
5241
  }
@@ -4308,7 +5265,7 @@ Available prompts: ${promptNames.join(", ")}`
4308
5265
  target,
4309
5266
  {
4310
5267
  name,
4311
- arguments: callArgs ?? {}
5268
+ arguments: finalArgs ?? {}
4312
5269
  },
4313
5270
  timeout_ms,
4314
5271
  max_text_length
@@ -4347,6 +5304,7 @@ ${item.text}` });
4347
5304
  text: `--- metadata ---
4348
5305
  ${JSON.stringify(meta)}`
4349
5306
  });
5307
+ result.meta = meta;
4350
5308
  }
4351
5309
  break;
4352
5310
  }
@@ -4396,6 +5354,230 @@ ${JSON.stringify(meta)}`
4396
5354
  };
4397
5355
  }
4398
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
+ );
4399
5581
  const transport = new StdioServerTransport();
4400
5582
  mcpServer.server.onclose = async () => {
4401
5583
  if (target) {
@@ -4410,7 +5592,8 @@ ${JSON.stringify(meta)}`
4410
5592
 
4411
5593
  // src/index.ts
4412
5594
  function requireTargetCommand(targetCommand, subcommandUsage) {
4413
- if (!activeTargetCommand) {
5595
+ const target = activeTargetCommand ?? targetCommand;
5596
+ if (!target || target.length === 0) {
4414
5597
  process.stderr.write(`Error: Target server command must be separated by '--'.
4415
5598
  `);
4416
5599
  process.stderr.write(`This avoids option parsing conflicts.
@@ -4418,19 +5601,19 @@ function requireTargetCommand(targetCommand, subcommandUsage) {
4418
5601
  `);
4419
5602
  process.stderr.write(`Usage: ${subcommandUsage}
4420
5603
  `);
4421
- process.exit(2);
5604
+ process.exit(64);
4422
5605
  }
4423
- return activeTargetCommand;
5606
+ return target;
4424
5607
  }
4425
- var SESSION_DIR = join2(tmpdir2(), "run-mcp", "sessions");
5608
+ var SESSION_DIR = join6(tmpdir3(), "run-mcp", "sessions");
4426
5609
  function getSessionPath(name) {
4427
- return join2(SESSION_DIR, `${name}.json`);
5610
+ return join6(SESSION_DIR, `${name}.json`);
4428
5611
  }
4429
5612
  async function getSession(name) {
4430
5613
  const path2 = getSessionPath(name);
4431
- if (!existsSync2(path2)) return null;
5614
+ if (!existsSync6(path2)) return null;
4432
5615
  try {
4433
- const data = await readFile3(path2, "utf8");
5616
+ const data = await readFile5(path2, "utf8");
4434
5617
  const parsed = JSON.parse(data);
4435
5618
  try {
4436
5619
  process.kill(parsed.pid, 0);
@@ -4445,7 +5628,7 @@ async function getSession(name) {
4445
5628
  }
4446
5629
  }
4447
5630
  function sendDaemonRequest(port, request) {
4448
- return new Promise((resolve2, reject) => {
5631
+ return new Promise((resolve3, reject) => {
4449
5632
  const socket = createConnection({ port });
4450
5633
  let buffer = "";
4451
5634
  socket.on("connect", () => {
@@ -4460,7 +5643,7 @@ function sendDaemonRequest(port, request) {
4460
5643
  if (parsed.error) {
4461
5644
  reject(new Error(parsed.error.message));
4462
5645
  } else {
4463
- resolve2(parsed.result);
5646
+ resolve3(parsed.result);
4464
5647
  }
4465
5648
  } catch (err) {
4466
5649
  reject(new Error(`Failed to parse daemon response: ${err}`));
@@ -4482,11 +5665,16 @@ async function handleHeadlessSession(sessionName, targetCommand, operation, opts
4482
5665
  `);
4483
5666
  process.stderr.write(`Usage: ${subcommandUsage}
4484
5667
  `);
4485
- process.exit(2);
5668
+ process.exit(64);
4486
5669
  }
4487
5670
  const target = activeTargetCommand;
4488
- const binPath = resolve(import.meta.dirname, "./index.js");
4489
- 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], {
4490
5678
  detached: true,
4491
5679
  stdio: "ignore"
4492
5680
  });
@@ -4495,7 +5683,7 @@ async function handleHeadlessSession(sessionName, targetCommand, operation, opts
4495
5683
  while (attempts < 50) {
4496
5684
  session = await getSession(sessionName);
4497
5685
  if (session) break;
4498
- await new Promise((resolve2) => setTimeout(resolve2, 100));
5686
+ await new Promise((resolve3) => setTimeout(resolve3, 100));
4499
5687
  attempts++;
4500
5688
  }
4501
5689
  if (!session) {
@@ -4528,7 +5716,14 @@ function parseHeadlessOpts(opts) {
4528
5716
  timeoutMs: opts.timeout ? Number.parseInt(opts.timeout, 10) : void 0,
4529
5717
  raw: opts.raw,
4530
5718
  showStderr: opts.showStderr,
4531
- 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
4532
5727
  };
4533
5728
  }
4534
5729
  var activeTargetCommand;
@@ -4542,7 +5737,7 @@ program.enablePositionalOptions();
4542
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(
4543
5738
  "-m, --media-threshold <kb>",
4544
5739
  "Media size threshold in KB to save to disk (0 to always save, -1 to keep inline)"
4545
- ).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(
4546
5741
  async (tool, jsonArgs, targetCommand, opts) => {
4547
5742
  const operation = { type: "call", tool, args: jsonArgs };
4548
5743
  const parsedOpts = parseHeadlessOpts(opts);
@@ -4563,7 +5758,7 @@ program.command("call").argument("<tool>", "Tool name to call").argument("[json_
4563
5758
  }
4564
5759
  }
4565
5760
  );
4566
- 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) => {
4567
5762
  const operation = { type: "list-tools" };
4568
5763
  const parsedOpts = parseHeadlessOpts(opts);
4569
5764
  if (opts.session) {
@@ -4582,7 +5777,7 @@ program.command("list-tools").argument("[target_command...]", "Target server com
4582
5777
  await runHeadless(target, operation, parsedOpts);
4583
5778
  }
4584
5779
  });
4585
- 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) => {
4586
5781
  const operation = { type: "list-resources" };
4587
5782
  const parsedOpts = parseHeadlessOpts(opts);
4588
5783
  if (opts.session) {
@@ -4601,7 +5796,7 @@ program.command("list-resources").argument("[target_command...]", "Target server
4601
5796
  await runHeadless(target, operation, parsedOpts);
4602
5797
  }
4603
5798
  });
4604
- 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) => {
4605
5800
  const operation = { type: "list-prompts" };
4606
5801
  const parsedOpts = parseHeadlessOpts(opts);
4607
5802
  if (opts.session) {
@@ -4623,7 +5818,7 @@ program.command("list-prompts").argument("[target_command...]", "Target server c
4623
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(
4624
5819
  "-m, --media-threshold <kb>",
4625
5820
  "Media size threshold in KB to save to disk (0 to always save, -1 to keep inline)"
4626
- ).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) => {
4627
5822
  const operation = { type: "read", uri };
4628
5823
  const parsedOpts = parseHeadlessOpts(opts);
4629
5824
  if (opts.session) {
@@ -4642,7 +5837,7 @@ program.command("read").argument("<uri>", "Resource URI to read").argument("[tar
4642
5837
  await runHeadless(target, operation, parsedOpts);
4643
5838
  }
4644
5839
  });
4645
- 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) => {
4646
5841
  const operation = { type: "describe", tool };
4647
5842
  const parsedOpts = parseHeadlessOpts(opts);
4648
5843
  if (opts.session) {
@@ -4664,7 +5859,7 @@ program.command("describe").argument("<tool>", "Tool name to describe").argument
4664
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(
4665
5860
  "-m, --media-threshold <kb>",
4666
5861
  "Media size threshold in KB to save to disk (0 to always save, -1 to keep inline)"
4667
- ).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(
4668
5863
  async (name, jsonArgs, targetCommand, opts) => {
4669
5864
  const operation = { type: "get-prompt", name, args: jsonArgs };
4670
5865
  const parsedOpts = parseHeadlessOpts(opts);
@@ -4685,17 +5880,19 @@ program.command("get-prompt").argument("<name>", "Prompt name").argument("[json_
4685
5880
  }
4686
5881
  }
4687
5882
  );
4688
- 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) => {
4689
5884
  const targetCmd = activeTargetCommand ?? targetCommand;
4690
5885
  if (!targetCmd || targetCmd.length === 0) {
4691
5886
  process.stderr.write("Error: No target command provided for daemon.\n");
4692
- process.exit(1);
5887
+ process.exit(64);
4693
5888
  }
4694
5889
  const server = createServer();
4695
5890
  server.listen(0, "127.0.0.1", async () => {
4696
5891
  const addr = server.address();
4697
5892
  const port = addr.port;
4698
- const target = new TargetManager(targetCmd[0], targetCmd.slice(1));
5893
+ const target = new TargetManager(targetCmd[0], targetCmd.slice(1), {
5894
+ sandbox: opts.sandbox
5895
+ });
4699
5896
  const interceptor = new ResponseInterceptor();
4700
5897
  try {
4701
5898
  await target.connect();
@@ -4704,8 +5901,8 @@ program.command("daemon").argument("<session_name>", "Session name").argument("[
4704
5901
  `);
4705
5902
  process.exit(1);
4706
5903
  }
4707
- await mkdir2(SESSION_DIR, { recursive: true });
4708
- await writeFile2(
5904
+ await mkdir3(SESSION_DIR, { recursive: true });
5905
+ await writeFile3(
4709
5906
  getSessionPath(sessionName),
4710
5907
  JSON.stringify({ port, pid: process.pid }),
4711
5908
  "utf8"
@@ -4722,12 +5919,12 @@ program.command("daemon").argument("<session_name>", "Session name").argument("[
4722
5919
  try {
4723
5920
  const req = JSON.parse(trimmed);
4724
5921
  if (req.method === "execute") {
4725
- const { operation, opts } = req.params;
5922
+ const { operation, opts: opts2 } = req.params;
4726
5923
  const { result, hasError } = await executeOperation(
4727
5924
  target,
4728
5925
  interceptor,
4729
5926
  operation,
4730
- opts
5927
+ opts2
4731
5928
  );
4732
5929
  socket.write(
4733
5930
  JSON.stringify({ jsonrpc: "2.0", result: { result, hasError }, id: req.id }) + "\n"
@@ -4778,7 +5975,7 @@ program.command("close-session").argument("<session_name>", "Session name").desc
4778
5975
  }
4779
5976
  }
4780
5977
  });
4781
- program.name("run-mcp").description("A smart interactive REPL and live test harness for MCP servers").version("1.6.3").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(
4782
5979
  "[target_command...]",
4783
5980
  "Command to spawn the target MCP server (starts REPL if provided, Agent server otherwise)"
4784
5981
  ).option("-o, --out-dir <path>", "Directory to save intercepted images and audio").option(
@@ -4790,7 +5987,10 @@ program.name("run-mcp").description("A smart interactive REPL and live test harn
4790
5987
  ).option(
4791
5988
  "-m, --media-threshold <kb>",
4792
5989
  "Media size threshold in KB to save to disk (0 to always save, -1 to keep inline)"
4793
- ).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(
4794
5994
  "after",
4795
5995
  `
4796
5996
  Examples:
@@ -4827,6 +6027,8 @@ Agent Mode Tools:
4827
6027
  disconnect_from_mcp \u2192 Tear down and reconnect after changes
4828
6028
  mcp_server_status \u2192 Check connection status
4829
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
4830
6032
 
4831
6033
  REPL Mode Commands (once connected):
4832
6034
  tools/list List all available tools
@@ -4855,18 +6057,20 @@ REPL Mode Commands (once connected):
4855
6057
  Shortcuts: tl td tc ts rl rr rt rs ru pl pg (see help for details)`
4856
6058
  ).action(
4857
6059
  async (targetCommand, opts) => {
4858
- if (targetCommand && targetCommand.length > 0 && !activeTargetCommand) {
4859
- process.stderr.write(
4860
- "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"
4861
- );
4862
- process.exit(1);
4863
- }
4864
- const target = activeTargetCommand ?? [];
6060
+ const target = activeTargetCommand ?? targetCommand ?? [];
4865
6061
  if (target && target.length > 0) {
4866
6062
  await startRepl(target, {
4867
6063
  script: opts.script,
4868
6064
  outDir: opts.outDir,
4869
- 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
4870
6074
  });
4871
6075
  } else {
4872
6076
  if (opts.mcp || !process.stdin.isTTY) {
@@ -4874,7 +6078,14 @@ Shortcuts: tl td tc ts rl rr rt rs ru pl pg (see help for details)`
4874
6078
  outDir: opts.outDir,
4875
6079
  timeoutMs: opts.timeout ? Number.parseInt(opts.timeout, 10) : void 0,
4876
6080
  maxTextLength: opts.maxText ? Number.parseInt(opts.maxText, 10) : void 0,
4877
- 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
4878
6089
  });
4879
6090
  } else {
4880
6091
  const selected = await pickDiscoveredServer();
@@ -4888,10 +6099,26 @@ Shortcuts: tl td tc ts rl rr rt rs ru pl pg (see help for details)`
4888
6099
  await startRepl([selected.config.command, ...selected.config.args || []], {
4889
6100
  script: opts.script,
4890
6101
  outDir: opts.outDir,
4891
- 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
4892
6111
  });
4893
6112
  }
4894
6113
  }
4895
6114
  }
4896
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
+ }
4897
6124
  program.parse(argvToParse);