run-mcp 1.6.3 → 1.7.1

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 +157 -38
  2. package/dist/index.js +2136 -859
  3. package/package.json +10 -4
package/dist/index.js CHANGED
@@ -3,15 +3,15 @@
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
13
13
  import { existsSync } from "fs";
14
- import { readFile } from "fs/promises";
14
+ import { readFile, readdir } from "fs/promises";
15
15
  import { homedir } from "os";
16
16
  import path from "path";
17
17
  import process2 from "process";
@@ -57,10 +57,14 @@ function getConfigPaths() {
57
57
  { source: "Gemini CLI (Project)", file: path.join(cwd, ".gemini", "settings.json") },
58
58
  { source: "Claude Code (Global)", file: path.join(home, ".claude.json") },
59
59
  { source: "Claude Code (Project)", file: path.join(cwd, ".mcp.json") },
60
- { source: "Antigravity", file: path.join(home, ".gemini", "antigravity", "mcp_config.json") }
60
+ { source: "Antigravity", file: path.join(home, ".gemini", "antigravity", "mcp_config.json") },
61
+ {
62
+ source: "Gemini App (Global)",
63
+ file: path.join(home, ".gemini", "config", "mcp_config.json")
64
+ }
61
65
  ];
62
66
  }
63
- async function discoverServers() {
67
+ async function discoverServers(options) {
64
68
  const servers = [];
65
69
  const paths = getConfigPaths();
66
70
  for (const { source, file } of paths) {
@@ -86,10 +90,52 @@ async function discoverServers() {
86
90
  } catch {
87
91
  }
88
92
  }
93
+ if (options?.scan) {
94
+ try {
95
+ let currentDir = process2.cwd();
96
+ const visited = /* @__PURE__ */ new Set();
97
+ while (currentDir && !visited.has(currentDir)) {
98
+ visited.add(currentDir);
99
+ if (existsSync(currentDir)) {
100
+ const files = await readdir(currentDir, { withFileTypes: true });
101
+ for (const file of files) {
102
+ if (file.isFile() && file.name.endsWith(".json")) {
103
+ if (file.name === "package-lock.json" || file.name === "package.json" || file.name === "tsconfig.json") {
104
+ continue;
105
+ }
106
+ const filePath = path.join(currentDir, file.name);
107
+ try {
108
+ const content = await readFile(filePath, "utf8");
109
+ if (content.includes("mcpServers")) {
110
+ const json = JSON.parse(content);
111
+ if (json.mcpServers && typeof json.mcpServers === "object") {
112
+ for (const [name, config] of Object.entries(json.mcpServers)) {
113
+ if (config && typeof config === "object" && config.command) {
114
+ servers.push({
115
+ name,
116
+ config,
117
+ source: `Local Workspace (${file.name})`
118
+ });
119
+ }
120
+ }
121
+ }
122
+ }
123
+ } catch {
124
+ }
125
+ }
126
+ }
127
+ }
128
+ const parent = path.dirname(currentDir);
129
+ if (parent === currentDir) break;
130
+ currentDir = parent;
131
+ }
132
+ } catch {
133
+ }
134
+ }
89
135
  return servers;
90
136
  }
91
- async function pickDiscoveredServer() {
92
- const servers = await discoverServers();
137
+ async function pickDiscoveredServer(options) {
138
+ const servers = await discoverServers(options);
93
139
  if (servers.length === 0) {
94
140
  return null;
95
141
  }
@@ -99,7 +145,7 @@ async function pickDiscoveredServer() {
99
145
  if (!uniqueServers.has(key)) {
100
146
  uniqueServers.set(key, s);
101
147
  } else {
102
- if (s.source.includes("Project")) {
148
+ if (s.source.includes("Project") || s.source.includes("Local Workspace")) {
103
149
  uniqueServers.set(key, s);
104
150
  }
105
151
  }
@@ -143,6 +189,7 @@ async function pickDiscoveredServer() {
143
189
  import { mkdir, writeFile } from "fs/promises";
144
190
  import { tmpdir } from "os";
145
191
  import { join } from "path";
192
+ import { exec } from "child_process";
146
193
  var BASE64_PATTERN = /^[A-Za-z0-9+/]{1000,}={0,2}$/;
147
194
  var DEFAULT_TIMEOUT_MS = 3e5;
148
195
  var DEFAULT_MAX_TEXT_LENGTH = 5e4;
@@ -151,12 +198,14 @@ var ResponseInterceptor = class {
151
198
  defaultTimeoutMs;
152
199
  maxTextLength;
153
200
  mediaThresholdKb;
201
+ openMedia;
154
202
  fileCounter = 0;
155
203
  constructor(opts = {}) {
156
204
  this.outDir = opts.outDir ?? join(tmpdir(), "run-mcp");
157
205
  this.defaultTimeoutMs = opts.defaultTimeoutMs ?? DEFAULT_TIMEOUT_MS;
158
206
  this.maxTextLength = opts.maxTextLength ?? DEFAULT_MAX_TEXT_LENGTH;
159
207
  this.mediaThresholdKb = opts.mediaThresholdKb ?? 0;
208
+ this.openMedia = opts.openMedia ?? false;
160
209
  }
161
210
  /**
162
211
  * Call a tool on the target, applying timeout, media extraction, and truncation.
@@ -373,6 +422,13 @@ var ResponseInterceptor = class {
373
422
  const filepath = join(this.outDir, filename);
374
423
  const buffer = Buffer.from(base64Data, "base64");
375
424
  await writeFile(filepath, buffer);
425
+ if (this.openMedia) {
426
+ const isMac = process.platform === "darwin";
427
+ const isWin = process.platform === "win32";
428
+ const cmd = isMac ? "open" : isWin ? "start" : "xdg-open";
429
+ exec(`${cmd} "${filepath}"`, () => {
430
+ });
431
+ }
376
432
  const sizeKB = (buffer.length / 1024).toFixed(1);
377
433
  const label = mediaType === "audio" ? "Audio" : "Image";
378
434
  return {
@@ -423,8 +479,41 @@ var ResponseInterceptor = class {
423
479
  }
424
480
  };
425
481
 
426
- // src/parsing.ts
482
+ // src/colors.ts
427
483
  import pc from "picocolors";
484
+ function shouldEnableColor() {
485
+ const dashDashIdx = process.argv.indexOf("--");
486
+ const argsToCheck = dashDashIdx === -1 ? process.argv : process.argv.slice(0, dashDashIdx);
487
+ for (let i = 0; i < argsToCheck.length; i++) {
488
+ const arg = argsToCheck[i];
489
+ if (arg.startsWith("--color=")) {
490
+ const val = arg.split("=")[1];
491
+ if (val === "always") return true;
492
+ if (val === "never") return false;
493
+ } else if (arg === "--color") {
494
+ const val = argsToCheck[i + 1];
495
+ if (val === "always") return true;
496
+ if (val === "never") return false;
497
+ }
498
+ }
499
+ if (process.env.CLICOLOR_FORCE !== void 0 && process.env.CLICOLOR_FORCE !== "0") {
500
+ return true;
501
+ }
502
+ if (process.env.NO_COLOR) {
503
+ return false;
504
+ }
505
+ if (process.env.CLICOLOR === "0") {
506
+ return false;
507
+ }
508
+ const isatty = !!(process.stdout?.isTTY || process.stderr?.isTTY);
509
+ if (!isatty) {
510
+ return false;
511
+ }
512
+ return true;
513
+ }
514
+ var colors = pc.createColors(shouldEnableColor());
515
+
516
+ // src/parsing.ts
428
517
  function parseCommandLine(input3) {
429
518
  const spaceIdx = input3.indexOf(" ");
430
519
  if (spaceIdx === -1) {
@@ -466,10 +555,10 @@ function colorizeJson(json) {
466
555
  if (ch === '"') {
467
556
  const str = consumeString(json, i);
468
557
  if (expectingValue) {
469
- result.push(pc.green(str));
558
+ result.push(colors.green(str));
470
559
  expectingValue = false;
471
560
  } else {
472
- result.push(pc.cyan(str));
561
+ result.push(colors.cyan(str));
473
562
  }
474
563
  i += str.length;
475
564
  continue;
@@ -493,19 +582,19 @@ function colorizeJson(json) {
493
582
  continue;
494
583
  }
495
584
  if (json.startsWith("true", i)) {
496
- result.push(pc.magenta("true"));
585
+ result.push(colors.magenta("true"));
497
586
  expectingValue = false;
498
587
  i += 4;
499
588
  continue;
500
589
  }
501
590
  if (json.startsWith("false", i)) {
502
- result.push(pc.magenta("false"));
591
+ result.push(colors.magenta("false"));
503
592
  expectingValue = false;
504
593
  i += 5;
505
594
  continue;
506
595
  }
507
596
  if (json.startsWith("null", i)) {
508
- result.push(pc.dim("null"));
597
+ result.push(colors.dim("null"));
509
598
  expectingValue = false;
510
599
  i += 4;
511
600
  continue;
@@ -516,7 +605,7 @@ function colorizeJson(json) {
516
605
  num += json[i];
517
606
  i++;
518
607
  }
519
- result.push(pc.yellow(num));
608
+ result.push(colors.yellow(num));
520
609
  expectingValue = false;
521
610
  continue;
522
611
  }
@@ -834,7 +923,425 @@ function interpolateString(input3, context) {
834
923
 
835
924
  // src/target-manager.ts
836
925
  import { EventEmitter } from "events";
926
+ import { execSync } from "child_process";
927
+ import { writeFileSync, rmSync, existsSync as existsSync3 } from "fs";
928
+ import { join as join3 } from "path";
929
+ import { tmpdir as tmpdir2 } from "os";
837
930
  import treeKill from "tree-kill";
931
+
932
+ // src/settings.ts
933
+ import { existsSync as existsSync2, readFileSync } from "fs";
934
+ import { dirname, join as join2, resolve, isAbsolute } from "path";
935
+ import { homedir as homedir2 } from "os";
936
+ function resolvePath(p, settingsFileDir) {
937
+ if (p.startsWith("~")) {
938
+ return resolve(homedir2(), p.slice(1).replace(/^[/\\]/, ""));
939
+ }
940
+ if (p.startsWith("$HOME")) {
941
+ return resolve(homedir2(), p.slice(5).replace(/^[/\\]/, ""));
942
+ }
943
+ if (isAbsolute(p)) {
944
+ return resolve(p);
945
+ }
946
+ return resolve(settingsFileDir, p);
947
+ }
948
+ function matchDomain(host, pattern) {
949
+ if (pattern === "*") return true;
950
+ const [hostName, hostPort] = host.split(":");
951
+ const [patternName, patternPort] = pattern.split(":");
952
+ if (patternPort && patternPort !== "*") {
953
+ if (hostPort !== patternPort) return false;
954
+ }
955
+ const regexPattern = patternName.replace(/\./g, "\\.").replace(/\*/g, ".*");
956
+ const regex = new RegExp(`^${regexPattern}$`, "i");
957
+ return regex.test(hostName);
958
+ }
959
+ var SandboxPolicy = class {
960
+ // Allowlists (Absolute paths or patterns)
961
+ fileReadAllow = /* @__PURE__ */ new Set();
962
+ fileWriteAllow = /* @__PURE__ */ new Set();
963
+ networkAllow = /* @__PURE__ */ new Set();
964
+ // Denylists (Absolute paths or patterns)
965
+ fileReadDeny = /* @__PURE__ */ new Set();
966
+ fileWriteDeny = /* @__PURE__ */ new Set();
967
+ networkDeny = /* @__PURE__ */ new Set();
968
+ constructor() {
969
+ }
970
+ /**
971
+ * Merge a SandboxConfig block into the policy.
972
+ * Path rules are resolved relative to the settingsFileDir.
973
+ */
974
+ mergeConfig(config, settingsFileDir) {
975
+ if (config.file?.allow?.read) {
976
+ for (const p of config.file.allow.read) {
977
+ this.fileReadAllow.add(resolvePath(p, settingsFileDir));
978
+ }
979
+ }
980
+ if (config.file?.allow?.write) {
981
+ for (const p of config.file.allow.write) {
982
+ this.fileWriteAllow.add(resolvePath(p, settingsFileDir));
983
+ }
984
+ }
985
+ if (config.file?.deny?.read) {
986
+ for (const p of config.file.deny.read) {
987
+ this.fileReadDeny.add(resolvePath(p, settingsFileDir));
988
+ }
989
+ }
990
+ if (config.file?.deny?.write) {
991
+ for (const p of config.file.deny.write) {
992
+ this.fileWriteDeny.add(resolvePath(p, settingsFileDir));
993
+ }
994
+ }
995
+ if (config.network?.allow) {
996
+ for (const host of config.network.allow) {
997
+ this.networkAllow.add(host);
998
+ }
999
+ }
1000
+ if (config.network?.deny) {
1001
+ for (const host of config.network.deny) {
1002
+ this.networkDeny.add(host);
1003
+ }
1004
+ }
1005
+ }
1006
+ /**
1007
+ * Merge raw command line overrides.
1008
+ * CLI overrides are resolved relative to the process CWD.
1009
+ */
1010
+ mergeCliOverrides(options) {
1011
+ const cwd = process.cwd();
1012
+ if (options.allowRead) {
1013
+ for (const p of options.allowRead) this.fileReadAllow.add(resolvePath(p, cwd));
1014
+ }
1015
+ if (options.allowWrite) {
1016
+ for (const p of options.allowWrite) this.fileWriteAllow.add(resolvePath(p, cwd));
1017
+ }
1018
+ if (options.allowNet) {
1019
+ for (const host of options.allowNet) this.networkAllow.add(host);
1020
+ }
1021
+ if (options.denyRead) {
1022
+ for (const p of options.denyRead) this.fileReadDeny.add(resolvePath(p, cwd));
1023
+ }
1024
+ if (options.denyWrite) {
1025
+ for (const p of options.denyWrite) this.fileWriteDeny.add(resolvePath(p, cwd));
1026
+ }
1027
+ if (options.denyNet) {
1028
+ for (const host of options.denyNet) this.networkDeny.add(host);
1029
+ }
1030
+ }
1031
+ /**
1032
+ * Automatically deny reading sensitive credential directories if network outbound is allowed.
1033
+ */
1034
+ applyCredentialProtections() {
1035
+ if (this.networkAllow.size > 0 && !this.networkDeny.has("*")) {
1036
+ const sensitivePatterns = [
1037
+ "~/.ssh",
1038
+ "~/.aws",
1039
+ "~/.kube",
1040
+ "~/.config/gcloud",
1041
+ "~/.netrc",
1042
+ "~/.npmrc"
1043
+ ];
1044
+ const cwd = process.cwd();
1045
+ for (const pattern of sensitivePatterns) {
1046
+ const resolved = resolvePath(pattern, cwd);
1047
+ if (!this.fileReadAllow.has(resolved)) {
1048
+ this.fileReadDeny.add(resolved);
1049
+ }
1050
+ }
1051
+ }
1052
+ }
1053
+ /**
1054
+ * Evaluates if a file read path is allowed, under Deny-Wins precedence.
1055
+ */
1056
+ isFileReadAllowed(p) {
1057
+ const absPath = resolve(p);
1058
+ for (const denyPath of this.fileReadDeny) {
1059
+ if (absPath === denyPath || absPath.startsWith(denyPath + "/")) {
1060
+ return false;
1061
+ }
1062
+ }
1063
+ for (const allowPath of this.fileReadAllow) {
1064
+ if (absPath === allowPath || absPath.startsWith(allowPath + "/")) {
1065
+ return true;
1066
+ }
1067
+ }
1068
+ return false;
1069
+ }
1070
+ /**
1071
+ * Evaluates if a file write path is allowed, under Deny-Wins precedence.
1072
+ */
1073
+ isFileWriteAllowed(p) {
1074
+ const absPath = resolve(p);
1075
+ for (const denyPath of this.fileWriteDeny) {
1076
+ if (absPath === denyPath || absPath.startsWith(denyPath + "/")) {
1077
+ return false;
1078
+ }
1079
+ }
1080
+ for (const allowPath of this.fileWriteAllow) {
1081
+ if (absPath === allowPath || absPath.startsWith(allowPath + "/")) {
1082
+ return true;
1083
+ }
1084
+ }
1085
+ return false;
1086
+ }
1087
+ /**
1088
+ * Evaluates if network access to a host (hostname:port or hostname) is allowed.
1089
+ */
1090
+ isNetworkAllowed(host) {
1091
+ for (const denyPattern of this.networkDeny) {
1092
+ if (matchDomain(host, denyPattern)) {
1093
+ return false;
1094
+ }
1095
+ }
1096
+ for (const allowPattern of this.networkAllow) {
1097
+ if (matchDomain(host, allowPattern)) {
1098
+ return true;
1099
+ }
1100
+ }
1101
+ return false;
1102
+ }
1103
+ /**
1104
+ * Generate Scheme-based macOS Seatbelt profile.
1105
+ */
1106
+ getSeatbeltProfile(options) {
1107
+ if (options.audit) {
1108
+ return `(version 1)
1109
+ (allow default)
1110
+ (deny network*)
1111
+ (deny file-write*)
1112
+ (deny process-fork)
1113
+ (deny process-exec*)
1114
+ (allow process-exec*
1115
+ (subpath "/System")
1116
+ (subpath "/usr")
1117
+ (subpath "/bin")
1118
+ (subpath "${options.nodeBinDir}")
1119
+ )
1120
+ (deny file-read*)
1121
+ (allow file-read*
1122
+ (literal "/")
1123
+ (subpath "/System")
1124
+ (subpath "/usr")
1125
+ (subpath "/dev")
1126
+ (subpath "/private/var")
1127
+ (subpath "/var")
1128
+ (subpath "/private/etc")
1129
+ (subpath "/etc")
1130
+ (subpath "${options.nodeBinDir}")
1131
+ (subpath "${options.nodeInstallDir}")
1132
+ (subpath "${options.cwd}")
1133
+ (path-ancestors "${options.cwd}")
1134
+ (path-ancestors "${options.nodeBinDir}")
1135
+ )
1136
+ `;
1137
+ }
1138
+ const readDirectives = Array.from(this.fileReadAllow).map((p) => ` (subpath "${p}")`).join("\n");
1139
+ const writeDirectives = Array.from(this.fileWriteAllow).map((p) => ` (subpath "${p}")`).join("\n");
1140
+ const readDenyDirectives = Array.from(this.fileReadDeny).map((p) => ` (subpath "${p}")`).join("\n");
1141
+ const writeDenyDirectives = Array.from(this.fileWriteDeny).map((p) => ` (subpath "${p}")`).join("\n");
1142
+ let netRules;
1143
+ if (this.networkAllow.size > 0 && !this.networkDeny.has("*")) {
1144
+ netRules = `(allow network-outbound)
1145
+ `;
1146
+ if (this.networkDeny.size > 0) {
1147
+ for (const pattern of this.networkDeny) {
1148
+ if (pattern !== "*") {
1149
+ netRules += `(deny network-outbound (remote ip "${pattern}"))
1150
+ `;
1151
+ }
1152
+ }
1153
+ }
1154
+ } else {
1155
+ netRules = `(deny network*)
1156
+ `;
1157
+ }
1158
+ return `(version 1)
1159
+ (allow default)
1160
+ ${netRules}
1161
+ (deny file-write*)
1162
+ (allow file-write*
1163
+ (subpath "${options.tmp}")
1164
+ (subpath "/private/tmp")
1165
+ (subpath "/tmp")
1166
+ ${writeDirectives}
1167
+ )
1168
+ ${writeDenyDirectives ? `(deny file-write*
1169
+ ${writeDenyDirectives}
1170
+ )
1171
+ ` : ""}
1172
+ (deny file-read*)
1173
+ (allow file-read*
1174
+ (literal "/")
1175
+ (subpath "/System")
1176
+ (subpath "/usr")
1177
+ (subpath "/dev")
1178
+ (subpath "/private/var")
1179
+ (subpath "/var")
1180
+ (subpath "/private/etc")
1181
+ (subpath "/etc")
1182
+ (subpath "/private/tmp")
1183
+ (subpath "/tmp")
1184
+ (subpath "${options.nodeBinDir}")
1185
+ (subpath "${options.nodeInstallDir}")
1186
+ (subpath "${options.cwd}")
1187
+ (subpath "${options.tmp}")
1188
+ (path-ancestors "${options.cwd}")
1189
+ (path-ancestors "${options.nodeBinDir}")
1190
+ ${readDirectives}
1191
+ )
1192
+ ${readDenyDirectives ? `(deny file-read*
1193
+ ${readDenyDirectives}
1194
+ )
1195
+ ` : ""}
1196
+ `;
1197
+ }
1198
+ };
1199
+ function loadSettings(cwd = process.cwd()) {
1200
+ const settings = { sandbox: {} };
1201
+ const loadedConfigs = [];
1202
+ let managedPath;
1203
+ if (process.platform === "darwin") {
1204
+ managedPath = "/Library/Application Support/run-mcp/settings.json";
1205
+ } else if (process.platform === "win32") {
1206
+ managedPath = join2(process.env.ProgramFiles || "C:\\Program Files", "run-mcp", "settings.json");
1207
+ } else {
1208
+ managedPath = "/etc/run-mcp/settings.json";
1209
+ }
1210
+ if (existsSync2(managedPath)) {
1211
+ try {
1212
+ const config = JSON.parse(readFileSync(managedPath, "utf8"));
1213
+ if (config.sandbox) loadedConfigs.push({ config: config.sandbox, dir: dirname(managedPath) });
1214
+ } catch {
1215
+ }
1216
+ }
1217
+ const userPath = join2(homedir2(), ".run-mcp", "settings.json");
1218
+ if (existsSync2(userPath)) {
1219
+ try {
1220
+ const config = JSON.parse(readFileSync(userPath, "utf8"));
1221
+ if (config.sandbox) loadedConfigs.push({ config: config.sandbox, dir: dirname(userPath) });
1222
+ } catch {
1223
+ }
1224
+ }
1225
+ const projectPath = join2(cwd, ".run-mcp", "settings.json");
1226
+ if (existsSync2(projectPath)) {
1227
+ try {
1228
+ const config = JSON.parse(readFileSync(projectPath, "utf8"));
1229
+ if (config.sandbox) loadedConfigs.push({ config: config.sandbox, dir: dirname(projectPath) });
1230
+ } catch {
1231
+ }
1232
+ }
1233
+ const localPath = join2(cwd, ".run-mcp", "settings.local.json");
1234
+ if (existsSync2(localPath)) {
1235
+ try {
1236
+ const config = JSON.parse(readFileSync(localPath, "utf8"));
1237
+ if (config.sandbox) loadedConfigs.push({ config: config.sandbox, dir: dirname(localPath) });
1238
+ } catch {
1239
+ }
1240
+ }
1241
+ const policy = new SandboxPolicy();
1242
+ for (const item of loadedConfigs) {
1243
+ policy.mergeConfig(item.config, item.dir);
1244
+ }
1245
+ settings.sandbox = {
1246
+ file: {
1247
+ allow: {
1248
+ read: Array.from(policy.fileReadAllow),
1249
+ write: Array.from(policy.fileWriteAllow)
1250
+ },
1251
+ deny: {
1252
+ read: Array.from(policy.fileReadDeny),
1253
+ write: Array.from(policy.fileWriteDeny)
1254
+ }
1255
+ },
1256
+ network: {
1257
+ allow: Array.from(policy.networkAllow),
1258
+ deny: Array.from(policy.networkDeny)
1259
+ }
1260
+ };
1261
+ return settings;
1262
+ }
1263
+
1264
+ // src/proxy-audit.ts
1265
+ import http from "http";
1266
+ import net from "net";
1267
+ var NetworkAuditProxy = class {
1268
+ server;
1269
+ port = 0;
1270
+ constructor() {
1271
+ this.server = http.createServer((req, res) => {
1272
+ const url = req.url || "";
1273
+ console.error(`\x1B[36m\u{1F310} [NETWORK AUDIT] HTTP request to: ${url}\x1B[0m`);
1274
+ try {
1275
+ const parsedUrl = new URL(url);
1276
+ const options = {
1277
+ hostname: parsedUrl.hostname,
1278
+ port: parsedUrl.port || 80,
1279
+ path: parsedUrl.pathname + parsedUrl.search,
1280
+ method: req.method,
1281
+ headers: req.headers
1282
+ };
1283
+ const proxyReq = http.request(options, (proxyRes) => {
1284
+ res.writeHead(proxyRes.statusCode || 200, proxyRes.headers);
1285
+ proxyRes.pipe(res);
1286
+ });
1287
+ proxyReq.on("error", (err) => {
1288
+ res.writeHead(502);
1289
+ res.end(`Proxy Error: ${err.message}`);
1290
+ });
1291
+ req.pipe(proxyReq);
1292
+ } catch (err) {
1293
+ res.writeHead(400);
1294
+ res.end(`Invalid URL: ${err.message}`);
1295
+ }
1296
+ });
1297
+ this.server.on("connect", (req, clientSocket, head) => {
1298
+ const parts = req.url ? req.url.split(":") : [];
1299
+ const hostname = parts[0];
1300
+ const port = parts[1] ? Number.parseInt(parts[1], 10) : 443;
1301
+ console.error(
1302
+ `\x1B[36m\u{1F310} [NETWORK AUDIT] HTTPS connection established to: ${hostname}:${port}\x1B[0m`
1303
+ );
1304
+ const serverSocket = net.connect(port, hostname, () => {
1305
+ clientSocket.write("HTTP/1.1 200 Connection Established\r\n\r\n");
1306
+ serverSocket.write(head);
1307
+ serverSocket.pipe(clientSocket);
1308
+ clientSocket.pipe(serverSocket);
1309
+ });
1310
+ serverSocket.on("error", (err) => {
1311
+ clientSocket.end(`HTTP/1.1 502 Bad Gateway\r
1312
+ \r
1313
+ Proxy Connection Error: ${err.message}`);
1314
+ });
1315
+ clientSocket.on("error", () => {
1316
+ serverSocket.end();
1317
+ });
1318
+ });
1319
+ }
1320
+ async start() {
1321
+ return new Promise((resolve3, reject) => {
1322
+ this.server.on("error", (err) => {
1323
+ reject(err);
1324
+ });
1325
+ this.server.listen(0, "127.0.0.1", () => {
1326
+ const addr = this.server.address();
1327
+ if (addr && typeof addr === "object") {
1328
+ this.port = addr.port;
1329
+ }
1330
+ resolve3(this.port);
1331
+ });
1332
+ });
1333
+ }
1334
+ async close() {
1335
+ return new Promise((resolve3) => {
1336
+ this.server.close(() => resolve3());
1337
+ });
1338
+ }
1339
+ getPort() {
1340
+ return this.port;
1341
+ }
1342
+ };
1343
+
1344
+ // src/target-manager.ts
838
1345
  import { Client } from "@modelcontextprotocol/sdk/client/index.js";
839
1346
  import { SSEClientTransport } from "@modelcontextprotocol/sdk/client/sse.js";
840
1347
  import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
@@ -852,11 +1359,29 @@ var MIN_UPTIME_FOR_RESTART_MS = 5e3;
852
1359
  var MAX_RECONNECT_ATTEMPTS = 3;
853
1360
  var STABLE_CONNECTION_RESET_MS = 6e4;
854
1361
  var MAX_HISTORY = 100;
1362
+ function isCommandAvailable(cmd) {
1363
+ try {
1364
+ const checkCmd = process.platform === "win32" ? `where ${cmd}` : `command -v ${cmd}`;
1365
+ execSync(checkCmd, { stdio: "ignore" });
1366
+ return true;
1367
+ } catch {
1368
+ return false;
1369
+ }
1370
+ }
855
1371
  var TargetManager = class _TargetManager extends EventEmitter {
856
- constructor(command, args) {
1372
+ constructor(command, args, options = {}) {
857
1373
  super();
858
1374
  this.command = command;
859
1375
  this.args = args;
1376
+ this.sandboxMode = options.sandbox ?? "none";
1377
+ this.sandboxOptions = {
1378
+ allowRead: options.allowRead,
1379
+ allowWrite: options.allowWrite,
1380
+ allowNet: options.allowNet,
1381
+ denyRead: options.denyRead,
1382
+ denyWrite: options.denyWrite,
1383
+ denyNet: options.denyNet
1384
+ };
860
1385
  }
861
1386
  client = null;
862
1387
  transport = null;
@@ -883,6 +1408,12 @@ var TargetManager = class _TargetManager extends EventEmitter {
883
1408
  static MAX_NOTIFICATIONS = 200;
884
1409
  // Roots
885
1410
  _roots = [];
1411
+ _proxy = null;
1412
+ _proxyPort = 0;
1413
+ sandboxMode;
1414
+ _tempSbPath = null;
1415
+ _useWindowsMxc = false;
1416
+ sandboxOptions;
886
1417
  /**
887
1418
  * Enable auto-reconnect behavior.
888
1419
  * Only applies to interactive REPL mode — proxy mode manages its own lifecycle.
@@ -901,11 +1432,27 @@ var TargetManager = class _TargetManager extends EventEmitter {
901
1432
  if (this.command.startsWith("http://") || this.command.startsWith("https://")) {
902
1433
  this.transport = new SSEClientTransport(new URL(this.command));
903
1434
  } else {
1435
+ const policy = new SandboxPolicy();
1436
+ const fileSettings = loadSettings();
1437
+ if (fileSettings.sandbox) {
1438
+ policy.mergeConfig(fileSettings.sandbox, process.cwd());
1439
+ }
1440
+ policy.mergeCliOverrides(this.sandboxOptions);
1441
+ policy.applyCredentialProtections();
1442
+ if (this.sandboxMode !== "none" && this.sandboxMode !== "audit" && policy.networkAllow.size > 0 && !policy.networkDeny.has("*")) {
1443
+ this._proxy = new NetworkAuditProxy();
1444
+ this._proxyPort = await this._proxy.start();
1445
+ }
1446
+ const { command: finalCommand, args: finalArgs } = await this._maybeWrapCommand(policy);
904
1447
  const stdioTransport = new StdioClientTransport({
905
- command: this.command,
906
- args: this.args,
907
- stderr: "pipe"
1448
+ command: finalCommand,
1449
+ args: finalArgs,
1450
+ stderr: "pipe",
1451
+ env: this._getDefaultEnvironment()
908
1452
  });
1453
+ if (this._useWindowsMxc) {
1454
+ await this._applyWindowsMxcOverride(stdioTransport, finalCommand, finalArgs, policy);
1455
+ }
909
1456
  stdioTransport.stderr?.on("data", (chunk) => {
910
1457
  const text = chunk.toString().trimEnd();
911
1458
  if (text) {
@@ -915,13 +1462,21 @@ var TargetManager = class _TargetManager extends EventEmitter {
915
1462
  if (this._stderrLines.length > _TargetManager.MAX_STDERR_LINES) {
916
1463
  this._stderrLines = this._stderrLines.slice(-_TargetManager.MAX_STDERR_LINES);
917
1464
  }
1465
+ if (this.sandboxMode === "audit") {
1466
+ const lowerText = text.toLowerCase();
1467
+ 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")) {
1468
+ console.error(
1469
+ `\x1B[31m\u26A0\uFE0F [SANDBOX AUDIT] Blocked unauthorized side-effect: ${text}\x1B[0m`
1470
+ );
1471
+ }
1472
+ }
918
1473
  this.emit("stderr", text);
919
1474
  }
920
1475
  });
921
1476
  this.transport = stdioTransport;
922
1477
  }
923
1478
  this.client = new Client(
924
- { name: "run-mcp", version: "1.6.3" },
1479
+ { name: "run-mcp", version: "1.7.1" },
925
1480
  {
926
1481
  capabilities: {
927
1482
  roots: { listChanged: true },
@@ -979,7 +1534,7 @@ var TargetManager = class _TargetManager extends EventEmitter {
979
1534
  this.emit("notification", record);
980
1535
  });
981
1536
  this.client.setRequestHandler(CreateMessageRequestSchema, async (request) => {
982
- return new Promise((resolve2, reject) => {
1537
+ return new Promise((resolve3, reject) => {
983
1538
  const timeout = setTimeout(() => {
984
1539
  reject(new Error("Sampling request timed out (no response from user in 5 minutes)"));
985
1540
  }, 3e5);
@@ -987,7 +1542,7 @@ var TargetManager = class _TargetManager extends EventEmitter {
987
1542
  request: request.params,
988
1543
  respond: (result) => {
989
1544
  clearTimeout(timeout);
990
- resolve2(result);
1545
+ resolve3(result);
991
1546
  },
992
1547
  reject: (err) => {
993
1548
  clearTimeout(timeout);
@@ -997,7 +1552,7 @@ var TargetManager = class _TargetManager extends EventEmitter {
997
1552
  });
998
1553
  });
999
1554
  this.client.setRequestHandler(ElicitRequestSchema, async (request) => {
1000
- return new Promise((resolve2, reject) => {
1555
+ return new Promise((resolve3, reject) => {
1001
1556
  const timeout = setTimeout(() => {
1002
1557
  reject(new Error("Elicitation request timed out (no response from user in 5 minutes)"));
1003
1558
  }, 3e5);
@@ -1005,7 +1560,7 @@ var TargetManager = class _TargetManager extends EventEmitter {
1005
1560
  request: request.params,
1006
1561
  respond: (result) => {
1007
1562
  clearTimeout(timeout);
1008
- resolve2(result);
1563
+ resolve3(result);
1009
1564
  },
1010
1565
  reject: (err) => {
1011
1566
  clearTimeout(timeout);
@@ -1343,7 +1898,8 @@ var TargetManager = class _TargetManager extends EventEmitter {
1343
1898
  lastResponseTime: this._lastResponseTime,
1344
1899
  stderrLineCount: this._stderrLineCount,
1345
1900
  reconnectAttempts: this._reconnectAttempts,
1346
- maxReconnectAttempts: MAX_RECONNECT_ATTEMPTS
1901
+ maxReconnectAttempts: MAX_RECONNECT_ATTEMPTS,
1902
+ sandbox: this.sandboxMode
1347
1903
  };
1348
1904
  }
1349
1905
  /**
@@ -1368,10 +1924,25 @@ var TargetManager = class _TargetManager extends EventEmitter {
1368
1924
  this.transport = null;
1369
1925
  }
1370
1926
  if (pidToKill) {
1371
- await new Promise((resolve2) => {
1372
- treeKill(pidToKill, "SIGKILL", () => resolve2());
1927
+ await new Promise((resolve3) => {
1928
+ treeKill(pidToKill, "SIGKILL", () => resolve3());
1373
1929
  });
1374
1930
  }
1931
+ if (this._tempSbPath && existsSync3(this._tempSbPath)) {
1932
+ try {
1933
+ rmSync(this._tempSbPath, { force: true });
1934
+ } catch {
1935
+ }
1936
+ this._tempSbPath = null;
1937
+ }
1938
+ if (this._proxy) {
1939
+ try {
1940
+ await this._proxy.close();
1941
+ } catch {
1942
+ }
1943
+ this._proxy = null;
1944
+ this._proxyPort = 0;
1945
+ }
1375
1946
  this._connected = false;
1376
1947
  this.childPid = null;
1377
1948
  }
@@ -1443,6 +2014,264 @@ var TargetManager = class _TargetManager extends EventEmitter {
1443
2014
  }
1444
2015
  }
1445
2016
  // ─── Internal helpers ──────────────────────────────────────────────────────
2017
+ async _maybeWrapCommand(policy) {
2018
+ let command = this.command;
2019
+ let args = [...this.args];
2020
+ let modeToUse = this.sandboxMode;
2021
+ if (modeToUse === "auto") {
2022
+ if (process.platform === "darwin") {
2023
+ if (isCommandAvailable("sandbox-exec")) {
2024
+ modeToUse = "native";
2025
+ } else {
2026
+ process.stderr.write("Warning: sandbox-exec is not available. Sandboxing disabled.\n");
2027
+ modeToUse = "none";
2028
+ }
2029
+ } else if (process.platform === "linux") {
2030
+ if (isCommandAvailable("bwrap")) {
2031
+ modeToUse = "native";
2032
+ } else {
2033
+ process.stderr.write("Warning: bwrap is not available. Sandboxing disabled.\n");
2034
+ modeToUse = "none";
2035
+ }
2036
+ } else if (process.platform === "win32") {
2037
+ let hasMxc = false;
2038
+ try {
2039
+ const mxcModule = "@microsoft/mxc-sdk";
2040
+ await import(mxcModule);
2041
+ hasMxc = true;
2042
+ } catch {
2043
+ }
2044
+ if (hasMxc) {
2045
+ modeToUse = "native";
2046
+ } else {
2047
+ process.stderr.write(
2048
+ "Warning: @microsoft/mxc-sdk is not available. Sandboxing disabled.\n"
2049
+ );
2050
+ modeToUse = "none";
2051
+ }
2052
+ } else {
2053
+ process.stderr.write(
2054
+ `Warning: Sandboxing not supported on platform "${process.platform}". Sandboxing disabled.
2055
+ `
2056
+ );
2057
+ modeToUse = "none";
2058
+ }
2059
+ }
2060
+ if (modeToUse === "docker") {
2061
+ let image = "node:20";
2062
+ if (command.includes("python")) {
2063
+ image = "python:3";
2064
+ }
2065
+ const cwd = process.cwd();
2066
+ args = [
2067
+ "run",
2068
+ "-i",
2069
+ "--rm",
2070
+ "--net=none",
2071
+ "-v",
2072
+ `${cwd}:/workspace`,
2073
+ "-w",
2074
+ "/workspace",
2075
+ image,
2076
+ command,
2077
+ ...args
2078
+ ];
2079
+ command = "docker";
2080
+ } else if (modeToUse === "native" || modeToUse === "audit") {
2081
+ const isAudit = modeToUse === "audit";
2082
+ if (process.platform === "darwin") {
2083
+ if (!isCommandAvailable("sandbox-exec")) {
2084
+ throw new Error(
2085
+ "sandbox-exec not found. Native sandboxing is not available on this macOS host."
2086
+ );
2087
+ }
2088
+ const cwd = process.cwd();
2089
+ const tmp = tmpdir2();
2090
+ const nodeBinDir = join3(process.execPath, "..");
2091
+ const nodeInstallDir = join3(nodeBinDir, "..");
2092
+ const profile = policy.getSeatbeltProfile({
2093
+ tmp,
2094
+ cwd,
2095
+ nodeBinDir,
2096
+ nodeInstallDir,
2097
+ audit: isAudit
2098
+ });
2099
+ const tempSbPath = join3(
2100
+ tmp,
2101
+ `run-mcp-sandbox-${Date.now()}-${Math.random().toString(36).slice(2)}.sb`
2102
+ );
2103
+ writeFileSync(tempSbPath, profile, "utf8");
2104
+ this._tempSbPath = tempSbPath;
2105
+ args = ["-f", tempSbPath, command, ...args];
2106
+ command = "sandbox-exec";
2107
+ } else if (process.platform === "linux") {
2108
+ if (!isCommandAvailable("bwrap")) {
2109
+ throw new Error(
2110
+ "bwrap not found. Native sandboxing is not available on this Linux host."
2111
+ );
2112
+ }
2113
+ const cwd = process.cwd();
2114
+ const tmp = tmpdir2();
2115
+ const bwrapArgs = [
2116
+ "--ro-bind",
2117
+ "/usr",
2118
+ "/usr",
2119
+ "--ro-bind",
2120
+ "/lib",
2121
+ "/lib",
2122
+ "--ro-bind-try",
2123
+ "/lib64",
2124
+ "/lib64",
2125
+ "--ro-bind-try",
2126
+ "/bin",
2127
+ "/bin",
2128
+ "--ro-bind-try",
2129
+ "/sbin",
2130
+ "/sbin",
2131
+ "--ro-bind-try",
2132
+ "/etc",
2133
+ "/etc",
2134
+ "--ro-bind",
2135
+ cwd,
2136
+ cwd,
2137
+ "--dev",
2138
+ "/dev",
2139
+ "--proc",
2140
+ "/proc",
2141
+ "--unshare-pid",
2142
+ "--unshare-user",
2143
+ "--unshare-ipc"
2144
+ ];
2145
+ if (isAudit) {
2146
+ bwrapArgs.push("--unshare-net");
2147
+ bwrapArgs.push("--ro-bind", tmp, tmp);
2148
+ } else {
2149
+ bwrapArgs.push("--bind", tmp, tmp);
2150
+ if (policy.networkAllow.size === 0 || policy.networkDeny.has("*")) {
2151
+ bwrapArgs.push("--unshare-net");
2152
+ }
2153
+ for (const p of policy.fileReadAllow) {
2154
+ if (existsSync3(p)) {
2155
+ bwrapArgs.push("--ro-bind", p, p);
2156
+ }
2157
+ }
2158
+ for (const p of policy.fileWriteAllow) {
2159
+ if (existsSync3(p)) {
2160
+ bwrapArgs.push("--bind", p, p);
2161
+ }
2162
+ }
2163
+ }
2164
+ args = [...bwrapArgs, command, ...args];
2165
+ command = "bwrap";
2166
+ } else if (process.platform === "win32") {
2167
+ let hasMxc = false;
2168
+ try {
2169
+ const mxcModule = "@microsoft/mxc-sdk";
2170
+ await import(mxcModule);
2171
+ hasMxc = true;
2172
+ } catch {
2173
+ }
2174
+ if (!hasMxc) {
2175
+ throw new Error(
2176
+ "@microsoft/mxc-sdk not found. Native sandboxing is not available on this Windows host."
2177
+ );
2178
+ }
2179
+ this._useWindowsMxc = true;
2180
+ } else {
2181
+ throw new Error(`Native sandboxing not supported on platform "${process.platform}"`);
2182
+ }
2183
+ }
2184
+ return { command, args };
2185
+ }
2186
+ async _applyWindowsMxcOverride(transport, command, args, policyObj) {
2187
+ const mxcModule = "@microsoft/mxc-sdk";
2188
+ const mxcSdk = await import(mxcModule);
2189
+ const isAudit = this.sandboxMode === "audit";
2190
+ const readPaths = isAudit ? [process.cwd()] : [process.cwd(), tmpdir2(), ...Array.from(policyObj.fileReadAllow)];
2191
+ const writePaths = isAudit ? [] : [tmpdir2(), ...Array.from(policyObj.fileWriteAllow)];
2192
+ const policy = {
2193
+ filesystem: {
2194
+ read: readPaths,
2195
+ write: writePaths
2196
+ },
2197
+ network: {
2198
+ outbound: isAudit ? "block" : policyObj.networkAllow.size > 0 ? "allow" : "block"
2199
+ }
2200
+ };
2201
+ const config = mxcSdk.createConfigFromPolicy(policy);
2202
+ transport.start = async () => {
2203
+ if (transport._process) {
2204
+ throw new Error("StdioClientTransport already started!");
2205
+ }
2206
+ return new Promise((resolve3, reject) => {
2207
+ try {
2208
+ const env = {
2209
+ ...this._getDefaultEnvironment(),
2210
+ ...transport._serverParams.env
2211
+ };
2212
+ transport._process = mxcSdk.spawnSandboxFromConfig(config, command, args, {
2213
+ env,
2214
+ stdio: ["pipe", "pipe", transport._serverParams.stderr ?? "inherit"],
2215
+ cwd: transport._serverParams.cwd
2216
+ });
2217
+ transport._process.on("error", (error) => {
2218
+ reject(error);
2219
+ transport.onerror?.(error);
2220
+ });
2221
+ transport._process.on("spawn", () => {
2222
+ resolve3();
2223
+ });
2224
+ transport._process.on("close", () => {
2225
+ transport._process = void 0;
2226
+ transport.onclose?.();
2227
+ });
2228
+ transport._process.stdin?.on("error", (error) => {
2229
+ transport.onerror?.(error);
2230
+ });
2231
+ transport._process.stdout?.on("data", (chunk) => {
2232
+ transport._readBuffer.append(chunk);
2233
+ transport.processReadBuffer();
2234
+ });
2235
+ transport._process.stdout?.on("error", (error) => {
2236
+ transport.onerror?.(error);
2237
+ });
2238
+ if (transport._stderrStream && transport._process.stderr) {
2239
+ transport._process.stderr.pipe(transport._stderrStream);
2240
+ }
2241
+ } catch (err) {
2242
+ reject(err);
2243
+ }
2244
+ });
2245
+ };
2246
+ }
2247
+ _getDefaultEnvironment() {
2248
+ const env = {};
2249
+ const safeVars = process.platform === "win32" ? [
2250
+ "APPDATA",
2251
+ "HOMEDRIVE",
2252
+ "HOMEPATH",
2253
+ "LOCALAPPDATA",
2254
+ "PATH",
2255
+ "TEMP",
2256
+ "USERPROFILE",
2257
+ "SYSTEMROOT"
2258
+ ] : ["HOME", "PATH", "SHELL", "USER"];
2259
+ for (const key of safeVars) {
2260
+ if (process.env[key]) {
2261
+ env[key] = process.env[key];
2262
+ }
2263
+ }
2264
+ if (this._proxyPort) {
2265
+ const proxyUrl = `http://127.0.0.1:${this._proxyPort}`;
2266
+ env["http_proxy"] = proxyUrl;
2267
+ env["https_proxy"] = proxyUrl;
2268
+ env["HTTP_PROXY"] = proxyUrl;
2269
+ env["HTTPS_PROXY"] = proxyUrl;
2270
+ env["all_proxy"] = proxyUrl;
2271
+ env["ALL_PROXY"] = proxyUrl;
2272
+ }
2273
+ return env;
2274
+ }
1446
2275
  _assertConnected() {
1447
2276
  if (!this._connected || !this.client) {
1448
2277
  throw new Error("Not connected to target MCP server");
@@ -1476,7 +2305,15 @@ var TargetManager = class _TargetManager extends EventEmitter {
1476
2305
  var DEFAULT_HEADLESS_TIMEOUT_MS = 3e4;
1477
2306
  async function runHeadless(targetCommand, operation, opts = {}) {
1478
2307
  const [command, ...args] = targetCommand;
1479
- const target = new TargetManager(command, args);
2308
+ const target = new TargetManager(command, args, {
2309
+ sandbox: opts.sandbox,
2310
+ allowRead: opts.allowRead,
2311
+ allowWrite: opts.allowWrite,
2312
+ allowNet: opts.allowNet,
2313
+ denyRead: opts.denyRead,
2314
+ denyWrite: opts.denyWrite,
2315
+ denyNet: opts.denyNet
2316
+ });
1480
2317
  const interceptor = new ResponseInterceptor({
1481
2318
  outDir: opts.outDir,
1482
2319
  defaultTimeoutMs: opts.timeoutMs ?? DEFAULT_HEADLESS_TIMEOUT_MS
@@ -1504,21 +2341,21 @@ async function runHeadless(targetCommand, operation, opts = {}) {
1504
2341
  process.exit(hasError ? 1 : 0);
1505
2342
  } catch (err) {
1506
2343
  const msg = err.message ?? String(err);
2344
+ let exitCode;
1507
2345
  if (msg.includes("ENOENT") || msg.includes("spawn")) {
1508
2346
  process.stderr.write(
1509
2347
  `Error: command "${command}" not found. Check that it is installed and in your PATH.
1510
2348
  `
1511
2349
  );
1512
- } else if (msg.includes("timed out")) {
1513
- process.stderr.write(`Error: ${msg}
1514
- `);
2350
+ exitCode = 66;
1515
2351
  } else {
1516
2352
  process.stderr.write(`Error: ${msg}
1517
2353
  `);
2354
+ exitCode = 69;
1518
2355
  }
1519
2356
  await target.close().catch(() => {
1520
2357
  });
1521
- process.exit(1);
2358
+ process.exit(exitCode);
1522
2359
  }
1523
2360
  }
1524
2361
  async function executeOperation(target, interceptor, operation, opts) {
@@ -1535,7 +2372,7 @@ async function executeOperation(target, interceptor, operation, opts) {
1535
2372
  `);
1536
2373
  process.stderr.write(` Received: ${operation.args}
1537
2374
  `);
1538
- process.exit(2);
2375
+ process.exit(65);
1539
2376
  }
1540
2377
  } else {
1541
2378
  parsedArgs = parseHttpieArgs(trimmed);
@@ -1583,7 +2420,7 @@ async function executeOperation(target, interceptor, operation, opts) {
1583
2420
  Available tools: ${available}
1584
2421
  `
1585
2422
  );
1586
- process.exit(1);
2423
+ process.exit(64);
1587
2424
  }
1588
2425
  return { result: tool, hasError: false };
1589
2426
  }
@@ -1599,7 +2436,7 @@ Available tools: ${available}
1599
2436
  `);
1600
2437
  process.stderr.write(` Received: ${operation.args}
1601
2438
  `);
1602
- process.exit(2);
2439
+ process.exit(65);
1603
2440
  }
1604
2441
  } else {
1605
2442
  parsedArgs = parseHttpieArgs(trimmed);
@@ -1614,15 +2451,16 @@ Available tools: ${available}
1614
2451
  }
1615
2452
  }
1616
2453
 
1617
- // src/repl.ts
1618
- import { readFile as readFile2 } from "fs/promises";
2454
+ // src/repl/index.ts
2455
+ import { readFile as readFile4 } from "fs/promises";
1619
2456
  import { createInterface } from "readline";
1620
- import { checkbox, confirm, input as input2, search } from "@inquirer/prompts";
1621
- import pc2 from "picocolors";
2457
+
2458
+ // src/repl/state.ts
1622
2459
  var KNOWN_COMMANDS = [
1623
2460
  "menu",
1624
2461
  "explore",
1625
2462
  "interactive",
2463
+ "view",
1626
2464
  "tools/list",
1627
2465
  "tools/describe",
1628
2466
  "tools/call",
@@ -1664,48 +2502,259 @@ var KNOWN_COMMANDS = [
1664
2502
  "pl",
1665
2503
  "pg"
1666
2504
  ];
2505
+ var activeRl = null;
2506
+ function setActiveRl(rl) {
2507
+ activeRl = rl;
2508
+ }
2509
+ var closed = false;
2510
+ function setClosed(val) {
2511
+ closed = val;
2512
+ }
2513
+ var isScriptMode = false;
2514
+ function setIsScriptMode(val) {
2515
+ isScriptMode = val;
2516
+ }
2517
+ var globalPauseReadlineClose = false;
2518
+ function setGlobalPauseReadlineClose(val) {
2519
+ globalPauseReadlineClose = val;
2520
+ }
2521
+ var deferNextPrompt = false;
2522
+ function setDeferNextPrompt(val) {
2523
+ deferNextPrompt = val;
2524
+ }
1667
2525
  var cachedToolNames = [];
2526
+ function setCachedToolNames(names) {
2527
+ cachedToolNames = names;
2528
+ }
1668
2529
  var cachedResourceUris = [];
2530
+ function setCachedResourceUris(uris) {
2531
+ cachedResourceUris = uris;
2532
+ }
1669
2533
  var cachedPromptNames = [];
2534
+ function setCachedPromptNames(names) {
2535
+ cachedPromptNames = names;
2536
+ }
1670
2537
  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;
1682
- }
1683
- async function refreshCaches(target) {
1684
- const caps = target.getServerCapabilities() ?? {};
2538
+ function setActiveCapabilities(caps) {
1685
2539
  activeCapabilities = caps;
2540
+ }
2541
+ var tabCycleState = null;
2542
+ function setTabCycleState(state) {
2543
+ tabCycleState = state;
2544
+ }
2545
+ var callHistory = [];
2546
+ var lastToolArgsMap = /* @__PURE__ */ new Map();
2547
+
2548
+ // src/repl/history.ts
2549
+ import { readFile as readFile2, mkdir as mkdir2, appendFile } from "fs/promises";
2550
+ import { existsSync as existsSync4 } from "fs";
2551
+ import { join as join4 } from "path";
2552
+ import { homedir as homedir3 } from "os";
2553
+ var RUN_MCP_DIR = join4(homedir3(), ".run-mcp");
2554
+ var HISTORY_FILE = join4(RUN_MCP_DIR, "history");
2555
+ var replHistory = [];
2556
+ async function ensureRunMcpDir() {
1686
2557
  try {
1687
- const { tools } = await target.listTools();
1688
- cachedToolNames = tools.map((t) => t.name);
2558
+ await mkdir2(RUN_MCP_DIR, { recursive: true });
1689
2559
  } catch {
1690
2560
  }
1691
- if (caps.resources) {
2561
+ }
2562
+ async function loadHistory() {
2563
+ await ensureRunMcpDir();
2564
+ if (existsSync4(HISTORY_FILE)) {
1692
2565
  try {
1693
- const { resources } = await target.listResources();
1694
- cachedResourceUris = resources.map((r) => r.uri);
2566
+ const content = await readFile2(HISTORY_FILE, "utf8");
2567
+ const lines = content.split("\n").map((l) => l.trim()).filter(Boolean);
2568
+ replHistory.length = 0;
2569
+ replHistory.push(...lines);
1695
2570
  } catch {
1696
2571
  }
1697
2572
  }
1698
- if (caps.prompts) {
2573
+ }
2574
+ async function appendToHistoryFile(line) {
2575
+ await ensureRunMcpDir();
2576
+ try {
2577
+ await appendFile(HISTORY_FILE, line + "\n", "utf8");
2578
+ } catch {
2579
+ }
2580
+ }
2581
+
2582
+ // src/repl/wizard.ts
2583
+ import { readFile as readFile3, writeFile as writeFile2 } from "fs/promises";
2584
+ import { existsSync as existsSync5 } from "fs";
2585
+ import { join as join5 } from "path";
2586
+ var WIZARD_DEFAULTS_FILE = join5(RUN_MCP_DIR, "wizard_defaults.json");
2587
+ async function loadWizardDefaults() {
2588
+ await ensureRunMcpDir();
2589
+ if (existsSync5(WIZARD_DEFAULTS_FILE)) {
1699
2590
  try {
1700
- const { prompts } = await target.listPrompts();
1701
- cachedPromptNames = prompts.map((p) => p.name);
2591
+ const content = await readFile3(WIZARD_DEFAULTS_FILE, "utf8");
2592
+ const parsed = JSON.parse(content);
2593
+ lastToolArgsMap.clear();
2594
+ for (const [key, val] of Object.entries(parsed)) {
2595
+ lastToolArgsMap.set(key, val);
2596
+ }
1702
2597
  } catch {
1703
2598
  }
1704
2599
  }
1705
2600
  }
1706
- var tabCycleState = null;
2601
+ async function saveWizardDefaults() {
2602
+ await ensureRunMcpDir();
2603
+ try {
2604
+ const obj = Object.fromEntries(lastToolArgsMap.entries());
2605
+ await writeFile2(WIZARD_DEFAULTS_FILE, JSON.stringify(obj, null, 2), "utf8");
2606
+ } catch {
2607
+ }
2608
+ }
2609
+
2610
+ // src/repl/ui.ts
2611
+ function printResultBlock(opts) {
2612
+ const colorFn = colors[opts.labelColor];
2613
+ const elapsedStr = opts.elapsed < 1e3 ? `${opts.elapsed}ms` : `${(opts.elapsed / 1e3).toFixed(1)}s`;
2614
+ const detail = opts.detail ?? opts.toolName ?? "";
2615
+ console.log(` ${colorFn(opts.label)} ${colors.dim(detail)} ${colors.dim(`(${elapsedStr})`)}`);
2616
+ console.log(colors.dim(` ${"\u2500".repeat(60)}`));
2617
+ }
2618
+ function printShortHelp() {
2619
+ const hasTools = !!activeCapabilities?.tools;
2620
+ const hasResources = !!activeCapabilities?.resources;
2621
+ const hasPrompts = !!activeCapabilities?.prompts;
2622
+ const tC = hasTools ? colors.green : colors.dim;
2623
+ const rC = hasResources ? colors.green : colors.dim;
2624
+ const pC = hasPrompts ? colors.green : colors.dim;
2625
+ console.log(`
2626
+ ${colors.bold("Quick Reference:")}
2627
+
2628
+ ${tC("tl")} ${tC("tools/list")} ${rC("rl")} ${rC("resources/list")} ${pC("pl")} ${pC("prompts/list")}
2629
+ ${tC("td")} ${tC("tools/describe")} ${rC("rr")} ${rC("resources/read")} ${pC("pg")} ${pC("prompts/get")}
2630
+ ${tC("tc")} ${tC("tools/call")} ${rC("rt")} ${rC("resources/templates")}
2631
+ ${tC("ts")} ${tC("tools/scaffold")} ${rC("rs")} ${rC("resources/subscribe")}
2632
+ ${rC("ru")} ${rC("resources/unsubscribe")}
2633
+
2634
+ ${colors.green("ping")} ${colors.green("status")} ${colors.green("timing")} ${colors.green("history")} ${colors.green("!!")} ${colors.green("explore")} ${colors.green("reconnect")}
2635
+
2636
+ ${colors.dim("Type 'help' for full command reference.")}
2637
+ `);
2638
+ }
2639
+ function printHelp() {
2640
+ const hasTools = !!activeCapabilities?.tools;
2641
+ const hasResources = !!activeCapabilities?.resources;
2642
+ const hasPrompts = !!activeCapabilities?.prompts;
2643
+ const hasLogging = !!activeCapabilities?.logging;
2644
+ const tC = hasTools ? colors.green : colors.dim;
2645
+ const rC = hasResources ? colors.green : colors.dim;
2646
+ const pC = hasPrompts ? colors.green : colors.dim;
2647
+ const lC = hasLogging ? colors.green : colors.dim;
2648
+ const tD = hasTools ? (s) => s : colors.dim;
2649
+ const rD = hasResources ? (s) => s : colors.dim;
2650
+ const pD = hasPrompts ? (s) => s : colors.dim;
2651
+ const lD = hasLogging ? (s) => s : colors.dim;
2652
+ const tH = hasTools ? colors.bold("Tool Commands:") : colors.dim(colors.bold("Tool Commands:")) + colors.dim(" (Unsupported)");
2653
+ const rH = hasResources ? colors.bold("Resource Commands:") : colors.dim(colors.bold("Resource Commands:")) + colors.dim(" (Unsupported)");
2654
+ const pH = hasPrompts ? colors.bold("Prompt Commands:") : colors.dim(colors.bold("Prompt Commands:")) + colors.dim(" (Unsupported)");
2655
+ console.log(`
2656
+ ${tH}
2657
+
2658
+ ${tC("tools/list")} ${tD("List all available tools")}
2659
+ ${tC("tools/describe")} <name> ${tD("Show a tool's input schema")}
2660
+ ${tC("tools/call")} <name> [json] [opts] ${tD("Call a tool (interactive if no json)")}
2661
+ ${tD("Options:")} ${colors.dim("--timeout <ms>")} ${tD("Override default timeout (60s)")}
2662
+ ${colors.dim("--clear")} ${tD("Ignore remembered argument defaults")}
2663
+ ${tC("tools/scaffold")} <name> ${tD("Generate a template for a tool's arguments")}
2664
+ ${tC("tools/forget")} [name] ${tD("Clear remembered interactive defaults")}
2665
+
2666
+ ${rH}
2667
+
2668
+ ${rC("resources/list")} ${rD("List all available resources")}
2669
+ ${rC("resources/read")} <uri> ${rD("Read a resource by URI")}
2670
+ ${rC("resources/templates")} ${rD("List resource templates")}
2671
+ ${rC("resources/subscribe")} <uri> ${rD("Subscribe to resource changes")}
2672
+ ${rC("resources/unsubscribe")} <uri> ${rD("Unsubscribe from resource changes")}
2673
+
2674
+ ${pH}
2675
+
2676
+ ${pC("prompts/list")} ${pD("List all available prompts")}
2677
+ ${pC("prompts/get")} <name> [json_args] ${pD("Get a prompt with arguments")}
2678
+
2679
+ ${colors.bold("Protocol Commands:")}
2680
+
2681
+ ${colors.green("ping")} Verify connection, show round-trip time
2682
+ ${lC("log-level")} <level> ${lD("Set server logging verbosity")}${hasLogging ? "" : colors.dim(" (Unsupported)")}
2683
+ ${colors.green("history")} [count|clear] Show request/response history
2684
+ ${colors.green("notifications")} [count|clear] Show server notifications
2685
+
2686
+ ${colors.bold("Roots Management:")}
2687
+
2688
+ ${colors.green("roots/list")} Show configured client roots
2689
+ ${colors.green("roots/add")} <uri> [name] Add a root directory
2690
+ ${colors.green("roots/remove")} <uri> Remove a root directory
2691
+
2692
+ ${colors.bold("Session Commands:")}
2693
+
2694
+ ${colors.green("!!")} / ${colors.green("last")} Re-run the last command
2695
+ ${colors.green("reconnect")} Disconnect and reconnect to the server
2696
+ ${colors.green("timing")} Show tool call performance stats
2697
+ ${colors.green("status")} Show target server status
2698
+ ${colors.green("help")} Show this help
2699
+ ${colors.green("exit")} / ${colors.green("quit")} Disconnect and exit
2700
+
2701
+ ${colors.bold("Shortcuts:")}
2702
+
2703
+ ${tC("tl")} ${tC("tools/list")} ${rC("rl")} ${rC("resources/list")} ${pC("pl")} ${pC("prompts/list")}
2704
+ ${tC("td")} ${tC("tools/describe")} ${rC("rr")} ${rC("resources/read")} ${pC("pg")} ${pC("prompts/get")}
2705
+ ${tC("tc")} ${tC("tools/call")} ${rC("rt")} ${rC("resources/templates")}
2706
+ ${tC("ts")} ${tC("tools/scaffold")} ${rC("rs")} ${rC("resources/subscribe")}
2707
+ ${rC("ru")} ${rC("resources/unsubscribe")}
2708
+
2709
+ ${colors.dim("Lines starting with # are treated as comments.")}
2710
+ ${colors.dim('JSON arguments can contain spaces: tools/call say {"message": "hello world"}')}
2711
+ ${colors.dim("Run tools/call <name> without JSON for interactive argument prompting.")}
2712
+ ${colors.dim("Use tools/call <name> --clear to ignore remembered defaults.")}
2713
+ `);
2714
+ }
2715
+ function stripAnsi(str) {
2716
+ return str.replace(/\x1B\[\d+m/g, "");
2717
+ }
2718
+ var BOX_WIDTH = 58;
2719
+ function padLine(content) {
2720
+ const clean = stripAnsi(content);
2721
+ const padding = Math.max(0, BOX_WIDTH - clean.length);
2722
+ return `${colors.cyan(" \u2502")}${content}${"".padEnd(padding)}${colors.cyan("\u2502")}`;
2723
+ }
2724
+ function printBanner(serverName, serverVersion, toolCount, resourceCount, promptCount, harnessMode) {
2725
+ const parts = [];
2726
+ parts.push(`${colors.bold(toolCount.toString())} tools`);
2727
+ if (resourceCount > 0) parts.push(`${colors.bold(resourceCount.toString())} resources`);
2728
+ if (promptCount > 0) parts.push(`${colors.bold(promptCount.toString())} prompts`);
2729
+ const baseTitle = serverVersion ? `${serverName} ${colors.dim(`v${serverVersion}`)}` : serverName;
2730
+ const title = harnessMode ? `${baseTitle} ${colors.bgBlue(colors.white(" AGENT HARNESS "))}` : baseTitle;
2731
+ console.log(colors.cyan(` \u250C${"\u2500".repeat(BOX_WIDTH)}\u2510`));
2732
+ console.log(padLine(` Connected to ${title}`));
2733
+ console.log(padLine(` Discovered ${parts.join(", ")}`));
2734
+ console.log(colors.cyan(` \u251C${"\u2500".repeat(BOX_WIDTH)}\u2524`));
2735
+ console.log(padLine(` ${colors.green("tools/list")} See all tools`));
2736
+ console.log(padLine(` ${colors.green("tools/call")} ${colors.dim("<name>")} Call a tool`));
2737
+ console.log(padLine(` ${colors.green("help")} All commands`));
2738
+ console.log(padLine(""));
2739
+ console.log(padLine(colors.dim(" Tab completion is active. Start typing to explore.")));
2740
+ console.log(colors.cyan(` \u2514${"\u2500".repeat(BOX_WIDTH)}\u2518`));
2741
+ }
2742
+
2743
+ // src/repl/completer.ts
1707
2744
  function resetTabCycle() {
1708
- tabCycleState = null;
2745
+ setTabCycleState(null);
2746
+ }
2747
+ function getActiveCommands() {
2748
+ let commands = [...KNOWN_COMMANDS];
2749
+ if (!activeCapabilities?.resources) {
2750
+ commands = commands.filter(
2751
+ (c) => !c.startsWith("resources/") && !["rl", "rr", "rt", "rs", "ru"].includes(c)
2752
+ );
2753
+ }
2754
+ if (!activeCapabilities?.prompts) {
2755
+ commands = commands.filter((c) => !c.startsWith("prompts/") && !["pl", "pg"].includes(c));
2756
+ }
2757
+ return commands;
1709
2758
  }
1710
2759
  function computeMatches(line) {
1711
2760
  const expanded = resolveAlias(line);
@@ -1734,8 +2783,12 @@ var completer = (line) => {
1734
2783
  if (tabCycleState) {
1735
2784
  const inCycle = line === tabCycleState.original || tabCycleState.matches.includes(line);
1736
2785
  if (inCycle) {
1737
- tabCycleState.index = (tabCycleState.index + 1) % tabCycleState.matches.length;
1738
- const next = tabCycleState.matches[tabCycleState.index];
2786
+ const nextIndex = (tabCycleState.index + 1) % tabCycleState.matches.length;
2787
+ setTabCycleState({
2788
+ ...tabCycleState,
2789
+ index: nextIndex
2790
+ });
2791
+ const next = tabCycleState.matches[nextIndex];
1739
2792
  setImmediate(() => {
1740
2793
  if (activeRl) {
1741
2794
  activeRl.line = next;
@@ -1745,421 +2798,70 @@ var completer = (line) => {
1745
2798
  });
1746
2799
  return [[], ""];
1747
2800
  }
1748
- tabCycleState = null;
2801
+ setTabCycleState(null);
1749
2802
  }
1750
2803
  const [matches, matchLine] = computeMatches(line);
1751
2804
  if (matches.length > 1) {
1752
- tabCycleState = { matches, index: -1, original: line };
2805
+ setTabCycleState({ matches, index: -1, original: line });
1753
2806
  }
1754
2807
  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
- }
2808
+ };
2809
+ async function refreshCaches(target) {
2810
+ const caps = target.getServerCapabilities() ?? {};
2811
+ setActiveCapabilities(caps);
2812
+ try {
2813
+ const { tools } = await target.listTools();
2814
+ setCachedToolNames(tools.map((t) => t.name));
2815
+ } catch {
2816
+ }
2817
+ if (caps.resources) {
2818
+ try {
2819
+ const { resources } = await target.listResources();
2820
+ setCachedResourceUris(resources.map((r) => r.uri));
2821
+ } catch {
2015
2822
  }
2016
- } catch (err) {
2017
- console.log(pc2.yellow(` Warning: Could not list tools: ${err.message}
2018
- `));
2019
2823
  }
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;
2824
+ if (caps.prompts) {
2825
+ try {
2826
+ const { prompts } = await target.listPrompts();
2827
+ setCachedPromptNames(prompts.map((p) => p.name));
2828
+ } catch {
2077
2829
  }
2078
- console.log(pc2.dim("\nShutting down..."));
2079
- await target.close();
2080
- process.exit(0);
2081
- } else {
2082
- mainMenuLoop(target, interceptor);
2083
2830
  }
2084
2831
  }
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
- });
2832
+
2833
+ // src/repl/commands.ts
2834
+ import { exec as exec2 } from "child_process";
2835
+ import { checkbox, confirm, input as input2, search } from "@inquirer/prompts";
2836
+ var lastCommand = null;
2837
+ var AbortFlowError = class extends Error {
2838
+ constructor() {
2839
+ super("Aborted by user.");
2840
+ this.name = "AbortFlowError";
2103
2841
  }
2104
- if (!deferNextPrompt) {
2105
- rl.prompt();
2842
+ };
2843
+ function isAbortError(err) {
2844
+ if (!err) return false;
2845
+ return err.name === "ExitPromptError" || err.name === "AbortError" || err.message === "Prompt was aborted" || typeof err.message === "string" && err.message.includes("User force closed");
2846
+ }
2847
+ async function withSuspendedReadline(target, interceptor, fn) {
2848
+ const wasActive = !!activeRl;
2849
+ if (wasActive) {
2850
+ setGlobalPauseReadlineClose(true);
2851
+ activeRl.close();
2852
+ setActiveRl(null);
2106
2853
  }
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
- });
2854
+ try {
2855
+ return await fn();
2856
+ } finally {
2857
+ if (wasActive) {
2858
+ setGlobalPauseReadlineClose(false);
2859
+ if (!isScriptMode) {
2860
+ setDeferNextPrompt(true);
2861
+ startReadlineLoop(target, interceptor);
2140
2862
  }
2141
2863
  }
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
- });
2864
+ }
2163
2865
  }
2164
2866
  async function handleCommand(input3, target, interceptor) {
2165
2867
  const expanded = resolveAlias(input3);
@@ -2175,14 +2877,32 @@ async function handleCommand(input3, target, interceptor) {
2175
2877
  case "?":
2176
2878
  printShortHelp();
2177
2879
  return;
2880
+ case "view": {
2881
+ const filepath = rest.trim();
2882
+ if (!filepath) {
2883
+ console.error(colors.red("Error: Please specify a file path to view."));
2884
+ return;
2885
+ }
2886
+ const isMac = process.platform === "darwin";
2887
+ const isWin = process.platform === "win32";
2888
+ const cmdToRun = isMac ? "open" : isWin ? "start" : "xdg-open";
2889
+ exec2(`${cmdToRun} "${filepath}"`, (err) => {
2890
+ if (err) {
2891
+ console.error(colors.red(`Error opening file: ${err.message}`));
2892
+ } else {
2893
+ console.log(colors.green(`Opened ${filepath}`));
2894
+ }
2895
+ });
2896
+ return;
2897
+ }
2178
2898
  case "menu":
2179
2899
  case "explore":
2180
2900
  case "interactive":
2181
2901
  if (activeRl) {
2182
- globalPauseReadlineClose = true;
2902
+ setGlobalPauseReadlineClose(true);
2183
2903
  activeRl.close();
2184
- activeRl = null;
2185
- globalPauseReadlineClose = false;
2904
+ setActiveRl(null);
2905
+ setGlobalPauseReadlineClose(false);
2186
2906
  }
2187
2907
  mainMenuLoop(target, interceptor);
2188
2908
  return;
@@ -2249,10 +2969,10 @@ async function handleCommand(input3, target, interceptor) {
2249
2969
  case "!!":
2250
2970
  case "last":
2251
2971
  if (lastCommand) {
2252
- console.log(pc2.dim(` Re-running: ${lastCommand}`));
2972
+ console.log(colors.dim(` Re-running: ${lastCommand}`));
2253
2973
  return await handleCommand(lastCommand, target, interceptor);
2254
2974
  } else {
2255
- console.log(pc2.yellow("No previous command to re-run."));
2975
+ console.log(colors.yellow("No previous command to re-run."));
2256
2976
  }
2257
2977
  return;
2258
2978
  case "status":
@@ -2260,7 +2980,7 @@ async function handleCommand(input3, target, interceptor) {
2260
2980
  return;
2261
2981
  case "exit":
2262
2982
  case "quit": {
2263
- console.log(pc2.dim("Shutting down..."));
2983
+ console.log(colors.dim("Shutting down..."));
2264
2984
  await target.close();
2265
2985
  process.exit(0);
2266
2986
  return;
@@ -2271,11 +2991,11 @@ async function handleCommand(input3, target, interceptor) {
2271
2991
  }
2272
2992
  const suggestion = suggestCommand(cmd, getActiveCommands());
2273
2993
  if (suggestion) {
2274
- console.log(pc2.yellow(`Unknown command: ${cmd}.`));
2994
+ console.log(colors.yellow(`Unknown command: ${cmd}.`));
2275
2995
  try {
2276
2996
  await withSuspendedReadline(target, interceptor, async () => {
2277
2997
  const runIt = await confirm({
2278
- message: `Did you mean ${pc2.bold(suggestion)}?`,
2998
+ message: `Did you mean ${colors.bold(suggestion)}?`,
2279
2999
  default: true
2280
3000
  });
2281
3001
  if (runIt) {
@@ -2288,7 +3008,7 @@ async function handleCommand(input3, target, interceptor) {
2288
3008
  throw new AbortFlowError();
2289
3009
  }
2290
3010
  } else {
2291
- console.log(pc2.yellow(`Unknown command: ${cmd}. Type ${pc2.bold("help")} for usage.`));
3011
+ console.log(colors.yellow(`Unknown command: ${cmd}. Type ${colors.bold("help")} for usage.`));
2292
3012
  }
2293
3013
  }
2294
3014
  }
@@ -2296,25 +3016,25 @@ async function handleCommand(input3, target, interceptor) {
2296
3016
  async function cmdToolsList(target) {
2297
3017
  const { tools } = await target.listTools();
2298
3018
  if (tools.length === 0) {
2299
- console.log(pc2.dim(" No tools available."));
3019
+ console.log(colors.dim(" No tools available."));
2300
3020
  return;
2301
3021
  }
2302
3022
  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)}`));
3023
+ console.log(colors.bold(` ${"Name".padEnd(nameWidth)} Description`));
3024
+ console.log(colors.dim(` ${"\u2500".repeat(nameWidth)} ${"\u2500".repeat(50)}`));
2305
3025
  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}`);
3026
+ const desc = tool.description ? tool.description.length > 60 ? `${tool.description.slice(0, 57)}...` : tool.description : colors.dim("(no description)");
3027
+ console.log(` ${colors.green(tool.name.padEnd(nameWidth))} ${desc}`);
2308
3028
  }
2309
- console.log(pc2.dim(`
3029
+ console.log(colors.dim(`
2310
3030
  ${tools.length} tool(s) total.`));
2311
3031
  if (tools.length >= 10) {
2312
3032
  const groups = groupToolsByPrefix(tools.map((t) => t.name));
2313
3033
  if (!groups.has("All")) {
2314
3034
  console.log();
2315
- console.log(pc2.bold(" Groups:"));
3035
+ console.log(colors.bold(" Groups:"));
2316
3036
  for (const [label, members] of groups) {
2317
- console.log(` ${pc2.cyan(label.padEnd(16))} ${pc2.dim(members.join(", "))}`);
3037
+ console.log(` ${colors.cyan(label.padEnd(16))} ${colors.dim(members.join(", "))}`);
2318
3038
  }
2319
3039
  }
2320
3040
  }
@@ -2322,28 +3042,28 @@ async function cmdToolsList(target) {
2322
3042
  async function cmdToolsDescribe(target, rest) {
2323
3043
  const name = rest.trim();
2324
3044
  if (!name) {
2325
- console.log(pc2.yellow(" Usage: tools/describe <name>"));
3045
+ console.log(colors.yellow(" Usage: tools/describe <name>"));
2326
3046
  if (cachedToolNames.length > 0) {
2327
3047
  const preview = cachedToolNames.slice(0, 6);
2328
3048
  const more = cachedToolNames.length > 6 ? `, ... (${cachedToolNames.length} total)` : "";
2329
- console.log(pc2.dim(`
3049
+ console.log(colors.dim(`
2330
3050
  Available tools: ${preview.join(", ")}${more}`));
2331
- console.log(pc2.dim(` Type ${pc2.bold("tools/list")} for all.`));
3051
+ console.log(colors.dim(` Type ${colors.bold("tools/list")} for all.`));
2332
3052
  }
2333
3053
  return;
2334
3054
  }
2335
3055
  const { tools } = await target.listTools();
2336
3056
  const tool = tools.find((t) => t.name === name);
2337
3057
  if (!tool) {
2338
- console.log(pc2.red(`Tool "${name}" not found.`));
3058
+ console.log(colors.red(`Tool "${name}" not found.`));
2339
3059
  const suggestion = suggestCommand(
2340
3060
  name,
2341
3061
  tools.map((t) => t.name)
2342
3062
  );
2343
3063
  if (suggestion) {
2344
- console.log(pc2.yellow(`Did you mean ${pc2.bold(suggestion)}?`));
3064
+ console.log(colors.yellow(`Did you mean ${colors.bold(suggestion)}?`));
2345
3065
  } else {
2346
- console.log(pc2.dim(`Available: ${tools.map((t) => t.name).join(", ")}`));
3066
+ console.log(colors.dim(`Available: ${tools.map((t) => t.name).join(", ")}`));
2347
3067
  }
2348
3068
  return;
2349
3069
  }
@@ -2374,14 +3094,14 @@ async function cmdToolsCall(target, interceptor, rest) {
2374
3094
  if (!picked) return;
2375
3095
  return cmdToolsCall(target, interceptor, picked);
2376
3096
  }
2377
- console.log(pc2.yellow(" Usage: tools/call <name> [json_args] [--timeout <ms>]"));
3097
+ console.log(colors.yellow(" Usage: tools/call <name> [json_args] [--timeout <ms>]"));
2378
3098
  if (cachedToolNames.length > 0) {
2379
3099
  const preview = cachedToolNames.slice(0, 6);
2380
3100
  const more = cachedToolNames.length > 6 ? `, ... (${cachedToolNames.length} total)` : "";
2381
- console.log(pc2.dim(`
3101
+ console.log(colors.dim(`
2382
3102
  Available tools: ${preview.join(", ")}${more}`));
2383
3103
  console.log(
2384
- pc2.dim(` Run without args for ${pc2.bold("interactive mode")}: tools/call <name>`)
3104
+ colors.dim(` Run without args for ${colors.bold("interactive mode")}: tools/call <name>`)
2385
3105
  );
2386
3106
  }
2387
3107
  return;
@@ -2393,31 +3113,31 @@ async function cmdToolsCall(target, interceptor, rest) {
2393
3113
  try {
2394
3114
  args = JSON.parse(trimmed);
2395
3115
  } catch (err) {
2396
- console.error(pc2.red(`Invalid JSON: ${err.message}`));
2397
- console.log(pc2.dim(` Received: ${jsonArgs}`));
3116
+ console.error(colors.red(`Invalid JSON: ${err.message}`));
3117
+ console.log(colors.dim(` Received: ${jsonArgs}`));
2398
3118
  return;
2399
3119
  }
2400
3120
  } else {
2401
3121
  try {
2402
3122
  args = parseHttpieArgs(trimmed);
2403
3123
  } catch (err) {
2404
- console.error(pc2.red(`Invalid shorthand arguments: ${err.message}`));
3124
+ console.error(colors.red(`Invalid shorthand arguments: ${err.message}`));
2405
3125
  return;
2406
3126
  }
2407
3127
  }
2408
3128
  const { tools } = await target.listTools();
2409
3129
  const tool = tools.find((t) => t.name === toolName);
2410
3130
  if (!tool) {
2411
- console.log(pc2.red(`
3131
+ console.log(colors.red(`
2412
3132
  \u2717 Tool "${toolName}" not found.`));
2413
3133
  const toolNames = tools.map((t) => t.name);
2414
3134
  const suggestion = suggestCommand(toolName, toolNames);
2415
3135
  if (suggestion) {
2416
- console.log(pc2.yellow(` \u{1F4A1} Did you mean "${suggestion}"?`));
3136
+ console.log(colors.yellow(` \u{1F4A1} Did you mean "${suggestion}"?`));
2417
3137
  } else {
2418
3138
  const preview = toolNames.slice(0, 6);
2419
3139
  const more = toolNames.length > 6 ? `, ... (${toolNames.length} total)` : "";
2420
- console.log(pc2.dim(` Available tools: ${preview.join(", ")}${more}`));
3140
+ console.log(colors.dim(` Available tools: ${preview.join(", ")}${more}`));
2421
3141
  }
2422
3142
  return { isError: true, content: [{ type: "text", text: `Tool not found: ${toolName}` }] };
2423
3143
  }
@@ -2425,14 +3145,14 @@ async function cmdToolsCall(target, interceptor, rest) {
2425
3145
  const required = schema.required ?? [];
2426
3146
  const missing = required.filter((r) => !(r in args));
2427
3147
  if (missing.length > 0) {
2428
- console.log(pc2.yellow(`
3148
+ console.log(colors.yellow(`
2429
3149
  Missing required arguments: ${missing.join(", ")}`));
2430
3150
  console.log();
2431
3151
  const scaffolded = scaffoldArgs(schema);
2432
- console.log(pc2.dim(" Try:"));
3152
+ console.log(colors.dim(" Try:"));
2433
3153
  console.log(` tools/call ${toolName} ${scaffolded}`);
2434
3154
  console.log();
2435
- console.log(pc2.dim(" Or run without args for interactive mode:"));
3155
+ console.log(colors.dim(" Or run without args for interactive mode:"));
2436
3156
  console.log(` tools/call ${toolName}`);
2437
3157
  console.log();
2438
3158
  return;
@@ -2444,17 +3164,21 @@ async function cmdToolsCall(target, interceptor, rest) {
2444
3164
  if (!isScriptMode) {
2445
3165
  const fullCmd = `tools/call ${toolName} ${JSON.stringify(args)}`;
2446
3166
  replHistory.push(fullCmd);
3167
+ appendToHistoryFile(fullCmd).catch(() => {
3168
+ });
2447
3169
  if (activeRl) {
2448
3170
  activeRl.history.unshift(fullCmd);
2449
3171
  }
2450
3172
  }
2451
3173
  }
2452
- console.log(pc2.dim(` Calling ${toolName}...`));
3174
+ console.log(colors.dim(` Calling ${toolName}...`));
2453
3175
  const startTime = Date.now();
2454
3176
  const result = await interceptor.callTool(target, toolName, args, timeoutMs);
2455
3177
  const elapsed = Date.now() - startTime;
2456
3178
  callHistory.push({ toolName, durationMs: elapsed, timestamp: startTime });
2457
3179
  lastToolArgsMap.set(toolName, { ...args });
3180
+ saveWizardDefaults().catch(() => {
3181
+ });
2458
3182
  const isError = result.isError === true;
2459
3183
  console.log();
2460
3184
  printResultBlock({
@@ -2468,17 +3192,17 @@ async function cmdToolsCall(target, interceptor, rest) {
2468
3192
  for (const item of content) {
2469
3193
  if (item.type === "text") {
2470
3194
  if (isError) {
2471
- console.log(pc2.red(` \u2717 ${item.text}`));
3195
+ console.log(colors.red(` \u2717 ${item.text}`));
2472
3196
  } else {
2473
3197
  try {
2474
3198
  const parsed = JSON.parse(item.text);
2475
3199
  if (typeof parsed === "object" && parsed !== null) {
2476
3200
  console.log(formatJson(parsed, 2, true));
2477
3201
  } else {
2478
- console.log(pc2.yellow(` ${item.text}`));
3202
+ console.log(colors.yellow(` ${item.text}`));
2479
3203
  }
2480
3204
  } catch {
2481
- console.log(pc2.yellow(` ${item.text}`));
3205
+ console.log(colors.yellow(` ${item.text}`));
2482
3206
  }
2483
3207
  }
2484
3208
  } else {
@@ -2492,7 +3216,7 @@ async function cmdToolsCall(target, interceptor, rest) {
2492
3216
  const errText = Array.isArray(content) ? content.map((c) => c.text || "").join(" ").toLowerCase() : typeof content === "object" ? (content.text || "").toLowerCase() : "";
2493
3217
  if (errText.includes("argument") || errText.includes("validation") || errText.includes("schema") || errText.includes("missing") || errText.includes("invalid")) {
2494
3218
  console.log(
2495
- pc2.yellow(
3219
+ colors.yellow(
2496
3220
  ` \u{1F4A1} Tip: Check the tool arguments via 'tools/describe ${toolName}'
2497
3221
  or view the raw server stderr above.`
2498
3222
  )
@@ -2506,15 +3230,15 @@ async function interactiveArgPrompt(target, interceptor, toolName, clearPrevious
2506
3230
  const { tools } = await target.listTools();
2507
3231
  const tool = tools.find((t) => t.name === toolName);
2508
3232
  if (!tool) {
2509
- console.log(pc2.red(`Tool "${toolName}" not found.`));
3233
+ console.log(colors.red(`Tool "${toolName}" not found.`));
2510
3234
  const suggestion = suggestCommand(
2511
3235
  toolName,
2512
3236
  tools.map((t) => t.name)
2513
3237
  );
2514
3238
  if (suggestion) {
2515
- console.log(pc2.yellow(`Did you mean ${pc2.bold(suggestion)}?`));
3239
+ console.log(colors.yellow(`Did you mean ${colors.bold(suggestion)}?`));
2516
3240
  } else {
2517
- console.log(pc2.dim(`Available: ${tools.map((t) => t.name).join(", ")}`));
3241
+ console.log(colors.dim(`Available: ${tools.map((t) => t.name).join(", ")}`));
2518
3242
  }
2519
3243
  return null;
2520
3244
  }
@@ -2524,9 +3248,9 @@ async function interactiveArgPrompt(target, interceptor, toolName, clearPrevious
2524
3248
  return {};
2525
3249
  }
2526
3250
  if (isScriptMode) {
2527
- console.log(pc2.yellow(` Tool "${toolName}" requires arguments.`));
3251
+ console.log(colors.yellow(` Tool "${toolName}" requires arguments.`));
2528
3252
  const scaffolded = scaffoldArgs(schema);
2529
- console.log(pc2.dim(` Usage: tools/call ${toolName} ${scaffolded}`));
3253
+ console.log(colors.dim(` Usage: tools/call ${toolName} ${scaffolded}`));
2530
3254
  return null;
2531
3255
  }
2532
3256
  const required = schema.required ?? [];
@@ -2535,10 +3259,10 @@ async function interactiveArgPrompt(target, interceptor, toolName, clearPrevious
2535
3259
  const optionalProps = allProps.filter(([name]) => !required.includes(name));
2536
3260
  const previousArgs = clearPrevious ? void 0 : lastToolArgsMap.get(toolName);
2537
3261
  console.log();
2538
- console.log(` ${pc2.bold(tool.name)}${tool.description ? pc2.dim(` \u2014 ${tool.description}`) : ""}`);
3262
+ console.log(` ${colors.bold(tool.name)}${tool.description ? colors.dim(` \u2014 ${tool.description}`) : ""}`);
2539
3263
  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."));
3264
+ console.log(colors.dim(` Previous: ${JSON.stringify(previousArgs)}`));
3265
+ console.log(colors.dim(" Press Enter to reuse values, or type to override."));
2542
3266
  }
2543
3267
  console.log();
2544
3268
  const collectedArgs = {};
@@ -2556,7 +3280,7 @@ async function interactiveArgPrompt(target, interceptor, toolName, clearPrevious
2556
3280
  const typeStr = prop.type ?? "any";
2557
3281
  const desc = prop.description ?? "";
2558
3282
  const prevVal = previousArgs?.[name];
2559
- const label = desc ? `${name} ${pc2.dim(`(${typeStr})`)} ${pc2.dim(desc)}` : `${name} ${pc2.dim(`(${typeStr})`)}`;
3283
+ const label = desc ? `${name} ${colors.dim(`(${typeStr})`)} ${colors.dim(desc)}` : `${name} ${colors.dim(`(${typeStr})`)}`;
2560
3284
  const answerStr = await input2(
2561
3285
  {
2562
3286
  message: label,
@@ -2598,7 +3322,7 @@ async function interactiveArgPrompt(target, interceptor, toolName, clearPrevious
2598
3322
  const typeStr = prop.type ?? "any";
2599
3323
  const desc = prop.description ?? "";
2600
3324
  const prevVal = previousArgs?.[name];
2601
- const label = desc ? `${name} ${pc2.dim(`(${typeStr})`)} ${pc2.dim(desc)}` : `${name} ${pc2.dim(`(${typeStr})`)}`;
3325
+ const label = desc ? `${name} ${colors.dim(`(${typeStr})`)} ${colors.dim(desc)}` : `${name} ${colors.dim(`(${typeStr})`)}`;
2602
3326
  const answerStr = await input2(
2603
3327
  {
2604
3328
  message: label,
@@ -2637,14 +3361,14 @@ function printJsonTemplate(filled, allProps, currentProp) {
2637
3361
  const parts = [];
2638
3362
  for (const [name] of allProps) {
2639
3363
  if (name in filled) {
2640
- parts.push(`"${name}": ${pc2.green(JSON.stringify(filled[name]))}`);
3364
+ parts.push(`"${name}": ${colors.green(JSON.stringify(filled[name]))}`);
2641
3365
  } else if (name === currentProp) {
2642
- parts.push(`"${name}": ${pc2.yellow("\u2592")}`);
3366
+ parts.push(`"${name}": ${colors.yellow("\u2592")}`);
2643
3367
  } else {
2644
- parts.push(`"${name}": ${pc2.dim("\u2592")}`);
3368
+ parts.push(`"${name}": ${colors.dim("\u2592")}`);
2645
3369
  }
2646
3370
  }
2647
- console.log(pc2.dim(" { ") + parts.join(pc2.dim(", ")) + pc2.dim(" }"));
3371
+ console.log(colors.dim(" { ") + parts.join(colors.dim(", ")) + colors.dim(" }"));
2648
3372
  console.log();
2649
3373
  }
2650
3374
  function coerceValue(input3, type) {
@@ -2661,7 +3385,7 @@ function coerceValue(input3, type) {
2661
3385
  }
2662
3386
  }
2663
3387
  function question(rl, prompt) {
2664
- return new Promise((resolve2, reject) => {
3388
+ return new Promise((resolve3, reject) => {
2665
3389
  let aborted = false;
2666
3390
  const onKeypress = (_str, key) => {
2667
3391
  if (key && key.name === "escape") {
@@ -2685,7 +3409,7 @@ function question(rl, prompt) {
2685
3409
  if (aborted) {
2686
3410
  reject(new AbortFlowError());
2687
3411
  } else {
2688
- resolve2(answer);
3412
+ resolve3(answer);
2689
3413
  }
2690
3414
  });
2691
3415
  });
@@ -2693,11 +3417,11 @@ function question(rl, prompt) {
2693
3417
  async function cmdToolsScaffold(target, rest) {
2694
3418
  const name = rest.trim();
2695
3419
  if (!name) {
2696
- console.log(pc2.yellow(" Usage: tools/scaffold <name>"));
3420
+ console.log(colors.yellow(" Usage: tools/scaffold <name>"));
2697
3421
  if (cachedToolNames.length > 0) {
2698
3422
  const preview = cachedToolNames.slice(0, 6);
2699
3423
  const more = cachedToolNames.length > 6 ? `, ... (${cachedToolNames.length} total)` : "";
2700
- console.log(pc2.dim(`
3424
+ console.log(colors.dim(`
2701
3425
  Available tools: ${preview.join(", ")}${more}`));
2702
3426
  }
2703
3427
  return;
@@ -2705,38 +3429,38 @@ async function cmdToolsScaffold(target, rest) {
2705
3429
  const { tools } = await target.listTools();
2706
3430
  const tool = tools.find((t) => t.name === name);
2707
3431
  if (!tool) {
2708
- console.log(pc2.red(`Tool "${name}" not found.`));
3432
+ console.log(colors.red(`Tool "${name}" not found.`));
2709
3433
  const suggestion = suggestCommand(
2710
3434
  name,
2711
3435
  tools.map((t) => t.name)
2712
3436
  );
2713
3437
  if (suggestion) {
2714
- console.log(pc2.yellow(`Did you mean ${pc2.bold(suggestion)}?`));
3438
+ console.log(colors.yellow(`Did you mean ${colors.bold(suggestion)}?`));
2715
3439
  } else {
2716
- console.log(pc2.dim(`Available: ${tools.map((t) => t.name).join(", ")}`));
3440
+ console.log(colors.dim(`Available: ${tools.map((t) => t.name).join(", ")}`));
2717
3441
  }
2718
3442
  return;
2719
3443
  }
2720
3444
  const scaffolded = scaffoldArgs(tool.inputSchema);
2721
- console.log(pc2.cyan("\n Ready-to-paste command:"));
3445
+ console.log(colors.cyan("\n Ready-to-paste command:"));
2722
3446
  console.log(` tools/call ${name} ${scaffolded}
2723
3447
  `);
2724
3448
  }
2725
3449
  async function cmdResourcesList(target) {
2726
3450
  const { resources } = await target.listResources();
2727
3451
  if (resources.length === 0) {
2728
- console.log(pc2.dim(" No resources available."));
3452
+ console.log(colors.dim(" No resources available."));
2729
3453
  return;
2730
3454
  }
2731
3455
  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)}`));
3456
+ console.log(colors.bold(` ${"URI".padEnd(uriWidth)} Name`));
3457
+ console.log(colors.dim(` ${"\u2500".repeat(uriWidth)} ${"\u2500".repeat(40)}`));
2734
3458
  for (const r of resources) {
2735
3459
  const uri = r.uri;
2736
- const name = r.name ?? pc2.dim("(unnamed)");
2737
- console.log(` ${pc2.green(uri.padEnd(uriWidth))} ${name}`);
3460
+ const name = r.name ?? colors.dim("(unnamed)");
3461
+ console.log(` ${colors.green(uri.padEnd(uriWidth))} ${name}`);
2738
3462
  }
2739
- console.log(pc2.dim(`
3463
+ console.log(colors.dim(`
2740
3464
  ${resources.length} resource(s) total.`));
2741
3465
  }
2742
3466
  async function cmdResourcesRead(target, rest, interceptor) {
@@ -2756,11 +3480,11 @@ async function cmdResourcesRead(target, rest, interceptor) {
2756
3480
  if (!picked) return;
2757
3481
  return cmdResourcesRead(target, picked, interceptor);
2758
3482
  }
2759
- console.log(pc2.yellow(" Usage: resources/read <uri>"));
3483
+ console.log(colors.yellow(" Usage: resources/read <uri>"));
2760
3484
  if (cachedResourceUris.length > 0) {
2761
3485
  const preview = cachedResourceUris.slice(0, 5);
2762
3486
  const more = cachedResourceUris.length > 5 ? `, ... (${cachedResourceUris.length} total)` : "";
2763
- console.log(pc2.dim(`
3487
+ console.log(colors.dim(`
2764
3488
  Available resources: ${preview.join(", ")}${more}`));
2765
3489
  }
2766
3490
  return;
@@ -2778,15 +3502,15 @@ async function cmdResourcesRead(target, rest, interceptor) {
2778
3502
  if (typeof parsed === "object" && parsed !== null) {
2779
3503
  console.log(formatJson(parsed, 2, true));
2780
3504
  } else {
2781
- console.log(pc2.yellow(` ${text}`));
3505
+ console.log(colors.yellow(` ${text}`));
2782
3506
  }
2783
3507
  } catch {
2784
- console.log(pc2.yellow(` ${text}`));
3508
+ console.log(colors.yellow(` ${text}`));
2785
3509
  }
2786
3510
  } else if (item.blob !== void 0) {
2787
3511
  const mimeType = item.mimeType ?? "application/octet-stream";
2788
3512
  const sizeBytes = Buffer.from(item.blob, "base64").length;
2789
- console.log(pc2.dim(` [Binary: ${mimeType}, ${sizeBytes} bytes]`));
3513
+ console.log(colors.dim(` [Binary: ${mimeType}, ${sizeBytes} bytes]`));
2790
3514
  } else {
2791
3515
  console.log(formatJson(item, 2, true));
2792
3516
  }
@@ -2797,37 +3521,37 @@ async function cmdResourcesRead(target, rest, interceptor) {
2797
3521
  async function cmdResourcesTemplates(target) {
2798
3522
  const { resourceTemplates } = await target.listResourceTemplates();
2799
3523
  if (resourceTemplates.length === 0) {
2800
- console.log(pc2.dim(" No resource templates available."));
3524
+ console.log(colors.dim(" No resource templates available."));
2801
3525
  return;
2802
3526
  }
2803
3527
  const uriWidth = Math.max(
2804
3528
  12,
2805
3529
  ...resourceTemplates.map((t) => t.uriTemplate.length)
2806
3530
  );
2807
- console.log(pc2.bold(` ${"URI Template".padEnd(uriWidth)} Name`));
2808
- console.log(pc2.dim(` ${"\u2500".repeat(uriWidth)} ${"\u2500".repeat(40)}`));
3531
+ console.log(colors.bold(` ${"URI Template".padEnd(uriWidth)} Name`));
3532
+ console.log(colors.dim(` ${"\u2500".repeat(uriWidth)} ${"\u2500".repeat(40)}`));
2809
3533
  for (const t of resourceTemplates) {
2810
3534
  const uriTemplate = t.uriTemplate;
2811
- const name = t.name ?? pc2.dim("(unnamed)");
2812
- console.log(` ${pc2.green(uriTemplate.padEnd(uriWidth))} ${name}`);
3535
+ const name = t.name ?? colors.dim("(unnamed)");
3536
+ console.log(` ${colors.green(uriTemplate.padEnd(uriWidth))} ${name}`);
2813
3537
  }
2814
- console.log(pc2.dim(`
3538
+ console.log(colors.dim(`
2815
3539
  ${resourceTemplates.length} template(s) total.`));
2816
3540
  }
2817
3541
  async function cmdPromptsList(target) {
2818
3542
  const { prompts } = await target.listPrompts();
2819
3543
  if (prompts.length === 0) {
2820
- console.log(pc2.dim(" No prompts available."));
3544
+ console.log(colors.dim(" No prompts available."));
2821
3545
  return;
2822
3546
  }
2823
3547
  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)}`));
3548
+ console.log(colors.bold(` ${"Name".padEnd(nameWidth)} Description`));
3549
+ console.log(colors.dim(` ${"\u2500".repeat(nameWidth)} ${"\u2500".repeat(50)}`));
2826
3550
  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}`);
3551
+ const desc = p.description ? p.description.length > 60 ? `${p.description.slice(0, 57)}...` : p.description : colors.dim("(no description)");
3552
+ console.log(` ${colors.green(p.name.padEnd(nameWidth))} ${desc}`);
2829
3553
  }
2830
- console.log(pc2.dim(`
3554
+ console.log(colors.dim(`
2831
3555
  ${prompts.length} prompt(s) total.`));
2832
3556
  }
2833
3557
  async function cmdPromptsGet(target, rest, interceptor) {
@@ -2844,9 +3568,9 @@ async function cmdPromptsGet(target, rest, interceptor) {
2844
3568
  if (!picked) return;
2845
3569
  return cmdPromptsGet(target, picked);
2846
3570
  }
2847
- console.log(pc2.yellow(" Usage: prompts/get <name> [json_args]"));
3571
+ console.log(colors.yellow(" Usage: prompts/get <name> [json_args]"));
2848
3572
  if (cachedPromptNames.length > 0) {
2849
- console.log(pc2.dim(`
3573
+ console.log(colors.dim(`
2850
3574
  Available prompts: ${cachedPromptNames.join(", ")}`));
2851
3575
  }
2852
3576
  return;
@@ -2858,15 +3582,15 @@ async function cmdPromptsGet(target, rest, interceptor) {
2858
3582
  try {
2859
3583
  promptArgs = JSON.parse(trimmed);
2860
3584
  } catch (err) {
2861
- console.error(pc2.red(`Invalid JSON: ${err.message}`));
2862
- console.log(pc2.dim(` Received: ${jsonArgs}`));
3585
+ console.error(colors.red(`Invalid JSON: ${err.message}`));
3586
+ console.log(colors.dim(` Received: ${jsonArgs}`));
2863
3587
  return;
2864
3588
  }
2865
3589
  } else {
2866
3590
  try {
2867
3591
  promptArgs = parseHttpieArgs(trimmed);
2868
3592
  } catch (err) {
2869
- console.error(pc2.red(`Invalid shorthand arguments: ${err.message}`));
3593
+ console.error(colors.red(`Invalid shorthand arguments: ${err.message}`));
2870
3594
  return;
2871
3595
  }
2872
3596
  }
@@ -2874,14 +3598,14 @@ async function cmdPromptsGet(target, rest, interceptor) {
2874
3598
  const { prompts } = await target.listPrompts();
2875
3599
  const prompt = prompts.find((p) => p.name === promptName);
2876
3600
  if (!prompt) {
2877
- console.log(pc2.red(`
3601
+ console.log(colors.red(`
2878
3602
  \u2717 Prompt "${promptName}" not found.`));
2879
3603
  const promptNames = prompts.map((p) => p.name);
2880
3604
  const suggestion = suggestCommand(promptName, promptNames);
2881
3605
  if (suggestion) {
2882
- console.log(pc2.yellow(` \u{1F4A1} Did you mean "${suggestion}"?`));
3606
+ console.log(colors.yellow(` \u{1F4A1} Did you mean "${suggestion}"?`));
2883
3607
  } else {
2884
- console.log(pc2.dim(` Available prompts: ${promptNames.join(", ")}`));
3608
+ console.log(colors.dim(` Available prompts: ${promptNames.join(", ")}`));
2885
3609
  }
2886
3610
  return { isError: true, content: [{ type: "text", text: `Prompt not found: ${promptName}` }] };
2887
3611
  }
@@ -2889,24 +3613,24 @@ async function cmdPromptsGet(target, rest, interceptor) {
2889
3613
  const result = await target.getPrompt({ name: promptName, arguments: promptArgs });
2890
3614
  const elapsed = Date.now() - startTime;
2891
3615
  if (result.messages.length === 0) {
2892
- console.log(pc2.dim(" No messages returned."));
3616
+ console.log(colors.dim(" No messages returned."));
2893
3617
  return;
2894
3618
  }
2895
3619
  console.log();
2896
3620
  printResultBlock({ label: "Prompt", labelColor: "blue", elapsed, detail: promptName });
2897
3621
  for (const msg of result.messages) {
2898
- const role = msg.role === "user" ? pc2.blue("user") : pc2.magenta("assistant");
3622
+ const role = msg.role === "user" ? colors.blue("user") : colors.magenta("assistant");
2899
3623
  const text = msg.content.text ?? JSON.stringify(msg.content);
2900
3624
  try {
2901
3625
  const parsed = JSON.parse(text);
2902
3626
  if (typeof parsed === "object" && parsed !== null) {
2903
- console.log(` ${pc2.bold(role)}:`);
3627
+ console.log(` ${colors.bold(role)}:`);
2904
3628
  console.log(formatJson(parsed, 4, true));
2905
3629
  } else {
2906
- console.log(` ${pc2.bold(role)}: ${pc2.yellow(text)}`);
3630
+ console.log(` ${colors.bold(role)}: ${colors.yellow(text)}`);
2907
3631
  }
2908
3632
  } catch {
2909
- console.log(` ${pc2.bold(role)}: ${pc2.yellow(text)}`);
3633
+ console.log(` ${colors.bold(role)}: ${colors.yellow(text)}`);
2910
3634
  }
2911
3635
  }
2912
3636
  console.log();
@@ -2916,36 +3640,36 @@ async function cmdPing(target) {
2916
3640
  try {
2917
3641
  const elapsed = await target.ping();
2918
3642
  console.log();
2919
- console.log(pc2.green(` \u2713 Pong! Round-trip: ${elapsed}ms`));
3643
+ console.log(colors.green(` \u2713 Pong! Round-trip: ${elapsed}ms`));
2920
3644
  console.log();
2921
3645
  } catch (err) {
2922
3646
  console.log();
2923
- console.error(pc2.red(` \u2717 Ping failed: ${err.message}`));
3647
+ console.error(colors.red(` \u2717 Ping failed: ${err.message}`));
2924
3648
  console.log();
2925
3649
  }
2926
3650
  }
2927
3651
  async function cmdLogLevel(target, rest) {
2928
3652
  const level = rest.trim().toLowerCase();
2929
3653
  if (!level) {
2930
- console.log(pc2.yellow(" Usage: log-level <level>"));
2931
- console.log(pc2.dim(` Valid levels: ${LOG_LEVELS.join(", ")}`));
3654
+ console.log(colors.yellow(" Usage: log-level <level>"));
3655
+ console.log(colors.dim(` Valid levels: ${LOG_LEVELS.join(", ")}`));
2932
3656
  return;
2933
3657
  }
2934
3658
  if (!LOG_LEVELS.includes(level)) {
2935
3659
  const suggestion = suggestCommand(level, [...LOG_LEVELS]);
2936
3660
  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(", ")}`));
3661
+ console.log(colors.red(` Unknown log level: "${level}".${hint}`));
3662
+ console.log(colors.dim(` Valid levels: ${LOG_LEVELS.join(", ")}`));
2939
3663
  return;
2940
3664
  }
2941
3665
  try {
2942
3666
  await target.setLoggingLevel(level);
2943
- console.log(pc2.green(` \u2713 Logging level set to: ${level}`));
3667
+ console.log(colors.green(` \u2713 Logging level set to: ${level}`));
2944
3668
  } catch (err) {
2945
- console.error(pc2.red(` \u2717 Failed to set log level: ${err.message}`));
3669
+ console.error(colors.red(` \u2717 Failed to set log level: ${err.message}`));
2946
3670
  const caps = target.getServerCapabilities();
2947
3671
  if (!caps?.logging) {
2948
- console.log(pc2.dim(" The server does not advertise logging support."));
3672
+ console.log(colors.dim(" The server does not advertise logging support."));
2949
3673
  }
2950
3674
  }
2951
3675
  }
@@ -2953,28 +3677,28 @@ function cmdHistory(target, rest) {
2953
3677
  const arg = rest.trim();
2954
3678
  if (arg === "clear") {
2955
3679
  target.clearHistory();
2956
- console.log(pc2.dim(" History cleared."));
3680
+ console.log(colors.dim(" History cleared."));
2957
3681
  return;
2958
3682
  }
2959
3683
  const count = arg ? Number.parseInt(arg, 10) : 20;
2960
3684
  const records = target.getHistory(Number.isNaN(count) ? 20 : count);
2961
3685
  if (records.length === 0) {
2962
- console.log(pc2.dim(" No request history yet."));
3686
+ console.log(colors.dim(" No request history yet."));
2963
3687
  return;
2964
3688
  }
2965
- console.log(pc2.bold("\n Request History"));
2966
- console.log(pc2.dim(` ${"\u2500".repeat(70)}`));
3689
+ console.log(colors.bold("\n Request History"));
3690
+ console.log(colors.dim(` ${"\u2500".repeat(70)}`));
2967
3691
  for (const rec of records) {
2968
3692
  const time = new Date(rec.timestamp).toLocaleTimeString();
2969
3693
  const dur = `${rec.durationMs}ms`;
2970
- const hasError = rec.error ? pc2.red(" \u2717") : "";
3694
+ const hasError = rec.error ? colors.red(" \u2717") : "";
2971
3695
  console.log(
2972
- ` ${pc2.dim(`#${rec.id}`)} ${pc2.dim(time)} ${pc2.green(rec.method.padEnd(30))} ${pc2.cyan(dur.padStart(8))}${hasError}`
3696
+ ` ${colors.dim(`#${rec.id}`)} ${colors.dim(time)} ${colors.green(rec.method.padEnd(30))} ${colors.cyan(dur.padStart(8))}${hasError}`
2973
3697
  );
2974
3698
  }
2975
3699
  const total = target.getHistory().length;
2976
3700
  console.log(
2977
- pc2.dim(`
3701
+ colors.dim(`
2978
3702
  Showing ${records.length} of ${total} total. Use "history clear" to reset.`)
2979
3703
  );
2980
3704
  console.log();
@@ -2983,26 +3707,26 @@ function cmdNotifications(target, rest) {
2983
3707
  const arg = rest.trim();
2984
3708
  if (arg === "clear") {
2985
3709
  target.clearNotifications();
2986
- console.log(pc2.dim(" Notifications cleared."));
3710
+ console.log(colors.dim(" Notifications cleared."));
2987
3711
  return;
2988
3712
  }
2989
3713
  const count = arg ? Number.parseInt(arg, 10) : 20;
2990
3714
  const records = target.getNotifications(Number.isNaN(count) ? 20 : count);
2991
3715
  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."));
3716
+ console.log(colors.dim(" No notifications received yet."));
3717
+ console.log(colors.dim(" Notifications appear inline as they arrive from the server."));
2994
3718
  return;
2995
3719
  }
2996
- console.log(pc2.bold("\n Server Notifications"));
2997
- console.log(pc2.dim(` ${"\u2500".repeat(70)}`));
3720
+ console.log(colors.bold("\n Server Notifications"));
3721
+ console.log(colors.dim(` ${"\u2500".repeat(70)}`));
2998
3722
  for (const n of records) {
2999
3723
  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}`);
3724
+ const params = n.params ? ` ${colors.dim(JSON.stringify(n.params))}` : "";
3725
+ console.log(` ${colors.dim(time)} ${colors.yellow(n.method)}${params}`);
3002
3726
  }
3003
3727
  const total = target.getNotifications().length;
3004
3728
  console.log(
3005
- pc2.dim(`
3729
+ colors.dim(`
3006
3730
  Showing ${records.length} of ${total} total. Use "notifications clear" to reset.`)
3007
3731
  );
3008
3732
  console.log();
@@ -3010,44 +3734,44 @@ function cmdNotifications(target, rest) {
3010
3734
  async function cmdResourcesSubscribe(target, rest) {
3011
3735
  const uri = rest.trim();
3012
3736
  if (!uri) {
3013
- console.log(pc2.yellow(" Usage: resources/subscribe <uri>"));
3737
+ console.log(colors.yellow(" Usage: resources/subscribe <uri>"));
3014
3738
  if (cachedResourceUris.length > 0) {
3015
- console.log(pc2.dim(` Available: ${cachedResourceUris.join(", ")}`));
3739
+ console.log(colors.dim(` Available: ${cachedResourceUris.join(", ")}`));
3016
3740
  }
3017
3741
  return;
3018
3742
  }
3019
3743
  try {
3020
3744
  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."));
3745
+ console.log(colors.green(` \u2713 Subscribed to: ${uri}`));
3746
+ console.log(colors.dim(" You'll see notifications when this resource changes."));
3023
3747
  } catch (err) {
3024
- console.error(pc2.red(` \u2717 Subscribe failed: ${err.message}`));
3748
+ console.error(colors.red(` \u2717 Subscribe failed: ${err.message}`));
3025
3749
  }
3026
3750
  }
3027
3751
  async function cmdResourcesUnsubscribe(target, rest) {
3028
3752
  const uri = rest.trim();
3029
3753
  if (!uri) {
3030
- console.log(pc2.yellow(" Usage: resources/unsubscribe <uri>"));
3754
+ console.log(colors.yellow(" Usage: resources/unsubscribe <uri>"));
3031
3755
  return;
3032
3756
  }
3033
3757
  try {
3034
3758
  await target.unsubscribeResource({ uri });
3035
- console.log(pc2.green(` \u2713 Unsubscribed from: ${uri}`));
3759
+ console.log(colors.green(` \u2713 Unsubscribed from: ${uri}`));
3036
3760
  } catch (err) {
3037
- console.error(pc2.red(` \u2717 Unsubscribe failed: ${err.message}`));
3761
+ console.error(colors.red(` \u2717 Unsubscribe failed: ${err.message}`));
3038
3762
  }
3039
3763
  }
3040
3764
  function cmdRootsList(target) {
3041
3765
  const roots = target.getRoots();
3042
3766
  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."));
3767
+ console.log(colors.dim(" No roots configured."));
3768
+ console.log(colors.dim(" Use roots/add <uri> [name] to add one."));
3045
3769
  return;
3046
3770
  }
3047
- console.log(pc2.bold("\n Client Roots"));
3771
+ console.log(colors.bold("\n Client Roots"));
3048
3772
  for (const r of roots) {
3049
3773
  const name = r.name ? ` (${r.name})` : "";
3050
- console.log(` ${pc2.green(r.uri)}${pc2.dim(name)}`);
3774
+ console.log(` ${colors.green(r.uri)}${colors.dim(name)}`);
3051
3775
  }
3052
3776
  console.log();
3053
3777
  }
@@ -3056,53 +3780,53 @@ async function cmdRootsAdd(target, rest) {
3056
3780
  const uri = parts[0];
3057
3781
  const name = parts.slice(1).join(" ") || void 0;
3058
3782
  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"'));
3783
+ console.log(colors.yellow(" Usage: roots/add <uri> [name]"));
3784
+ console.log(colors.dim(' Example: roots/add file:///Users/me/project "My Project"'));
3061
3785
  return;
3062
3786
  }
3063
3787
  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."));
3788
+ console.log(colors.green(` \u2713 Root added: ${uri}`));
3789
+ console.log(colors.dim(" Server has been notified of the change."));
3066
3790
  }
3067
3791
  async function cmdRootsRemove(target, rest) {
3068
3792
  const uri = rest.trim();
3069
3793
  if (!uri) {
3070
- console.log(pc2.yellow(" Usage: roots/remove <uri>"));
3794
+ console.log(colors.yellow(" Usage: roots/remove <uri>"));
3071
3795
  const roots = target.getRoots();
3072
3796
  if (roots.length > 0) {
3073
- console.log(pc2.dim(` Current roots: ${roots.map((r) => r.uri).join(", ")}`));
3797
+ console.log(colors.dim(` Current roots: ${roots.map((r) => r.uri).join(", ")}`));
3074
3798
  }
3075
3799
  return;
3076
3800
  }
3077
3801
  const removed = await target.removeRoot(uri);
3078
3802
  if (removed) {
3079
- console.log(pc2.green(` \u2713 Root removed: ${uri}`));
3803
+ console.log(colors.green(` \u2713 Root removed: ${uri}`));
3080
3804
  } else {
3081
- console.log(pc2.yellow(` Root not found: ${uri}`));
3805
+ console.log(colors.yellow(` Root not found: ${uri}`));
3082
3806
  }
3083
3807
  }
3084
3808
  async function cmdReconnect(target) {
3085
- console.log(pc2.cyan("\u27F3 Disconnecting..."));
3809
+ console.log(colors.cyan("\u27F3 Disconnecting..."));
3086
3810
  await target.close();
3087
- await new Promise((resolve2) => setTimeout(resolve2, 200));
3088
- console.log(pc2.cyan("\u27F3 Reconnecting..."));
3811
+ await new Promise((resolve3) => setTimeout(resolve3, 200));
3812
+ console.log(colors.cyan("\u27F3 Reconnecting..."));
3089
3813
  const { command, args } = target.getStatus();
3090
- console.log(pc2.dim(` Command: ${command} ${args.join(" ")}`));
3814
+ console.log(colors.dim(` Command: ${command} ${args.join(" ")}`));
3091
3815
  try {
3092
3816
  await target.connect();
3093
3817
  const s = target.getStatus();
3094
- console.log(pc2.green(`\u2713 Reconnected (PID: ${s.pid})`));
3818
+ console.log(colors.green(`\u2713 Reconnected (PID: ${s.pid})`));
3095
3819
  const { tools } = await target.listTools();
3096
- console.log(pc2.cyan(` ${tools.length} tool(s) available.
3820
+ console.log(colors.cyan(` ${tools.length} tool(s) available.
3097
3821
  `));
3098
3822
  await refreshCaches(target);
3099
3823
  } catch (err) {
3100
- console.error(pc2.red(`\u2717 Failed to reconnect: ${err.message}`));
3824
+ console.error(colors.red(`\u2717 Failed to reconnect: ${err.message}`));
3101
3825
  }
3102
3826
  }
3103
3827
  function cmdTiming() {
3104
3828
  if (callHistory.length === 0) {
3105
- console.log(pc2.dim(" No tool calls recorded yet."));
3829
+ console.log(colors.dim(" No tool calls recorded yet."));
3106
3830
  return;
3107
3831
  }
3108
3832
  const groups = /* @__PURE__ */ new Map();
@@ -3112,172 +3836,68 @@ function cmdTiming() {
3112
3836
  groups.set(record.toolName, list);
3113
3837
  }
3114
3838
  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)}`));
3839
+ console.log(colors.bold("\n Tool Call Performance"));
3840
+ console.log(colors.dim(` ${"\u2500".repeat(nameWidth + 50)}`));
3117
3841
  let totalCalls = 0;
3118
3842
  let totalMs = 0;
3119
3843
  let slowestName = "";
3120
3844
  let slowestMs = 0;
3121
3845
  for (const [name, durations] of groups) {
3122
3846
  const count = durations.length;
3123
- 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
- `);
3847
+ totalCalls += count;
3848
+ const sorted = [...durations].sort((a, b) => a - b);
3849
+ const avg = Math.round(sorted.reduce((a, b) => a + b, 0) / count);
3850
+ const p95 = sorted[Math.floor(count * 0.95)];
3851
+ const max = sorted[sorted.length - 1];
3852
+ totalMs += sorted.reduce((a, b) => a + b, 0);
3853
+ if (max > slowestMs) {
3854
+ slowestMs = max;
3855
+ slowestName = name;
3856
+ }
3857
+ console.log(
3858
+ ` ${colors.green(name.padEnd(nameWidth))} \xD7 ${count} avg: ${avg}ms p95: ${p95}ms max: ${max}ms`
3859
+ );
3860
+ }
3861
+ const overallAvg = Math.round(totalMs / totalCalls);
3862
+ console.log(
3863
+ colors.dim(
3864
+ `
3865
+ Total: ${totalCalls} call(s), avg ${overallAvg}ms, slowest: ${slowestName} (${slowestMs}ms)`
3866
+ )
3867
+ );
3868
+ console.log();
3263
3869
  }
3264
- async function readScriptLines(filepath) {
3265
- const content = await readFile2(filepath, "utf-8");
3266
- return content.split("\n");
3870
+ function cmdStatus(target) {
3871
+ const s = target.getStatus();
3872
+ const uptimeStr = s.uptime >= 60 ? `${Math.floor(s.uptime / 60)}m ${(s.uptime % 60).toFixed(0)}s` : `${s.uptime.toFixed(1)}s`;
3873
+ const lastRespStr = s.lastResponseTime ? `${((Date.now() - s.lastResponseTime) / 1e3).toFixed(1)}s ago` : "never";
3874
+ console.log(colors.bold("\n Target Server Status"));
3875
+ console.log(` ${colors.dim("Connected:")} ${s.connected ? colors.green("yes") : colors.red("no")}`);
3876
+ console.log(` ${colors.dim("PID:")} ${s.pid ?? "N/A"}`);
3877
+ console.log(` ${colors.dim("Uptime:")} ${uptimeStr}`);
3878
+ console.log(` ${colors.dim("Last response:")} ${lastRespStr}`);
3879
+ console.log(` ${colors.dim("Stderr lines:")} ${s.stderrLineCount.toLocaleString()}`);
3880
+ console.log(` ${colors.dim("Reconnects:")} ${s.reconnectAttempts}/${s.maxReconnectAttempts}`);
3881
+ console.log(` ${colors.dim("Command:")} ${s.command} ${s.args.join(" ")}`);
3882
+ console.log();
3267
3883
  }
3268
3884
  function cmdToolsForget(rest) {
3269
3885
  const toolName = rest.trim();
3270
3886
  if (toolName) {
3271
3887
  if (lastToolArgsMap.has(toolName)) {
3272
3888
  lastToolArgsMap.delete(toolName);
3273
- console.log(pc2.green(` Cleared remembered args for ${pc2.bold(toolName)}.`));
3889
+ saveWizardDefaults().catch(() => {
3890
+ });
3891
+ console.log(colors.green(` Cleared remembered args for ${colors.bold(toolName)}.`));
3274
3892
  } else {
3275
- console.log(pc2.yellow(` No remembered args for "${toolName}".`));
3893
+ console.log(colors.yellow(` No remembered args for "${toolName}".`));
3276
3894
  }
3277
3895
  } else {
3278
3896
  const count = lastToolArgsMap.size;
3279
3897
  lastToolArgsMap.clear();
3280
- console.log(pc2.green(` Cleared remembered args for ${count} tool${count === 1 ? "" : "s"}.`));
3898
+ saveWizardDefaults().catch(() => {
3899
+ });
3900
+ console.log(colors.green(` Cleared remembered args for ${count} tool${count === 1 ? "" : "s"}.`));
3281
3901
  }
3282
3902
  }
3283
3903
  async function pickInteractive(items, message) {
@@ -3377,55 +3997,416 @@ async function showMainMenu(target, interceptor) {
3377
3997
  );
3378
3998
  }
3379
3999
  });
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);
4000
+ if (answer.type === "tool") {
4001
+ await cmdToolsCall(target, interceptor, answer.name);
4002
+ } else if (answer.type === "resource") {
4003
+ await cmdResourcesRead(target, answer.uri);
4004
+ } else if (answer.type === "prompt") {
4005
+ await cmdPromptsGet(target, answer.name);
4006
+ } else if (answer.type === "command") {
4007
+ if (answer.name === "status") {
4008
+ cmdStatus(target);
4009
+ } else if (answer.name === "reconnect") {
4010
+ await cmdReconnect(target);
4011
+ } else if (answer.name === "exit") {
4012
+ console.log(colors.dim("\nShutting down..."));
4013
+ await target.close();
4014
+ process.exit(0);
4015
+ }
4016
+ } else if (answer.type === "raw") {
4017
+ return true;
4018
+ }
4019
+ return false;
4020
+ } catch (err) {
4021
+ if (isAbortError(err)) {
4022
+ console.log(colors.dim("\nShutting down..."));
4023
+ await target.close();
4024
+ process.exit(0);
4025
+ }
4026
+ throw err;
4027
+ }
4028
+ }
4029
+ async function mainMenuLoop(target, interceptor) {
4030
+ if (isScriptMode || !process.stdin.isTTY) {
4031
+ startReadlineLoop(target, interceptor);
4032
+ return;
4033
+ }
4034
+ while (true) {
4035
+ try {
4036
+ const dropToRaw = await showMainMenu(target, interceptor);
4037
+ if (dropToRaw) {
4038
+ console.log(colors.dim(" Entering raw command mode. Type 'menu' to return."));
4039
+ startReadlineLoop(target, interceptor);
4040
+ break;
4041
+ }
4042
+ } catch (err) {
4043
+ console.error(colors.red(`Error in menu: ${err.message}`));
4044
+ startReadlineLoop(target, interceptor);
4045
+ break;
4046
+ }
4047
+ }
4048
+ }
4049
+
4050
+ // src/repl/index.ts
4051
+ function getPrompt(target) {
4052
+ if (target.connected) return `${colors.green("\u2713")}${colors.cyan("> ")}`;
4053
+ return `${colors.red("\u2717")}${colors.cyan("> ")}`;
4054
+ }
4055
+ function startReadlineLoop(target, interceptor) {
4056
+ if (isScriptMode || activeRl) return;
4057
+ const rl = createInterface({
4058
+ input: process.stdin,
4059
+ output: process.stdout,
4060
+ prompt: getPrompt(target),
4061
+ terminal: true,
4062
+ completer,
4063
+ history: [...replHistory].reverse()
4064
+ // Node's readline history expects newest first
4065
+ });
4066
+ setActiveRl(rl);
4067
+ setClosed(false);
4068
+ if (process.stdin.isTTY) {
4069
+ process.stdin.on("keypress", (_str, key) => {
4070
+ if (!key || key.name !== "tab") {
4071
+ resetTabCycle();
4072
+ }
4073
+ });
4074
+ }
4075
+ if (!deferNextPrompt) {
4076
+ rl.prompt();
4077
+ }
4078
+ setDeferNextPrompt(false);
4079
+ let processing = false;
4080
+ const queue = [];
4081
+ const processQueue = async () => {
4082
+ if (processing) return;
4083
+ processing = true;
4084
+ while (queue.length > 0) {
4085
+ const trimmed = queue.shift();
4086
+ try {
4087
+ await handleCommand(trimmed, target, interceptor);
4088
+ } catch (err) {
4089
+ if (err instanceof AbortFlowError) {
4090
+ console.log(colors.yellow(" Aborted."));
4091
+ } else if (err?.message?.includes("-32601") || err?.code === -32601) {
4092
+ let msg = "Server does not support this feature (Method not found)";
4093
+ if (trimmed.startsWith("prompts/")) msg = "This server does not have any prompts.";
4094
+ else if (trimmed.startsWith("resources/"))
4095
+ msg = "This server does not have any resources.";
4096
+ else if (trimmed.startsWith("tools/")) msg = "This server does not have any tools.";
4097
+ console.log(colors.yellow(` ${msg}`));
4098
+ } else {
4099
+ console.error(colors.red(`\u2717 Error: ${err.message}`));
4100
+ }
4101
+ }
4102
+ if (activeRl) {
4103
+ setImmediate(() => {
4104
+ if (activeRl) {
4105
+ console.log();
4106
+ activeRl.setPrompt(getPrompt(target));
4107
+ activeRl.prompt();
4108
+ }
4109
+ });
4110
+ }
4111
+ }
4112
+ processing = false;
4113
+ };
4114
+ rl.on("line", (line) => {
4115
+ const trimmed = line.trim();
4116
+ if (!trimmed || trimmed.startsWith("#")) {
4117
+ if (!closed && activeRl) activeRl.prompt();
4118
+ return;
4119
+ }
4120
+ replHistory.push(trimmed);
4121
+ appendToHistoryFile(trimmed).catch(() => {
4122
+ });
4123
+ queue.push(trimmed);
4124
+ processQueue();
4125
+ });
4126
+ rl.on("close", async () => {
4127
+ setClosed(true);
4128
+ setActiveRl(null);
4129
+ if (!globalPauseReadlineClose) {
4130
+ console.log(colors.dim("\nShutting down..."));
4131
+ await target.close();
4132
+ process.exit(0);
4133
+ }
4134
+ });
4135
+ }
4136
+ async function startRepl(targetCommand, opts) {
4137
+ const [command, ...args] = targetCommand;
4138
+ const target = new TargetManager(command, args, {
4139
+ sandbox: opts.sandbox,
4140
+ allowRead: opts.allowRead,
4141
+ allowWrite: opts.allowWrite,
4142
+ allowNet: opts.allowNet,
4143
+ denyRead: opts.denyRead,
4144
+ denyWrite: opts.denyWrite,
4145
+ denyNet: opts.denyNet
4146
+ });
4147
+ const interceptor = new ResponseInterceptor({
4148
+ outDir: opts.outDir,
4149
+ mediaThresholdKb: opts.mediaThresholdKb,
4150
+ openMedia: opts.openMedia
4151
+ });
4152
+ setIsScriptMode(!!opts.script);
4153
+ if (!isScriptMode) {
4154
+ await loadHistory();
4155
+ await loadWizardDefaults();
4156
+ }
4157
+ target.on("stderr", (text) => {
4158
+ for (const line of text.split("\n")) {
4159
+ console.error(colors.dim(`[server] ${line}`));
4160
+ }
4161
+ });
4162
+ console.log(colors.cyan("\u27F3 Connecting to target MCP server..."));
4163
+ console.log(colors.dim(` Command: ${targetCommand.join(" ")}`));
4164
+ try {
4165
+ await target.connect();
4166
+ } catch (err) {
4167
+ const msg = err.message ?? String(err);
4168
+ if (msg.includes("ENOENT") || msg.includes("spawn")) {
4169
+ console.error(colors.red(`\u2717 Failed to start server: command "${command}" not found.`));
4170
+ console.error(colors.dim(` Check that "${command}" is installed and in your PATH.`));
4171
+ } else {
4172
+ console.error(colors.red(`\u2717 Failed to connect: ${msg}`));
4173
+ console.error(colors.dim(` Check that the target command starts a valid MCP server on stdio.`));
4174
+ }
4175
+ process.exit(1);
4176
+ }
4177
+ const status = target.getStatus();
4178
+ console.log(colors.green(`\u2713 Connected (PID: ${status.pid})`));
4179
+ if (!isScriptMode) {
4180
+ target.enableAutoReconnect();
4181
+ target.on(
4182
+ "reconnecting",
4183
+ ({ attempt, maxAttempts }) => {
4184
+ console.log(
4185
+ colors.yellow(`
4186
+ \u27F3 Server disconnected. Reconnecting (${attempt}/${maxAttempts})...`)
4187
+ );
4188
+ }
4189
+ );
4190
+ target.on("reconnected", async ({ attempt }) => {
4191
+ const s = target.getStatus();
4192
+ console.log(colors.green(`\u2713 Reconnected (PID: ${s.pid}, attempt ${attempt})`));
4193
+ await refreshCaches(target);
4194
+ });
4195
+ target.on("reconnect_failed", ({ reason, message }) => {
4196
+ console.error(colors.red(`\u2717 ${message}`));
4197
+ if (reason === "max_retries") {
4198
+ console.log(
4199
+ colors.dim(" Use 'exit' to quit or wait for the server to be fixed and restart manually.")
4200
+ );
4201
+ }
4202
+ });
4203
+ target.on("notification", (notification) => {
4204
+ const method = notification.method;
4205
+ if (method === "notifications/message") {
4206
+ const lvl = notification.params?.level ?? "info";
4207
+ const data = notification.params?.data ?? "";
4208
+ const text = typeof data === "string" ? data : JSON.stringify(data);
4209
+ console.log(colors.dim(`
4210
+ [${lvl}] ${text}`));
4211
+ } else if (method === "notifications/tools/list_changed") {
4212
+ console.log(colors.yellow("\n \u27F3 Server tools changed. Run tools/list to see updates."));
4213
+ refreshCaches(target).catch(() => {
4214
+ });
4215
+ } else if (method === "notifications/resources/list_changed") {
4216
+ console.log(colors.yellow("\n \u27F3 Server resources changed. Run resources/list to see."));
4217
+ refreshCaches(target).catch(() => {
4218
+ });
4219
+ } else if (method === "notifications/resources/updated") {
4220
+ const uri = notification.params?.uri ?? "unknown";
4221
+ console.log(colors.yellow(`
4222
+ \u27F3 Resource updated: ${uri}`));
4223
+ } else if (method === "notifications/prompts/list_changed") {
4224
+ console.log(colors.yellow("\n \u27F3 Server prompts changed. Run prompts/list to see."));
4225
+ refreshCaches(target).catch(() => {
4226
+ });
4227
+ }
4228
+ });
4229
+ target.on("sampling_request", async ({ request, respond, reject: rejectFn }) => {
4230
+ 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"));
4231
+ const messages = request?.messages ?? [];
4232
+ for (const msg of messages) {
4233
+ const role = msg.role === "user" ? colors.blue("user") : colors.magenta("assistant");
4234
+ const text = msg.content?.text ?? JSON.stringify(msg.content);
4235
+ console.log(colors.magenta(` \u2551 ${role}: ${text}`));
4236
+ }
4237
+ 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"));
4238
+ if (activeRl) {
4239
+ try {
4240
+ const answer = await question(activeRl, ` ${colors.bold("Approve? [y/N/text]:")} `);
4241
+ const trimmed = answer.trim().toLowerCase();
4242
+ if (trimmed === "y" || trimmed === "yes") {
4243
+ respond({
4244
+ model: "user-approved",
4245
+ role: "assistant",
4246
+ content: { type: "text", text: "Approved by user." }
4247
+ });
4248
+ } else if (trimmed === "n" || trimmed === "no" || trimmed === "") {
4249
+ rejectFn(new Error("Sampling request rejected by user"));
4250
+ } else {
4251
+ respond({
4252
+ model: "user-provided",
4253
+ role: "assistant",
4254
+ content: { type: "text", text: answer.trim() }
4255
+ });
4256
+ }
4257
+ } catch (err) {
4258
+ if (err instanceof AbortFlowError) {
4259
+ rejectFn(new Error("Sampling request rejected by user"));
4260
+ } else {
4261
+ throw err;
4262
+ }
4263
+ }
4264
+ } else {
4265
+ rejectFn(new Error("No interactive terminal available for sampling approval"));
4266
+ }
4267
+ });
4268
+ target.on("elicitation_request", async ({ request, respond, reject: rejectFn }) => {
4269
+ 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"));
4270
+ console.log(colors.cyan(` \u2551 ${request?.message ?? "Server requests input"}`));
4271
+ 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"));
4272
+ if (activeRl) {
4273
+ try {
4274
+ const answer = await question(
4275
+ activeRl,
4276
+ ` ${colors.bold("Your response (empty to decline):")} `
4277
+ );
4278
+ if (answer.trim() === "") {
4279
+ respond({ action: "decline" });
4280
+ } else {
4281
+ try {
4282
+ const parsed = JSON.parse(answer.trim());
4283
+ respond({ action: "accept", content: parsed });
4284
+ } catch {
4285
+ respond({ action: "accept", content: { value: answer.trim() } });
4286
+ }
4287
+ }
4288
+ } catch (err) {
4289
+ if (err instanceof AbortFlowError) {
4290
+ respond({ action: "decline" });
4291
+ } else {
4292
+ throw err;
4293
+ }
4294
+ }
4295
+ } else {
4296
+ rejectFn(new Error("No interactive terminal available for elicitation"));
4297
+ }
4298
+ });
4299
+ }
4300
+ let toolCount;
4301
+ let resourceCount = 0;
4302
+ let promptCount = 0;
4303
+ try {
4304
+ const { tools } = await target.listTools();
4305
+ toolCount = tools.length;
4306
+ const caps = target.getServerCapabilities() ?? {};
4307
+ if (caps.resources) {
4308
+ try {
4309
+ const { resources } = await target.listResources();
4310
+ resourceCount = resources.length;
4311
+ } catch {
3395
4312
  }
3396
- } else if (answer.type === "raw") {
3397
- return true;
3398
4313
  }
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);
4314
+ if (caps.prompts) {
4315
+ try {
4316
+ const { prompts } = await target.listPrompts();
4317
+ promptCount = prompts.length;
4318
+ } catch {
4319
+ }
3405
4320
  }
3406
- throw err;
3407
- }
3408
- }
3409
- async function mainMenuLoop(target, interceptor) {
3410
- if (isScriptMode || !process.stdin.isTTY) {
3411
- startReadlineLoop(target, interceptor);
3412
- return;
4321
+ const serverInfo = target.getServerVersion();
4322
+ const serverName = serverInfo?.name ?? "MCP Server";
4323
+ const serverVersion = serverInfo?.version ?? "";
4324
+ const isHarness = serverName === "run-mcp";
4325
+ printBanner(serverName, serverVersion, toolCount, resourceCount, promptCount, isHarness);
4326
+ if (toolCount >= 10) {
4327
+ const groups = groupToolsByPrefix(
4328
+ cachedToolNames.length > 0 ? cachedToolNames : tools.map((t) => t.name)
4329
+ );
4330
+ if (!groups.has("All")) {
4331
+ for (const [label, members] of groups) {
4332
+ console.log(` ${colors.bold(label.padEnd(16))} ${colors.dim(members.join(", "))}`);
4333
+ }
4334
+ console.log();
4335
+ }
4336
+ }
4337
+ } catch (err) {
4338
+ console.log(colors.yellow(` Warning: Could not list tools: ${err.message}
4339
+ `));
3413
4340
  }
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;
4341
+ await refreshCaches(target);
4342
+ if (isScriptMode) {
4343
+ const lines = await readScriptLines(opts.script);
4344
+ const scriptContext = {};
4345
+ let expectError = false;
4346
+ for (const line of lines) {
4347
+ let trimmed = line.trim();
4348
+ if (!trimmed || trimmed.startsWith("#")) {
4349
+ if (trimmed === "# @expect-error") {
4350
+ expectError = true;
4351
+ }
4352
+ continue;
3421
4353
  }
3422
- } catch (err) {
3423
- console.error(pc2.red(`Error in menu: ${err.message}`));
3424
- startReadlineLoop(target, interceptor);
3425
- break;
4354
+ if (trimmed.endsWith("# @expect-error")) {
4355
+ expectError = true;
4356
+ trimmed = trimmed.replace(/\s*#\s*@expect-error$/, "");
4357
+ }
4358
+ const interpolated = interpolateString(trimmed, scriptContext);
4359
+ try {
4360
+ const res = await handleCommand(interpolated, target, interceptor);
4361
+ if (res !== void 0) {
4362
+ scriptContext.LAST = res;
4363
+ }
4364
+ const isErrorRes = res && typeof res === "object" && res.isError === true;
4365
+ if (expectError && !isErrorRes) {
4366
+ console.error(colors.red(`\u2717 Expected an error but the command succeeded.`));
4367
+ await target.close();
4368
+ process.exit(1);
4369
+ }
4370
+ if (!expectError && isErrorRes) {
4371
+ console.error(colors.red(`\u2717 Command failed unexpectedly.`));
4372
+ await target.close();
4373
+ process.exit(1);
4374
+ }
4375
+ if (expectError && isErrorRes) {
4376
+ console.log(colors.yellow(` \u2713 Expected error caught: tool returned isError: true`));
4377
+ }
4378
+ } catch (err) {
4379
+ if (expectError) {
4380
+ console.log(colors.yellow(` \u2713 Expected error caught: ${err.message}`));
4381
+ } else {
4382
+ if (err?.message?.includes("-32601") || err?.code === -32601) {
4383
+ let msg = "Server does not support this feature (Method not found)";
4384
+ if (trimmed.startsWith("prompts/")) msg = "This server does not have any prompts.";
4385
+ else if (trimmed.startsWith("resources/"))
4386
+ msg = "This server does not have any resources.";
4387
+ else if (trimmed.startsWith("tools/")) msg = "This server does not have any tools.";
4388
+ console.log(colors.yellow(` ${msg}`));
4389
+ } else {
4390
+ console.error(colors.red(`\u2717 Error: ${err.message}`));
4391
+ }
4392
+ console.log(colors.dim("\nShutting down..."));
4393
+ await target.close();
4394
+ process.exit(1);
4395
+ }
4396
+ }
4397
+ expectError = false;
3426
4398
  }
4399
+ console.log(colors.dim("\nShutting down..."));
4400
+ await target.close();
4401
+ process.exit(0);
4402
+ } else {
4403
+ mainMenuLoop(target, interceptor);
3427
4404
  }
3428
4405
  }
4406
+ async function readScriptLines(filepath) {
4407
+ const content = await readFile4(filepath, "utf8");
4408
+ return content.split("\n");
4409
+ }
3429
4410
 
3430
4411
  // src/server.ts
3431
4412
  import { createHash } from "crypto";
@@ -3490,7 +4471,7 @@ async function startServer(opts) {
3490
4471
  mediaThresholdKb: opts.mediaThresholdKb
3491
4472
  });
3492
4473
  const mcpServer = new McpServer(
3493
- { name: "run-mcp", version: "1.6.3" },
4474
+ { name: "run-mcp", version: "1.7.1" },
3494
4475
  {
3495
4476
  capabilities: {
3496
4477
  tools: {},
@@ -3651,7 +4632,15 @@ async function startServer(opts) {
3651
4632
  process.env[key] = value;
3652
4633
  }
3653
4634
  }
3654
- target = new TargetManager(cmdToUse, argsToUse ?? []);
4635
+ target = new TargetManager(cmdToUse, argsToUse ?? [], {
4636
+ sandbox: opts.sandbox,
4637
+ allowRead: opts.allowRead,
4638
+ allowWrite: opts.allowWrite,
4639
+ allowNet: opts.allowNet,
4640
+ denyRead: opts.denyRead,
4641
+ denyWrite: opts.denyWrite,
4642
+ denyNet: opts.denyNet
4643
+ });
3655
4644
  setupTargetListeners(target);
3656
4645
  try {
3657
4646
  await target.connect();
@@ -3755,10 +4744,11 @@ async function startServer(opts) {
3755
4744
  ),
3756
4745
  summary: z.boolean().optional().describe(
3757
4746
  "If true, returns only the name and description of each primitive (omitting full schemas) when included to save tokens."
3758
- )
4747
+ ),
4748
+ sandbox: z.enum(["auto", "docker", "native", "audit", "none"]).optional().describe("Sandbox mode to use for this server")
3759
4749
  }
3760
4750
  },
3761
- async ({ command, args, env, include, summary }) => {
4751
+ async ({ command, args, env, include, summary, sandbox }) => {
3762
4752
  if (target?.connected) {
3763
4753
  return {
3764
4754
  content: [
@@ -3780,7 +4770,15 @@ async function startServer(opts) {
3780
4770
  process.env[key] = value;
3781
4771
  }
3782
4772
  }
3783
- target = new TargetManager(command, args ?? []);
4773
+ target = new TargetManager(command, args ?? [], {
4774
+ sandbox: sandbox ?? opts.sandbox,
4775
+ allowRead: opts.allowRead,
4776
+ allowWrite: opts.allowWrite,
4777
+ allowNet: opts.allowNet,
4778
+ denyRead: opts.denyRead,
4779
+ denyWrite: opts.denyWrite,
4780
+ denyNet: opts.denyNet
4781
+ });
3784
4782
  setupTargetListeners(target);
3785
4783
  try {
3786
4784
  await target.connect();
@@ -4068,7 +5066,7 @@ Available: ${available}`
4068
5066
  },
4069
5067
  async () => {
4070
5068
  try {
4071
- const servers = await discoverServers();
5069
+ const servers = await discoverServers({ scan: opts.scan });
4072
5070
  if (servers.length === 0) {
4073
5071
  return {
4074
5072
  content: [
@@ -4115,6 +5113,7 @@ Available: ${available}`
4115
5113
  type: z.enum(["tool", "resource", "prompt"]).describe("The MCP primitive type to invoke"),
4116
5114
  name: z.string().describe("Tool name, resource URI, or prompt name"),
4117
5115
  arguments: z.record(z.unknown()).optional().describe("Arguments for the tool or prompt (not used for resources)"),
5116
+ args: z.record(z.unknown()).optional().describe("Arguments for the tool or prompt (alias for 'arguments')"),
4118
5117
  // Auto-connect params (only needed if not already connected)
4119
5118
  auto_connect: z.object({
4120
5119
  command: z.string().describe("Command to spawn the server (e.g. 'node')."),
@@ -4138,12 +5137,14 @@ Available: ${available}`
4138
5137
  type: primitiveType,
4139
5138
  name,
4140
5139
  arguments: callArgs,
5140
+ args: callArgsAlias,
4141
5141
  auto_connect,
4142
5142
  disconnect_after,
4143
5143
  timeout_ms,
4144
5144
  include_metadata,
4145
5145
  max_text_length
4146
5146
  }) => {
5147
+ const finalArgs = callArgs ?? callArgsAlias;
4147
5148
  try {
4148
5149
  const connectError = await ensureConnected(
4149
5150
  auto_connect?.command,
@@ -4193,14 +5194,14 @@ Available tools: ${toolNames.join(", ")}`
4193
5194
  }
4194
5195
  const schema = matchedTool.inputSchema;
4195
5196
  const requiredProps = schema?.required ?? [];
4196
- const providedKeys = Object.keys(callArgs ?? {});
5197
+ const providedKeys = Object.keys(finalArgs ?? {});
4197
5198
  const missingProps = requiredProps.filter((p) => !providedKeys.includes(p));
4198
5199
  if (missingProps.length > 0) {
4199
5200
  return {
4200
5201
  content: [
4201
5202
  {
4202
5203
  type: "text",
4203
- text: `Tool "${name}" requires: ${missingProps.join(", ")}. Received: ${JSON.stringify(callArgs ?? {})}`
5204
+ text: `Tool "${name}" requires: ${missingProps.join(", ")}. Received: ${JSON.stringify(finalArgs ?? {})}`
4204
5205
  }
4205
5206
  ],
4206
5207
  isError: true
@@ -4214,7 +5215,7 @@ Available tools: ${toolNames.join(", ")}`
4214
5215
  const { result: toolResult, metadata } = await interceptor.callToolWithMetadata(
4215
5216
  target,
4216
5217
  name,
4217
- callArgs ?? {},
5218
+ finalArgs ?? {},
4218
5219
  timeout_ms,
4219
5220
  max_text_length
4220
5221
  );
@@ -4224,7 +5225,7 @@ Available tools: ${toolNames.join(", ")}`
4224
5225
  result = await interceptor.callTool(
4225
5226
  target,
4226
5227
  name,
4227
- callArgs ?? {},
5228
+ finalArgs ?? {},
4228
5229
  timeout_ms,
4229
5230
  max_text_length
4230
5231
  );
@@ -4248,6 +5249,7 @@ Available tools: ${toolNames.join(", ")}`
4248
5249
  text: `--- metadata ---
4249
5250
  ${JSON.stringify(meta)}`
4250
5251
  });
5252
+ result.meta = meta;
4251
5253
  }
4252
5254
  break;
4253
5255
  }
@@ -4279,6 +5281,7 @@ ${JSON.stringify(meta)}`
4279
5281
  text: `--- metadata ---
4280
5282
  ${JSON.stringify(meta)}`
4281
5283
  });
5284
+ result.meta = meta;
4282
5285
  }
4283
5286
  break;
4284
5287
  }
@@ -4308,7 +5311,7 @@ Available prompts: ${promptNames.join(", ")}`
4308
5311
  target,
4309
5312
  {
4310
5313
  name,
4311
- arguments: callArgs ?? {}
5314
+ arguments: finalArgs ?? {}
4312
5315
  },
4313
5316
  timeout_ms,
4314
5317
  max_text_length
@@ -4347,6 +5350,7 @@ ${item.text}` });
4347
5350
  text: `--- metadata ---
4348
5351
  ${JSON.stringify(meta)}`
4349
5352
  });
5353
+ result.meta = meta;
4350
5354
  }
4351
5355
  break;
4352
5356
  }
@@ -4396,6 +5400,230 @@ ${JSON.stringify(meta)}`
4396
5400
  };
4397
5401
  }
4398
5402
  );
5403
+ mcpServer.registerTool(
5404
+ "validate_mcp_server",
5405
+ {
5406
+ title: "Validate MCP Server",
5407
+ 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.",
5408
+ inputSchema: {
5409
+ command: z.string().describe("Command to run (e.g. 'node', 'python')"),
5410
+ args: z.array(z.string()).optional().describe("Arguments to pass"),
5411
+ env: z.record(z.string()).optional().describe("Extra environment variables")
5412
+ }
5413
+ },
5414
+ async ({ command, args, env }) => {
5415
+ const originalEnv = {};
5416
+ if (env) {
5417
+ for (const [key, value] of Object.entries(env)) {
5418
+ originalEnv[key] = process.env[key];
5419
+ process.env[key] = value;
5420
+ }
5421
+ }
5422
+ const tempTarget = new TargetManager(command, args ?? [], {
5423
+ sandbox: opts.sandbox,
5424
+ allowRead: opts.allowRead,
5425
+ allowWrite: opts.allowWrite,
5426
+ allowNet: opts.allowNet,
5427
+ denyRead: opts.denyRead,
5428
+ denyWrite: opts.denyWrite,
5429
+ denyNet: opts.denyNet
5430
+ });
5431
+ const stderrLines = [];
5432
+ tempTarget.on("stderr", (text) => {
5433
+ stderrLines.push(text);
5434
+ });
5435
+ try {
5436
+ const connectPromise = tempTarget.connect();
5437
+ const timeoutPromise = new Promise(
5438
+ (_, reject) => setTimeout(() => reject(new Error("Connection timed out after 5000ms")), 5e3)
5439
+ );
5440
+ await Promise.race([connectPromise, timeoutPromise]);
5441
+ const toolsResult = await tempTarget.listTools();
5442
+ const caps = tempTarget.getServerCapabilities() ?? {};
5443
+ const ver = tempTarget.getServerVersion();
5444
+ await tempTarget.close();
5445
+ return {
5446
+ content: [
5447
+ {
5448
+ type: "text",
5449
+ text: `Validation Result: SUCCESS
5450
+ Server Name: ${ver?.name ?? "unknown"}
5451
+ Server Version: ${ver?.version ?? "unknown"}
5452
+ Tools Count: ${toolsResult.tools.length}
5453
+ Capabilities: ${Object.keys(caps).join(", ") || "none"}
5454
+
5455
+ Captured Stderr:
5456
+ ${stderrLines.join("\n") || "(none)"}`
5457
+ }
5458
+ ]
5459
+ };
5460
+ } catch (err) {
5461
+ await tempTarget.close().catch(() => {
5462
+ });
5463
+ return {
5464
+ content: [
5465
+ {
5466
+ type: "text",
5467
+ text: `Validation Result: FAILED
5468
+ Error: ${err.message}
5469
+
5470
+ Captured Stderr:
5471
+ ${stderrLines.join("\n") || "(none)"}`
5472
+ }
5473
+ ],
5474
+ isError: true
5475
+ };
5476
+ } finally {
5477
+ if (env) {
5478
+ for (const key of Object.keys(env)) {
5479
+ if (originalEnv[key] === void 0) {
5480
+ delete process.env[key];
5481
+ } else {
5482
+ process.env[key] = originalEnv[key];
5483
+ }
5484
+ }
5485
+ }
5486
+ }
5487
+ }
5488
+ );
5489
+ mcpServer.registerTool(
5490
+ "search_all_local_mcp_servers",
5491
+ {
5492
+ title: "Search All Local MCP Servers",
5493
+ 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.",
5494
+ inputSchema: {
5495
+ query: z.string().describe("Search query (case-insensitive substring match)"),
5496
+ type: z.array(z.enum(["tools", "resources", "prompts"])).optional().describe("Primitives to search. Defaults to ['tools'].")
5497
+ }
5498
+ },
5499
+ async ({ query, type }) => {
5500
+ const searchTypes = type ?? ["tools"];
5501
+ const lowerQuery = query.toLowerCase();
5502
+ try {
5503
+ const servers = await discoverServers({ scan: opts.scan });
5504
+ if (servers.length === 0) {
5505
+ return {
5506
+ content: [{ type: "text", text: "No local MCP servers found to search." }]
5507
+ };
5508
+ }
5509
+ const uniqueServers = /* @__PURE__ */ new Map();
5510
+ for (const s of servers) {
5511
+ const key = `${s.name}::${s.config.command}::${(s.config.args || []).join(" ")}`;
5512
+ if (!uniqueServers.has(key)) {
5513
+ uniqueServers.set(key, s);
5514
+ } else if (s.source.includes("Project")) {
5515
+ uniqueServers.set(key, s);
5516
+ }
5517
+ }
5518
+ const matchResults = [];
5519
+ for (const s of uniqueServers.values()) {
5520
+ const originalEnv = {};
5521
+ if (s.config.env) {
5522
+ for (const [key, value] of Object.entries(s.config.env)) {
5523
+ originalEnv[key] = process.env[key];
5524
+ process.env[key] = value;
5525
+ }
5526
+ }
5527
+ const tempTarget = new TargetManager(s.config.command, s.config.args || [], {
5528
+ sandbox: opts.sandbox,
5529
+ allowRead: opts.allowRead,
5530
+ allowWrite: opts.allowWrite,
5531
+ allowNet: opts.allowNet,
5532
+ denyRead: opts.denyRead,
5533
+ denyWrite: opts.denyWrite,
5534
+ denyNet: opts.denyNet
5535
+ });
5536
+ try {
5537
+ await Promise.race([
5538
+ tempTarget.connect(),
5539
+ new Promise(
5540
+ (_, reject) => setTimeout(() => reject(new Error("Timeout")), 3e3)
5541
+ )
5542
+ ]);
5543
+ const caps = tempTarget.getServerCapabilities() ?? {};
5544
+ if (searchTypes.includes("tools") && caps.tools) {
5545
+ const { tools } = await tempTarget.listTools();
5546
+ for (const t of tools) {
5547
+ if (t.name.toLowerCase().includes(lowerQuery) || t.description && t.description.toLowerCase().includes(lowerQuery)) {
5548
+ matchResults.push({
5549
+ server: s.name,
5550
+ primitive: "tool",
5551
+ name: t.name,
5552
+ description: t.description
5553
+ });
5554
+ }
5555
+ }
5556
+ }
5557
+ if (searchTypes.includes("resources") && caps.resources) {
5558
+ const { resources } = await tempTarget.listResources();
5559
+ for (const r of resources) {
5560
+ if (r.name && r.name.toLowerCase().includes(lowerQuery) || r.uri.toLowerCase().includes(lowerQuery) || r.description && r.description.toLowerCase().includes(lowerQuery)) {
5561
+ matchResults.push({
5562
+ server: s.name,
5563
+ primitive: "resource",
5564
+ name: r.name || r.uri,
5565
+ uri: r.uri,
5566
+ description: r.description
5567
+ });
5568
+ }
5569
+ }
5570
+ }
5571
+ if (searchTypes.includes("prompts") && caps.prompts) {
5572
+ const { prompts } = await tempTarget.listPrompts();
5573
+ for (const p of prompts) {
5574
+ if (p.name.toLowerCase().includes(lowerQuery) || p.description && p.description.toLowerCase().includes(lowerQuery)) {
5575
+ matchResults.push({
5576
+ server: s.name,
5577
+ primitive: "prompt",
5578
+ name: p.name,
5579
+ description: p.description
5580
+ });
5581
+ }
5582
+ }
5583
+ }
5584
+ } catch {
5585
+ } finally {
5586
+ await tempTarget.close().catch(() => {
5587
+ });
5588
+ if (s.config.env) {
5589
+ for (const key of Object.keys(s.config.env)) {
5590
+ if (originalEnv[key] === void 0) {
5591
+ delete process.env[key];
5592
+ } else {
5593
+ process.env[key] = originalEnv[key];
5594
+ }
5595
+ }
5596
+ }
5597
+ }
5598
+ }
5599
+ if (matchResults.length === 0) {
5600
+ return {
5601
+ content: [
5602
+ {
5603
+ type: "text",
5604
+ text: `No matches found for query "${query}".`
5605
+ }
5606
+ ]
5607
+ };
5608
+ }
5609
+ return {
5610
+ content: [
5611
+ {
5612
+ type: "text",
5613
+ text: `Search results for "${query}":
5614
+
5615
+ ${JSON.stringify(matchResults, null, 2)}`
5616
+ }
5617
+ ]
5618
+ };
5619
+ } catch (err) {
5620
+ return {
5621
+ content: [{ type: "text", text: `Error searching servers: ${err.message}` }],
5622
+ isError: true
5623
+ };
5624
+ }
5625
+ }
5626
+ );
4399
5627
  const transport = new StdioServerTransport();
4400
5628
  mcpServer.server.onclose = async () => {
4401
5629
  if (target) {
@@ -4410,7 +5638,8 @@ ${JSON.stringify(meta)}`
4410
5638
 
4411
5639
  // src/index.ts
4412
5640
  function requireTargetCommand(targetCommand, subcommandUsage) {
4413
- if (!activeTargetCommand) {
5641
+ const target = activeTargetCommand ?? targetCommand;
5642
+ if (!target || target.length === 0) {
4414
5643
  process.stderr.write(`Error: Target server command must be separated by '--'.
4415
5644
  `);
4416
5645
  process.stderr.write(`This avoids option parsing conflicts.
@@ -4418,19 +5647,19 @@ function requireTargetCommand(targetCommand, subcommandUsage) {
4418
5647
  `);
4419
5648
  process.stderr.write(`Usage: ${subcommandUsage}
4420
5649
  `);
4421
- process.exit(2);
5650
+ process.exit(64);
4422
5651
  }
4423
- return activeTargetCommand;
5652
+ return target;
4424
5653
  }
4425
- var SESSION_DIR = join2(tmpdir2(), "run-mcp", "sessions");
5654
+ var SESSION_DIR = join6(tmpdir3(), "run-mcp", "sessions");
4426
5655
  function getSessionPath(name) {
4427
- return join2(SESSION_DIR, `${name}.json`);
5656
+ return join6(SESSION_DIR, `${name}.json`);
4428
5657
  }
4429
5658
  async function getSession(name) {
4430
5659
  const path2 = getSessionPath(name);
4431
- if (!existsSync2(path2)) return null;
5660
+ if (!existsSync6(path2)) return null;
4432
5661
  try {
4433
- const data = await readFile3(path2, "utf8");
5662
+ const data = await readFile5(path2, "utf8");
4434
5663
  const parsed = JSON.parse(data);
4435
5664
  try {
4436
5665
  process.kill(parsed.pid, 0);
@@ -4445,7 +5674,7 @@ async function getSession(name) {
4445
5674
  }
4446
5675
  }
4447
5676
  function sendDaemonRequest(port, request) {
4448
- return new Promise((resolve2, reject) => {
5677
+ return new Promise((resolve3, reject) => {
4449
5678
  const socket = createConnection({ port });
4450
5679
  let buffer = "";
4451
5680
  socket.on("connect", () => {
@@ -4460,7 +5689,7 @@ function sendDaemonRequest(port, request) {
4460
5689
  if (parsed.error) {
4461
5690
  reject(new Error(parsed.error.message));
4462
5691
  } else {
4463
- resolve2(parsed.result);
5692
+ resolve3(parsed.result);
4464
5693
  }
4465
5694
  } catch (err) {
4466
5695
  reject(new Error(`Failed to parse daemon response: ${err}`));
@@ -4482,11 +5711,16 @@ async function handleHeadlessSession(sessionName, targetCommand, operation, opts
4482
5711
  `);
4483
5712
  process.stderr.write(`Usage: ${subcommandUsage}
4484
5713
  `);
4485
- process.exit(2);
5714
+ process.exit(64);
4486
5715
  }
4487
5716
  const target = activeTargetCommand;
4488
- const binPath = resolve(import.meta.dirname, "./index.js");
4489
- const daemonProcess = spawn("node", [binPath, "daemon", sessionName, ...target], {
5717
+ const binPath = resolve2(import.meta.dirname, "./index.js");
5718
+ const daemonArgs = ["daemon", sessionName];
5719
+ if (opts.sandbox) {
5720
+ daemonArgs.push("--sandbox", opts.sandbox);
5721
+ }
5722
+ daemonArgs.push(...target);
5723
+ const daemonProcess = spawn("node", [binPath, ...daemonArgs], {
4490
5724
  detached: true,
4491
5725
  stdio: "ignore"
4492
5726
  });
@@ -4495,7 +5729,7 @@ async function handleHeadlessSession(sessionName, targetCommand, operation, opts
4495
5729
  while (attempts < 50) {
4496
5730
  session = await getSession(sessionName);
4497
5731
  if (session) break;
4498
- await new Promise((resolve2) => setTimeout(resolve2, 100));
5732
+ await new Promise((resolve3) => setTimeout(resolve3, 100));
4499
5733
  attempts++;
4500
5734
  }
4501
5735
  if (!session) {
@@ -4528,7 +5762,14 @@ function parseHeadlessOpts(opts) {
4528
5762
  timeoutMs: opts.timeout ? Number.parseInt(opts.timeout, 10) : void 0,
4529
5763
  raw: opts.raw,
4530
5764
  showStderr: opts.showStderr,
4531
- mediaThresholdKb: opts.mediaThreshold ? Number.parseInt(opts.mediaThreshold, 10) : void 0
5765
+ mediaThresholdKb: opts.mediaThreshold ? Number.parseInt(opts.mediaThreshold, 10) : void 0,
5766
+ sandbox: opts.sandbox,
5767
+ allowRead: opts.allowRead,
5768
+ allowWrite: opts.allowWrite,
5769
+ allowNet: opts.allowNet,
5770
+ denyRead: opts.denyRead,
5771
+ denyWrite: opts.denyWrite,
5772
+ denyNet: opts.denyNet
4532
5773
  };
4533
5774
  }
4534
5775
  var activeTargetCommand;
@@ -4542,7 +5783,7 @@ program.enablePositionalOptions();
4542
5783
  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
5784
  "-m, --media-threshold <kb>",
4544
5785
  "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(
5786
+ ).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
5787
  async (tool, jsonArgs, targetCommand, opts) => {
4547
5788
  const operation = { type: "call", tool, args: jsonArgs };
4548
5789
  const parsedOpts = parseHeadlessOpts(opts);
@@ -4563,7 +5804,7 @@ program.command("call").argument("<tool>", "Tool name to call").argument("[json_
4563
5804
  }
4564
5805
  }
4565
5806
  );
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) => {
5807
+ 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
5808
  const operation = { type: "list-tools" };
4568
5809
  const parsedOpts = parseHeadlessOpts(opts);
4569
5810
  if (opts.session) {
@@ -4582,7 +5823,7 @@ program.command("list-tools").argument("[target_command...]", "Target server com
4582
5823
  await runHeadless(target, operation, parsedOpts);
4583
5824
  }
4584
5825
  });
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) => {
5826
+ 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
5827
  const operation = { type: "list-resources" };
4587
5828
  const parsedOpts = parseHeadlessOpts(opts);
4588
5829
  if (opts.session) {
@@ -4601,7 +5842,7 @@ program.command("list-resources").argument("[target_command...]", "Target server
4601
5842
  await runHeadless(target, operation, parsedOpts);
4602
5843
  }
4603
5844
  });
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) => {
5845
+ 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
5846
  const operation = { type: "list-prompts" };
4606
5847
  const parsedOpts = parseHeadlessOpts(opts);
4607
5848
  if (opts.session) {
@@ -4623,7 +5864,7 @@ program.command("list-prompts").argument("[target_command...]", "Target server c
4623
5864
  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
5865
  "-m, --media-threshold <kb>",
4625
5866
  "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) => {
5867
+ ).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
5868
  const operation = { type: "read", uri };
4628
5869
  const parsedOpts = parseHeadlessOpts(opts);
4629
5870
  if (opts.session) {
@@ -4642,7 +5883,7 @@ program.command("read").argument("<uri>", "Resource URI to read").argument("[tar
4642
5883
  await runHeadless(target, operation, parsedOpts);
4643
5884
  }
4644
5885
  });
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) => {
5886
+ 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
5887
  const operation = { type: "describe", tool };
4647
5888
  const parsedOpts = parseHeadlessOpts(opts);
4648
5889
  if (opts.session) {
@@ -4664,7 +5905,7 @@ program.command("describe").argument("<tool>", "Tool name to describe").argument
4664
5905
  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
5906
  "-m, --media-threshold <kb>",
4666
5907
  "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(
5908
+ ).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
5909
  async (name, jsonArgs, targetCommand, opts) => {
4669
5910
  const operation = { type: "get-prompt", name, args: jsonArgs };
4670
5911
  const parsedOpts = parseHeadlessOpts(opts);
@@ -4685,17 +5926,19 @@ program.command("get-prompt").argument("<name>", "Prompt name").argument("[json_
4685
5926
  }
4686
5927
  }
4687
5928
  );
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) => {
5929
+ 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
5930
  const targetCmd = activeTargetCommand ?? targetCommand;
4690
5931
  if (!targetCmd || targetCmd.length === 0) {
4691
5932
  process.stderr.write("Error: No target command provided for daemon.\n");
4692
- process.exit(1);
5933
+ process.exit(64);
4693
5934
  }
4694
5935
  const server = createServer();
4695
5936
  server.listen(0, "127.0.0.1", async () => {
4696
5937
  const addr = server.address();
4697
5938
  const port = addr.port;
4698
- const target = new TargetManager(targetCmd[0], targetCmd.slice(1));
5939
+ const target = new TargetManager(targetCmd[0], targetCmd.slice(1), {
5940
+ sandbox: opts.sandbox
5941
+ });
4699
5942
  const interceptor = new ResponseInterceptor();
4700
5943
  try {
4701
5944
  await target.connect();
@@ -4704,8 +5947,8 @@ program.command("daemon").argument("<session_name>", "Session name").argument("[
4704
5947
  `);
4705
5948
  process.exit(1);
4706
5949
  }
4707
- await mkdir2(SESSION_DIR, { recursive: true });
4708
- await writeFile2(
5950
+ await mkdir3(SESSION_DIR, { recursive: true });
5951
+ await writeFile3(
4709
5952
  getSessionPath(sessionName),
4710
5953
  JSON.stringify({ port, pid: process.pid }),
4711
5954
  "utf8"
@@ -4722,12 +5965,12 @@ program.command("daemon").argument("<session_name>", "Session name").argument("[
4722
5965
  try {
4723
5966
  const req = JSON.parse(trimmed);
4724
5967
  if (req.method === "execute") {
4725
- const { operation, opts } = req.params;
5968
+ const { operation, opts: opts2 } = req.params;
4726
5969
  const { result, hasError } = await executeOperation(
4727
5970
  target,
4728
5971
  interceptor,
4729
5972
  operation,
4730
- opts
5973
+ opts2
4731
5974
  );
4732
5975
  socket.write(
4733
5976
  JSON.stringify({ jsonrpc: "2.0", result: { result, hasError }, id: req.id }) + "\n"
@@ -4778,7 +6021,7 @@ program.command("close-session").argument("<session_name>", "Session name").desc
4778
6021
  }
4779
6022
  }
4780
6023
  });
4781
- program.name("run-mcp").description("A smart interactive REPL and live test harness for MCP servers").version("1.6.3").passThroughOptions().allowUnknownOption().argument(
6024
+ program.name("run-mcp").description("A smart interactive REPL and live test harness for MCP servers").version("1.7.1").passThroughOptions().allowUnknownOption().argument(
4782
6025
  "[target_command...]",
4783
6026
  "Command to spawn the target MCP server (starts REPL if provided, Agent server otherwise)"
4784
6027
  ).option("-o, --out-dir <path>", "Directory to save intercepted images and audio").option(
@@ -4790,7 +6033,13 @@ program.name("run-mcp").description("A smart interactive REPL and live test harn
4790
6033
  ).option(
4791
6034
  "-m, --media-threshold <kb>",
4792
6035
  "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(
6036
+ ).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(
6037
+ "--open-media",
6038
+ "Automatically open intercepted images and audio files using the host OS viewer"
6039
+ ).option("--sandbox <mode>", "Sandbox execution mode: auto, docker, native, audit, none", "none").option(
6040
+ "--scan",
6041
+ "Scan the current workspace and parent directories for any JSON files containing mcpServers"
6042
+ ).addHelpText(
4794
6043
  "after",
4795
6044
  `
4796
6045
  Examples:
@@ -4827,6 +6076,8 @@ Agent Mode Tools:
4827
6076
  disconnect_from_mcp \u2192 Tear down and reconnect after changes
4828
6077
  mcp_server_status \u2192 Check connection status
4829
6078
  get_mcp_server_stderr \u2192 View target server stderr output
6079
+ validate_mcp_server \u2192 Validate an MCP server command and collect diagnostics
6080
+ search_all_local_mcp_servers \u2192 Scan and search all local MCP servers for a query
4830
6081
 
4831
6082
  REPL Mode Commands (once connected):
4832
6083
  tools/list List all available tools
@@ -4855,18 +6106,20 @@ REPL Mode Commands (once connected):
4855
6106
  Shortcuts: tl td tc ts rl rr rt rs ru pl pg (see help for details)`
4856
6107
  ).action(
4857
6108
  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 ?? [];
6109
+ const target = activeTargetCommand ?? targetCommand ?? [];
4865
6110
  if (target && target.length > 0) {
4866
6111
  await startRepl(target, {
4867
6112
  script: opts.script,
4868
6113
  outDir: opts.outDir,
4869
- mediaThresholdKb: opts.mediaThreshold ? Number.parseInt(opts.mediaThreshold, 10) : void 0
6114
+ mediaThresholdKb: opts.mediaThreshold ? Number.parseInt(opts.mediaThreshold, 10) : void 0,
6115
+ openMedia: opts.openMedia,
6116
+ sandbox: opts.sandbox,
6117
+ allowRead: opts.allowRead,
6118
+ allowWrite: opts.allowWrite,
6119
+ allowNet: opts.allowNet,
6120
+ denyRead: opts.denyRead,
6121
+ denyWrite: opts.denyWrite,
6122
+ denyNet: opts.denyNet
4870
6123
  });
4871
6124
  } else {
4872
6125
  if (opts.mcp || !process.stdin.isTTY) {
@@ -4874,10 +6127,18 @@ Shortcuts: tl td tc ts rl rr rt rs ru pl pg (see help for details)`
4874
6127
  outDir: opts.outDir,
4875
6128
  timeoutMs: opts.timeout ? Number.parseInt(opts.timeout, 10) : void 0,
4876
6129
  maxTextLength: opts.maxText ? Number.parseInt(opts.maxText, 10) : void 0,
4877
- mediaThresholdKb: opts.mediaThreshold ? Number.parseInt(opts.mediaThreshold, 10) : void 0
6130
+ mediaThresholdKb: opts.mediaThreshold ? Number.parseInt(opts.mediaThreshold, 10) : void 0,
6131
+ sandbox: opts.sandbox,
6132
+ allowRead: opts.allowRead,
6133
+ allowWrite: opts.allowWrite,
6134
+ allowNet: opts.allowNet,
6135
+ denyRead: opts.denyRead,
6136
+ denyWrite: opts.denyWrite,
6137
+ denyNet: opts.denyNet,
6138
+ scan: opts.scan
4878
6139
  });
4879
6140
  } else {
4880
- const selected = await pickDiscoveredServer();
6141
+ const selected = await pickDiscoveredServer({ scan: opts.scan });
4881
6142
  if (!selected) {
4882
6143
  console.log("Run 'run-mcp --help' to see manual usage instructions.");
4883
6144
  return;
@@ -4888,10 +6149,26 @@ Shortcuts: tl td tc ts rl rr rt rs ru pl pg (see help for details)`
4888
6149
  await startRepl([selected.config.command, ...selected.config.args || []], {
4889
6150
  script: opts.script,
4890
6151
  outDir: opts.outDir,
4891
- mediaThresholdKb: opts.mediaThreshold ? Number.parseInt(opts.mediaThreshold, 10) : void 0
6152
+ mediaThresholdKb: opts.mediaThreshold ? Number.parseInt(opts.mediaThreshold, 10) : void 0,
6153
+ openMedia: opts.openMedia,
6154
+ sandbox: opts.sandbox,
6155
+ allowRead: opts.allowRead,
6156
+ allowWrite: opts.allowWrite,
6157
+ allowNet: opts.allowNet,
6158
+ denyRead: opts.denyRead,
6159
+ denyWrite: opts.denyWrite,
6160
+ denyNet: opts.denyNet
4892
6161
  });
4893
6162
  }
4894
6163
  }
4895
6164
  }
4896
6165
  );
6166
+ for (const cmd of [program, ...program.commands]) {
6167
+ if (cmd.options.some((o) => o.long === "--sandbox")) {
6168
+ cmd.option("--allow-read <paths...>", "Paths to allow reading under the sandbox").option("--allow-write <paths...>", "Paths to allow writing under the sandbox").option(
6169
+ "--allow-net <domains...>",
6170
+ "Network domains to allow connecting to under the sandbox"
6171
+ ).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");
6172
+ }
6173
+ }
4897
6174
  program.parse(argvToParse);