run-mcp 1.5.1 → 1.6.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 +121 -58
  2. package/dist/index.js +2223 -680
  3. package/package.json +10 -5
package/dist/index.js CHANGED
@@ -2,12 +2,142 @@
2
2
 
3
3
  // src/index.ts
4
4
  import { program } from "commander";
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";
10
+ import { spawn } from "child_process";
5
11
 
6
- // src/repl.ts
12
+ // src/config-scanner.ts
13
+ import { existsSync } from "fs";
7
14
  import { readFile } from "fs/promises";
8
- import { createInterface } from "readline";
9
- import { checkbox, confirm, input, search } from "@inquirer/prompts";
10
- import pc from "picocolors";
15
+ import { homedir } from "os";
16
+ import path from "path";
17
+ import process2 from "process";
18
+ import { input, select } from "@inquirer/prompts";
19
+ function getConfigPaths() {
20
+ const home = homedir();
21
+ const cwd = process2.cwd();
22
+ const isWin = process2.platform === "win32";
23
+ const isMac = process2.platform === "darwin";
24
+ const appData = process2.env.APPDATA || path.join(home, "AppData", "Roaming");
25
+ let claudeDesktopGlob;
26
+ if (isWin) {
27
+ claudeDesktopGlob = path.join(appData, "Claude", "claude_desktop_config.json");
28
+ } else if (isMac) {
29
+ claudeDesktopGlob = path.join(
30
+ home,
31
+ "Library",
32
+ "Application Support",
33
+ "Claude",
34
+ "claude_desktop_config.json"
35
+ );
36
+ } else {
37
+ claudeDesktopGlob = path.join(home, ".config", "Claude", "claude_desktop_config.json");
38
+ }
39
+ return [
40
+ { source: "Cursor (Global)", file: path.join(home, ".cursor", "mcp.json") },
41
+ { source: "Cursor (Project)", file: path.join(cwd, ".cursor", "mcp.json") },
42
+ { source: "Windsurf", file: path.join(home, ".codeium", "windsurf", "mcp_config.json") },
43
+ { source: "Claude Desktop", file: claudeDesktopGlob },
44
+ { source: "Cline", file: path.join(home, "Documents", "Cline", "MCP", "mcp.json") },
45
+ { source: "VS Code (Project)", file: path.join(cwd, ".vscode", "mcp.json") },
46
+ {
47
+ source: "VS Code (Global)",
48
+ file: path.join(
49
+ isMac ? path.join(home, "Library", "Application Support") : isWin ? appData : path.join(home, ".config"),
50
+ "Code",
51
+ "User",
52
+ "settings.json"
53
+ )
54
+ },
55
+ { source: "Copilot CLI (Global)", file: path.join(home, ".copilot", "mcp-config.json") },
56
+ { source: "Gemini CLI (Global)", file: path.join(home, ".gemini", "settings.json") },
57
+ { source: "Gemini CLI (Project)", file: path.join(cwd, ".gemini", "settings.json") },
58
+ { source: "Claude Code (Global)", file: path.join(home, ".claude.json") },
59
+ { source: "Claude Code (Project)", file: path.join(cwd, ".mcp.json") },
60
+ { source: "Antigravity", file: path.join(home, ".gemini", "antigravity", "mcp_config.json") }
61
+ ];
62
+ }
63
+ async function discoverServers() {
64
+ const servers = [];
65
+ const paths = getConfigPaths();
66
+ for (const { source, file } of paths) {
67
+ if (!existsSync(file)) continue;
68
+ try {
69
+ const content = await readFile(file, "utf8");
70
+ const json = JSON.parse(content);
71
+ let mcpServers;
72
+ if (json.mcpServers && typeof json.mcpServers === "object") {
73
+ mcpServers = json.mcpServers;
74
+ } else if (json.mcp?.servers && typeof json.mcp.servers === "object") {
75
+ mcpServers = json.mcp.servers;
76
+ } else if (json.servers && typeof json.servers === "object") {
77
+ mcpServers = json.servers;
78
+ }
79
+ if (mcpServers) {
80
+ for (const [name, config] of Object.entries(mcpServers)) {
81
+ if (config.command) {
82
+ servers.push({ name, config, source });
83
+ }
84
+ }
85
+ }
86
+ } catch {
87
+ }
88
+ }
89
+ return servers;
90
+ }
91
+ async function pickDiscoveredServer() {
92
+ const servers = await discoverServers();
93
+ if (servers.length === 0) {
94
+ return null;
95
+ }
96
+ const uniqueServers = /* @__PURE__ */ new Map();
97
+ for (const s of servers) {
98
+ const key = `${s.name}::${s.config.command}::${(s.config.args || []).join(" ")}`;
99
+ if (!uniqueServers.has(key)) {
100
+ uniqueServers.set(key, s);
101
+ } else {
102
+ if (s.source.includes("Project")) {
103
+ uniqueServers.set(key, s);
104
+ }
105
+ }
106
+ }
107
+ const choices = Array.from(uniqueServers.values()).map((s) => {
108
+ return {
109
+ name: `${s.name} (from ${s.source})`,
110
+ value: s,
111
+ description: `${s.config.command} ${(s.config.args || []).join(" ")}`
112
+ };
113
+ });
114
+ choices.push({
115
+ name: "Enter custom server command...",
116
+ value: "CUSTOM",
117
+ description: "Manually specify a command, e.g. 'npx foo' or 'python server.py'"
118
+ });
119
+ try {
120
+ const answer = await select({
121
+ message: "Select an MCP server to launch:",
122
+ choices,
123
+ pageSize: 15
124
+ });
125
+ if (answer === "CUSTOM") {
126
+ const customCommand = await input({ message: "Command to spawn target MCP server:" });
127
+ if (!customCommand.trim()) return null;
128
+ const parts = customCommand.trim().match(/(?:[^\s"']+|"[^"]*"|'[^']*')+/g)?.map((p) => p.replace(/^["']|["']$/g, ""));
129
+ if (!parts || parts.length === 0) return null;
130
+ return {
131
+ name: "Custom",
132
+ config: { command: parts[0], args: parts.slice(1) },
133
+ source: "Manual"
134
+ };
135
+ }
136
+ return answer;
137
+ } catch {
138
+ return null;
139
+ }
140
+ }
11
141
 
12
142
  // src/interceptor.ts
13
143
  import { mkdir, writeFile } from "fs/promises";
@@ -20,11 +150,13 @@ var ResponseInterceptor = class {
20
150
  outDir;
21
151
  defaultTimeoutMs;
22
152
  maxTextLength;
153
+ mediaThresholdKb;
23
154
  fileCounter = 0;
24
155
  constructor(opts = {}) {
25
156
  this.outDir = opts.outDir ?? join(tmpdir(), "run-mcp");
26
157
  this.defaultTimeoutMs = opts.defaultTimeoutMs ?? DEFAULT_TIMEOUT_MS;
27
158
  this.maxTextLength = opts.maxTextLength ?? DEFAULT_MAX_TEXT_LENGTH;
159
+ this.mediaThresholdKb = opts.mediaThresholdKb ?? 0;
28
160
  }
29
161
  /**
30
162
  * Call a tool on the target, applying timeout, media extraction, and truncation.
@@ -32,40 +164,196 @@ var ResponseInterceptor = class {
32
164
  * Returns the full result object as-is (including structuredContent, isError, _meta)
33
165
  * with only the content array items modified when interception is needed.
34
166
  */
35
- async callTool(target, name, args = {}, timeoutMs) {
167
+ async callTool(target, name, args = {}, timeoutMs, maxTextLength) {
168
+ const { result } = await this._callToolInternal(target, name, args, timeoutMs, maxTextLength);
169
+ return result;
170
+ }
171
+ /**
172
+ * Call a tool and return both the result and metadata about interception actions.
173
+ * Used by the agent server when `include_metadata` is requested.
174
+ */
175
+ async callToolWithMetadata(target, name, args = {}, timeoutMs, maxTextLength) {
176
+ return this._callToolInternal(target, name, args, timeoutMs, maxTextLength);
177
+ }
178
+ /**
179
+ * Read a resource on the target, applying timeout, media extraction, and truncation.
180
+ */
181
+ async readResource(target, params, timeoutMs, maxTextLength) {
182
+ const timeout = timeoutMs ?? this.defaultTimeoutMs;
183
+ const metadata = {
184
+ truncated: false,
185
+ imagesSaved: 0,
186
+ audioSaved: 0,
187
+ originalSizeBytes: 0
188
+ };
189
+ const targetCall = target.readResource(params);
190
+ targetCall.catch(() => {
191
+ });
192
+ const result = await Promise.race([
193
+ targetCall,
194
+ this._timeout(timeout, `resource:${params.uri}`)
195
+ ]);
196
+ const contents = result.contents;
197
+ if (Array.isArray(contents)) {
198
+ for (const item of contents) {
199
+ if (item.text) {
200
+ metadata.originalSizeBytes += Buffer.byteLength(item.text, "utf8");
201
+ } else if (item.blob) {
202
+ metadata.originalSizeBytes += Buffer.byteLength(item.blob, "base64");
203
+ }
204
+ }
205
+ for (let i = 0; i < contents.length; i++) {
206
+ contents[i] = await this._processResourceItem(contents[i], metadata, maxTextLength);
207
+ }
208
+ }
209
+ return result;
210
+ }
211
+ /**
212
+ * Get a prompt on the target, applying timeout, media extraction, and truncation.
213
+ */
214
+ async getPrompt(target, params, timeoutMs, maxTextLength) {
36
215
  const timeout = timeoutMs ?? this.defaultTimeoutMs;
216
+ const metadata = {
217
+ truncated: false,
218
+ imagesSaved: 0,
219
+ audioSaved: 0,
220
+ originalSizeBytes: 0
221
+ };
222
+ const targetCall = target.getPrompt(params);
223
+ targetCall.catch(() => {
224
+ });
225
+ const result = await Promise.race([
226
+ targetCall,
227
+ this._timeout(timeout, `prompt:${params.name}`)
228
+ ]);
229
+ const messages = result.messages;
230
+ if (Array.isArray(messages)) {
231
+ for (const msg of messages) {
232
+ const content = msg.content;
233
+ if (content) {
234
+ if (Array.isArray(content)) {
235
+ for (const item of content) {
236
+ if (item.type === "text" && item.text) {
237
+ metadata.originalSizeBytes += Buffer.byteLength(item.text, "utf8");
238
+ } else if ((item.type === "image" || item.type === "audio") && item.data) {
239
+ metadata.originalSizeBytes += Buffer.byteLength(item.data, "base64");
240
+ }
241
+ }
242
+ for (let i = 0; i < content.length; i++) {
243
+ content[i] = await this._processItem(content[i], metadata, maxTextLength);
244
+ }
245
+ } else if (typeof content === "object") {
246
+ if (content.type === "text" && content.text) {
247
+ metadata.originalSizeBytes += Buffer.byteLength(content.text, "utf8");
248
+ } else if ((content.type === "image" || content.type === "audio") && content.data) {
249
+ metadata.originalSizeBytes += Buffer.byteLength(content.data, "base64");
250
+ }
251
+ msg.content = await this._processItem(content, metadata, maxTextLength);
252
+ }
253
+ }
254
+ }
255
+ }
256
+ return result;
257
+ }
258
+ /**
259
+ * Internal implementation shared by callTool and callToolWithMetadata.
260
+ */
261
+ async _callToolInternal(target, name, args = {}, timeoutMs, maxTextLength) {
262
+ const timeout = timeoutMs ?? this.defaultTimeoutMs;
263
+ const metadata = {
264
+ truncated: false,
265
+ imagesSaved: 0,
266
+ audioSaved: 0,
267
+ originalSizeBytes: 0
268
+ };
37
269
  const targetCall = target.callTool(name, args);
38
270
  targetCall.catch(() => {
39
271
  });
40
272
  const result = await Promise.race([targetCall, this._timeout(timeout, name)]);
41
273
  const content = result.content;
42
274
  if (Array.isArray(content)) {
275
+ for (const item of content) {
276
+ if (item.type === "text" && item.text) {
277
+ metadata.originalSizeBytes += Buffer.byteLength(item.text, "utf8");
278
+ } else if ((item.type === "image" || item.type === "audio") && item.data) {
279
+ metadata.originalSizeBytes += Buffer.byteLength(item.data, "base64");
280
+ }
281
+ }
43
282
  for (let i = 0; i < content.length; i++) {
44
- content[i] = await this._processItem(content[i]);
283
+ content[i] = await this._processItem(content[i], metadata, maxTextLength);
45
284
  }
46
285
  }
47
- return result;
286
+ return { result, metadata };
48
287
  }
49
288
  /**
50
289
  * Process a single content item — extract media, truncate text.
51
290
  * Preserves all item properties not related to the intercepted data
52
291
  * (e.g., annotations, _meta).
53
292
  */
54
- async _processItem(item) {
293
+ async _processItem(item, metadata, maxTextLength) {
55
294
  if (item.type === "image" && item.data) {
295
+ const sizeKB = Buffer.byteLength(item.data, "base64") / 1024;
296
+ if (this.mediaThresholdKb === -1 || this.mediaThresholdKb > 0 && sizeKB <= this.mediaThresholdKb) {
297
+ return item;
298
+ }
299
+ metadata.imagesSaved++;
56
300
  return this._saveMedia(item.data, item.mimeType ?? "image/png", "image");
57
301
  }
58
302
  if (item.type === "audio" && item.data) {
303
+ const sizeKB = Buffer.byteLength(item.data, "base64") / 1024;
304
+ if (this.mediaThresholdKb === -1 || this.mediaThresholdKb > 0 && sizeKB <= this.mediaThresholdKb) {
305
+ return item;
306
+ }
307
+ metadata.audioSaved++;
59
308
  return this._saveMedia(item.data, item.mimeType ?? "audio/wav", "audio");
60
309
  }
61
310
  if (item.type === "text" && item.text && BASE64_PATTERN.test(item.text.trim())) {
311
+ const sizeKB = Buffer.byteLength(item.text.trim(), "base64") / 1024;
312
+ if (this.mediaThresholdKb === -1 || this.mediaThresholdKb > 0 && sizeKB <= this.mediaThresholdKb) {
313
+ return item;
314
+ }
315
+ metadata.imagesSaved++;
62
316
  return this._saveMedia(item.text.trim(), "image/png", "image");
63
317
  }
64
- if (item.type === "text" && item.text && item.text.length > this.maxTextLength) {
318
+ const limit = maxTextLength ?? this.maxTextLength;
319
+ if (item.type === "text" && item.text && limit !== -1 && item.text.length > limit) {
320
+ const totalLength = item.text.length;
321
+ metadata.truncated = true;
322
+ return {
323
+ ...item,
324
+ text: item.text.slice(0, limit) + `
325
+ ... (truncated, ${totalLength.toLocaleString()} chars total)`
326
+ };
327
+ }
328
+ return item;
329
+ }
330
+ /**
331
+ * Process a single resource content item.
332
+ */
333
+ async _processResourceItem(item, metadata, maxTextLength) {
334
+ if (item.blob) {
335
+ const mime = item.mimeType ?? "image/png";
336
+ const isAudio = mime.startsWith("audio/");
337
+ const sizeKB = Buffer.byteLength(item.blob, "base64") / 1024;
338
+ if (this.mediaThresholdKb === -1 || this.mediaThresholdKb > 0 && sizeKB <= this.mediaThresholdKb) {
339
+ return item;
340
+ }
341
+ if (isAudio) metadata.audioSaved++;
342
+ else metadata.imagesSaved++;
343
+ const saved = await this._saveMedia(item.blob, mime, isAudio ? "audio" : "image");
344
+ return {
345
+ uri: item.uri,
346
+ mimeType: "text/plain",
347
+ text: saved.text
348
+ };
349
+ }
350
+ const limit = maxTextLength ?? this.maxTextLength;
351
+ if (item.text && limit !== -1 && item.text.length > limit) {
65
352
  const totalLength = item.text.length;
353
+ metadata.truncated = true;
66
354
  return {
67
355
  ...item,
68
- text: item.text.slice(0, this.maxTextLength) + `
356
+ text: item.text.slice(0, limit) + `
69
357
  ... (truncated, ${totalLength.toLocaleString()} chars total)`
70
358
  };
71
359
  }
@@ -95,13 +383,14 @@ var ResponseInterceptor = class {
95
383
  /**
96
384
  * Returns a promise that rejects after the given timeout.
97
385
  */
98
- _timeout(ms, toolName) {
386
+ _timeout(ms, targetName) {
99
387
  return new Promise((_, reject) => {
100
388
  setTimeout(() => {
101
389
  const humanMs = ms >= 1e3 ? `${(ms / 1e3).toFixed(1)}s` : `${ms}ms`;
390
+ const typeLabel2 = targetName.includes(":") ? "Request" : "Tool";
102
391
  reject(
103
392
  new Error(
104
- `Tool "${toolName}" timed out after ${ms}ms (${humanMs}). Use --timeout <ms> to increase the limit.`
393
+ `${typeLabel2} "${targetName}" timed out after ${ms}ms (${humanMs}). Use --timeout <ms> to increase the limit.`
105
394
  )
106
395
  );
107
396
  }, ms);
@@ -135,6 +424,7 @@ var ResponseInterceptor = class {
135
424
  };
136
425
 
137
426
  // src/parsing.ts
427
+ import pc from "picocolors";
138
428
  function parseCommandLine(input3) {
139
429
  const spaceIdx = input3.indexOf(" ");
140
430
  if (spaceIdx === -1) {
@@ -162,9 +452,92 @@ function parseCallArgs(rest) {
162
452
  }
163
453
  return { toolName, jsonArgs: remainder, timeoutMs };
164
454
  }
165
- function formatJson(obj, indent = 2) {
455
+ function formatJson(obj, indent = 2, colorize = false) {
166
456
  const json = JSON.stringify(obj, null, indent);
167
- return json.split("\n").map((line) => " ".repeat(indent) + line).join("\n");
457
+ const output = colorize ? colorizeJson(json) : json;
458
+ return output.split("\n").map((line) => " ".repeat(indent) + line).join("\n");
459
+ }
460
+ function colorizeJson(json) {
461
+ const result = [];
462
+ let i = 0;
463
+ let expectingValue = false;
464
+ while (i < json.length) {
465
+ const ch = json[i];
466
+ if (ch === '"') {
467
+ const str = consumeString(json, i);
468
+ if (expectingValue) {
469
+ result.push(pc.green(str));
470
+ expectingValue = false;
471
+ } else {
472
+ result.push(pc.cyan(str));
473
+ }
474
+ i += str.length;
475
+ continue;
476
+ }
477
+ if (ch === ":") {
478
+ result.push(ch);
479
+ expectingValue = true;
480
+ i++;
481
+ continue;
482
+ }
483
+ if (ch === "," || ch === "}" || ch === "]") {
484
+ result.push(ch);
485
+ expectingValue = false;
486
+ i++;
487
+ continue;
488
+ }
489
+ if (ch === "{" || ch === "[") {
490
+ result.push(ch);
491
+ if (ch === "[") expectingValue = true;
492
+ i++;
493
+ continue;
494
+ }
495
+ if (json.startsWith("true", i)) {
496
+ result.push(pc.magenta("true"));
497
+ expectingValue = false;
498
+ i += 4;
499
+ continue;
500
+ }
501
+ if (json.startsWith("false", i)) {
502
+ result.push(pc.magenta("false"));
503
+ expectingValue = false;
504
+ i += 5;
505
+ continue;
506
+ }
507
+ if (json.startsWith("null", i)) {
508
+ result.push(pc.dim("null"));
509
+ expectingValue = false;
510
+ i += 4;
511
+ continue;
512
+ }
513
+ if (ch === "-" || ch >= "0" && ch <= "9") {
514
+ let num = "";
515
+ while (i < json.length && /[0-9.eE+-]/.test(json[i])) {
516
+ num += json[i];
517
+ i++;
518
+ }
519
+ result.push(pc.yellow(num));
520
+ expectingValue = false;
521
+ continue;
522
+ }
523
+ result.push(ch);
524
+ i++;
525
+ }
526
+ return result.join("");
527
+ }
528
+ function consumeString(json, start) {
529
+ let i = start + 1;
530
+ while (i < json.length) {
531
+ if (json[i] === "\\") {
532
+ i += 2;
533
+ continue;
534
+ }
535
+ if (json[i] === '"') {
536
+ return json.slice(start, i + 1);
537
+ }
538
+ i++;
539
+ }
540
+ return json.slice(start);
168
541
  }
169
542
  function levenshtein(a, b) {
170
543
  const m = a.length;
@@ -198,6 +571,13 @@ function scaffoldArgs(schema) {
198
571
  return JSON.stringify(scaffoldObject(schema), null, 2);
199
572
  }
200
573
  function scaffoldValue(prop) {
574
+ if (Array.isArray(prop.enum) && prop.enum.length > 0) {
575
+ return prop.enum[0];
576
+ }
577
+ const variants = prop.anyOf ?? prop.oneOf;
578
+ if (Array.isArray(variants) && variants.length > 0) {
579
+ return scaffoldValue(variants[0]);
580
+ }
201
581
  switch (prop.type) {
202
582
  case "string":
203
583
  return "<string>";
@@ -218,12 +598,21 @@ function scaffoldValue(prop) {
218
598
  }
219
599
  function scaffoldObject(schema) {
220
600
  const properties = schema.properties;
221
- if (!properties) return {};
222
- const result = {};
223
- for (const [key, prop] of Object.entries(properties)) {
224
- result[key] = scaffoldValue(prop);
601
+ if (properties) {
602
+ const result = {};
603
+ for (const [key, prop] of Object.entries(properties)) {
604
+ result[key] = scaffoldValue(prop);
605
+ }
606
+ return result;
225
607
  }
226
- return result;
608
+ const additionalProperties = schema.additionalProperties;
609
+ if (additionalProperties && typeof additionalProperties === "object") {
610
+ return { "<key>": scaffoldValue(additionalProperties) };
611
+ }
612
+ if (additionalProperties === true || schema.type === "object" && !properties) {
613
+ return { "<key>": "<value>" };
614
+ }
615
+ return {};
227
616
  }
228
617
  function formatToolDescription(tool) {
229
618
  const lines = [];
@@ -259,14 +648,15 @@ function formatToolDescription(tool) {
259
648
  } else {
260
649
  lines.push(` tools/call ${tool.name}`);
261
650
  }
262
- if (tool.annotations && Object.keys(tool.annotations).length > 0) {
263
- lines.push("");
264
- lines.push(" Annotations:");
265
- const annotationParts = [];
266
- for (const [key, value] of Object.entries(tool.annotations)) {
267
- annotationParts.push(`${key}: ${value}`);
651
+ if (tool.annotations) {
652
+ const entries = Object.entries(tool.annotations).filter(([key]) => key !== "title");
653
+ if (entries.length > 0) {
654
+ lines.push("");
655
+ lines.push(" Annotations:");
656
+ for (const [key, value] of entries) {
657
+ lines.push(` ${key}: ${value}`);
658
+ }
268
659
  }
269
- lines.push(` ${annotationParts.join(", ")}`);
270
660
  }
271
661
  return lines.join("\n");
272
662
  }
@@ -340,9 +730,111 @@ function resolveAlias(input3) {
340
730
  if (!expanded) return null;
341
731
  return expanded + rest;
342
732
  }
733
+ function splitArgs(input3) {
734
+ const tokens = [];
735
+ let current = "";
736
+ let inDoubleQuote = false;
737
+ let inSingleQuote = false;
738
+ let escape = false;
739
+ for (let i = 0; i < input3.length; i++) {
740
+ const ch = input3[i];
741
+ if (escape) {
742
+ current += ch;
743
+ escape = false;
744
+ continue;
745
+ }
746
+ if (ch === "\\") {
747
+ escape = true;
748
+ continue;
749
+ }
750
+ if (ch === '"' && !inSingleQuote) {
751
+ inDoubleQuote = !inDoubleQuote;
752
+ current += ch;
753
+ continue;
754
+ }
755
+ if (ch === "'" && !inDoubleQuote) {
756
+ inSingleQuote = !inSingleQuote;
757
+ current += ch;
758
+ continue;
759
+ }
760
+ if (ch === " " && !inDoubleQuote && !inSingleQuote) {
761
+ if (current.trim()) {
762
+ tokens.push(current.trim());
763
+ }
764
+ current = "";
765
+ continue;
766
+ }
767
+ current += ch;
768
+ }
769
+ if (current.trim()) {
770
+ tokens.push(current.trim());
771
+ }
772
+ return tokens;
773
+ }
774
+ function parseHttpieArgs(argsString) {
775
+ const result = {};
776
+ const trimmedArgs = argsString.trim();
777
+ if (!trimmedArgs) return result;
778
+ const tokens = splitArgs(trimmedArgs);
779
+ for (const token of tokens) {
780
+ const eqIdx = token.indexOf("=");
781
+ if (eqIdx === -1) continue;
782
+ const isJson = eqIdx > 0 && token[eqIdx - 1] === ":";
783
+ const key = isJson ? token.slice(0, eqIdx - 1).trim() : token.slice(0, eqIdx).trim();
784
+ let rawVal = token.slice(eqIdx + 1).trim();
785
+ if (rawVal.startsWith('"') && rawVal.endsWith('"') || rawVal.startsWith("'") && rawVal.endsWith("'")) {
786
+ rawVal = rawVal.slice(1, -1);
787
+ }
788
+ if (isJson) {
789
+ try {
790
+ result[key] = JSON.parse(rawVal);
791
+ } catch {
792
+ result[key] = rawVal;
793
+ }
794
+ } else {
795
+ result[key] = rawVal;
796
+ }
797
+ }
798
+ return result;
799
+ }
800
+ function resolveJsonPath(obj, path2) {
801
+ const parts = path2.replace(/\["([^"]+)"\]/g, ".$1").replace(/\['([^']+)'\]/g, ".$1").replace(/\[(\d+)\]/g, ".$1").split(".").filter(Boolean);
802
+ let current = obj;
803
+ for (const part of parts) {
804
+ if (current === void 0 || current === null) return void 0;
805
+ current = current[part];
806
+ }
807
+ return current;
808
+ }
809
+ function interpolateString(input3, context) {
810
+ const regex = /\$([a-zA-Z_][a-zA-Z0-9_]*|\[\d+\])?((?:\.[a-zA-Z0-9_]+|\[\d+\]|\["[^"]+"\]|\['[^']+'\])*)/g;
811
+ return input3.replace(regex, (match, root, path2) => {
812
+ let baseName = root;
813
+ let fullPath = path2 || "";
814
+ if (baseName && baseName.startsWith("[")) {
815
+ fullPath = baseName + fullPath;
816
+ baseName = "LAST";
817
+ }
818
+ if (!baseName) {
819
+ baseName = "LAST";
820
+ }
821
+ if (!(baseName in context)) {
822
+ return match;
823
+ }
824
+ const value = resolveJsonPath(context[baseName], fullPath);
825
+ if (value === void 0) {
826
+ return match;
827
+ }
828
+ if (typeof value === "object") {
829
+ return JSON.stringify(value);
830
+ }
831
+ return String(value);
832
+ });
833
+ }
343
834
 
344
835
  // src/target-manager.ts
345
836
  import { EventEmitter } from "events";
837
+ import treeKill from "tree-kill";
346
838
  import { Client } from "@modelcontextprotocol/sdk/client/index.js";
347
839
  import { SSEClientTransport } from "@modelcontextprotocol/sdk/client/sse.js";
348
840
  import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
@@ -382,6 +874,7 @@ var TargetManager = class _TargetManager extends EventEmitter {
382
874
  _autoReconnect = false;
383
875
  _reconnecting = false;
384
876
  _intentionalClose = false;
877
+ _everConnected = false;
385
878
  // Request history
386
879
  _history = [];
387
880
  _historyIdCounter = 0;
@@ -403,146 +896,154 @@ var TargetManager = class _TargetManager extends EventEmitter {
403
896
  */
404
897
  async connect() {
405
898
  this._intentionalClose = false;
406
- if (this.command.startsWith("http://") || this.command.startsWith("https://")) {
407
- this.transport = new SSEClientTransport(new URL(this.command));
408
- } else {
409
- const stdioTransport = new StdioClientTransport({
410
- command: this.command,
411
- args: this.args,
412
- stderr: "pipe"
413
- });
414
- stdioTransport.stderr?.on("data", (chunk) => {
415
- const text = chunk.toString().trimEnd();
416
- if (text) {
417
- const lines = text.split("\n");
418
- this._stderrLineCount += lines.length;
419
- this._stderrLines.push(...lines);
420
- if (this._stderrLines.length > _TargetManager.MAX_STDERR_LINES) {
421
- this._stderrLines = this._stderrLines.slice(-_TargetManager.MAX_STDERR_LINES);
899
+ this._everConnected = false;
900
+ try {
901
+ if (this.command.startsWith("http://") || this.command.startsWith("https://")) {
902
+ this.transport = new SSEClientTransport(new URL(this.command));
903
+ } else {
904
+ const stdioTransport = new StdioClientTransport({
905
+ command: this.command,
906
+ args: this.args,
907
+ stderr: "pipe"
908
+ });
909
+ stdioTransport.stderr?.on("data", (chunk) => {
910
+ const text = chunk.toString().trimEnd();
911
+ if (text) {
912
+ const lines = text.split("\n");
913
+ this._stderrLineCount += lines.length;
914
+ this._stderrLines.push(...lines);
915
+ if (this._stderrLines.length > _TargetManager.MAX_STDERR_LINES) {
916
+ this._stderrLines = this._stderrLines.slice(-_TargetManager.MAX_STDERR_LINES);
917
+ }
918
+ this.emit("stderr", text);
919
+ }
920
+ });
921
+ this.transport = stdioTransport;
922
+ }
923
+ this.client = new Client(
924
+ { name: "run-mcp", version: "1.6.1" },
925
+ {
926
+ capabilities: {
927
+ roots: { listChanged: true },
928
+ sampling: {},
929
+ elicitation: {}
422
930
  }
423
- this.emit("stderr", text);
424
931
  }
425
- });
426
- this.transport = stdioTransport;
427
- }
428
- this.client = new Client(
429
- { name: "run-mcp", version: "1.4.0" },
430
- {
431
- capabilities: {
432
- roots: { listChanged: true },
433
- sampling: {},
434
- elicitation: {}
932
+ );
933
+ this.client.setNotificationHandler(
934
+ LoggingMessageNotificationSchema,
935
+ async (notification) => {
936
+ const record = {
937
+ method: "notifications/message",
938
+ params: notification.params,
939
+ timestamp: Date.now()
940
+ };
941
+ this._pushNotification(record);
942
+ this.emit("notification", record);
435
943
  }
436
- }
437
- );
438
- this.client.setNotificationHandler(
439
- LoggingMessageNotificationSchema,
440
- async (notification) => {
944
+ );
945
+ this.client.setNotificationHandler(ToolListChangedNotificationSchema, async () => {
441
946
  const record = {
442
- method: "notifications/message",
443
- params: notification.params,
947
+ method: "notifications/tools/list_changed",
444
948
  timestamp: Date.now()
445
949
  };
446
950
  this._pushNotification(record);
447
951
  this.emit("notification", record);
448
- }
449
- );
450
- this.client.setNotificationHandler(ToolListChangedNotificationSchema, async () => {
451
- const record = {
452
- method: "notifications/tools/list_changed",
453
- timestamp: Date.now()
454
- };
455
- this._pushNotification(record);
456
- this.emit("notification", record);
457
- });
458
- this.client.setNotificationHandler(ResourceListChangedNotificationSchema, async () => {
459
- const record = {
460
- method: "notifications/resources/list_changed",
461
- timestamp: Date.now()
462
- };
463
- this._pushNotification(record);
464
- this.emit("notification", record);
465
- });
466
- this.client.setNotificationHandler(
467
- ResourceUpdatedNotificationSchema,
468
- async (notification) => {
952
+ });
953
+ this.client.setNotificationHandler(ResourceListChangedNotificationSchema, async () => {
469
954
  const record = {
470
- method: "notifications/resources/updated",
471
- params: notification.params,
955
+ method: "notifications/resources/list_changed",
472
956
  timestamp: Date.now()
473
957
  };
474
958
  this._pushNotification(record);
475
959
  this.emit("notification", record);
476
- }
477
- );
478
- this.client.setNotificationHandler(PromptListChangedNotificationSchema, async () => {
479
- const record = {
480
- method: "notifications/prompts/list_changed",
481
- timestamp: Date.now()
482
- };
483
- this._pushNotification(record);
484
- this.emit("notification", record);
485
- });
486
- this.client.setRequestHandler(CreateMessageRequestSchema, async (request) => {
487
- return new Promise((resolve, reject) => {
488
- const timeout = setTimeout(() => {
489
- reject(new Error("Sampling request timed out (no response from user in 5 minutes)"));
490
- }, 3e5);
491
- this.emit("sampling_request", {
492
- request: request.params,
493
- respond: (result) => {
494
- clearTimeout(timeout);
495
- resolve(result);
496
- },
497
- reject: (err) => {
498
- clearTimeout(timeout);
499
- reject(err);
500
- }
960
+ });
961
+ this.client.setNotificationHandler(
962
+ ResourceUpdatedNotificationSchema,
963
+ async (notification) => {
964
+ const record = {
965
+ method: "notifications/resources/updated",
966
+ params: notification.params,
967
+ timestamp: Date.now()
968
+ };
969
+ this._pushNotification(record);
970
+ this.emit("notification", record);
971
+ }
972
+ );
973
+ this.client.setNotificationHandler(PromptListChangedNotificationSchema, async () => {
974
+ const record = {
975
+ method: "notifications/prompts/list_changed",
976
+ timestamp: Date.now()
977
+ };
978
+ this._pushNotification(record);
979
+ this.emit("notification", record);
980
+ });
981
+ this.client.setRequestHandler(CreateMessageRequestSchema, async (request) => {
982
+ return new Promise((resolve2, reject) => {
983
+ const timeout = setTimeout(() => {
984
+ reject(new Error("Sampling request timed out (no response from user in 5 minutes)"));
985
+ }, 3e5);
986
+ this.emit("sampling_request", {
987
+ request: request.params,
988
+ respond: (result) => {
989
+ clearTimeout(timeout);
990
+ resolve2(result);
991
+ },
992
+ reject: (err) => {
993
+ clearTimeout(timeout);
994
+ reject(err);
995
+ }
996
+ });
501
997
  });
502
998
  });
503
- });
504
- this.client.setRequestHandler(ElicitRequestSchema, async (request) => {
505
- return new Promise((resolve, reject) => {
506
- const timeout = setTimeout(() => {
507
- reject(new Error("Elicitation request timed out (no response from user in 5 minutes)"));
508
- }, 3e5);
509
- this.emit("elicitation_request", {
510
- request: request.params,
511
- respond: (result) => {
512
- clearTimeout(timeout);
513
- resolve(result);
514
- },
515
- reject: (err) => {
516
- clearTimeout(timeout);
517
- reject(err);
518
- }
999
+ this.client.setRequestHandler(ElicitRequestSchema, async (request) => {
1000
+ return new Promise((resolve2, reject) => {
1001
+ const timeout = setTimeout(() => {
1002
+ reject(new Error("Elicitation request timed out (no response from user in 5 minutes)"));
1003
+ }, 3e5);
1004
+ this.emit("elicitation_request", {
1005
+ request: request.params,
1006
+ respond: (result) => {
1007
+ clearTimeout(timeout);
1008
+ resolve2(result);
1009
+ },
1010
+ reject: (err) => {
1011
+ clearTimeout(timeout);
1012
+ reject(err);
1013
+ }
1014
+ });
519
1015
  });
520
1016
  });
521
- });
522
- this.client.setRequestHandler(ListRootsRequestSchema, async () => {
523
- return { roots: this._roots };
524
- });
525
- this.client.onclose = () => {
526
- this._connected = false;
527
- this._clearStableTimer();
528
- if (this._intentionalClose) {
529
- return;
1017
+ this.client.setRequestHandler(ListRootsRequestSchema, async () => {
1018
+ return { roots: this._roots };
1019
+ });
1020
+ this.client.onclose = () => {
1021
+ this._connected = false;
1022
+ this._clearStableTimer();
1023
+ if (this._intentionalClose || !this._everConnected) {
1024
+ return;
1025
+ }
1026
+ this.emit("disconnected");
1027
+ this._maybeReconnect();
1028
+ };
1029
+ await this.client.connect(this.transport);
1030
+ this._connected = true;
1031
+ this._everConnected = true;
1032
+ this.startTime = Date.now();
1033
+ const proc = this.transport._process;
1034
+ if (proc?.pid) {
1035
+ this.childPid = proc.pid;
1036
+ } else {
1037
+ this.childPid = null;
530
1038
  }
531
- this.emit("disconnected");
532
- this._maybeReconnect();
533
- };
534
- await this.client.connect(this.transport);
535
- this._connected = true;
536
- this.startTime = Date.now();
537
- const proc = this.transport._process;
538
- if (proc?.pid) {
539
- this.childPid = proc.pid;
540
- } else {
541
- this.childPid = null;
1039
+ this.emit("connected");
1040
+ this._registerCleanup();
1041
+ this._startStableTimer();
1042
+ } catch (err) {
1043
+ await this.close().catch(() => {
1044
+ });
1045
+ throw err;
542
1046
  }
543
- this.emit("connected");
544
- this._registerCleanup();
545
- this._startStableTimer();
546
1047
  }
547
1048
  get connected() {
548
1049
  return this._connected;
@@ -846,11 +1347,12 @@ var TargetManager = class _TargetManager extends EventEmitter {
846
1347
  };
847
1348
  }
848
1349
  /**
849
- * Cleanly shut down the client connection and child process.
1350
+ * Cleanly shut down the client connection and forcefully kill the child process tree.
850
1351
  */
851
1352
  async close() {
852
1353
  this._intentionalClose = true;
853
1354
  this._clearStableTimer();
1355
+ const pidToKill = this.childPid;
854
1356
  if (this.client) {
855
1357
  try {
856
1358
  await this.client.close();
@@ -865,6 +1367,11 @@ var TargetManager = class _TargetManager extends EventEmitter {
865
1367
  }
866
1368
  this.transport = null;
867
1369
  }
1370
+ if (pidToKill) {
1371
+ await new Promise((resolve2) => {
1372
+ treeKill(pidToKill, "SIGKILL", () => resolve2());
1373
+ });
1374
+ }
868
1375
  this._connected = false;
869
1376
  this.childPid = null;
870
1377
  }
@@ -965,7 +1472,153 @@ var TargetManager = class _TargetManager extends EventEmitter {
965
1472
  }
966
1473
  };
967
1474
 
1475
+ // src/headless.ts
1476
+ var DEFAULT_HEADLESS_TIMEOUT_MS = 3e4;
1477
+ async function runHeadless(targetCommand, operation, opts = {}) {
1478
+ const [command, ...args] = targetCommand;
1479
+ const target = new TargetManager(command, args);
1480
+ const interceptor = new ResponseInterceptor({
1481
+ outDir: opts.outDir,
1482
+ defaultTimeoutMs: opts.timeoutMs ?? DEFAULT_HEADLESS_TIMEOUT_MS
1483
+ });
1484
+ if (opts.showStderr) {
1485
+ target.on("stderr", (text) => {
1486
+ process.stderr.write(`${text}
1487
+ `);
1488
+ });
1489
+ } else {
1490
+ target.on("stderr", () => {
1491
+ });
1492
+ }
1493
+ try {
1494
+ process.stderr.write(`Connecting to ${targetCommand.join(" ")}...
1495
+ `);
1496
+ await target.connect();
1497
+ const status = target.getStatus();
1498
+ process.stderr.write(`Connected (PID: ${status.pid})
1499
+ `);
1500
+ const { result, hasError } = await executeOperation(target, interceptor, operation, opts);
1501
+ process.stdout.write(`${JSON.stringify(result, null, 2)}
1502
+ `);
1503
+ await target.close();
1504
+ process.exit(hasError ? 1 : 0);
1505
+ } catch (err) {
1506
+ const msg = err.message ?? String(err);
1507
+ if (msg.includes("ENOENT") || msg.includes("spawn")) {
1508
+ process.stderr.write(
1509
+ `Error: command "${command}" not found. Check that it is installed and in your PATH.
1510
+ `
1511
+ );
1512
+ } else if (msg.includes("timed out")) {
1513
+ process.stderr.write(`Error: ${msg}
1514
+ `);
1515
+ } else {
1516
+ process.stderr.write(`Error: ${msg}
1517
+ `);
1518
+ }
1519
+ await target.close().catch(() => {
1520
+ });
1521
+ process.exit(1);
1522
+ }
1523
+ }
1524
+ async function executeOperation(target, interceptor, operation, opts) {
1525
+ switch (operation.type) {
1526
+ case "call": {
1527
+ let parsedArgs = {};
1528
+ if (operation.args) {
1529
+ const trimmed = operation.args.trim();
1530
+ if (trimmed.startsWith("{") && trimmed.endsWith("}")) {
1531
+ try {
1532
+ parsedArgs = JSON.parse(trimmed);
1533
+ } catch (err) {
1534
+ process.stderr.write(`Error: Invalid JSON arguments: ${err.message}
1535
+ `);
1536
+ process.stderr.write(` Received: ${operation.args}
1537
+ `);
1538
+ process.exit(2);
1539
+ }
1540
+ } else {
1541
+ parsedArgs = parseHttpieArgs(trimmed);
1542
+ }
1543
+ }
1544
+ const result = await interceptor.callTool(target, operation.tool, parsedArgs);
1545
+ if (result.isError) {
1546
+ const content = result.content;
1547
+ if (Array.isArray(content)) {
1548
+ const errorText = content.filter((c) => c.type === "text").map((c) => c.text).join("\n");
1549
+ if (errorText) {
1550
+ process.stderr.write(`Tool error: ${errorText}
1551
+ `);
1552
+ }
1553
+ }
1554
+ if (opts.raw) return { result, hasError: true };
1555
+ return { result: result.content ?? result, hasError: true };
1556
+ }
1557
+ if (opts.raw) return { result, hasError: false };
1558
+ return { result: result.content ?? result, hasError: false };
1559
+ }
1560
+ case "list-tools": {
1561
+ const { tools } = await target.listTools();
1562
+ return { result: tools, hasError: false };
1563
+ }
1564
+ case "list-resources": {
1565
+ const { resources } = await target.listResources();
1566
+ return { result: resources, hasError: false };
1567
+ }
1568
+ case "list-prompts": {
1569
+ const { prompts } = await target.listPrompts();
1570
+ return { result: prompts, hasError: false };
1571
+ }
1572
+ case "read": {
1573
+ const result = await interceptor.readResource(target, { uri: operation.uri });
1574
+ return { result, hasError: false };
1575
+ }
1576
+ case "describe": {
1577
+ const { tools } = await target.listTools();
1578
+ const tool = tools.find((t) => t.name === operation.tool);
1579
+ if (!tool) {
1580
+ const available = tools.map((t) => t.name).join(", ");
1581
+ process.stderr.write(
1582
+ `Error: Tool "${operation.tool}" not found.
1583
+ Available tools: ${available}
1584
+ `
1585
+ );
1586
+ process.exit(1);
1587
+ }
1588
+ return { result: tool, hasError: false };
1589
+ }
1590
+ case "get-prompt": {
1591
+ let parsedArgs;
1592
+ if (operation.args) {
1593
+ const trimmed = operation.args.trim();
1594
+ if (trimmed.startsWith("{") && trimmed.endsWith("}")) {
1595
+ try {
1596
+ parsedArgs = JSON.parse(trimmed);
1597
+ } catch (err) {
1598
+ process.stderr.write(`Error: Invalid JSON arguments: ${err.message}
1599
+ `);
1600
+ process.stderr.write(` Received: ${operation.args}
1601
+ `);
1602
+ process.exit(2);
1603
+ }
1604
+ } else {
1605
+ parsedArgs = parseHttpieArgs(trimmed);
1606
+ }
1607
+ }
1608
+ const result = await interceptor.getPrompt(target, {
1609
+ name: operation.name,
1610
+ arguments: parsedArgs
1611
+ });
1612
+ return { result, hasError: false };
1613
+ }
1614
+ }
1615
+ }
1616
+
968
1617
  // src/repl.ts
1618
+ import { readFile as readFile2 } from "fs/promises";
1619
+ import { createInterface } from "readline";
1620
+ import { checkbox, confirm, input as input2, search } from "@inquirer/prompts";
1621
+ import pc2 from "picocolors";
969
1622
  var KNOWN_COMMANDS = [
970
1623
  "explore",
971
1624
  "interactive",
@@ -994,6 +1647,7 @@ var KNOWN_COMMANDS = [
994
1647
  "!!",
995
1648
  "last",
996
1649
  "help",
1650
+ "?",
997
1651
  "exit",
998
1652
  "quit",
999
1653
  // Short aliases
@@ -1012,8 +1666,22 @@ var KNOWN_COMMANDS = [
1012
1666
  var cachedToolNames = [];
1013
1667
  var cachedResourceUris = [];
1014
1668
  var cachedPromptNames = [];
1669
+ var activeCapabilities = null;
1670
+ function getActiveCommands() {
1671
+ let commands = [...KNOWN_COMMANDS];
1672
+ if (!activeCapabilities?.resources) {
1673
+ commands = commands.filter(
1674
+ (c) => !c.startsWith("resources/") && !["rl", "rr", "rt", "rs", "ru"].includes(c)
1675
+ );
1676
+ }
1677
+ if (!activeCapabilities?.prompts) {
1678
+ commands = commands.filter((c) => !c.startsWith("prompts/") && !["pl", "pg"].includes(c));
1679
+ }
1680
+ return commands;
1681
+ }
1015
1682
  async function refreshCaches(target) {
1016
1683
  const caps = target.getServerCapabilities() ?? {};
1684
+ activeCapabilities = caps;
1017
1685
  try {
1018
1686
  const { tools } = await target.listTools();
1019
1687
  cachedToolNames = tools.map((t) => t.name);
@@ -1058,7 +1726,7 @@ function computeMatches(line) {
1058
1726
  const matches2 = cachedPromptNames.filter((n) => n.startsWith(partial));
1059
1727
  return [matches2.map((m) => `prompts/get ${m}`), effective];
1060
1728
  }
1061
- const matches = KNOWN_COMMANDS.filter((c) => c.startsWith(line));
1729
+ const matches = getActiveCommands().filter((c) => c.startsWith(line));
1062
1730
  return [matches, line];
1063
1731
  }
1064
1732
  var completer = (line) => {
@@ -1122,34 +1790,36 @@ async function withSuspendedReadline(target, interceptor, fn) {
1122
1790
  }
1123
1791
  }
1124
1792
  function getPrompt(target) {
1125
- if (target.connected) return `${pc.green("\u2713")}${pc.cyan("> ")}`;
1126
- return `${pc.red("\u2717")}${pc.cyan("> ")}`;
1793
+ if (target.connected) return `${pc2.green("\u2713")}${pc2.cyan("> ")}`;
1794
+ return `${pc2.red("\u2717")}${pc2.cyan("> ")}`;
1127
1795
  }
1128
1796
  function printBanner(serverName, serverVersion, toolCount, resourceCount, promptCount) {
1129
1797
  const parts = [];
1130
- parts.push(`${pc.bold(toolCount.toString())} tools`);
1131
- if (resourceCount > 0) parts.push(`${pc.bold(resourceCount.toString())} resources`);
1132
- if (promptCount > 0) parts.push(`${pc.bold(promptCount.toString())} prompts`);
1133
- const title = serverVersion ? `${serverName} ${pc.dim(`v${serverVersion}`)}` : serverName;
1798
+ parts.push(`${pc2.bold(toolCount.toString())} tools`);
1799
+ if (resourceCount > 0) parts.push(`${pc2.bold(resourceCount.toString())} resources`);
1800
+ if (promptCount > 0) parts.push(`${pc2.bold(promptCount.toString())} prompts`);
1801
+ const baseTitle = serverVersion ? `${serverName} ${pc2.dim(`v${serverVersion}`)}` : serverName;
1802
+ const isAgentHarness = serverName === "run-mcp";
1803
+ const title = isAgentHarness ? `${baseTitle} ${pc2.bgBlue(pc2.white(" AGENT HARNESS "))}` : baseTitle;
1134
1804
  const BOX_WIDTH = 53;
1135
1805
  const padLine = (content) => {
1136
1806
  const visible = stripAnsi(content).length;
1137
1807
  const padding = Math.max(0, BOX_WIDTH - visible);
1138
- return `${pc.cyan(" \u2502")}${content}${"".padEnd(padding)}${pc.cyan("\u2502")}`;
1808
+ return `${pc2.cyan(" \u2502")}${content}${"".padEnd(padding)}${pc2.cyan("\u2502")}`;
1139
1809
  };
1140
1810
  const partsStr = ` ${parts.join(" \u2022 ")}`;
1141
1811
  console.log();
1142
- console.log(pc.cyan(` \u250C${"\u2500".repeat(BOX_WIDTH)}\u2510`));
1812
+ console.log(pc2.cyan(` \u250C${"\u2500".repeat(BOX_WIDTH)}\u2510`));
1143
1813
  console.log(padLine(` ${title}`));
1144
1814
  console.log(padLine(partsStr));
1145
- console.log(pc.cyan(` \u251C${"\u2500".repeat(BOX_WIDTH)}\u2524`));
1815
+ console.log(pc2.cyan(` \u251C${"\u2500".repeat(BOX_WIDTH)}\u2524`));
1146
1816
  console.log(padLine(" Quick start:"));
1147
- console.log(padLine(` ${pc.green("tools/list")} See all tools`));
1148
- console.log(padLine(` ${pc.green("tools/call")} ${pc.dim("<name>")} Call a tool`));
1149
- console.log(padLine(` ${pc.green("help")} All commands`));
1817
+ console.log(padLine(` ${pc2.green("tools/list")} See all tools`));
1818
+ console.log(padLine(` ${pc2.green("tools/call")} ${pc2.dim("<name>")} Call a tool`));
1819
+ console.log(padLine(` ${pc2.green("help")} All commands`));
1150
1820
  console.log(padLine(""));
1151
- console.log(padLine(pc.dim(" Tab completion is active. Start typing to explore.")));
1152
- console.log(pc.cyan(` \u2514${"\u2500".repeat(BOX_WIDTH)}\u2518`));
1821
+ console.log(padLine(pc2.dim(" Tab completion is active. Start typing to explore.")));
1822
+ console.log(pc2.cyan(` \u2514${"\u2500".repeat(BOX_WIDTH)}\u2518`));
1153
1823
  console.log();
1154
1824
  }
1155
1825
  function stripAnsi(str) {
@@ -1158,51 +1828,54 @@ function stripAnsi(str) {
1158
1828
  async function startRepl(targetCommand, opts) {
1159
1829
  const [command, ...args] = targetCommand;
1160
1830
  const target = new TargetManager(command, args);
1161
- const interceptor = new ResponseInterceptor({ outDir: opts.outDir });
1831
+ const interceptor = new ResponseInterceptor({
1832
+ outDir: opts.outDir,
1833
+ mediaThresholdKb: opts.mediaThresholdKb
1834
+ });
1162
1835
  isScriptMode = !!opts.script;
1163
1836
  target.on("stderr", (text) => {
1164
1837
  for (const line of text.split("\n")) {
1165
- console.error(pc.dim(`[server] ${line}`));
1838
+ console.error(pc2.dim(`[server] ${line}`));
1166
1839
  }
1167
1840
  });
1168
- console.log(pc.cyan("\u27F3 Connecting to target MCP server..."));
1169
- console.log(pc.dim(` Command: ${targetCommand.join(" ")}`));
1841
+ console.log(pc2.cyan("\u27F3 Connecting to target MCP server..."));
1842
+ console.log(pc2.dim(` Command: ${targetCommand.join(" ")}`));
1170
1843
  try {
1171
1844
  await target.connect();
1172
1845
  } catch (err) {
1173
1846
  const msg = err.message ?? String(err);
1174
1847
  if (msg.includes("ENOENT") || msg.includes("spawn")) {
1175
- console.error(pc.red(`\u2717 Failed to start server: command "${command}" not found.`));
1176
- console.error(pc.dim(` Check that "${command}" is installed and in your PATH.`));
1848
+ console.error(pc2.red(`\u2717 Failed to start server: command "${command}" not found.`));
1849
+ console.error(pc2.dim(` Check that "${command}" is installed and in your PATH.`));
1177
1850
  } else {
1178
- console.error(pc.red(`\u2717 Failed to connect: ${msg}`));
1179
- console.error(pc.dim(` Check that the target command starts a valid MCP server on stdio.`));
1851
+ console.error(pc2.red(`\u2717 Failed to connect: ${msg}`));
1852
+ console.error(pc2.dim(` Check that the target command starts a valid MCP server on stdio.`));
1180
1853
  }
1181
1854
  process.exit(1);
1182
1855
  }
1183
1856
  const status = target.getStatus();
1184
- console.log(pc.green(`\u2713 Connected (PID: ${status.pid})`));
1857
+ console.log(pc2.green(`\u2713 Connected (PID: ${status.pid})`));
1185
1858
  if (!isScriptMode) {
1186
1859
  target.enableAutoReconnect();
1187
1860
  target.on(
1188
1861
  "reconnecting",
1189
1862
  ({ attempt, maxAttempts }) => {
1190
1863
  console.log(
1191
- pc.yellow(`
1864
+ pc2.yellow(`
1192
1865
  \u27F3 Server disconnected. Reconnecting (${attempt}/${maxAttempts})...`)
1193
1866
  );
1194
1867
  }
1195
1868
  );
1196
1869
  target.on("reconnected", async ({ attempt }) => {
1197
1870
  const s = target.getStatus();
1198
- console.log(pc.green(`\u2713 Reconnected (PID: ${s.pid}, attempt ${attempt})`));
1871
+ console.log(pc2.green(`\u2713 Reconnected (PID: ${s.pid}, attempt ${attempt})`));
1199
1872
  await refreshCaches(target);
1200
1873
  });
1201
1874
  target.on("reconnect_failed", ({ reason, message }) => {
1202
- console.error(pc.red(`\u2717 ${message}`));
1875
+ console.error(pc2.red(`\u2717 ${message}`));
1203
1876
  if (reason === "max_retries") {
1204
1877
  console.log(
1205
- pc.dim(" Use 'exit' to quit or wait for the server to be fixed and restart manually.")
1878
+ pc2.dim(" Use 'exit' to quit or wait for the server to be fixed and restart manually.")
1206
1879
  );
1207
1880
  }
1208
1881
  });
@@ -1212,38 +1885,38 @@ async function startRepl(targetCommand, opts) {
1212
1885
  const lvl = notification.params?.level ?? "info";
1213
1886
  const data = notification.params?.data ?? "";
1214
1887
  const text = typeof data === "string" ? data : JSON.stringify(data);
1215
- console.log(pc.dim(`
1888
+ console.log(pc2.dim(`
1216
1889
  [${lvl}] ${text}`));
1217
1890
  } else if (method === "notifications/tools/list_changed") {
1218
- console.log(pc.yellow("\n \u27F3 Server tools changed. Run tools/list to see updates."));
1891
+ console.log(pc2.yellow("\n \u27F3 Server tools changed. Run tools/list to see updates."));
1219
1892
  refreshCaches(target).catch(() => {
1220
1893
  });
1221
1894
  } else if (method === "notifications/resources/list_changed") {
1222
- console.log(pc.yellow("\n \u27F3 Server resources changed. Run resources/list to see."));
1895
+ console.log(pc2.yellow("\n \u27F3 Server resources changed. Run resources/list to see."));
1223
1896
  refreshCaches(target).catch(() => {
1224
1897
  });
1225
1898
  } else if (method === "notifications/resources/updated") {
1226
1899
  const uri = notification.params?.uri ?? "unknown";
1227
- console.log(pc.yellow(`
1900
+ console.log(pc2.yellow(`
1228
1901
  \u27F3 Resource updated: ${uri}`));
1229
1902
  } else if (method === "notifications/prompts/list_changed") {
1230
- console.log(pc.yellow("\n \u27F3 Server prompts changed. Run prompts/list to see."));
1903
+ console.log(pc2.yellow("\n \u27F3 Server prompts changed. Run prompts/list to see."));
1231
1904
  refreshCaches(target).catch(() => {
1232
1905
  });
1233
1906
  }
1234
1907
  });
1235
1908
  target.on("sampling_request", async ({ request, respond, reject: rejectFn }) => {
1236
- console.log(pc.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"));
1909
+ console.log(pc2.magenta("\n \u2554\u2550\u2550 Sampling Request \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550"));
1237
1910
  const messages = request?.messages ?? [];
1238
1911
  for (const msg of messages) {
1239
- const role = msg.role === "user" ? pc.blue("user") : pc.magenta("assistant");
1912
+ const role = msg.role === "user" ? pc2.blue("user") : pc2.magenta("assistant");
1240
1913
  const text = msg.content?.text ?? JSON.stringify(msg.content);
1241
- console.log(pc.magenta(` \u2551 ${role}: ${text}`));
1914
+ console.log(pc2.magenta(` \u2551 ${role}: ${text}`));
1242
1915
  }
1243
- console.log(pc.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"));
1916
+ console.log(pc2.magenta(" \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550"));
1244
1917
  if (activeRl) {
1245
1918
  try {
1246
- const answer = await question(activeRl, ` ${pc.bold("Approve? [y/N/text]:")} `);
1919
+ const answer = await question(activeRl, ` ${pc2.bold("Approve? [y/N/text]:")} `);
1247
1920
  const trimmed = answer.trim().toLowerCase();
1248
1921
  if (trimmed === "y" || trimmed === "yes") {
1249
1922
  respond({
@@ -1272,14 +1945,14 @@ async function startRepl(targetCommand, opts) {
1272
1945
  }
1273
1946
  });
1274
1947
  target.on("elicitation_request", async ({ request, respond, reject: rejectFn }) => {
1275
- console.log(pc.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"));
1276
- console.log(pc.cyan(` \u2551 ${request?.message ?? "Server requests input"}`));
1277
- console.log(pc.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"));
1948
+ console.log(pc2.cyan("\n \u2554\u2550\u2550 Elicitation Request \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550"));
1949
+ console.log(pc2.cyan(` \u2551 ${request?.message ?? "Server requests input"}`));
1950
+ console.log(pc2.cyan(" \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550"));
1278
1951
  if (activeRl) {
1279
1952
  try {
1280
1953
  const answer = await question(
1281
1954
  activeRl,
1282
- ` ${pc.bold("Your response (empty to decline):")} `
1955
+ ` ${pc2.bold("Your response (empty to decline):")} `
1283
1956
  );
1284
1957
  if (answer.trim() === "") {
1285
1958
  respond({ action: "decline" });
@@ -1303,7 +1976,7 @@ async function startRepl(targetCommand, opts) {
1303
1976
  }
1304
1977
  });
1305
1978
  }
1306
- let toolCount = 0;
1979
+ let toolCount;
1307
1980
  let resourceCount = 0;
1308
1981
  let promptCount = 0;
1309
1982
  try {
@@ -1334,31 +2007,74 @@ async function startRepl(targetCommand, opts) {
1334
2007
  );
1335
2008
  if (!groups.has("All")) {
1336
2009
  for (const [label, members] of groups) {
1337
- console.log(` ${pc.bold(label.padEnd(16))} ${pc.dim(members.join(", "))}`);
2010
+ console.log(` ${pc2.bold(label.padEnd(16))} ${pc2.dim(members.join(", "))}`);
1338
2011
  }
1339
2012
  console.log();
1340
2013
  }
1341
2014
  }
1342
2015
  } catch (err) {
1343
- console.log(pc.yellow(` Warning: Could not list tools: ${err.message}
2016
+ console.log(pc2.yellow(` Warning: Could not list tools: ${err.message}
1344
2017
  `));
1345
2018
  }
1346
2019
  await refreshCaches(target);
1347
2020
  if (isScriptMode) {
1348
2021
  const lines = await readScriptLines(opts.script);
2022
+ const scriptContext = {};
2023
+ let expectError = false;
1349
2024
  for (const line of lines) {
1350
- const trimmed = line.trim();
1351
- if (!trimmed || trimmed.startsWith("#")) continue;
2025
+ let trimmed = line.trim();
2026
+ if (!trimmed || trimmed.startsWith("#")) {
2027
+ if (trimmed === "# @expect-error") {
2028
+ expectError = true;
2029
+ }
2030
+ continue;
2031
+ }
2032
+ if (trimmed.endsWith("# @expect-error")) {
2033
+ expectError = true;
2034
+ trimmed = trimmed.replace(/\s*#\s*@expect-error$/, "");
2035
+ }
2036
+ const interpolated = interpolateString(trimmed, scriptContext);
1352
2037
  try {
1353
- await handleCommand(trimmed, target, interceptor);
2038
+ const res = await handleCommand(interpolated, target, interceptor);
2039
+ if (res !== void 0) {
2040
+ scriptContext.LAST = res;
2041
+ }
2042
+ const isErrorRes = res && typeof res === "object" && res.isError === true;
2043
+ if (expectError && !isErrorRes) {
2044
+ console.error(pc2.red(`\u2717 Expected an error but the command succeeded.`));
2045
+ await target.close();
2046
+ process.exit(1);
2047
+ }
2048
+ if (!expectError && isErrorRes) {
2049
+ console.error(pc2.red(`\u2717 Command failed unexpectedly.`));
2050
+ await target.close();
2051
+ process.exit(1);
2052
+ }
2053
+ if (expectError && isErrorRes) {
2054
+ console.log(pc2.yellow(` \u2713 Expected error caught: tool returned isError: true`));
2055
+ }
1354
2056
  } catch (err) {
1355
- console.error(pc.red(`\u2717 Error: ${err.message}`));
1356
- console.log(pc.dim("\nShutting down..."));
1357
- await target.close();
1358
- process.exit(1);
2057
+ if (expectError) {
2058
+ console.log(pc2.yellow(` \u2713 Expected error caught: ${err.message}`));
2059
+ } else {
2060
+ if (err?.message?.includes("-32601") || err?.code === -32601) {
2061
+ let msg = "Server does not support this feature (Method not found)";
2062
+ if (trimmed.startsWith("prompts/")) msg = "This server does not have any prompts.";
2063
+ else if (trimmed.startsWith("resources/"))
2064
+ msg = "This server does not have any resources.";
2065
+ else if (trimmed.startsWith("tools/")) msg = "This server does not have any tools.";
2066
+ console.log(pc2.yellow(` ${msg}`));
2067
+ } else {
2068
+ console.error(pc2.red(`\u2717 Error: ${err.message}`));
2069
+ }
2070
+ console.log(pc2.dim("\nShutting down..."));
2071
+ await target.close();
2072
+ process.exit(1);
2073
+ }
1359
2074
  }
2075
+ expectError = false;
1360
2076
  }
1361
- console.log(pc.dim("\nShutting down..."));
2077
+ console.log(pc2.dim("\nShutting down..."));
1362
2078
  await target.close();
1363
2079
  process.exit(0);
1364
2080
  } else {
@@ -1400,9 +2116,16 @@ function startReadlineLoop(target, interceptor) {
1400
2116
  await handleCommand(trimmed, target, interceptor);
1401
2117
  } catch (err) {
1402
2118
  if (err instanceof AbortFlowError) {
1403
- console.log(pc.yellow(" Aborted."));
2119
+ console.log(pc2.yellow(" Aborted."));
2120
+ } else if (err?.message?.includes("-32601") || err?.code === -32601) {
2121
+ let msg = "Server does not support this feature (Method not found)";
2122
+ if (trimmed.startsWith("prompts/")) msg = "This server does not have any prompts.";
2123
+ else if (trimmed.startsWith("resources/"))
2124
+ msg = "This server does not have any resources.";
2125
+ else if (trimmed.startsWith("tools/")) msg = "This server does not have any tools.";
2126
+ console.log(pc2.yellow(` ${msg}`));
1404
2127
  } else {
1405
- console.error(pc.red(`\u2717 Error: ${err.message}`));
2128
+ console.error(pc2.red(`\u2717 Error: ${err.message}`));
1406
2129
  }
1407
2130
  }
1408
2131
  if (activeRl) {
@@ -1431,7 +2154,7 @@ function startReadlineLoop(target, interceptor) {
1431
2154
  closed = true;
1432
2155
  activeRl = null;
1433
2156
  if (!globalPauseReadlineClose) {
1434
- console.log(pc.dim("\nShutting down..."));
2157
+ console.log(pc2.dim("\nShutting down..."));
1435
2158
  await target.close();
1436
2159
  process.exit(0);
1437
2160
  }
@@ -1448,6 +2171,9 @@ async function handleCommand(input3, target, interceptor) {
1448
2171
  case "help":
1449
2172
  printHelp();
1450
2173
  return;
2174
+ case "?":
2175
+ printShortHelp();
2176
+ return;
1451
2177
  case "explore":
1452
2178
  case "interactive":
1453
2179
  await withSuspendedReadline(target, interceptor, async () => {
@@ -1461,8 +2187,7 @@ async function handleCommand(input3, target, interceptor) {
1461
2187
  await cmdToolsDescribe(target, rest);
1462
2188
  return;
1463
2189
  case "tools/call":
1464
- await cmdToolsCall(target, interceptor, rest);
1465
- return;
2190
+ return await cmdToolsCall(target, interceptor, rest);
1466
2191
  case "tools/scaffold":
1467
2192
  await cmdToolsScaffold(target, rest);
1468
2193
  return;
@@ -1473,8 +2198,7 @@ async function handleCommand(input3, target, interceptor) {
1473
2198
  await cmdResourcesList(target);
1474
2199
  return;
1475
2200
  case "resources/read":
1476
- await cmdResourcesRead(target, rest);
1477
- return;
2201
+ return await cmdResourcesRead(target, rest, interceptor);
1478
2202
  case "resources/templates":
1479
2203
  await cmdResourcesTemplates(target);
1480
2204
  return;
@@ -1482,8 +2206,7 @@ async function handleCommand(input3, target, interceptor) {
1482
2206
  await cmdPromptsList(target);
1483
2207
  return;
1484
2208
  case "prompts/get":
1485
- await cmdPromptsGet(target, rest);
1486
- return;
2209
+ return await cmdPromptsGet(target, rest, interceptor);
1487
2210
  case "timing":
1488
2211
  cmdTiming();
1489
2212
  return;
@@ -1520,32 +2243,38 @@ async function handleCommand(input3, target, interceptor) {
1520
2243
  case "!!":
1521
2244
  case "last":
1522
2245
  if (lastCommand) {
1523
- console.log(pc.dim(` Re-running: ${lastCommand}`));
1524
- await handleCommand(lastCommand, target, interceptor);
2246
+ console.log(pc2.dim(` Re-running: ${lastCommand}`));
2247
+ return await handleCommand(lastCommand, target, interceptor);
1525
2248
  } else {
1526
- console.log(pc.yellow("No previous command to re-run."));
2249
+ console.log(pc2.yellow("No previous command to re-run."));
1527
2250
  }
1528
2251
  return;
1529
2252
  case "status":
1530
2253
  cmdStatus(target);
1531
2254
  return;
1532
2255
  case "exit":
1533
- case "quit":
1534
- process.emit("SIGINT", "SIGINT");
2256
+ case "quit": {
2257
+ console.log(pc2.dim("Shutting down..."));
2258
+ await target.close();
2259
+ process.exit(0);
1535
2260
  return;
2261
+ }
1536
2262
  default: {
1537
- const suggestion = suggestCommand(cmd, KNOWN_COMMANDS);
2263
+ if (cachedToolNames.includes(cmd)) {
2264
+ return await cmdToolsCall(target, interceptor, input3);
2265
+ }
2266
+ const suggestion = suggestCommand(cmd, getActiveCommands());
1538
2267
  if (suggestion) {
1539
- console.log(pc.yellow(`Unknown command: ${cmd}.`));
2268
+ console.log(pc2.yellow(`Unknown command: ${cmd}.`));
1540
2269
  try {
1541
2270
  await withSuspendedReadline(target, interceptor, async () => {
1542
2271
  const runIt = await confirm({
1543
- message: `Did you mean ${pc.bold(suggestion)}?`,
2272
+ message: `Did you mean ${pc2.bold(suggestion)}?`,
1544
2273
  default: true
1545
2274
  });
1546
2275
  if (runIt) {
1547
2276
  const rebuiltCommand = rest ? `${suggestion} ${rest}` : suggestion;
1548
- await handleCommand(rebuiltCommand, target, interceptor);
2277
+ return await handleCommand(rebuiltCommand, target, interceptor);
1549
2278
  }
1550
2279
  });
1551
2280
  } catch (err) {
@@ -1553,7 +2282,7 @@ async function handleCommand(input3, target, interceptor) {
1553
2282
  throw new AbortFlowError();
1554
2283
  }
1555
2284
  } else {
1556
- console.log(pc.yellow(`Unknown command: ${cmd}. Type ${pc.bold("help")} for usage.`));
2285
+ console.log(pc2.yellow(`Unknown command: ${cmd}. Type ${pc2.bold("help")} for usage.`));
1557
2286
  }
1558
2287
  }
1559
2288
  }
@@ -1561,25 +2290,25 @@ async function handleCommand(input3, target, interceptor) {
1561
2290
  async function cmdToolsList(target) {
1562
2291
  const { tools } = await target.listTools();
1563
2292
  if (tools.length === 0) {
1564
- console.log(pc.dim(" No tools available."));
2293
+ console.log(pc2.dim(" No tools available."));
1565
2294
  return;
1566
2295
  }
1567
2296
  const nameWidth = Math.max(8, ...tools.map((t) => t.name.length));
1568
- console.log(pc.bold(` ${"Name".padEnd(nameWidth)} Description`));
1569
- console.log(pc.dim(` ${"\u2500".repeat(nameWidth)} ${"\u2500".repeat(50)}`));
2297
+ console.log(pc2.bold(` ${"Name".padEnd(nameWidth)} Description`));
2298
+ console.log(pc2.dim(` ${"\u2500".repeat(nameWidth)} ${"\u2500".repeat(50)}`));
1570
2299
  for (const tool of tools) {
1571
- const desc = tool.description ? tool.description.length > 60 ? `${tool.description.slice(0, 57)}...` : tool.description : pc.dim("(no description)");
1572
- console.log(` ${pc.green(tool.name.padEnd(nameWidth))} ${desc}`);
2300
+ const desc = tool.description ? tool.description.length > 60 ? `${tool.description.slice(0, 57)}...` : tool.description : pc2.dim("(no description)");
2301
+ console.log(` ${pc2.green(tool.name.padEnd(nameWidth))} ${desc}`);
1573
2302
  }
1574
- console.log(pc.dim(`
2303
+ console.log(pc2.dim(`
1575
2304
  ${tools.length} tool(s) total.`));
1576
2305
  if (tools.length >= 10) {
1577
2306
  const groups = groupToolsByPrefix(tools.map((t) => t.name));
1578
2307
  if (!groups.has("All")) {
1579
2308
  console.log();
1580
- console.log(pc.bold(" Groups:"));
2309
+ console.log(pc2.bold(" Groups:"));
1581
2310
  for (const [label, members] of groups) {
1582
- console.log(` ${pc.cyan(label.padEnd(16))} ${pc.dim(members.join(", "))}`);
2311
+ console.log(` ${pc2.cyan(label.padEnd(16))} ${pc2.dim(members.join(", "))}`);
1583
2312
  }
1584
2313
  }
1585
2314
  }
@@ -1587,28 +2316,28 @@ async function cmdToolsList(target) {
1587
2316
  async function cmdToolsDescribe(target, rest) {
1588
2317
  const name = rest.trim();
1589
2318
  if (!name) {
1590
- console.log(pc.yellow(" Usage: tools/describe <name>"));
2319
+ console.log(pc2.yellow(" Usage: tools/describe <name>"));
1591
2320
  if (cachedToolNames.length > 0) {
1592
2321
  const preview = cachedToolNames.slice(0, 6);
1593
2322
  const more = cachedToolNames.length > 6 ? `, ... (${cachedToolNames.length} total)` : "";
1594
- console.log(pc.dim(`
2323
+ console.log(pc2.dim(`
1595
2324
  Available tools: ${preview.join(", ")}${more}`));
1596
- console.log(pc.dim(` Type ${pc.bold("tools/list")} for all.`));
2325
+ console.log(pc2.dim(` Type ${pc2.bold("tools/list")} for all.`));
1597
2326
  }
1598
2327
  return;
1599
2328
  }
1600
2329
  const { tools } = await target.listTools();
1601
2330
  const tool = tools.find((t) => t.name === name);
1602
2331
  if (!tool) {
1603
- console.log(pc.red(`Tool "${name}" not found.`));
2332
+ console.log(pc2.red(`Tool "${name}" not found.`));
1604
2333
  const suggestion = suggestCommand(
1605
2334
  name,
1606
2335
  tools.map((t) => t.name)
1607
2336
  );
1608
2337
  if (suggestion) {
1609
- console.log(pc.yellow(`Did you mean ${pc.bold(suggestion)}?`));
2338
+ console.log(pc2.yellow(`Did you mean ${pc2.bold(suggestion)}?`));
1610
2339
  } else {
1611
- console.log(pc.dim(`Available: ${tools.map((t) => t.name).join(", ")}`));
2340
+ console.log(pc2.dim(`Available: ${tools.map((t) => t.name).join(", ")}`));
1612
2341
  }
1613
2342
  return;
1614
2343
  }
@@ -1628,46 +2357,79 @@ async function cmdToolsCall(target, interceptor, rest) {
1628
2357
  const cleanedRest = clearPrevious ? rest.replace(/\s*--clear/, "").trim() : rest;
1629
2358
  const { toolName, jsonArgs, timeoutMs } = parseCallArgs(cleanedRest);
1630
2359
  if (!toolName) {
1631
- console.log(pc.yellow(" Usage: tools/call <name> [json_args] [--timeout <ms>]"));
2360
+ if (!isScriptMode && cachedToolNames.length > 0 && process.stdin.isTTY) {
2361
+ const picked = await withSuspendedReadline(target, interceptor, async () => {
2362
+ const tools = await target.listTools();
2363
+ return pickInteractive(
2364
+ tools.tools.map((t) => ({ name: t.name, description: t.description })),
2365
+ "Pick a tool to call:"
2366
+ );
2367
+ });
2368
+ if (!picked) return;
2369
+ return cmdToolsCall(target, interceptor, picked);
2370
+ }
2371
+ console.log(pc2.yellow(" Usage: tools/call <name> [json_args] [--timeout <ms>]"));
1632
2372
  if (cachedToolNames.length > 0) {
1633
2373
  const preview = cachedToolNames.slice(0, 6);
1634
2374
  const more = cachedToolNames.length > 6 ? `, ... (${cachedToolNames.length} total)` : "";
1635
- console.log(pc.dim(`
2375
+ console.log(pc2.dim(`
1636
2376
  Available tools: ${preview.join(", ")}${more}`));
1637
2377
  console.log(
1638
- pc.dim(` Run without args for ${pc.bold("interactive mode")}: tools/call <name>`)
2378
+ pc2.dim(` Run without args for ${pc2.bold("interactive mode")}: tools/call <name>`)
1639
2379
  );
1640
2380
  }
1641
2381
  return;
1642
2382
  }
1643
2383
  let args = {};
1644
2384
  if (jsonArgs) {
1645
- try {
1646
- args = JSON.parse(jsonArgs);
1647
- } catch (err) {
1648
- console.error(pc.red(`Invalid JSON: ${err.message}`));
1649
- console.log(pc.dim(` Received: ${jsonArgs}`));
1650
- return;
2385
+ const trimmed = jsonArgs.trim();
2386
+ if (trimmed.startsWith("{") && trimmed.endsWith("}")) {
2387
+ try {
2388
+ args = JSON.parse(trimmed);
2389
+ } catch (err) {
2390
+ console.error(pc2.red(`Invalid JSON: ${err.message}`));
2391
+ console.log(pc2.dim(` Received: ${jsonArgs}`));
2392
+ return;
2393
+ }
2394
+ } else {
2395
+ try {
2396
+ args = parseHttpieArgs(trimmed);
2397
+ } catch (err) {
2398
+ console.error(pc2.red(`Invalid shorthand arguments: ${err.message}`));
2399
+ return;
2400
+ }
1651
2401
  }
1652
2402
  const { tools } = await target.listTools();
1653
2403
  const tool = tools.find((t) => t.name === toolName);
1654
- if (tool) {
1655
- const schema = tool.inputSchema;
1656
- const required = schema.required ?? [];
1657
- const missing = required.filter((r) => !(r in args));
1658
- if (missing.length > 0) {
1659
- console.log(pc.yellow(`
1660
- Missing required arguments: ${missing.join(", ")}`));
1661
- console.log();
1662
- const scaffolded = scaffoldArgs(schema);
1663
- console.log(pc.dim(" Try:"));
1664
- console.log(` tools/call ${toolName} ${scaffolded}`);
1665
- console.log();
1666
- console.log(pc.dim(" Or run without args for interactive mode:"));
1667
- console.log(` tools/call ${toolName}`);
1668
- console.log();
1669
- return;
2404
+ if (!tool) {
2405
+ console.log(pc2.red(`
2406
+ \u2717 Tool "${toolName}" not found.`));
2407
+ const toolNames = tools.map((t) => t.name);
2408
+ const suggestion = suggestCommand(toolName, toolNames);
2409
+ if (suggestion) {
2410
+ console.log(pc2.yellow(` \u{1F4A1} Did you mean "${suggestion}"?`));
2411
+ } else {
2412
+ const preview = toolNames.slice(0, 6);
2413
+ const more = toolNames.length > 6 ? `, ... (${toolNames.length} total)` : "";
2414
+ console.log(pc2.dim(` Available tools: ${preview.join(", ")}${more}`));
1670
2415
  }
2416
+ return { isError: true, content: [{ type: "text", text: `Tool not found: ${toolName}` }] };
2417
+ }
2418
+ const schema = tool.inputSchema;
2419
+ const required = schema.required ?? [];
2420
+ const missing = required.filter((r) => !(r in args));
2421
+ if (missing.length > 0) {
2422
+ console.log(pc2.yellow(`
2423
+ Missing required arguments: ${missing.join(", ")}`));
2424
+ console.log();
2425
+ const scaffolded = scaffoldArgs(schema);
2426
+ console.log(pc2.dim(" Try:"));
2427
+ console.log(` tools/call ${toolName} ${scaffolded}`);
2428
+ console.log();
2429
+ console.log(pc2.dim(" Or run without args for interactive mode:"));
2430
+ console.log(` tools/call ${toolName}`);
2431
+ console.log();
2432
+ return;
1671
2433
  }
1672
2434
  } else {
1673
2435
  const collectedArgs = await interactiveArgPrompt(target, interceptor, toolName, clearPrevious);
@@ -1681,56 +2443,72 @@ async function cmdToolsCall(target, interceptor, rest) {
1681
2443
  }
1682
2444
  }
1683
2445
  }
1684
- console.log(pc.dim(` Calling ${toolName}...`));
2446
+ console.log(pc2.dim(` Calling ${toolName}...`));
1685
2447
  const startTime = Date.now();
1686
2448
  const result = await interceptor.callTool(target, toolName, args, timeoutMs);
1687
2449
  const elapsed = Date.now() - startTime;
1688
2450
  callHistory.push({ toolName, durationMs: elapsed, timestamp: startTime });
1689
2451
  lastToolArgsMap.set(toolName, { ...args });
1690
- const width = 60;
1691
2452
  const isError = result.isError === true;
1692
- const topText = isError ? "Error" : "Result";
1693
- const resultLabel = isError ? pc.red("Error") : pc.green("Result");
1694
- const topPads = Math.max(0, width - 4 - topText.length);
1695
- console.log(`
1696
- ${pc.dim("\u2500\u2500")} ${resultLabel} ${pc.dim("\u2500".repeat(topPads))}`);
2453
+ console.log();
2454
+ printResultBlock({
2455
+ label: isError ? "Error" : "Result",
2456
+ labelColor: isError ? "red" : "green",
2457
+ elapsed,
2458
+ toolName
2459
+ });
1697
2460
  const content = result.content;
1698
2461
  if (Array.isArray(content)) {
1699
2462
  for (const item of content) {
1700
2463
  if (item.type === "text") {
1701
- console.log(isError ? pc.red(` \u2717 ${item.text}`) : ` ${item.text}`);
2464
+ if (isError) {
2465
+ console.log(pc2.red(` \u2717 ${item.text}`));
2466
+ } else {
2467
+ try {
2468
+ const parsed = JSON.parse(item.text);
2469
+ if (typeof parsed === "object" && parsed !== null) {
2470
+ console.log(formatJson(parsed, 2, true));
2471
+ } else {
2472
+ console.log(pc2.yellow(` ${item.text}`));
2473
+ }
2474
+ } catch {
2475
+ console.log(pc2.yellow(` ${item.text}`));
2476
+ }
2477
+ }
1702
2478
  } else {
1703
- console.log(formatJson(item, 2));
2479
+ console.log(formatJson(item, 2, true));
1704
2480
  }
1705
2481
  }
1706
2482
  } else {
1707
- console.log(formatJson(result, 2));
2483
+ console.log(formatJson(result, 2, true));
1708
2484
  }
1709
2485
  if (isError) {
1710
- console.log(
1711
- pc.yellow(
1712
- ` \u{1F4A1} Tip: Check the tool arguments via 'tools/describe ${toolName}'
2486
+ const errText = Array.isArray(content) ? content.map((c) => c.text || "").join(" ").toLowerCase() : typeof content === "object" ? (content.text || "").toLowerCase() : "";
2487
+ if (errText.includes("argument") || errText.includes("validation") || errText.includes("schema") || errText.includes("missing") || errText.includes("invalid")) {
2488
+ console.log(
2489
+ pc2.yellow(
2490
+ ` \u{1F4A1} Tip: Check the tool arguments via 'tools/describe ${toolName}'
1713
2491
  or view the raw server stderr above.`
1714
- )
1715
- );
2492
+ )
2493
+ );
2494
+ }
1716
2495
  }
1717
- const elapsedStr = `${elapsed}ms`;
1718
- const bottomPads = Math.max(0, width - 4 - elapsedStr.length);
1719
- console.log(` ${pc.dim("\u2500".repeat(bottomPads))} ${pc.dim(elapsedStr)} ${pc.dim("\u2500\u2500")}`);
2496
+ console.log();
2497
+ return result;
1720
2498
  }
1721
2499
  async function interactiveArgPrompt(target, interceptor, toolName, clearPrevious = false) {
1722
2500
  const { tools } = await target.listTools();
1723
2501
  const tool = tools.find((t) => t.name === toolName);
1724
2502
  if (!tool) {
1725
- console.log(pc.red(`Tool "${toolName}" not found.`));
2503
+ console.log(pc2.red(`Tool "${toolName}" not found.`));
1726
2504
  const suggestion = suggestCommand(
1727
2505
  toolName,
1728
2506
  tools.map((t) => t.name)
1729
2507
  );
1730
2508
  if (suggestion) {
1731
- console.log(pc.yellow(`Did you mean ${pc.bold(suggestion)}?`));
2509
+ console.log(pc2.yellow(`Did you mean ${pc2.bold(suggestion)}?`));
1732
2510
  } else {
1733
- console.log(pc.dim(`Available: ${tools.map((t) => t.name).join(", ")}`));
2511
+ console.log(pc2.dim(`Available: ${tools.map((t) => t.name).join(", ")}`));
1734
2512
  }
1735
2513
  return null;
1736
2514
  }
@@ -1740,9 +2518,9 @@ async function interactiveArgPrompt(target, interceptor, toolName, clearPrevious
1740
2518
  return {};
1741
2519
  }
1742
2520
  if (isScriptMode) {
1743
- console.log(pc.yellow(` Tool "${toolName}" requires arguments.`));
2521
+ console.log(pc2.yellow(` Tool "${toolName}" requires arguments.`));
1744
2522
  const scaffolded = scaffoldArgs(schema);
1745
- console.log(pc.dim(` Usage: tools/call ${toolName} ${scaffolded}`));
2523
+ console.log(pc2.dim(` Usage: tools/call ${toolName} ${scaffolded}`));
1746
2524
  return null;
1747
2525
  }
1748
2526
  const required = schema.required ?? [];
@@ -1751,10 +2529,10 @@ async function interactiveArgPrompt(target, interceptor, toolName, clearPrevious
1751
2529
  const optionalProps = allProps.filter(([name]) => !required.includes(name));
1752
2530
  const previousArgs = clearPrevious ? void 0 : lastToolArgsMap.get(toolName);
1753
2531
  console.log();
1754
- console.log(` ${pc.bold(tool.name)}${tool.description ? pc.dim(` \u2014 ${tool.description}`) : ""}`);
2532
+ console.log(` ${pc2.bold(tool.name)}${tool.description ? pc2.dim(` \u2014 ${tool.description}`) : ""}`);
1755
2533
  if (previousArgs) {
1756
- console.log(pc.dim(` Previous: ${JSON.stringify(previousArgs)}`));
1757
- console.log(pc.dim(" Press Enter to reuse values, or type to override."));
2534
+ console.log(pc2.dim(` Previous: ${JSON.stringify(previousArgs)}`));
2535
+ console.log(pc2.dim(" Press Enter to reuse values, or type to override."));
1758
2536
  }
1759
2537
  console.log();
1760
2538
  const collectedArgs = {};
@@ -1772,9 +2550,21 @@ async function interactiveArgPrompt(target, interceptor, toolName, clearPrevious
1772
2550
  const typeStr = prop.type ?? "any";
1773
2551
  const desc = prop.description ?? "";
1774
2552
  const prevVal = previousArgs?.[name];
1775
- const label = desc ? `${name} ${pc.dim(`(${typeStr})`)} ${pc.dim(desc)}` : `${name} ${pc.dim(`(${typeStr})`)}`;
1776
- const answerStr = await input(
1777
- { message: label, default: prevVal !== void 0 ? String(prevVal) : void 0 },
2553
+ const label = desc ? `${name} ${pc2.dim(`(${typeStr})`)} ${pc2.dim(desc)}` : `${name} ${pc2.dim(`(${typeStr})`)}`;
2554
+ const answerStr = await input2(
2555
+ {
2556
+ message: label,
2557
+ default: prevVal !== void 0 ? String(prevVal) : void 0,
2558
+ validate: (val) => {
2559
+ if (!val && typeStr !== "string") {
2560
+ return "This argument is required and cannot be empty.";
2561
+ }
2562
+ if (val && (typeStr === "number" || typeStr === "integer") && Number.isNaN(Number(val))) {
2563
+ return "Must be a valid number.";
2564
+ }
2565
+ return true;
2566
+ }
2567
+ },
1778
2568
  { signal: abortController.signal }
1779
2569
  );
1780
2570
  collectedArgs[name] = coerceValue(answerStr, typeStr);
@@ -1802,16 +2592,25 @@ async function interactiveArgPrompt(target, interceptor, toolName, clearPrevious
1802
2592
  const typeStr = prop.type ?? "any";
1803
2593
  const desc = prop.description ?? "";
1804
2594
  const prevVal = previousArgs?.[name];
1805
- const label = desc ? `${name} ${pc.dim(`(${typeStr})`)} ${pc.dim(desc)}` : `${name} ${pc.dim(`(${typeStr})`)}`;
1806
- const answerStr = await input(
1807
- { message: label, default: prevVal !== void 0 ? String(prevVal) : void 0 },
2595
+ const label = desc ? `${name} ${pc2.dim(`(${typeStr})`)} ${pc2.dim(desc)}` : `${name} ${pc2.dim(`(${typeStr})`)}`;
2596
+ const answerStr = await input2(
2597
+ {
2598
+ message: label,
2599
+ default: prevVal !== void 0 ? String(prevVal) : void 0,
2600
+ validate: (val) => {
2601
+ if (val && (typeStr === "number" || typeStr === "integer") && Number.isNaN(Number(val))) {
2602
+ return "Must be a valid number.";
2603
+ }
2604
+ return true;
2605
+ }
2606
+ },
1808
2607
  { signal: abortController.signal }
1809
2608
  );
1810
2609
  collectedArgs[name] = coerceValue(answerStr, typeStr);
1811
2610
  }
1812
2611
  }
1813
2612
  console.log();
1814
- console.log(pc.dim(` ${JSON.stringify(collectedArgs)}`));
2613
+ console.log(formatJson(collectedArgs, 2, true));
1815
2614
  const shouldExecute = await confirm(
1816
2615
  { message: "Execute?", default: true },
1817
2616
  { signal: abortController.signal }
@@ -1832,14 +2631,14 @@ function printJsonTemplate(filled, allProps, currentProp) {
1832
2631
  const parts = [];
1833
2632
  for (const [name] of allProps) {
1834
2633
  if (name in filled) {
1835
- parts.push(`"${name}": ${pc.green(JSON.stringify(filled[name]))}`);
2634
+ parts.push(`"${name}": ${pc2.green(JSON.stringify(filled[name]))}`);
1836
2635
  } else if (name === currentProp) {
1837
- parts.push(`"${name}": ${pc.yellow("\u2592")}`);
2636
+ parts.push(`"${name}": ${pc2.yellow("\u2592")}`);
1838
2637
  } else {
1839
- parts.push(`"${name}": ${pc.dim("\u2592")}`);
2638
+ parts.push(`"${name}": ${pc2.dim("\u2592")}`);
1840
2639
  }
1841
2640
  }
1842
- console.log(pc.dim(" { ") + parts.join(pc.dim(", ")) + pc.dim(" }"));
2641
+ console.log(pc2.dim(" { ") + parts.join(pc2.dim(", ")) + pc2.dim(" }"));
1843
2642
  console.log();
1844
2643
  }
1845
2644
  function coerceValue(input3, type) {
@@ -1856,7 +2655,7 @@ function coerceValue(input3, type) {
1856
2655
  }
1857
2656
  }
1858
2657
  function question(rl, prompt) {
1859
- return new Promise((resolve, reject) => {
2658
+ return new Promise((resolve2, reject) => {
1860
2659
  let aborted = false;
1861
2660
  const onKeypress = (_str, key) => {
1862
2661
  if (key && key.name === "escape") {
@@ -1880,7 +2679,7 @@ function question(rl, prompt) {
1880
2679
  if (aborted) {
1881
2680
  reject(new AbortFlowError());
1882
2681
  } else {
1883
- resolve(answer);
2682
+ resolve2(answer);
1884
2683
  }
1885
2684
  });
1886
2685
  });
@@ -1888,11 +2687,11 @@ function question(rl, prompt) {
1888
2687
  async function cmdToolsScaffold(target, rest) {
1889
2688
  const name = rest.trim();
1890
2689
  if (!name) {
1891
- console.log(pc.yellow(" Usage: tools/scaffold <name>"));
2690
+ console.log(pc2.yellow(" Usage: tools/scaffold <name>"));
1892
2691
  if (cachedToolNames.length > 0) {
1893
2692
  const preview = cachedToolNames.slice(0, 6);
1894
2693
  const more = cachedToolNames.length > 6 ? `, ... (${cachedToolNames.length} total)` : "";
1895
- console.log(pc.dim(`
2694
+ console.log(pc2.dim(`
1896
2695
  Available tools: ${preview.join(", ")}${more}`));
1897
2696
  }
1898
2697
  return;
@@ -1900,158 +2699,247 @@ async function cmdToolsScaffold(target, rest) {
1900
2699
  const { tools } = await target.listTools();
1901
2700
  const tool = tools.find((t) => t.name === name);
1902
2701
  if (!tool) {
1903
- console.log(pc.red(`Tool "${name}" not found.`));
2702
+ console.log(pc2.red(`Tool "${name}" not found.`));
1904
2703
  const suggestion = suggestCommand(
1905
2704
  name,
1906
2705
  tools.map((t) => t.name)
1907
2706
  );
1908
2707
  if (suggestion) {
1909
- console.log(pc.yellow(`Did you mean ${pc.bold(suggestion)}?`));
2708
+ console.log(pc2.yellow(`Did you mean ${pc2.bold(suggestion)}?`));
1910
2709
  } else {
1911
- console.log(pc.dim(`Available: ${tools.map((t) => t.name).join(", ")}`));
2710
+ console.log(pc2.dim(`Available: ${tools.map((t) => t.name).join(", ")}`));
1912
2711
  }
1913
2712
  return;
1914
2713
  }
1915
2714
  const scaffolded = scaffoldArgs(tool.inputSchema);
1916
- console.log(pc.cyan("\n Ready-to-paste command:"));
2715
+ console.log(pc2.cyan("\n Ready-to-paste command:"));
1917
2716
  console.log(` tools/call ${name} ${scaffolded}
1918
2717
  `);
1919
2718
  }
1920
2719
  async function cmdResourcesList(target) {
1921
2720
  const { resources } = await target.listResources();
1922
2721
  if (resources.length === 0) {
1923
- console.log(pc.dim(" No resources available."));
2722
+ console.log(pc2.dim(" No resources available."));
1924
2723
  return;
1925
2724
  }
1926
2725
  const uriWidth = Math.max(6, ...resources.map((r) => r.uri.length));
1927
- console.log(pc.bold(` ${"URI".padEnd(uriWidth)} Name`));
1928
- console.log(pc.dim(` ${"\u2500".repeat(uriWidth)} ${"\u2500".repeat(40)}`));
2726
+ console.log(pc2.bold(` ${"URI".padEnd(uriWidth)} Name`));
2727
+ console.log(pc2.dim(` ${"\u2500".repeat(uriWidth)} ${"\u2500".repeat(40)}`));
1929
2728
  for (const r of resources) {
1930
2729
  const uri = r.uri;
1931
- const name = r.name ?? pc.dim("(unnamed)");
1932
- console.log(` ${pc.green(uri.padEnd(uriWidth))} ${name}`);
2730
+ const name = r.name ?? pc2.dim("(unnamed)");
2731
+ console.log(` ${pc2.green(uri.padEnd(uriWidth))} ${name}`);
1933
2732
  }
1934
- console.log(pc.dim(`
2733
+ console.log(pc2.dim(`
1935
2734
  ${resources.length} resource(s) total.`));
1936
2735
  }
1937
- async function cmdResourcesRead(target, rest) {
2736
+ async function cmdResourcesRead(target, rest, interceptor) {
1938
2737
  const uri = rest.trim();
1939
2738
  if (!uri) {
1940
- console.log(pc.yellow(" Usage: resources/read <uri>"));
2739
+ if (!isScriptMode && cachedResourceUris.length > 0 && process.stdin.isTTY && interceptor) {
2740
+ const picked = await withSuspendedReadline(target, interceptor, async () => {
2741
+ const { resources } = await target.listResources();
2742
+ return pickInteractive(
2743
+ resources.map((r) => ({
2744
+ name: r.uri,
2745
+ description: r.description || r.name
2746
+ })),
2747
+ "Pick a resource to read:"
2748
+ );
2749
+ });
2750
+ if (!picked) return;
2751
+ return cmdResourcesRead(target, picked, interceptor);
2752
+ }
2753
+ console.log(pc2.yellow(" Usage: resources/read <uri>"));
1941
2754
  if (cachedResourceUris.length > 0) {
1942
2755
  const preview = cachedResourceUris.slice(0, 5);
1943
2756
  const more = cachedResourceUris.length > 5 ? `, ... (${cachedResourceUris.length} total)` : "";
1944
- console.log(pc.dim(`
2757
+ console.log(pc2.dim(`
1945
2758
  Available resources: ${preview.join(", ")}${more}`));
1946
2759
  }
1947
2760
  return;
1948
2761
  }
2762
+ const startTime = Date.now();
1949
2763
  const result = await target.readResource({ uri });
2764
+ const elapsed = Date.now() - startTime;
2765
+ console.log();
2766
+ printResultBlock({ label: "Resource", labelColor: "cyan", elapsed, detail: uri });
1950
2767
  for (const item of result.contents) {
1951
2768
  if (item.text !== void 0) {
1952
- console.log(item.text);
2769
+ const text = item.text;
2770
+ try {
2771
+ const parsed = JSON.parse(text);
2772
+ if (typeof parsed === "object" && parsed !== null) {
2773
+ console.log(formatJson(parsed, 2, true));
2774
+ } else {
2775
+ console.log(pc2.yellow(` ${text}`));
2776
+ }
2777
+ } catch {
2778
+ console.log(pc2.yellow(` ${text}`));
2779
+ }
1953
2780
  } else if (item.blob !== void 0) {
1954
2781
  const mimeType = item.mimeType ?? "application/octet-stream";
1955
2782
  const sizeBytes = Buffer.from(item.blob, "base64").length;
1956
- console.log(pc.dim(`[Binary: ${mimeType}, ${sizeBytes} bytes]`));
2783
+ console.log(pc2.dim(` [Binary: ${mimeType}, ${sizeBytes} bytes]`));
1957
2784
  } else {
1958
- console.log(formatJson(item, 2));
2785
+ console.log(formatJson(item, 2, true));
1959
2786
  }
1960
2787
  }
2788
+ console.log();
2789
+ return result;
1961
2790
  }
1962
2791
  async function cmdResourcesTemplates(target) {
1963
2792
  const { resourceTemplates } = await target.listResourceTemplates();
1964
2793
  if (resourceTemplates.length === 0) {
1965
- console.log(pc.dim(" No resource templates available."));
2794
+ console.log(pc2.dim(" No resource templates available."));
1966
2795
  return;
1967
2796
  }
1968
2797
  const uriWidth = Math.max(
1969
2798
  12,
1970
2799
  ...resourceTemplates.map((t) => t.uriTemplate.length)
1971
2800
  );
1972
- console.log(pc.bold(` ${"URI Template".padEnd(uriWidth)} Name`));
1973
- console.log(pc.dim(` ${"\u2500".repeat(uriWidth)} ${"\u2500".repeat(40)}`));
2801
+ console.log(pc2.bold(` ${"URI Template".padEnd(uriWidth)} Name`));
2802
+ console.log(pc2.dim(` ${"\u2500".repeat(uriWidth)} ${"\u2500".repeat(40)}`));
1974
2803
  for (const t of resourceTemplates) {
1975
2804
  const uriTemplate = t.uriTemplate;
1976
- const name = t.name ?? pc.dim("(unnamed)");
1977
- console.log(` ${pc.green(uriTemplate.padEnd(uriWidth))} ${name}`);
2805
+ const name = t.name ?? pc2.dim("(unnamed)");
2806
+ console.log(` ${pc2.green(uriTemplate.padEnd(uriWidth))} ${name}`);
1978
2807
  }
1979
- console.log(pc.dim(`
2808
+ console.log(pc2.dim(`
1980
2809
  ${resourceTemplates.length} template(s) total.`));
1981
2810
  }
1982
2811
  async function cmdPromptsList(target) {
1983
2812
  const { prompts } = await target.listPrompts();
1984
2813
  if (prompts.length === 0) {
1985
- console.log(pc.dim(" No prompts available."));
2814
+ console.log(pc2.dim(" No prompts available."));
1986
2815
  return;
1987
2816
  }
1988
2817
  const nameWidth = Math.max(8, ...prompts.map((p) => p.name.length));
1989
- console.log(pc.bold(` ${"Name".padEnd(nameWidth)} Description`));
1990
- console.log(pc.dim(` ${"\u2500".repeat(nameWidth)} ${"\u2500".repeat(50)}`));
2818
+ console.log(pc2.bold(` ${"Name".padEnd(nameWidth)} Description`));
2819
+ console.log(pc2.dim(` ${"\u2500".repeat(nameWidth)} ${"\u2500".repeat(50)}`));
1991
2820
  for (const p of prompts) {
1992
- const desc = p.description ? p.description.length > 60 ? `${p.description.slice(0, 57)}...` : p.description : pc.dim("(no description)");
1993
- console.log(` ${pc.green(p.name.padEnd(nameWidth))} ${desc}`);
2821
+ const desc = p.description ? p.description.length > 60 ? `${p.description.slice(0, 57)}...` : p.description : pc2.dim("(no description)");
2822
+ console.log(` ${pc2.green(p.name.padEnd(nameWidth))} ${desc}`);
1994
2823
  }
1995
- console.log(pc.dim(`
2824
+ console.log(pc2.dim(`
1996
2825
  ${prompts.length} prompt(s) total.`));
1997
2826
  }
1998
- async function cmdPromptsGet(target, rest) {
2827
+ async function cmdPromptsGet(target, rest, interceptor) {
1999
2828
  const { toolName: promptName, jsonArgs } = parseCallArgs(rest);
2000
2829
  if (!promptName) {
2001
- console.log(pc.yellow(" Usage: prompts/get <name> [json_args]"));
2830
+ if (!isScriptMode && cachedPromptNames.length > 0 && process.stdin.isTTY && interceptor) {
2831
+ const picked = await withSuspendedReadline(target, interceptor, async () => {
2832
+ const { prompts: prompts2 } = await target.listPrompts();
2833
+ return pickInteractive(
2834
+ prompts2.map((p) => ({ name: p.name, description: p.description })),
2835
+ "Pick a prompt to get:"
2836
+ );
2837
+ });
2838
+ if (!picked) return;
2839
+ return cmdPromptsGet(target, picked);
2840
+ }
2841
+ console.log(pc2.yellow(" Usage: prompts/get <name> [json_args]"));
2002
2842
  if (cachedPromptNames.length > 0) {
2003
- console.log(pc.dim(`
2843
+ console.log(pc2.dim(`
2004
2844
  Available prompts: ${cachedPromptNames.join(", ")}`));
2005
2845
  }
2006
2846
  return;
2007
2847
  }
2008
2848
  let promptArgs = {};
2009
2849
  if (jsonArgs) {
2010
- try {
2011
- promptArgs = JSON.parse(jsonArgs);
2012
- } catch (err) {
2013
- console.error(pc.red(`Invalid JSON: ${err.message}`));
2014
- console.log(pc.dim(` Received: ${jsonArgs}`));
2015
- return;
2850
+ const trimmed = jsonArgs.trim();
2851
+ if (trimmed.startsWith("{") && trimmed.endsWith("}")) {
2852
+ try {
2853
+ promptArgs = JSON.parse(trimmed);
2854
+ } catch (err) {
2855
+ console.error(pc2.red(`Invalid JSON: ${err.message}`));
2856
+ console.log(pc2.dim(` Received: ${jsonArgs}`));
2857
+ return;
2858
+ }
2859
+ } else {
2860
+ try {
2861
+ promptArgs = parseHttpieArgs(trimmed);
2862
+ } catch (err) {
2863
+ console.error(pc2.red(`Invalid shorthand arguments: ${err.message}`));
2864
+ return;
2865
+ }
2866
+ }
2867
+ }
2868
+ const { prompts } = await target.listPrompts();
2869
+ const prompt = prompts.find((p) => p.name === promptName);
2870
+ if (!prompt) {
2871
+ console.log(pc2.red(`
2872
+ \u2717 Prompt "${promptName}" not found.`));
2873
+ const promptNames = prompts.map((p) => p.name);
2874
+ const suggestion = suggestCommand(promptName, promptNames);
2875
+ if (suggestion) {
2876
+ console.log(pc2.yellow(` \u{1F4A1} Did you mean "${suggestion}"?`));
2877
+ } else {
2878
+ console.log(pc2.dim(` Available prompts: ${promptNames.join(", ")}`));
2016
2879
  }
2880
+ return { isError: true, content: [{ type: "text", text: `Prompt not found: ${promptName}` }] };
2017
2881
  }
2882
+ const startTime = Date.now();
2018
2883
  const result = await target.getPrompt({ name: promptName, arguments: promptArgs });
2884
+ const elapsed = Date.now() - startTime;
2885
+ if (result.messages.length === 0) {
2886
+ console.log(pc2.dim(" No messages returned."));
2887
+ return;
2888
+ }
2889
+ console.log();
2890
+ printResultBlock({ label: "Prompt", labelColor: "blue", elapsed, detail: promptName });
2019
2891
  for (const msg of result.messages) {
2020
- const role = msg.role === "user" ? pc.blue("user") : pc.magenta("assistant");
2892
+ const role = msg.role === "user" ? pc2.blue("user") : pc2.magenta("assistant");
2021
2893
  const text = msg.content.text ?? JSON.stringify(msg.content);
2022
- console.log(` ${pc.bold(role)}: ${text}`);
2894
+ try {
2895
+ const parsed = JSON.parse(text);
2896
+ if (typeof parsed === "object" && parsed !== null) {
2897
+ console.log(` ${pc2.bold(role)}:`);
2898
+ console.log(formatJson(parsed, 4, true));
2899
+ } else {
2900
+ console.log(` ${pc2.bold(role)}: ${pc2.yellow(text)}`);
2901
+ }
2902
+ } catch {
2903
+ console.log(` ${pc2.bold(role)}: ${pc2.yellow(text)}`);
2904
+ }
2023
2905
  }
2906
+ console.log();
2907
+ return result;
2024
2908
  }
2025
2909
  async function cmdPing(target) {
2026
2910
  try {
2027
2911
  const elapsed = await target.ping();
2028
- console.log(pc.green(` \u2713 Pong! Round-trip: ${elapsed}ms`));
2912
+ console.log();
2913
+ console.log(pc2.green(` \u2713 Pong! Round-trip: ${elapsed}ms`));
2914
+ console.log();
2029
2915
  } catch (err) {
2030
- console.error(pc.red(` \u2717 Ping failed: ${err.message}`));
2916
+ console.log();
2917
+ console.error(pc2.red(` \u2717 Ping failed: ${err.message}`));
2918
+ console.log();
2031
2919
  }
2032
2920
  }
2033
2921
  async function cmdLogLevel(target, rest) {
2034
2922
  const level = rest.trim().toLowerCase();
2035
2923
  if (!level) {
2036
- console.log(pc.yellow(" Usage: log-level <level>"));
2037
- console.log(pc.dim(` Valid levels: ${LOG_LEVELS.join(", ")}`));
2924
+ console.log(pc2.yellow(" Usage: log-level <level>"));
2925
+ console.log(pc2.dim(` Valid levels: ${LOG_LEVELS.join(", ")}`));
2038
2926
  return;
2039
2927
  }
2040
2928
  if (!LOG_LEVELS.includes(level)) {
2041
2929
  const suggestion = suggestCommand(level, [...LOG_LEVELS]);
2042
2930
  const hint = suggestion ? ` Did you mean "${suggestion}"?` : "";
2043
- console.log(pc.red(` Unknown log level: "${level}".${hint}`));
2044
- console.log(pc.dim(` Valid levels: ${LOG_LEVELS.join(", ")}`));
2931
+ console.log(pc2.red(` Unknown log level: "${level}".${hint}`));
2932
+ console.log(pc2.dim(` Valid levels: ${LOG_LEVELS.join(", ")}`));
2045
2933
  return;
2046
2934
  }
2047
2935
  try {
2048
2936
  await target.setLoggingLevel(level);
2049
- console.log(pc.green(` \u2713 Logging level set to: ${level}`));
2937
+ console.log(pc2.green(` \u2713 Logging level set to: ${level}`));
2050
2938
  } catch (err) {
2051
- console.error(pc.red(` \u2717 Failed to set log level: ${err.message}`));
2939
+ console.error(pc2.red(` \u2717 Failed to set log level: ${err.message}`));
2052
2940
  const caps = target.getServerCapabilities();
2053
2941
  if (!caps?.logging) {
2054
- console.log(pc.dim(" The server does not advertise logging support."));
2942
+ console.log(pc2.dim(" The server does not advertise logging support."));
2055
2943
  }
2056
2944
  }
2057
2945
  }
@@ -2059,28 +2947,28 @@ function cmdHistory(target, rest) {
2059
2947
  const arg = rest.trim();
2060
2948
  if (arg === "clear") {
2061
2949
  target.clearHistory();
2062
- console.log(pc.dim(" History cleared."));
2950
+ console.log(pc2.dim(" History cleared."));
2063
2951
  return;
2064
2952
  }
2065
2953
  const count = arg ? Number.parseInt(arg, 10) : 20;
2066
2954
  const records = target.getHistory(Number.isNaN(count) ? 20 : count);
2067
2955
  if (records.length === 0) {
2068
- console.log(pc.dim(" No request history yet."));
2956
+ console.log(pc2.dim(" No request history yet."));
2069
2957
  return;
2070
2958
  }
2071
- console.log(pc.bold("\n Request History"));
2072
- console.log(pc.dim(` ${"\u2500".repeat(70)}`));
2959
+ console.log(pc2.bold("\n Request History"));
2960
+ console.log(pc2.dim(` ${"\u2500".repeat(70)}`));
2073
2961
  for (const rec of records) {
2074
2962
  const time = new Date(rec.timestamp).toLocaleTimeString();
2075
2963
  const dur = `${rec.durationMs}ms`;
2076
- const hasError = rec.error ? pc.red(" \u2717") : "";
2964
+ const hasError = rec.error ? pc2.red(" \u2717") : "";
2077
2965
  console.log(
2078
- ` ${pc.dim(`#${rec.id}`)} ${pc.dim(time)} ${pc.green(rec.method.padEnd(30))} ${pc.cyan(dur.padStart(8))}${hasError}`
2966
+ ` ${pc2.dim(`#${rec.id}`)} ${pc2.dim(time)} ${pc2.green(rec.method.padEnd(30))} ${pc2.cyan(dur.padStart(8))}${hasError}`
2079
2967
  );
2080
2968
  }
2081
2969
  const total = target.getHistory().length;
2082
2970
  console.log(
2083
- pc.dim(`
2971
+ pc2.dim(`
2084
2972
  Showing ${records.length} of ${total} total. Use "history clear" to reset.`)
2085
2973
  );
2086
2974
  console.log();
@@ -2089,26 +2977,26 @@ function cmdNotifications(target, rest) {
2089
2977
  const arg = rest.trim();
2090
2978
  if (arg === "clear") {
2091
2979
  target.clearNotifications();
2092
- console.log(pc.dim(" Notifications cleared."));
2980
+ console.log(pc2.dim(" Notifications cleared."));
2093
2981
  return;
2094
2982
  }
2095
2983
  const count = arg ? Number.parseInt(arg, 10) : 20;
2096
2984
  const records = target.getNotifications(Number.isNaN(count) ? 20 : count);
2097
2985
  if (records.length === 0) {
2098
- console.log(pc.dim(" No notifications received yet."));
2099
- console.log(pc.dim(" Notifications appear inline as they arrive from the server."));
2986
+ console.log(pc2.dim(" No notifications received yet."));
2987
+ console.log(pc2.dim(" Notifications appear inline as they arrive from the server."));
2100
2988
  return;
2101
2989
  }
2102
- console.log(pc.bold("\n Server Notifications"));
2103
- console.log(pc.dim(` ${"\u2500".repeat(70)}`));
2990
+ console.log(pc2.bold("\n Server Notifications"));
2991
+ console.log(pc2.dim(` ${"\u2500".repeat(70)}`));
2104
2992
  for (const n of records) {
2105
2993
  const time = new Date(n.timestamp).toLocaleTimeString();
2106
- const params = n.params ? ` ${pc.dim(JSON.stringify(n.params))}` : "";
2107
- console.log(` ${pc.dim(time)} ${pc.yellow(n.method)}${params}`);
2994
+ const params = n.params ? ` ${pc2.dim(JSON.stringify(n.params))}` : "";
2995
+ console.log(` ${pc2.dim(time)} ${pc2.yellow(n.method)}${params}`);
2108
2996
  }
2109
2997
  const total = target.getNotifications().length;
2110
2998
  console.log(
2111
- pc.dim(`
2999
+ pc2.dim(`
2112
3000
  Showing ${records.length} of ${total} total. Use "notifications clear" to reset.`)
2113
3001
  );
2114
3002
  console.log();
@@ -2116,44 +3004,44 @@ function cmdNotifications(target, rest) {
2116
3004
  async function cmdResourcesSubscribe(target, rest) {
2117
3005
  const uri = rest.trim();
2118
3006
  if (!uri) {
2119
- console.log(pc.yellow(" Usage: resources/subscribe <uri>"));
3007
+ console.log(pc2.yellow(" Usage: resources/subscribe <uri>"));
2120
3008
  if (cachedResourceUris.length > 0) {
2121
- console.log(pc.dim(` Available: ${cachedResourceUris.join(", ")}`));
3009
+ console.log(pc2.dim(` Available: ${cachedResourceUris.join(", ")}`));
2122
3010
  }
2123
3011
  return;
2124
3012
  }
2125
3013
  try {
2126
3014
  await target.subscribeResource({ uri });
2127
- console.log(pc.green(` \u2713 Subscribed to: ${uri}`));
2128
- console.log(pc.dim(" You'll see notifications when this resource changes."));
3015
+ console.log(pc2.green(` \u2713 Subscribed to: ${uri}`));
3016
+ console.log(pc2.dim(" You'll see notifications when this resource changes."));
2129
3017
  } catch (err) {
2130
- console.error(pc.red(` \u2717 Subscribe failed: ${err.message}`));
3018
+ console.error(pc2.red(` \u2717 Subscribe failed: ${err.message}`));
2131
3019
  }
2132
3020
  }
2133
3021
  async function cmdResourcesUnsubscribe(target, rest) {
2134
3022
  const uri = rest.trim();
2135
3023
  if (!uri) {
2136
- console.log(pc.yellow(" Usage: resources/unsubscribe <uri>"));
3024
+ console.log(pc2.yellow(" Usage: resources/unsubscribe <uri>"));
2137
3025
  return;
2138
3026
  }
2139
3027
  try {
2140
3028
  await target.unsubscribeResource({ uri });
2141
- console.log(pc.green(` \u2713 Unsubscribed from: ${uri}`));
3029
+ console.log(pc2.green(` \u2713 Unsubscribed from: ${uri}`));
2142
3030
  } catch (err) {
2143
- console.error(pc.red(` \u2717 Unsubscribe failed: ${err.message}`));
3031
+ console.error(pc2.red(` \u2717 Unsubscribe failed: ${err.message}`));
2144
3032
  }
2145
3033
  }
2146
3034
  function cmdRootsList(target) {
2147
3035
  const roots = target.getRoots();
2148
3036
  if (roots.length === 0) {
2149
- console.log(pc.dim(" No roots configured."));
2150
- console.log(pc.dim(" Use roots/add <uri> [name] to add one."));
3037
+ console.log(pc2.dim(" No roots configured."));
3038
+ console.log(pc2.dim(" Use roots/add <uri> [name] to add one."));
2151
3039
  return;
2152
3040
  }
2153
- console.log(pc.bold("\n Client Roots"));
3041
+ console.log(pc2.bold("\n Client Roots"));
2154
3042
  for (const r of roots) {
2155
3043
  const name = r.name ? ` (${r.name})` : "";
2156
- console.log(` ${pc.green(r.uri)}${pc.dim(name)}`);
3044
+ console.log(` ${pc2.green(r.uri)}${pc2.dim(name)}`);
2157
3045
  }
2158
3046
  console.log();
2159
3047
  }
@@ -2162,53 +3050,53 @@ async function cmdRootsAdd(target, rest) {
2162
3050
  const uri = parts[0];
2163
3051
  const name = parts.slice(1).join(" ") || void 0;
2164
3052
  if (!uri) {
2165
- console.log(pc.yellow(" Usage: roots/add <uri> [name]"));
2166
- console.log(pc.dim(' Example: roots/add file:///Users/me/project "My Project"'));
3053
+ console.log(pc2.yellow(" Usage: roots/add <uri> [name]"));
3054
+ console.log(pc2.dim(' Example: roots/add file:///Users/me/project "My Project"'));
2167
3055
  return;
2168
3056
  }
2169
3057
  await target.addRoot({ uri, name });
2170
- console.log(pc.green(` \u2713 Root added: ${uri}`));
2171
- console.log(pc.dim(" Server has been notified of the change."));
3058
+ console.log(pc2.green(` \u2713 Root added: ${uri}`));
3059
+ console.log(pc2.dim(" Server has been notified of the change."));
2172
3060
  }
2173
3061
  async function cmdRootsRemove(target, rest) {
2174
3062
  const uri = rest.trim();
2175
3063
  if (!uri) {
2176
- console.log(pc.yellow(" Usage: roots/remove <uri>"));
3064
+ console.log(pc2.yellow(" Usage: roots/remove <uri>"));
2177
3065
  const roots = target.getRoots();
2178
3066
  if (roots.length > 0) {
2179
- console.log(pc.dim(` Current roots: ${roots.map((r) => r.uri).join(", ")}`));
3067
+ console.log(pc2.dim(` Current roots: ${roots.map((r) => r.uri).join(", ")}`));
2180
3068
  }
2181
3069
  return;
2182
3070
  }
2183
3071
  const removed = await target.removeRoot(uri);
2184
3072
  if (removed) {
2185
- console.log(pc.green(` \u2713 Root removed: ${uri}`));
3073
+ console.log(pc2.green(` \u2713 Root removed: ${uri}`));
2186
3074
  } else {
2187
- console.log(pc.yellow(` Root not found: ${uri}`));
3075
+ console.log(pc2.yellow(` Root not found: ${uri}`));
2188
3076
  }
2189
3077
  }
2190
3078
  async function cmdReconnect(target) {
2191
- console.log(pc.cyan("\u27F3 Disconnecting..."));
3079
+ console.log(pc2.cyan("\u27F3 Disconnecting..."));
2192
3080
  await target.close();
2193
- await new Promise((resolve) => setTimeout(resolve, 200));
2194
- console.log(pc.cyan("\u27F3 Reconnecting..."));
3081
+ await new Promise((resolve2) => setTimeout(resolve2, 200));
3082
+ console.log(pc2.cyan("\u27F3 Reconnecting..."));
2195
3083
  const { command, args } = target.getStatus();
2196
- console.log(pc.dim(` Command: ${command} ${args.join(" ")}`));
3084
+ console.log(pc2.dim(` Command: ${command} ${args.join(" ")}`));
2197
3085
  try {
2198
3086
  await target.connect();
2199
3087
  const s = target.getStatus();
2200
- console.log(pc.green(`\u2713 Reconnected (PID: ${s.pid})`));
3088
+ console.log(pc2.green(`\u2713 Reconnected (PID: ${s.pid})`));
2201
3089
  const { tools } = await target.listTools();
2202
- console.log(pc.cyan(` ${tools.length} tool(s) available.
3090
+ console.log(pc2.cyan(` ${tools.length} tool(s) available.
2203
3091
  `));
2204
3092
  await refreshCaches(target);
2205
3093
  } catch (err) {
2206
- console.error(pc.red(`\u2717 Failed to reconnect: ${err.message}`));
3094
+ console.error(pc2.red(`\u2717 Failed to reconnect: ${err.message}`));
2207
3095
  }
2208
3096
  }
2209
3097
  function cmdTiming() {
2210
3098
  if (callHistory.length === 0) {
2211
- console.log(pc.dim(" No tool calls recorded yet."));
3099
+ console.log(pc2.dim(" No tool calls recorded yet."));
2212
3100
  return;
2213
3101
  }
2214
3102
  const groups = /* @__PURE__ */ new Map();
@@ -2218,8 +3106,8 @@ function cmdTiming() {
2218
3106
  groups.set(record.toolName, list);
2219
3107
  }
2220
3108
  const nameWidth = Math.max(8, ...[...groups.keys()].map((n) => n.length));
2221
- console.log(pc.bold("\n Tool Call Performance"));
2222
- console.log(pc.dim(` ${"\u2500".repeat(nameWidth + 50)}`));
3109
+ console.log(pc2.bold("\n Tool Call Performance"));
3110
+ console.log(pc2.dim(` ${"\u2500".repeat(nameWidth + 50)}`));
2223
3111
  let totalCalls = 0;
2224
3112
  let totalMs = 0;
2225
3113
  let slowestName = "";
@@ -2237,12 +3125,12 @@ function cmdTiming() {
2237
3125
  slowestName = name;
2238
3126
  }
2239
3127
  console.log(
2240
- ` ${pc.green(name.padEnd(nameWidth))} \xD7 ${count} avg: ${avg}ms p95: ${p95}ms max: ${max}ms`
3128
+ ` ${pc2.green(name.padEnd(nameWidth))} \xD7 ${count} avg: ${avg}ms p95: ${p95}ms max: ${max}ms`
2241
3129
  );
2242
3130
  }
2243
3131
  const overallAvg = Math.round(totalMs / totalCalls);
2244
3132
  console.log(
2245
- pc.dim(
3133
+ pc2.dim(
2246
3134
  `
2247
3135
  Total: ${totalCalls} call(s), avg ${overallAvg}ms, slowest: ${slowestName} (${slowestMs}ms)`
2248
3136
  )
@@ -2253,79 +3141,122 @@ function cmdStatus(target) {
2253
3141
  const s = target.getStatus();
2254
3142
  const uptimeStr = s.uptime >= 60 ? `${Math.floor(s.uptime / 60)}m ${(s.uptime % 60).toFixed(0)}s` : `${s.uptime.toFixed(1)}s`;
2255
3143
  const lastRespStr = s.lastResponseTime ? `${((Date.now() - s.lastResponseTime) / 1e3).toFixed(1)}s ago` : "never";
2256
- console.log(pc.bold("\n Target Server Status"));
2257
- console.log(` ${pc.dim("Connected:")} ${s.connected ? pc.green("yes") : pc.red("no")}`);
2258
- console.log(` ${pc.dim("PID:")} ${s.pid ?? "N/A"}`);
2259
- console.log(` ${pc.dim("Uptime:")} ${uptimeStr}`);
2260
- console.log(` ${pc.dim("Last response:")} ${lastRespStr}`);
2261
- console.log(` ${pc.dim("Stderr lines:")} ${s.stderrLineCount.toLocaleString()}`);
2262
- console.log(` ${pc.dim("Reconnects:")} ${s.reconnectAttempts}/${s.maxReconnectAttempts}`);
2263
- console.log(` ${pc.dim("Command:")} ${s.command} ${s.args.join(" ")}`);
3144
+ console.log(pc2.bold("\n Target Server Status"));
3145
+ console.log(` ${pc2.dim("Connected:")} ${s.connected ? pc2.green("yes") : pc2.red("no")}`);
3146
+ console.log(` ${pc2.dim("PID:")} ${s.pid ?? "N/A"}`);
3147
+ console.log(` ${pc2.dim("Uptime:")} ${uptimeStr}`);
3148
+ console.log(` ${pc2.dim("Last response:")} ${lastRespStr}`);
3149
+ console.log(` ${pc2.dim("Stderr lines:")} ${s.stderrLineCount.toLocaleString()}`);
3150
+ console.log(` ${pc2.dim("Reconnects:")} ${s.reconnectAttempts}/${s.maxReconnectAttempts}`);
3151
+ console.log(` ${pc2.dim("Command:")} ${s.command} ${s.args.join(" ")}`);
2264
3152
  console.log();
2265
3153
  }
3154
+ function printResultBlock(opts) {
3155
+ const colorFn = pc2[opts.labelColor];
3156
+ const elapsedStr = opts.elapsed < 1e3 ? `${opts.elapsed}ms` : `${(opts.elapsed / 1e3).toFixed(1)}s`;
3157
+ const detail = opts.detail ?? opts.toolName ?? "";
3158
+ console.log(` ${colorFn(opts.label)} ${pc2.dim(detail)} ${pc2.dim(`(${elapsedStr})`)}`);
3159
+ console.log(pc2.dim(` ${"\u2500".repeat(60)}`));
3160
+ }
3161
+ function printShortHelp() {
3162
+ const hasTools = !!activeCapabilities?.tools;
3163
+ const hasResources = !!activeCapabilities?.resources;
3164
+ const hasPrompts = !!activeCapabilities?.prompts;
3165
+ const tC = hasTools ? pc2.green : pc2.dim;
3166
+ const rC = hasResources ? pc2.green : pc2.dim;
3167
+ const pC = hasPrompts ? pc2.green : pc2.dim;
3168
+ console.log(`
3169
+ ${pc2.bold("Quick Reference:")}
3170
+
3171
+ ${tC("tl")} ${tC("tools/list")} ${rC("rl")} ${rC("resources/list")} ${pC("pl")} ${pC("prompts/list")}
3172
+ ${tC("td")} ${tC("tools/describe")} ${rC("rr")} ${rC("resources/read")} ${pC("pg")} ${pC("prompts/get")}
3173
+ ${tC("tc")} ${tC("tools/call")} ${rC("rt")} ${rC("resources/templates")}
3174
+ ${tC("ts")} ${tC("tools/scaffold")} ${rC("rs")} ${rC("resources/subscribe")}
3175
+ ${rC("ru")} ${rC("resources/unsubscribe")}
3176
+
3177
+ ${pc2.green("ping")} ${pc2.green("status")} ${pc2.green("timing")} ${pc2.green("history")} ${pc2.green("!!")} ${pc2.green("explore")} ${pc2.green("reconnect")}
3178
+
3179
+ ${pc2.dim("Type 'help' for full command reference.")}
3180
+ `);
3181
+ }
2266
3182
  function printHelp() {
3183
+ const hasTools = !!activeCapabilities?.tools;
3184
+ const hasResources = !!activeCapabilities?.resources;
3185
+ const hasPrompts = !!activeCapabilities?.prompts;
3186
+ const hasLogging = !!activeCapabilities?.logging;
3187
+ const tC = hasTools ? pc2.green : pc2.dim;
3188
+ const rC = hasResources ? pc2.green : pc2.dim;
3189
+ const pC = hasPrompts ? pc2.green : pc2.dim;
3190
+ const lC = hasLogging ? pc2.green : pc2.dim;
3191
+ const tD = hasTools ? (s) => s : pc2.dim;
3192
+ const rD = hasResources ? (s) => s : pc2.dim;
3193
+ const pD = hasPrompts ? (s) => s : pc2.dim;
3194
+ const lD = hasLogging ? (s) => s : pc2.dim;
3195
+ const tH = hasTools ? pc2.bold("Tool Commands:") : pc2.dim(pc2.bold("Tool Commands:")) + pc2.dim(" (Unsupported)");
3196
+ const rH = hasResources ? pc2.bold("Resource Commands:") : pc2.dim(pc2.bold("Resource Commands:")) + pc2.dim(" (Unsupported)");
3197
+ const pH = hasPrompts ? pc2.bold("Prompt Commands:") : pc2.dim(pc2.bold("Prompt Commands:")) + pc2.dim(" (Unsupported)");
2267
3198
  console.log(`
2268
- ${pc.bold("Tool Commands:")}
3199
+ ${tH}
2269
3200
 
2270
- ${pc.green("tools/list")} List all available tools
2271
- ${pc.green("tools/describe")} <name> Show a tool's input schema
2272
- ${pc.green("tools/call")} <name> [json] [opts] Call a tool (interactive if no json)
2273
- Options: ${pc.dim("--timeout <ms>")} Override default timeout (60s)
2274
- ${pc.dim("--clear")} Ignore remembered argument defaults
2275
- ${pc.green("tools/scaffold")} <name> Generate a template for a tool's arguments
2276
- ${pc.green("tools/forget")} [name] Clear remembered interactive defaults
3201
+ ${tC("tools/list")} ${tD("List all available tools")}
3202
+ ${tC("tools/describe")} <name> ${tD("Show a tool's input schema")}
3203
+ ${tC("tools/call")} <name> [json] [opts] ${tD("Call a tool (interactive if no json)")}
3204
+ ${tD("Options:")} ${pc2.dim("--timeout <ms>")} ${tD("Override default timeout (60s)")}
3205
+ ${pc2.dim("--clear")} ${tD("Ignore remembered argument defaults")}
3206
+ ${tC("tools/scaffold")} <name> ${tD("Generate a template for a tool's arguments")}
3207
+ ${tC("tools/forget")} [name] ${tD("Clear remembered interactive defaults")}
2277
3208
 
2278
- ${pc.bold("Resource Commands:")}
3209
+ ${rH}
2279
3210
 
2280
- ${pc.green("resources/list")} List all available resources
2281
- ${pc.green("resources/read")} <uri> Read a resource by URI
2282
- ${pc.green("resources/templates")} List resource templates
2283
- ${pc.green("resources/subscribe")} <uri> Subscribe to resource changes
2284
- ${pc.green("resources/unsubscribe")} <uri> Unsubscribe from resource changes
3211
+ ${rC("resources/list")} ${rD("List all available resources")}
3212
+ ${rC("resources/read")} <uri> ${rD("Read a resource by URI")}
3213
+ ${rC("resources/templates")} ${rD("List resource templates")}
3214
+ ${rC("resources/subscribe")} <uri> ${rD("Subscribe to resource changes")}
3215
+ ${rC("resources/unsubscribe")} <uri> ${rD("Unsubscribe from resource changes")}
2285
3216
 
2286
- ${pc.bold("Prompt Commands:")}
3217
+ ${pH}
2287
3218
 
2288
- ${pc.green("prompts/list")} List all available prompts
2289
- ${pc.green("prompts/get")} <name> [json_args] Get a prompt with arguments
3219
+ ${pC("prompts/list")} ${pD("List all available prompts")}
3220
+ ${pC("prompts/get")} <name> [json_args] ${pD("Get a prompt with arguments")}
2290
3221
 
2291
- ${pc.bold("Protocol Commands:")}
3222
+ ${pc2.bold("Protocol Commands:")}
2292
3223
 
2293
- ${pc.green("ping")} Verify connection, show round-trip time
2294
- ${pc.green("log-level")} <level> Set server logging verbosity
2295
- ${pc.green("history")} [count|clear] Show request/response history
2296
- ${pc.green("notifications")} [count|clear] Show server notifications
3224
+ ${pc2.green("ping")} Verify connection, show round-trip time
3225
+ ${lC("log-level")} <level> ${lD("Set server logging verbosity")}${hasLogging ? "" : pc2.dim(" (Unsupported)")}
3226
+ ${pc2.green("history")} [count|clear] Show request/response history
3227
+ ${pc2.green("notifications")} [count|clear] Show server notifications
2297
3228
 
2298
- ${pc.bold("Roots Management:")}
3229
+ ${pc2.bold("Roots Management:")}
2299
3230
 
2300
- ${pc.green("roots/list")} Show configured client roots
2301
- ${pc.green("roots/add")} <uri> [name] Add a root directory
2302
- ${pc.green("roots/remove")} <uri> Remove a root directory
3231
+ ${pc2.green("roots/list")} Show configured client roots
3232
+ ${pc2.green("roots/add")} <uri> [name] Add a root directory
3233
+ ${pc2.green("roots/remove")} <uri> Remove a root directory
2303
3234
 
2304
- ${pc.bold("Session Commands:")}
3235
+ ${pc2.bold("Session Commands:")}
2305
3236
 
2306
- ${pc.green("!!")} / ${pc.green("last")} Re-run the last command
2307
- ${pc.green("reconnect")} Disconnect and reconnect to the server
2308
- ${pc.green("timing")} Show tool call performance stats
2309
- ${pc.green("status")} Show target server status
2310
- ${pc.green("help")} Show this help
2311
- ${pc.green("exit")} / ${pc.green("quit")} Disconnect and exit
3237
+ ${pc2.green("!!")} / ${pc2.green("last")} Re-run the last command
3238
+ ${pc2.green("reconnect")} Disconnect and reconnect to the server
3239
+ ${pc2.green("timing")} Show tool call performance stats
3240
+ ${pc2.green("status")} Show target server status
3241
+ ${pc2.green("help")} Show this help
3242
+ ${pc2.green("exit")} / ${pc2.green("quit")} Disconnect and exit
2312
3243
 
2313
- ${pc.bold("Shortcuts:")}
3244
+ ${pc2.bold("Shortcuts:")}
2314
3245
 
2315
- ${pc.green("tl")} tools/list ${pc.green("rl")} resources/list ${pc.green("pl")} prompts/list
2316
- ${pc.green("td")} tools/describe ${pc.green("rr")} resources/read ${pc.green("pg")} prompts/get
2317
- ${pc.green("tc")} tools/call ${pc.green("rt")} resources/templates
2318
- ${pc.green("ts")} tools/scaffold ${pc.green("rs")} resources/subscribe
2319
- ${pc.green("ru")} resources/unsubscribe
3246
+ ${tC("tl")} ${tC("tools/list")} ${rC("rl")} ${rC("resources/list")} ${pC("pl")} ${pC("prompts/list")}
3247
+ ${tC("td")} ${tC("tools/describe")} ${rC("rr")} ${rC("resources/read")} ${pC("pg")} ${pC("prompts/get")}
3248
+ ${tC("tc")} ${tC("tools/call")} ${rC("rt")} ${rC("resources/templates")}
3249
+ ${tC("ts")} ${tC("tools/scaffold")} ${rC("rs")} ${rC("resources/subscribe")}
3250
+ ${rC("ru")} ${rC("resources/unsubscribe")}
2320
3251
 
2321
- ${pc.dim("Lines starting with # are treated as comments.")}
2322
- ${pc.dim('JSON arguments can contain spaces: tools/call say {"message": "hello world"}')}
2323
- ${pc.dim("Run tools/call <name> without JSON for interactive argument prompting.")}
2324
- ${pc.dim("Use tools/call <name> --clear to ignore remembered defaults.")}
3252
+ ${pc2.dim("Lines starting with # are treated as comments.")}
3253
+ ${pc2.dim('JSON arguments can contain spaces: tools/call say {"message": "hello world"}')}
3254
+ ${pc2.dim("Run tools/call <name> without JSON for interactive argument prompting.")}
3255
+ ${pc2.dim("Use tools/call <name> --clear to ignore remembered defaults.")}
2325
3256
  `);
2326
3257
  }
2327
3258
  async function readScriptLines(filepath) {
2328
- const content = await readFile(filepath, "utf-8");
3259
+ const content = await readFile2(filepath, "utf-8");
2329
3260
  return content.split("\n");
2330
3261
  }
2331
3262
  function cmdToolsForget(rest) {
@@ -2333,14 +3264,39 @@ function cmdToolsForget(rest) {
2333
3264
  if (toolName) {
2334
3265
  if (lastToolArgsMap.has(toolName)) {
2335
3266
  lastToolArgsMap.delete(toolName);
2336
- console.log(pc.green(` Cleared remembered args for ${pc.bold(toolName)}.`));
3267
+ console.log(pc2.green(` Cleared remembered args for ${pc2.bold(toolName)}.`));
2337
3268
  } else {
2338
- console.log(pc.yellow(` No remembered args for "${toolName}".`));
3269
+ console.log(pc2.yellow(` No remembered args for "${toolName}".`));
2339
3270
  }
2340
3271
  } else {
2341
3272
  const count = lastToolArgsMap.size;
2342
3273
  lastToolArgsMap.clear();
2343
- console.log(pc.green(` Cleared remembered args for ${count} tool${count === 1 ? "" : "s"}.`));
3274
+ console.log(pc2.green(` Cleared remembered args for ${count} tool${count === 1 ? "" : "s"}.`));
3275
+ }
3276
+ }
3277
+ async function pickInteractive(items, message) {
3278
+ if (items.length === 0) return null;
3279
+ if (!process.stdin.isTTY) return null;
3280
+ const choices = items.map((item) => ({
3281
+ name: item.name,
3282
+ value: item.name,
3283
+ description: item.description || ""
3284
+ }));
3285
+ try {
3286
+ const picked = await search({
3287
+ message,
3288
+ source: async (term) => {
3289
+ if (!term) return choices;
3290
+ const lower = term.toLowerCase();
3291
+ return choices.filter(
3292
+ (c) => c.name.toLowerCase().includes(lower) || c.description.toLowerCase().includes(lower)
3293
+ );
3294
+ }
3295
+ });
3296
+ return picked;
3297
+ } catch (err) {
3298
+ if (isAbortError(err)) return null;
3299
+ throw err;
2344
3300
  }
2345
3301
  }
2346
3302
  async function cmdExplore(target, interceptor) {
@@ -2384,19 +3340,19 @@ async function cmdExplore(target, interceptor) {
2384
3340
  }
2385
3341
  }
2386
3342
  if (choices.length === 0) {
2387
- console.log(pc.yellow("No tools, resources, or prompts found."));
3343
+ console.log(pc2.yellow("No tools, resources, or prompts found."));
2388
3344
  return;
2389
3345
  }
2390
3346
  if (!process.stdin.isTTY) {
2391
- console.log(pc.bold("\n Server Capabilities\n"));
3347
+ console.log(pc2.bold("\n Server Capabilities\n"));
2392
3348
  for (let i = 0; i < choices.length; i++) {
2393
3349
  console.log(
2394
- ` ${pc.cyan(String(i + 1).padStart(2))}. ${choices[i].name} ${pc.dim(choices[i].description)}`
3350
+ ` ${pc2.cyan(String(i + 1).padStart(2))}. ${choices[i].name} ${pc2.dim(choices[i].description)}`
2395
3351
  );
2396
3352
  }
2397
3353
  console.log();
2398
3354
  console.log(
2399
- pc.dim(" Non-interactive mode \u2014 use specific commands instead (e.g., tools/call <name>).")
3355
+ pc2.dim(" Non-interactive mode \u2014 use specific commands instead (e.g., tools/call <name>).")
2400
3356
  );
2401
3357
  return;
2402
3358
  }
@@ -2406,163 +3362,30 @@ async function cmdExplore(target, interceptor) {
2406
3362
  source: async (term) => {
2407
3363
  if (!term) return choices;
2408
3364
  const lower = term.toLowerCase();
2409
- return choices.filter(
2410
- (c) => c.name.toLowerCase().includes(lower) || c.description.toLowerCase().includes(lower)
2411
- );
2412
- }
2413
- });
2414
- if (answer.type === "tool") {
2415
- await cmdToolsCall(target, interceptor, answer.name);
2416
- } else if (answer.type === "resource") {
2417
- await cmdResourcesRead(target, answer.uri);
2418
- } else if (answer.type === "prompt") {
2419
- await cmdPromptsGet(target, answer.name);
2420
- }
2421
- } catch (err) {
2422
- if (!isAbortError(err)) {
2423
- throw err;
2424
- }
2425
- }
2426
- }
2427
-
2428
- // src/server.ts
2429
- import { createHash } from "crypto";
2430
- import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2431
- import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
2432
- import { z } from "zod";
2433
-
2434
- // src/config-scanner.ts
2435
- import { existsSync } from "fs";
2436
- import { readFile as readFile2 } from "fs/promises";
2437
- import { homedir } from "os";
2438
- import path from "path";
2439
- import process2 from "process";
2440
- import { input as input2, select as select2 } from "@inquirer/prompts";
2441
- function getConfigPaths() {
2442
- const home = homedir();
2443
- const cwd = process2.cwd();
2444
- const isWin = process2.platform === "win32";
2445
- const isMac = process2.platform === "darwin";
2446
- const appData = process2.env.APPDATA || path.join(home, "AppData", "Roaming");
2447
- const localAppData = process2.env.LOCALAPPDATA || path.join(home, "AppData", "Local");
2448
- let claudeDesktopGlob;
2449
- if (isWin) {
2450
- claudeDesktopGlob = path.join(appData, "Claude", "claude_desktop_config.json");
2451
- } else if (isMac) {
2452
- claudeDesktopGlob = path.join(
2453
- home,
2454
- "Library",
2455
- "Application Support",
2456
- "Claude",
2457
- "claude_desktop_config.json"
2458
- );
2459
- } else {
2460
- claudeDesktopGlob = path.join(home, ".config", "Claude", "claude_desktop_config.json");
2461
- }
2462
- return [
2463
- { source: "Cursor (Global)", file: path.join(home, ".cursor", "mcp.json") },
2464
- { source: "Cursor (Project)", file: path.join(cwd, ".cursor", "mcp.json") },
2465
- { source: "Windsurf", file: path.join(home, ".codeium", "windsurf", "mcp_config.json") },
2466
- { source: "Claude Desktop", file: claudeDesktopGlob },
2467
- { source: "Cline", file: path.join(home, "Documents", "Cline", "MCP", "mcp.json") },
2468
- { source: "VS Code (Project)", file: path.join(cwd, ".vscode", "mcp.json") },
2469
- {
2470
- source: "VS Code (Global)",
2471
- file: path.join(
2472
- isMac ? path.join(home, "Library", "Application Support") : isWin ? appData : path.join(home, ".config"),
2473
- "Code",
2474
- "User",
2475
- "settings.json"
2476
- )
2477
- },
2478
- { source: "Copilot CLI (Global)", file: path.join(home, ".copilot", "mcp-config.json") },
2479
- { source: "Gemini CLI (Global)", file: path.join(home, ".gemini", "settings.json") },
2480
- { source: "Gemini CLI (Project)", file: path.join(cwd, ".gemini", "settings.json") },
2481
- { source: "Claude Code (Global)", file: path.join(home, ".claude.json") },
2482
- { source: "Claude Code (Project)", file: path.join(cwd, ".mcp.json") },
2483
- { source: "Antigravity", file: path.join(home, ".gemini", "antigravity", "mcp_config.json") }
2484
- ];
2485
- }
2486
- async function discoverServers() {
2487
- const servers = [];
2488
- const paths = getConfigPaths();
2489
- for (const { source, file } of paths) {
2490
- if (!existsSync(file)) continue;
2491
- try {
2492
- const content = await readFile2(file, "utf8");
2493
- const json = JSON.parse(content);
2494
- let mcpServers;
2495
- if (json.mcpServers && typeof json.mcpServers === "object") {
2496
- mcpServers = json.mcpServers;
2497
- } else if (json.mcp?.servers && typeof json.mcp.servers === "object") {
2498
- mcpServers = json.mcp.servers;
2499
- } else if (json.servers && typeof json.servers === "object") {
2500
- mcpServers = json.servers;
2501
- }
2502
- if (mcpServers) {
2503
- for (const [name, config] of Object.entries(mcpServers)) {
2504
- if (config.command) {
2505
- servers.push({ name, config, source });
2506
- }
2507
- }
2508
- }
2509
- } catch {
2510
- }
2511
- }
2512
- return servers;
2513
- }
2514
- async function pickDiscoveredServer() {
2515
- const servers = await discoverServers();
2516
- if (servers.length === 0) {
2517
- return null;
2518
- }
2519
- const uniqueServers = /* @__PURE__ */ new Map();
2520
- for (const s of servers) {
2521
- const key = `${s.name}::${s.config.command}::${(s.config.args || []).join(" ")}`;
2522
- if (!uniqueServers.has(key)) {
2523
- uniqueServers.set(key, s);
2524
- } else {
2525
- if (s.source.includes("Project")) {
2526
- uniqueServers.set(key, s);
2527
- }
2528
- }
2529
- }
2530
- const choices = Array.from(uniqueServers.values()).map((s) => {
2531
- return {
2532
- name: `${s.name} (from ${s.source})`,
2533
- value: s,
2534
- description: `${s.config.command} ${(s.config.args || []).join(" ")}`
2535
- };
2536
- });
2537
- choices.push({
2538
- name: "Enter custom server command...",
2539
- value: "CUSTOM",
2540
- description: "Manually specify a command, e.g. 'npx foo' or 'python server.py'"
2541
- });
2542
- try {
2543
- const answer = await select2({
2544
- message: "Select an MCP server to launch:",
2545
- choices,
2546
- pageSize: 15
3365
+ return choices.filter(
3366
+ (c) => c.name.toLowerCase().includes(lower) || c.description.toLowerCase().includes(lower)
3367
+ );
3368
+ }
2547
3369
  });
2548
- if (answer === "CUSTOM") {
2549
- const customCommand = await input2({ message: "Command to spawn target MCP server:" });
2550
- if (!customCommand.trim()) return null;
2551
- const parts = customCommand.trim().match(/(?:[^\s"']+|"[^"]*"|'[^']*')+/g)?.map((p) => p.replace(/^["']|["']$/g, ""));
2552
- if (!parts || parts.length === 0) return null;
2553
- return {
2554
- name: "Custom",
2555
- config: { command: parts[0], args: parts.slice(1) },
2556
- source: "Manual"
2557
- };
3370
+ if (answer.type === "tool") {
3371
+ await cmdToolsCall(target, interceptor, answer.name);
3372
+ } else if (answer.type === "resource") {
3373
+ await cmdResourcesRead(target, answer.uri);
3374
+ } else if (answer.type === "prompt") {
3375
+ await cmdPromptsGet(target, answer.name);
3376
+ }
3377
+ } catch (err) {
3378
+ if (!isAbortError(err)) {
3379
+ throw err;
2558
3380
  }
2559
- return answer;
2560
- } catch {
2561
- return null;
2562
3381
  }
2563
3382
  }
2564
3383
 
2565
3384
  // src/server.ts
3385
+ import { createHash } from "crypto";
3386
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
3387
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
3388
+ import { z } from "zod";
2566
3389
  function hashDefinition(obj) {
2567
3390
  return createHash("md5").update(JSON.stringify(obj)).digest("hex").slice(0, 12);
2568
3391
  }
@@ -2593,6 +3416,13 @@ function computeResourceDiff(prev, curr) {
2593
3416
  const removed = [...prevUris].filter((u) => !currUris.has(u));
2594
3417
  return { added, removed, modified: [] };
2595
3418
  }
3419
+ function computeResourceTemplateDiff(prev, curr) {
3420
+ const prevUris = new Set(prev.map((t) => t.uriTemplate));
3421
+ const currUris = new Set(curr.map((t) => t.uriTemplate));
3422
+ const added = [...currUris].filter((u) => !prevUris.has(u));
3423
+ const removed = [...prevUris].filter((u) => !currUris.has(u));
3424
+ return { added, removed, modified: [] };
3425
+ }
2596
3426
  function formatDiffLine(label, diff) {
2597
3427
  const parts = [];
2598
3428
  if (diff.added.length > 0) parts.push(`+${diff.added.length} added`);
@@ -2606,19 +3436,70 @@ function formatDiffLine(label, diff) {
2606
3436
  async function startServer(opts) {
2607
3437
  let target = null;
2608
3438
  let previousSnapshot = null;
3439
+ let cachedSpawnConfig = null;
2609
3440
  const interceptor = new ResponseInterceptor({
2610
3441
  outDir: opts.outDir,
2611
3442
  defaultTimeoutMs: opts.timeoutMs,
2612
- maxTextLength: opts.maxTextLength
3443
+ maxTextLength: opts.maxTextLength,
3444
+ mediaThresholdKb: opts.mediaThresholdKb
2613
3445
  });
2614
3446
  const mcpServer = new McpServer(
2615
- { name: "run-mcp", version: "1.4.0" },
3447
+ { name: "run-mcp", version: "1.6.1" },
2616
3448
  {
2617
3449
  capabilities: {
2618
- tools: {}
3450
+ tools: {},
3451
+ logging: {}
2619
3452
  }
2620
3453
  }
2621
3454
  );
3455
+ function setupTargetListeners(t) {
3456
+ t.on("stderr", (text) => {
3457
+ mcpServer.sendLoggingMessage({
3458
+ level: "info",
3459
+ logger: "target-stderr",
3460
+ data: text
3461
+ }).catch(() => {
3462
+ });
3463
+ });
3464
+ t.on("disconnected", () => {
3465
+ const pid = t.getStatus().pid;
3466
+ mcpServer.sendLoggingMessage({
3467
+ level: "error",
3468
+ logger: "run-mcp",
3469
+ data: `Target server disconnected unexpectedly! (PID: ${pid})`
3470
+ }).catch(() => {
3471
+ });
3472
+ });
3473
+ t.on("notification", (record) => {
3474
+ mcpServer.server.notification({
3475
+ method: record.method,
3476
+ params: record.params
3477
+ }).catch(() => {
3478
+ });
3479
+ });
3480
+ t.on("sampling_request", async ({ request, respond, reject }) => {
3481
+ try {
3482
+ const result = await mcpServer.server.request(
3483
+ { method: "sampling/createMessage", params: request },
3484
+ z.any()
3485
+ );
3486
+ respond(result);
3487
+ } catch (err) {
3488
+ reject(err);
3489
+ }
3490
+ });
3491
+ t.on("elicitation_request", async ({ request, respond, reject }) => {
3492
+ try {
3493
+ const result = await mcpServer.server.request(
3494
+ { method: "elicitation/create", params: request },
3495
+ z.any()
3496
+ );
3497
+ respond(result);
3498
+ } catch (err) {
3499
+ reject(err);
3500
+ }
3501
+ });
3502
+ }
2622
3503
  async function takeSnapshot() {
2623
3504
  if (!target?.connected) return {};
2624
3505
  const snap = {};
@@ -2645,6 +3526,14 @@ async function startServer(opts) {
2645
3526
  }));
2646
3527
  } catch {
2647
3528
  }
3529
+ try {
3530
+ const { resourceTemplates } = await target.listResourceTemplates();
3531
+ snap.resourceTemplates = resourceTemplates.map((t) => ({
3532
+ uriTemplate: t.uriTemplate,
3533
+ name: t.name ?? ""
3534
+ }));
3535
+ } catch {
3536
+ }
2648
3537
  }
2649
3538
  if (caps.prompts) {
2650
3539
  try {
@@ -2672,6 +3561,17 @@ async function startServer(opts) {
2672
3561
  )
2673
3562
  );
2674
3563
  }
3564
+ if (current.resourceTemplates && previousSnapshot.resourceTemplates) {
3565
+ lines.push(
3566
+ formatDiffLine(
3567
+ "Resource Templates",
3568
+ computeResourceTemplateDiff(
3569
+ previousSnapshot.resourceTemplates,
3570
+ current.resourceTemplates
3571
+ )
3572
+ )
3573
+ );
3574
+ }
2675
3575
  if (current.prompts && previousSnapshot.prompts) {
2676
3576
  lines.push(formatDiffLine("Prompts", computeDiff(previousSnapshot.prompts, current.prompts)));
2677
3577
  }
@@ -2685,29 +3585,59 @@ async function startServer(opts) {
2685
3585
  }
2686
3586
  async function ensureConnected(command, args, env) {
2687
3587
  if (target?.connected) return null;
2688
- if (!command) {
3588
+ let cmdToUse = command;
3589
+ let argsToUse = args;
3590
+ let envToUse = env;
3591
+ if (!cmdToUse && cachedSpawnConfig) {
3592
+ cmdToUse = cachedSpawnConfig.command;
3593
+ argsToUse = cachedSpawnConfig.args;
3594
+ envToUse = cachedSpawnConfig.env;
3595
+ }
3596
+ if (!cmdToUse) {
2689
3597
  return "Not connected to a target server. Provide command/args to auto-connect, or call connect_to_mcp first.";
2690
3598
  }
2691
3599
  if (target) {
2692
3600
  await target.close();
2693
3601
  target = null;
2694
3602
  }
2695
- if (env) {
2696
- for (const [key, value] of Object.entries(env)) {
3603
+ if (envToUse) {
3604
+ for (const [key, value] of Object.entries(envToUse)) {
2697
3605
  process.env[key] = value;
2698
3606
  }
2699
3607
  }
2700
- target = new TargetManager(command, args ?? []);
2701
- await target.connect();
3608
+ target = new TargetManager(cmdToUse, argsToUse ?? []);
3609
+ setupTargetListeners(target);
3610
+ try {
3611
+ await target.connect();
3612
+ } catch (err) {
3613
+ await target.close().catch(() => {
3614
+ });
3615
+ target = null;
3616
+ throw err;
3617
+ }
3618
+ cachedSpawnConfig = { command: cmdToUse, args: argsToUse ?? [], env: envToUse };
2702
3619
  return null;
2703
3620
  }
2704
- async function buildIncludeData(include) {
3621
+ async function buildIncludeData(include, summary = false) {
2705
3622
  if (!target?.connected || include.length === 0) return [];
2706
3623
  const lines = [];
2707
3624
  if (include.includes("tools")) {
2708
3625
  try {
2709
3626
  const { tools } = await target.listTools();
2710
- lines.push("", "--- Tools ---", JSON.stringify(tools, null, 2));
3627
+ let displayTools = summary ? tools.map((t) => ({ name: t.name, description: t.description })) : tools;
3628
+ let jsonStr = JSON.stringify(displayTools, null, 2);
3629
+ if (!summary && jsonStr.length > 2e4) {
3630
+ displayTools = tools.map((t) => ({ name: t.name, description: t.description }));
3631
+ jsonStr = JSON.stringify(displayTools, null, 2);
3632
+ lines.push(
3633
+ "",
3634
+ "--- Tools ---",
3635
+ jsonStr,
3636
+ "[Note: Full schemas omitted to protect context window. Use list_mcp_primitives with name='tool_name' to inspect schemas individually.]"
3637
+ );
3638
+ } else {
3639
+ lines.push("", "--- Tools ---", jsonStr);
3640
+ }
2711
3641
  } catch (err) {
2712
3642
  lines.push("", "--- Tools ---", `Error: ${err.message}`);
2713
3643
  }
@@ -2715,15 +3645,50 @@ async function startServer(opts) {
2715
3645
  if (include.includes("resources")) {
2716
3646
  try {
2717
3647
  const { resources } = await target.listResources();
2718
- lines.push("", "--- Resources ---", JSON.stringify(resources, null, 2));
3648
+ let displayResources = summary ? resources.map((r) => ({
3649
+ name: r.name,
3650
+ uri: r.uri,
3651
+ description: r.description
3652
+ })) : resources;
3653
+ let jsonStr = JSON.stringify(displayResources, null, 2);
3654
+ if (!summary && jsonStr.length > 2e4) {
3655
+ displayResources = resources.map((r) => ({
3656
+ name: r.name,
3657
+ uri: r.uri,
3658
+ description: r.description
3659
+ }));
3660
+ jsonStr = JSON.stringify(displayResources, null, 2);
3661
+ lines.push(
3662
+ "",
3663
+ "--- Resources ---",
3664
+ jsonStr,
3665
+ "[Note: Full schemas omitted to protect context window.]"
3666
+ );
3667
+ } else {
3668
+ lines.push("", "--- Resources ---", jsonStr);
3669
+ }
2719
3670
  } catch (err) {
2720
3671
  lines.push("", "--- Resources ---", `Error: ${err.message}`);
2721
3672
  }
2722
3673
  }
3674
+ if (include.includes("resource_templates")) {
3675
+ try {
3676
+ const { resourceTemplates } = await target.listResourceTemplates();
3677
+ const displayTemplates = summary ? resourceTemplates.map((t) => ({
3678
+ name: t.name,
3679
+ uriTemplate: t.uriTemplate,
3680
+ description: t.description
3681
+ })) : resourceTemplates;
3682
+ lines.push("", "--- Resource Templates ---", JSON.stringify(displayTemplates, null, 2));
3683
+ } catch (err) {
3684
+ lines.push("", "--- Resource Templates ---", `Error: ${err.message}`);
3685
+ }
3686
+ }
2723
3687
  if (include.includes("prompts")) {
2724
3688
  try {
2725
3689
  const { prompts } = await target.listPrompts();
2726
- lines.push("", "--- Prompts ---", JSON.stringify(prompts, null, 2));
3690
+ const displayPrompts = summary ? prompts.map((p) => ({ name: p.name, description: p.description })) : prompts;
3691
+ lines.push("", "--- Prompts ---", JSON.stringify(displayPrompts, null, 2));
2727
3692
  } catch (err) {
2728
3693
  lines.push("", "--- Prompts ---", `Error: ${err.message}`);
2729
3694
  }
@@ -2734,17 +3699,20 @@ async function startServer(opts) {
2734
3699
  "connect_to_mcp",
2735
3700
  {
2736
3701
  title: "Connect to MCP Server",
2737
- description: "Spawn and connect to a local MCP server process. Use this to test an MCP server you're building. Only one connection at a time \u2014 call disconnect_from_mcp first if already connected. Use the 'include' parameter to get tools/resources/prompts in the response, saving round trips.",
3702
+ description: "Spawn and connect to a local MCP server process. Use this to test an MCP server you're building. Only one connection at a time \u2014 call disconnect_from_mcp first if already connected. Use the 'include' parameter to get tools/resources/prompts/resource_templates in the response, saving round trips.",
2738
3703
  inputSchema: {
2739
3704
  command: z.string().describe("Command to run (e.g. 'node', 'python', 'npx')"),
2740
3705
  args: z.array(z.string()).optional().describe("Arguments to pass (e.g. ['src/index.js'] or ['-y', 'some-server'])"),
2741
3706
  env: z.record(z.string()).optional().describe("Extra environment variables for the child process"),
2742
- include: z.array(z.enum(["tools", "resources", "prompts"])).optional().describe(
3707
+ include: z.array(z.enum(["tools", "resources", "resource_templates", "prompts"])).optional().describe(
2743
3708
  "Primitives to include in the response. Saves round trips vs calling list_mcp_primitives separately. On reconnect, also shows a diff of what changed since the last connection."
3709
+ ),
3710
+ summary: z.boolean().optional().describe(
3711
+ "If true, returns only the name and description of each primitive (omitting full schemas) when included to save tokens."
2744
3712
  )
2745
3713
  }
2746
3714
  },
2747
- async ({ command, args, env, include }) => {
3715
+ async ({ command, args, env, include, summary }) => {
2748
3716
  if (target?.connected) {
2749
3717
  return {
2750
3718
  content: [
@@ -2767,7 +3735,16 @@ async function startServer(opts) {
2767
3735
  }
2768
3736
  }
2769
3737
  target = new TargetManager(command, args ?? []);
2770
- await target.connect();
3738
+ setupTargetListeners(target);
3739
+ try {
3740
+ await target.connect();
3741
+ } catch (err) {
3742
+ await target.close().catch(() => {
3743
+ });
3744
+ target = null;
3745
+ throw err;
3746
+ }
3747
+ cachedSpawnConfig = { command, args: args ?? [], env };
2771
3748
  const status = target.getStatus();
2772
3749
  const caps = target.getServerCapabilities() ?? {};
2773
3750
  const capSummary = [];
@@ -2796,7 +3773,7 @@ async function startServer(opts) {
2796
3773
  }
2797
3774
  previousSnapshot = currentSnapshot;
2798
3775
  if (include && include.length > 0) {
2799
- lines.push(...await buildIncludeData(include));
3776
+ lines.push(...await buildIncludeData(include, summary));
2800
3777
  }
2801
3778
  const instructions = target.getInstructions();
2802
3779
  if (instructions) {
@@ -2880,17 +3857,21 @@ Check that the command is correct and the server starts without errors. You can
2880
3857
  "list_mcp_primitives",
2881
3858
  {
2882
3859
  title: "List MCP Primitives",
2883
- description: "List tools, resources, and/or prompts on the connected MCP server. Specify which types to include. Defaults to all available. Use 'name' to filter to a specific item (e.g. describe a single tool's schema).",
3860
+ description: "List tools, resources, resource templates, and/or prompts on the connected MCP server. Specify which types to include. Defaults to all available. Use 'name' to filter to a specific item (e.g. describe a single tool's schema).",
2884
3861
  inputSchema: {
2885
- type: z.array(z.enum(["tools", "resources", "prompts"])).optional().describe(
3862
+ type: z.array(z.enum(["tools", "resources", "resource_templates", "prompts"])).optional().describe(
2886
3863
  "Which primitives to list. Defaults to all that the server supports. Example: ['tools'] to list only tools."
2887
3864
  ),
2888
3865
  name: z.string().optional().describe(
2889
- "Filter to a specific item by name. For tools: matches tool name. For resources: matches URI. For prompts: matches prompt name. Returns the full schema/details for just that item."
2890
- )
3866
+ "Filter to a specific item by name. For tools: matches tool name. For resources: matches URI. For resource templates: matches URI template. For prompts: matches prompt name. Returns the full schema/details for just that item."
3867
+ ),
3868
+ summary: z.boolean().optional().describe(
3869
+ "If true, returns only the name and description of each primitive (omitting full schemas) to save tokens."
3870
+ ),
3871
+ cursor: z.string().optional().describe("Cursor for pagination (returned from a previous list call)")
2891
3872
  }
2892
3873
  },
2893
- async ({ type, name }) => {
3874
+ async ({ type, name, summary, cursor }) => {
2894
3875
  if (!target?.connected) {
2895
3876
  return {
2896
3877
  content: [
@@ -2903,11 +3884,11 @@ Check that the command is correct and the server starts without errors. You can
2903
3884
  };
2904
3885
  }
2905
3886
  const caps = target.getServerCapabilities() ?? {};
2906
- const requested = type ?? ["tools", "resources", "prompts"];
3887
+ const requested = type ?? ["tools", "resources", "resource_templates", "prompts"];
2907
3888
  const sections = [];
2908
3889
  if (requested.includes("tools") && caps.tools) {
2909
3890
  try {
2910
- const result = await target.listTools();
3891
+ const result = await target.listTools({ cursor });
2911
3892
  let tools = result.tools;
2912
3893
  if (name) {
2913
3894
  tools = tools.filter((t) => t.name === name);
@@ -2919,7 +3900,11 @@ Available: ${available}`);
2919
3900
  sections.push("--- Tools ---", JSON.stringify(tools[0], null, 2));
2920
3901
  }
2921
3902
  } else {
2922
- sections.push("--- Tools ---", JSON.stringify(tools, null, 2));
3903
+ const displayTools = summary ? tools.map((t) => ({ name: t.name, description: t.description })) : tools;
3904
+ sections.push("--- Tools ---", JSON.stringify(displayTools, null, 2));
3905
+ }
3906
+ if (result.nextCursor) {
3907
+ sections.push(`--- Tools Next Cursor: ${result.nextCursor} ---`);
2923
3908
  }
2924
3909
  } catch (err) {
2925
3910
  sections.push("--- Tools ---", `Error: ${err.message}`);
@@ -2927,7 +3912,7 @@ Available: ${available}`);
2927
3912
  }
2928
3913
  if (requested.includes("resources") && caps.resources) {
2929
3914
  try {
2930
- const result = await target.listResources();
3915
+ const result = await target.listResources({ cursor });
2931
3916
  let resources = result.resources;
2932
3917
  if (name) {
2933
3918
  resources = resources.filter((r) => r.uri === name || r.name === name);
@@ -2942,15 +3927,54 @@ Available: ${available}`
2942
3927
  sections.push("--- Resources ---", JSON.stringify(resources[0], null, 2));
2943
3928
  }
2944
3929
  } else {
2945
- sections.push("--- Resources ---", JSON.stringify(resources, null, 2));
3930
+ const displayResources = summary ? resources.map((r) => ({
3931
+ name: r.name,
3932
+ uri: r.uri,
3933
+ description: r.description
3934
+ })) : resources;
3935
+ sections.push("--- Resources ---", JSON.stringify(displayResources, null, 2));
3936
+ }
3937
+ if (result.nextCursor) {
3938
+ sections.push(`--- Resources Next Cursor: ${result.nextCursor} ---`);
2946
3939
  }
2947
3940
  } catch (err) {
2948
3941
  sections.push("--- Resources ---", `Error: ${err.message}`);
2949
3942
  }
2950
3943
  }
3944
+ if (requested.includes("resource_templates") && caps.resources) {
3945
+ try {
3946
+ const result = await target.listResourceTemplates({ cursor });
3947
+ let templates = result.resourceTemplates;
3948
+ if (name) {
3949
+ templates = templates.filter((t) => t.uriTemplate === name || t.name === name);
3950
+ if (templates.length === 0) {
3951
+ const available = result.resourceTemplates.map((t) => t.uriTemplate).join(", ");
3952
+ sections.push(
3953
+ "--- Resource Templates ---",
3954
+ `Resource Template "${name}" not found.
3955
+ Available: ${available}`
3956
+ );
3957
+ } else {
3958
+ sections.push("--- Resource Templates ---", JSON.stringify(templates[0], null, 2));
3959
+ }
3960
+ } else {
3961
+ const displayTemplates = summary ? templates.map((t) => ({
3962
+ name: t.name,
3963
+ uriTemplate: t.uriTemplate,
3964
+ description: t.description
3965
+ })) : templates;
3966
+ sections.push("--- Resource Templates ---", JSON.stringify(displayTemplates, null, 2));
3967
+ }
3968
+ if (result.nextCursor) {
3969
+ sections.push(`--- Resource Templates Next Cursor: ${result.nextCursor} ---`);
3970
+ }
3971
+ } catch (err) {
3972
+ sections.push("--- Resource Templates ---", `Error: ${err.message}`);
3973
+ }
3974
+ }
2951
3975
  if (requested.includes("prompts") && caps.prompts) {
2952
3976
  try {
2953
- const result = await target.listPrompts();
3977
+ const result = await target.listPrompts({ cursor });
2954
3978
  let prompts = result.prompts;
2955
3979
  if (name) {
2956
3980
  prompts = prompts.filter((p) => p.name === name);
@@ -2965,7 +3989,11 @@ Available: ${available}`
2965
3989
  sections.push("--- Prompts ---", JSON.stringify(prompts[0], null, 2));
2966
3990
  }
2967
3991
  } else {
2968
- sections.push("--- Prompts ---", JSON.stringify(prompts, null, 2));
3992
+ const displayPrompts = summary ? prompts.map((p) => ({ name: p.name, description: p.description })) : prompts;
3993
+ sections.push("--- Prompts ---", JSON.stringify(displayPrompts, null, 2));
3994
+ }
3995
+ if (result.nextCursor) {
3996
+ sections.push(`--- Prompts Next Cursor: ${result.nextCursor} ---`);
2969
3997
  }
2970
3998
  } catch (err) {
2971
3999
  sections.push("--- Prompts ---", `Error: ${err.message}`);
@@ -2997,7 +4025,12 @@ Available: ${available}`
2997
4025
  const servers = await discoverServers();
2998
4026
  if (servers.length === 0) {
2999
4027
  return {
3000
- content: [{ type: "text", text: "No configured MCP servers discovered in common locations." }]
4028
+ content: [
4029
+ {
4030
+ type: "text",
4031
+ text: "No configured MCP servers discovered in common locations."
4032
+ }
4033
+ ]
3001
4034
  };
3002
4035
  }
3003
4036
  const lines = ["Discovered the following MCP server configurations:"];
@@ -3037,28 +4070,40 @@ Available: ${available}`
3037
4070
  name: z.string().describe("Tool name, resource URI, or prompt name"),
3038
4071
  arguments: z.record(z.unknown()).optional().describe("Arguments for the tool or prompt (not used for resources)"),
3039
4072
  // Auto-connect params (only needed if not already connected)
3040
- command: z.string().optional().describe(
3041
- "Command to spawn the server (e.g. 'node'). Required if not already connected."
4073
+ auto_connect: z.object({
4074
+ command: z.string().describe("Command to spawn the server (e.g. 'node')."),
4075
+ args: z.array(z.string()).optional().describe("Arguments for the server command (e.g. ['src/index.js'])"),
4076
+ env: z.record(z.string()).optional().describe("Extra environment variables for the server process")
4077
+ }).optional().describe(
4078
+ "Provide this to automatically spawn and connect to a server if not already connected. Required if no active connection exists."
3042
4079
  ),
3043
- args: z.array(z.string()).optional().describe("Arguments for the server command (e.g. ['src/index.js'])"),
3044
- env: z.record(z.string()).optional().describe("Extra environment variables for the server process"),
3045
4080
  // Lifecycle
3046
4081
  disconnect_after: z.boolean().optional().describe("Tear down the connection after this call (default: false)"),
3047
- timeout_ms: z.number().optional().describe("Timeout in ms (only applies to type: 'tool')")
4082
+ timeout_ms: z.number().optional().describe("Timeout in ms (only applies to type: 'tool')"),
4083
+ include_metadata: z.boolean().optional().describe(
4084
+ "Include a structured metadata content item with latency, interception info, and content statistics. Useful for programmatic consumption."
4085
+ ),
4086
+ max_text_length: z.number().optional().describe(
4087
+ "Max text response length before truncation for this call. Use -1 to disable truncation."
4088
+ )
3048
4089
  }
3049
4090
  },
3050
4091
  async ({
3051
4092
  type: primitiveType,
3052
4093
  name,
3053
4094
  arguments: callArgs,
3054
- command,
3055
- args,
3056
- env,
4095
+ auto_connect,
3057
4096
  disconnect_after,
3058
- timeout_ms
4097
+ timeout_ms,
4098
+ include_metadata,
4099
+ max_text_length
3059
4100
  }) => {
3060
4101
  try {
3061
- const connectError = await ensureConnected(command, args, env);
4102
+ const connectError = await ensureConnected(
4103
+ auto_connect?.command,
4104
+ auto_connect?.args,
4105
+ auto_connect?.env
4106
+ );
3062
4107
  if (connectError) {
3063
4108
  return {
3064
4109
  content: [{ type: "text", text: connectError }],
@@ -3118,41 +4163,145 @@ Available tools: ${toolNames.join(", ")}`
3118
4163
  } catch {
3119
4164
  }
3120
4165
  const startMs = Date.now();
3121
- result = await interceptor.callTool(
3122
- target,
3123
- name,
3124
- callArgs ?? {},
3125
- timeout_ms
3126
- );
4166
+ let interceptionMeta;
4167
+ if (include_metadata) {
4168
+ const { result: toolResult, metadata } = await interceptor.callToolWithMetadata(
4169
+ target,
4170
+ name,
4171
+ callArgs ?? {},
4172
+ timeout_ms,
4173
+ max_text_length
4174
+ );
4175
+ result = toolResult;
4176
+ interceptionMeta = metadata;
4177
+ } else {
4178
+ result = await interceptor.callTool(
4179
+ target,
4180
+ name,
4181
+ callArgs ?? {},
4182
+ timeout_ms,
4183
+ max_text_length
4184
+ );
4185
+ }
3127
4186
  const elapsedMs = Date.now() - startMs;
3128
4187
  const resultContent = result.content;
3129
- if (Array.isArray(resultContent) && resultContent.length > 0) {
3130
- const lastItem = resultContent[resultContent.length - 1];
3131
- if (lastItem.type === "text") {
3132
- lastItem.text += ` (${elapsedMs}ms)`;
4188
+ if (include_metadata && Array.isArray(resultContent)) {
4189
+ const meta = {
4190
+ latency_ms: elapsedMs,
4191
+ content_items: resultContent.length,
4192
+ is_error: result.isError === true
4193
+ };
4194
+ if (interceptionMeta) {
4195
+ meta.truncated = interceptionMeta.truncated;
4196
+ meta.images_saved = interceptionMeta.imagesSaved;
4197
+ meta.audio_saved = interceptionMeta.audioSaved;
4198
+ meta.original_size_bytes = interceptionMeta.originalSizeBytes;
3133
4199
  }
4200
+ resultContent.unshift({
4201
+ type: "text",
4202
+ text: `--- metadata ---
4203
+ ${JSON.stringify(meta)}`
4204
+ });
3134
4205
  }
3135
4206
  break;
3136
4207
  }
3137
4208
  case "resource": {
3138
- const resourceResult = await target.readResource({ uri: name });
3139
- result = {
3140
- content: [
3141
- { type: "text", text: JSON.stringify(resourceResult.contents, null, 2) }
3142
- ]
3143
- };
4209
+ const startMs = Date.now();
4210
+ const resourceResult = await interceptor.readResource(
4211
+ target,
4212
+ { uri: name },
4213
+ timeout_ms,
4214
+ max_text_length
4215
+ );
4216
+ const elapsedMs = Date.now() - startMs;
4217
+ const contentItems = resourceResult.contents.map((c) => {
4218
+ if (c.text !== void 0) {
4219
+ return { type: "text", text: c.text };
4220
+ } else {
4221
+ return { type: "text", text: `[Resource blob: ${c.uri}]` };
4222
+ }
4223
+ });
4224
+ result = { content: contentItems };
4225
+ if (include_metadata) {
4226
+ const meta = {
4227
+ latency_ms: elapsedMs,
4228
+ content_items: contentItems.length,
4229
+ is_error: false
4230
+ };
4231
+ contentItems.unshift({
4232
+ type: "text",
4233
+ text: `--- metadata ---
4234
+ ${JSON.stringify(meta)}`
4235
+ });
4236
+ }
3144
4237
  break;
3145
4238
  }
3146
4239
  case "prompt": {
3147
- const promptResult = await target.getPrompt({
3148
- name,
3149
- arguments: callArgs ?? {}
3150
- });
3151
- result = {
3152
- content: [
3153
- { type: "text", text: JSON.stringify(promptResult.messages, null, 2) }
3154
- ]
3155
- };
4240
+ try {
4241
+ const { prompts } = await target.listPrompts();
4242
+ const promptNames = prompts.map((p) => p.name);
4243
+ const matchedPrompt = prompts.find((p) => p.name === name);
4244
+ if (!matchedPrompt) {
4245
+ const suggestion = suggestCommand(name, promptNames);
4246
+ const hint = suggestion ? ` Did you mean "${suggestion}"?` : "";
4247
+ return {
4248
+ content: [
4249
+ {
4250
+ type: "text",
4251
+ text: `Prompt "${name}" not found.${hint}
4252
+ Available prompts: ${promptNames.join(", ")}`
4253
+ }
4254
+ ],
4255
+ isError: true
4256
+ };
4257
+ }
4258
+ } catch {
4259
+ }
4260
+ const startMs = Date.now();
4261
+ const promptResult = await interceptor.getPrompt(
4262
+ target,
4263
+ {
4264
+ name,
4265
+ arguments: callArgs ?? {}
4266
+ },
4267
+ timeout_ms,
4268
+ max_text_length
4269
+ );
4270
+ const elapsedMs = Date.now() - startMs;
4271
+ const contentItems = [];
4272
+ for (const msg of promptResult.messages) {
4273
+ const role = msg.role;
4274
+ const content = msg.content;
4275
+ const prefix = `[${role.toUpperCase()} MESSAGE]`;
4276
+ if (content.type === "text") {
4277
+ contentItems.push({ type: "text", text: `${prefix}
4278
+ ${content.text}` });
4279
+ } else if (Array.isArray(content)) {
4280
+ for (const item of content) {
4281
+ if (item.type === "text") {
4282
+ contentItems.push({ type: "text", text: `${prefix}
4283
+ ${item.text}` });
4284
+ } else {
4285
+ contentItems.push(item);
4286
+ }
4287
+ }
4288
+ } else {
4289
+ contentItems.push(content);
4290
+ }
4291
+ }
4292
+ result = { content: contentItems };
4293
+ if (include_metadata) {
4294
+ const meta = {
4295
+ latency_ms: elapsedMs,
4296
+ content_items: contentItems.length,
4297
+ is_error: false
4298
+ };
4299
+ contentItems.unshift({
4300
+ type: "text",
4301
+ text: `--- metadata ---
4302
+ ${JSON.stringify(meta)}`
4303
+ });
4304
+ }
3156
4305
  break;
3157
4306
  }
3158
4307
  }
@@ -3214,7 +4363,376 @@ Available tools: ${toolNames.join(", ")}`
3214
4363
  }
3215
4364
 
3216
4365
  // src/index.ts
3217
- program.name("run-mcp").description("A smart interactive REPL and live test harness for MCP servers").version("1.4.0").passThroughOptions().allowUnknownOption().argument(
4366
+ function requireTargetCommand(targetCommand, subcommandUsage) {
4367
+ if (!activeTargetCommand) {
4368
+ process.stderr.write(`Error: Target server command must be separated by '--'.
4369
+ `);
4370
+ process.stderr.write(`This avoids option parsing conflicts.
4371
+
4372
+ `);
4373
+ process.stderr.write(`Usage: ${subcommandUsage}
4374
+ `);
4375
+ process.exit(2);
4376
+ }
4377
+ return activeTargetCommand;
4378
+ }
4379
+ var SESSION_DIR = join2(tmpdir2(), "run-mcp", "sessions");
4380
+ function getSessionPath(name) {
4381
+ return join2(SESSION_DIR, `${name}.json`);
4382
+ }
4383
+ async function getSession(name) {
4384
+ const path2 = getSessionPath(name);
4385
+ if (!existsSync2(path2)) return null;
4386
+ try {
4387
+ const data = await readFile3(path2, "utf8");
4388
+ const parsed = JSON.parse(data);
4389
+ try {
4390
+ process.kill(parsed.pid, 0);
4391
+ return parsed;
4392
+ } catch {
4393
+ await rm(path2, { force: true }).catch(() => {
4394
+ });
4395
+ return null;
4396
+ }
4397
+ } catch {
4398
+ return null;
4399
+ }
4400
+ }
4401
+ function sendDaemonRequest(port, request) {
4402
+ return new Promise((resolve2, reject) => {
4403
+ const socket = createConnection({ port });
4404
+ let buffer = "";
4405
+ socket.on("connect", () => {
4406
+ socket.write(JSON.stringify(request) + "\n");
4407
+ });
4408
+ socket.on("data", (data) => {
4409
+ buffer += data.toString();
4410
+ });
4411
+ socket.on("end", () => {
4412
+ try {
4413
+ const parsed = JSON.parse(buffer);
4414
+ if (parsed.error) {
4415
+ reject(new Error(parsed.error.message));
4416
+ } else {
4417
+ resolve2(parsed.result);
4418
+ }
4419
+ } catch (err) {
4420
+ reject(new Error(`Failed to parse daemon response: ${err}`));
4421
+ }
4422
+ });
4423
+ socket.on("error", (err) => {
4424
+ reject(err);
4425
+ });
4426
+ });
4427
+ }
4428
+ async function handleHeadlessSession(sessionName, targetCommand, operation, opts, subcommandUsage) {
4429
+ let session = await getSession(sessionName);
4430
+ if (!session) {
4431
+ if (!activeTargetCommand) {
4432
+ process.stderr.write(`Error: Session "${sessionName}" is not running.
4433
+ `);
4434
+ process.stderr.write(`Please provide a target command after '--' to start it.
4435
+
4436
+ `);
4437
+ process.stderr.write(`Usage: ${subcommandUsage}
4438
+ `);
4439
+ process.exit(2);
4440
+ }
4441
+ const target = activeTargetCommand;
4442
+ const binPath = resolve(import.meta.dirname, "./index.js");
4443
+ const daemonProcess = spawn("node", [binPath, "daemon", sessionName, ...target], {
4444
+ detached: true,
4445
+ stdio: "ignore"
4446
+ });
4447
+ daemonProcess.unref();
4448
+ let attempts = 0;
4449
+ while (attempts < 50) {
4450
+ session = await getSession(sessionName);
4451
+ if (session) break;
4452
+ await new Promise((resolve2) => setTimeout(resolve2, 100));
4453
+ attempts++;
4454
+ }
4455
+ if (!session) {
4456
+ process.stderr.write(
4457
+ `Error: Failed to spawn background daemon for session "${sessionName}".
4458
+ `
4459
+ );
4460
+ process.exit(1);
4461
+ }
4462
+ }
4463
+ try {
4464
+ const response = await sendDaemonRequest(session.port, {
4465
+ jsonrpc: "2.0",
4466
+ method: "execute",
4467
+ params: { operation, opts },
4468
+ id: 1
4469
+ });
4470
+ process.stdout.write(`${JSON.stringify(response.result, null, 2)}
4471
+ `);
4472
+ process.exit(response.hasError ? 1 : 0);
4473
+ } catch (err) {
4474
+ process.stderr.write(`Error communicating with session daemon: ${err.message}
4475
+ `);
4476
+ process.exit(1);
4477
+ }
4478
+ }
4479
+ function parseHeadlessOpts(opts) {
4480
+ return {
4481
+ outDir: opts.outDir,
4482
+ timeoutMs: opts.timeout ? Number.parseInt(opts.timeout, 10) : void 0,
4483
+ raw: opts.raw,
4484
+ showStderr: opts.showStderr,
4485
+ mediaThresholdKb: opts.mediaThreshold ? Number.parseInt(opts.mediaThreshold, 10) : void 0
4486
+ };
4487
+ }
4488
+ var activeTargetCommand;
4489
+ var argvToParse = process.argv;
4490
+ var dashDashIndex = process.argv.indexOf("--");
4491
+ if (dashDashIndex !== -1) {
4492
+ activeTargetCommand = process.argv.slice(dashDashIndex + 1);
4493
+ argvToParse = [...process.argv.slice(0, dashDashIndex)];
4494
+ }
4495
+ program.enablePositionalOptions();
4496
+ program.command("call").argument("<tool>", "Tool name to call").argument("[json_args]", "JSON arguments for the tool").argument("[target_command...]", "Target server command (after --)").description("Call a tool on a target MCP server and print the result as JSON").option("-o, --out-dir <path>", "Output directory for saved media").option("-t, --timeout <ms>", "Timeout in milliseconds (default: 30000)").option(
4497
+ "-m, --media-threshold <kb>",
4498
+ "Media size threshold in KB to save to disk (0 to always save, -1 to keep inline)"
4499
+ ).option("--raw", "Print the full result object including metadata").option("--show-stderr", "Stream target server stderr to process stderr").option("--session <name>", "Persistent session name").allowUnknownOption().action(
4500
+ async (tool, jsonArgs, targetCommand, opts) => {
4501
+ const operation = { type: "call", tool, args: jsonArgs };
4502
+ const parsedOpts = parseHeadlessOpts(opts);
4503
+ if (opts.session) {
4504
+ await handleHeadlessSession(
4505
+ opts.session,
4506
+ targetCommand,
4507
+ operation,
4508
+ parsedOpts,
4509
+ "run-mcp call <tool> [json_args] -- <server_command...>"
4510
+ );
4511
+ } else {
4512
+ const target = requireTargetCommand(
4513
+ activeTargetCommand ?? targetCommand,
4514
+ "run-mcp call <tool> [json_args] -- <server_command...>"
4515
+ );
4516
+ await runHeadless(target, operation, parsedOpts);
4517
+ }
4518
+ }
4519
+ );
4520
+ program.command("list-tools").argument("[target_command...]", "Target server command (after --)").description("List all tools on a target MCP server as JSON").option("--show-stderr", "Stream target server stderr to process stderr").option("--session <name>", "Persistent session name").allowUnknownOption().action(async (targetCommand, opts) => {
4521
+ const operation = { type: "list-tools" };
4522
+ const parsedOpts = parseHeadlessOpts(opts);
4523
+ if (opts.session) {
4524
+ await handleHeadlessSession(
4525
+ opts.session,
4526
+ targetCommand,
4527
+ operation,
4528
+ parsedOpts,
4529
+ "run-mcp list-tools -- <server_command...>"
4530
+ );
4531
+ } else {
4532
+ const target = requireTargetCommand(
4533
+ activeTargetCommand ?? targetCommand,
4534
+ "run-mcp list-tools -- <server_command...>"
4535
+ );
4536
+ await runHeadless(target, operation, parsedOpts);
4537
+ }
4538
+ });
4539
+ program.command("list-resources").argument("[target_command...]", "Target server command (after --)").description("List all resources on a target MCP server as JSON").option("--show-stderr", "Stream target server stderr to process stderr").option("--session <name>", "Persistent session name").allowUnknownOption().action(async (targetCommand, opts) => {
4540
+ const operation = { type: "list-resources" };
4541
+ const parsedOpts = parseHeadlessOpts(opts);
4542
+ if (opts.session) {
4543
+ await handleHeadlessSession(
4544
+ opts.session,
4545
+ targetCommand,
4546
+ operation,
4547
+ parsedOpts,
4548
+ "run-mcp list-resources -- <server_command...>"
4549
+ );
4550
+ } else {
4551
+ const target = requireTargetCommand(
4552
+ activeTargetCommand ?? targetCommand,
4553
+ "run-mcp list-resources -- <server_command...>"
4554
+ );
4555
+ await runHeadless(target, operation, parsedOpts);
4556
+ }
4557
+ });
4558
+ program.command("list-prompts").argument("[target_command...]", "Target server command (after --)").description("List all prompts on a target MCP server as JSON").option("--show-stderr", "Stream target server stderr to process stderr").option("--session <name>", "Persistent session name").allowUnknownOption().action(async (targetCommand, opts) => {
4559
+ const operation = { type: "list-prompts" };
4560
+ const parsedOpts = parseHeadlessOpts(opts);
4561
+ if (opts.session) {
4562
+ await handleHeadlessSession(
4563
+ opts.session,
4564
+ targetCommand,
4565
+ operation,
4566
+ parsedOpts,
4567
+ "run-mcp list-prompts -- <server_command...>"
4568
+ );
4569
+ } else {
4570
+ const target = requireTargetCommand(
4571
+ activeTargetCommand ?? targetCommand,
4572
+ "run-mcp list-prompts -- <server_command...>"
4573
+ );
4574
+ await runHeadless(target, operation, parsedOpts);
4575
+ }
4576
+ });
4577
+ program.command("read").argument("<uri>", "Resource URI to read").argument("[target_command...]", "Target server command (after --)").description("Read a resource by URI from a target MCP server").option(
4578
+ "-m, --media-threshold <kb>",
4579
+ "Media size threshold in KB to save to disk (0 to always save, -1 to keep inline)"
4580
+ ).option("--show-stderr", "Stream target server stderr to process stderr").option("--session <name>", "Persistent session name").allowUnknownOption().action(async (uri, targetCommand, opts) => {
4581
+ const operation = { type: "read", uri };
4582
+ const parsedOpts = parseHeadlessOpts(opts);
4583
+ if (opts.session) {
4584
+ await handleHeadlessSession(
4585
+ opts.session,
4586
+ targetCommand,
4587
+ operation,
4588
+ parsedOpts,
4589
+ "run-mcp read <uri> -- <server_command...>"
4590
+ );
4591
+ } else {
4592
+ const target = requireTargetCommand(
4593
+ activeTargetCommand ?? targetCommand,
4594
+ "run-mcp read <uri> -- <server_command...>"
4595
+ );
4596
+ await runHeadless(target, operation, parsedOpts);
4597
+ }
4598
+ });
4599
+ program.command("describe").argument("<tool>", "Tool name to describe").argument("[target_command...]", "Target server command (after --)").description("Print a tool's full schema as JSON").option("--show-stderr", "Stream target server stderr to process stderr").option("--session <name>", "Persistent session name").allowUnknownOption().action(async (tool, targetCommand, opts) => {
4600
+ const operation = { type: "describe", tool };
4601
+ const parsedOpts = parseHeadlessOpts(opts);
4602
+ if (opts.session) {
4603
+ await handleHeadlessSession(
4604
+ opts.session,
4605
+ targetCommand,
4606
+ operation,
4607
+ parsedOpts,
4608
+ "run-mcp describe <tool> -- <server_command...>"
4609
+ );
4610
+ } else {
4611
+ const target = requireTargetCommand(
4612
+ activeTargetCommand ?? targetCommand,
4613
+ "run-mcp describe <tool> -- <server_command...>"
4614
+ );
4615
+ await runHeadless(target, operation, parsedOpts);
4616
+ }
4617
+ });
4618
+ program.command("get-prompt").argument("<name>", "Prompt name").argument("[json_args]", "JSON arguments for the prompt").argument("[target_command...]", "Target server command (after --)").description("Get a prompt with optional arguments from a target MCP server").option(
4619
+ "-m, --media-threshold <kb>",
4620
+ "Media size threshold in KB to save to disk (0 to always save, -1 to keep inline)"
4621
+ ).option("--show-stderr", "Stream target server stderr to process stderr").option("--session <name>", "Persistent session name").allowUnknownOption().action(
4622
+ async (name, jsonArgs, targetCommand, opts) => {
4623
+ const operation = { type: "get-prompt", name, args: jsonArgs };
4624
+ const parsedOpts = parseHeadlessOpts(opts);
4625
+ if (opts.session) {
4626
+ await handleHeadlessSession(
4627
+ opts.session,
4628
+ targetCommand,
4629
+ operation,
4630
+ parsedOpts,
4631
+ "run-mcp get-prompt <name> [json_args] -- <server_command...>"
4632
+ );
4633
+ } else {
4634
+ const target = requireTargetCommand(
4635
+ activeTargetCommand ?? targetCommand,
4636
+ "run-mcp get-prompt <name> [json_args] -- <server_command...>"
4637
+ );
4638
+ await runHeadless(target, operation, parsedOpts);
4639
+ }
4640
+ }
4641
+ );
4642
+ program.command("daemon").argument("<session_name>", "Session name").argument("[target_command...]", "Target server command").description("Start run-mcp in background session daemon mode").allowUnknownOption().action(async (sessionName, targetCommand) => {
4643
+ const targetCmd = activeTargetCommand ?? targetCommand;
4644
+ if (!targetCmd || targetCmd.length === 0) {
4645
+ process.stderr.write("Error: No target command provided for daemon.\n");
4646
+ process.exit(1);
4647
+ }
4648
+ const server = createServer();
4649
+ server.listen(0, "127.0.0.1", async () => {
4650
+ const addr = server.address();
4651
+ const port = addr.port;
4652
+ const target = new TargetManager(targetCmd[0], targetCmd.slice(1));
4653
+ const interceptor = new ResponseInterceptor();
4654
+ try {
4655
+ await target.connect();
4656
+ } catch (err) {
4657
+ process.stderr.write(`Daemon failed to connect to target: ${err.message}
4658
+ `);
4659
+ process.exit(1);
4660
+ }
4661
+ await mkdir2(SESSION_DIR, { recursive: true });
4662
+ await writeFile2(
4663
+ getSessionPath(sessionName),
4664
+ JSON.stringify({ port, pid: process.pid }),
4665
+ "utf8"
4666
+ );
4667
+ server.on("connection", (socket) => {
4668
+ let buffer = "";
4669
+ socket.on("data", async (data) => {
4670
+ buffer += data.toString();
4671
+ const lines = buffer.split("\n");
4672
+ buffer = lines.pop() ?? "";
4673
+ for (const line of lines) {
4674
+ const trimmed = line.trim();
4675
+ if (!trimmed) continue;
4676
+ try {
4677
+ const req = JSON.parse(trimmed);
4678
+ if (req.method === "execute") {
4679
+ const { operation, opts } = req.params;
4680
+ const { result, hasError } = await executeOperation(
4681
+ target,
4682
+ interceptor,
4683
+ operation,
4684
+ opts
4685
+ );
4686
+ socket.write(
4687
+ JSON.stringify({ jsonrpc: "2.0", result: { result, hasError }, id: req.id }) + "\n"
4688
+ );
4689
+ socket.end();
4690
+ } else if (req.method === "close") {
4691
+ socket.write(
4692
+ JSON.stringify({ jsonrpc: "2.0", result: { ok: true }, id: req.id }) + "\n"
4693
+ );
4694
+ socket.end();
4695
+ await target.close().catch(() => {
4696
+ });
4697
+ await rm(getSessionPath(sessionName), { force: true }).catch(() => {
4698
+ });
4699
+ process.exit(0);
4700
+ }
4701
+ } catch (err) {
4702
+ socket.write(
4703
+ JSON.stringify({ jsonrpc: "2.0", error: { message: err.message }, id: 1 }) + "\n"
4704
+ );
4705
+ socket.end();
4706
+ }
4707
+ }
4708
+ });
4709
+ });
4710
+ });
4711
+ });
4712
+ program.command("close-session").argument("<session_name>", "Session name").description("Stop a running session daemon").action(async (sessionName) => {
4713
+ const session = await getSession(sessionName);
4714
+ if (!session) {
4715
+ console.log(`Session "${sessionName}" is not running.`);
4716
+ return;
4717
+ }
4718
+ try {
4719
+ await sendDaemonRequest(session.port, {
4720
+ jsonrpc: "2.0",
4721
+ method: "close",
4722
+ params: {},
4723
+ id: 1
4724
+ });
4725
+ console.log(`Session "${sessionName}" stopped successfully.`);
4726
+ } catch {
4727
+ try {
4728
+ process.kill(session.pid, "SIGTERM");
4729
+ console.log(`Session "${sessionName}" stopped (SIGTERM).`);
4730
+ } catch {
4731
+ console.log(`Failed to stop session "${sessionName}".`);
4732
+ }
4733
+ }
4734
+ });
4735
+ program.name("run-mcp").description("A smart interactive REPL and live test harness for MCP servers").version("1.6.1").passThroughOptions().allowUnknownOption().argument(
3218
4736
  "[target_command...]",
3219
4737
  "Command to spawn the target MCP server (starts REPL if provided, Agent server otherwise)"
3220
4738
  ).option("-o, --out-dir <path>", "Directory to save intercepted images and audio").option(
@@ -3223,16 +4741,28 @@ program.name("run-mcp").description("A smart interactive REPL and live test harn
3223
4741
  ).option(
3224
4742
  "--max-text <chars>",
3225
4743
  "Max text response length before truncation (default: 50000) (Agent Mode only)"
4744
+ ).option(
4745
+ "-m, --media-threshold <kb>",
4746
+ "Media size threshold in KB to save to disk (0 to always save, -1 to keep inline)"
3226
4747
  ).option("--mcp", "Force start Agent Server mode even if run interactively without arguments").option("-s, --script <file>", "Read commands from a file instead of stdin (REPL Mode only)").addHelpText(
3227
4748
  "after",
3228
4749
  `
3229
4750
  Examples:
3230
4751
  $ run-mcp # Test harness (agent mode)
3231
- $ run-mcp node my-server.js # Interactive testing (human REPL mode)
3232
- $ run-mcp node my-server.js -s test.txt # Run a script in REPL mode
3233
- $ run-mcp npx -y some-mcp-server # Test an npx server
4752
+ $ run-mcp -- node my-server.js # Interactive testing (human REPL mode)
4753
+ $ run-mcp -s test.txt -- node my-server.js # Run a script in REPL mode
4754
+ $ run-mcp -- npx -y some-mcp-server # Test an npx server
3234
4755
  $ run-mcp --out-dir ./test-output # Agent mode with options
3235
- $ run-mcp --out-dir ./screenshots node srv.js # REPL mode with options
4756
+ $ run-mcp --out-dir ./screenshots -- node srv.js # REPL mode with options
4757
+
4758
+ Headless Commands (pipe-friendly, JSON output):
4759
+ $ run-mcp call echo '{"text":"hi"}' -- node my-server.js
4760
+ $ run-mcp list-tools -- node my-server.js | jq '.[].name'
4761
+ $ run-mcp list-resources -- node my-server.js
4762
+ $ run-mcp list-prompts -- node my-server.js
4763
+ $ run-mcp read docs://readme -- node my-server.js
4764
+ $ run-mcp describe echo -- node my-server.js
4765
+ $ run-mcp get-prompt greeting '{"name":"Ada"}' -- node my-server.js
3236
4766
 
3237
4767
  Agent Mode Configuration (mcp.json):
3238
4768
  {
@@ -3279,14 +4809,26 @@ REPL Mode Commands (once connected):
3279
4809
  Shortcuts: tl td tc ts rl rr rt rs ru pl pg (see help for details)`
3280
4810
  ).action(
3281
4811
  async (targetCommand, opts) => {
3282
- if (targetCommand && targetCommand.length > 0) {
3283
- await startRepl(targetCommand, { script: opts.script, outDir: opts.outDir });
4812
+ if (targetCommand && targetCommand.length > 0 && !activeTargetCommand) {
4813
+ process.stderr.write(
4814
+ "Error: Target server command must be separated by '--'.\nThis avoids argument parsing ambiguity.\n\nExample:\n run-mcp -- node my-server.js\n run-mcp -s script.txt -- node my-server.js\n"
4815
+ );
4816
+ process.exit(1);
4817
+ }
4818
+ const target = activeTargetCommand ?? [];
4819
+ if (target && target.length > 0) {
4820
+ await startRepl(target, {
4821
+ script: opts.script,
4822
+ outDir: opts.outDir,
4823
+ mediaThresholdKb: opts.mediaThreshold ? Number.parseInt(opts.mediaThreshold, 10) : void 0
4824
+ });
3284
4825
  } else {
3285
4826
  if (opts.mcp || !process.stdin.isTTY) {
3286
4827
  await startServer({
3287
4828
  outDir: opts.outDir,
3288
4829
  timeoutMs: opts.timeout ? Number.parseInt(opts.timeout, 10) : void 0,
3289
- maxTextLength: opts.maxText ? Number.parseInt(opts.maxText, 10) : void 0
4830
+ maxTextLength: opts.maxText ? Number.parseInt(opts.maxText, 10) : void 0,
4831
+ mediaThresholdKb: opts.mediaThreshold ? Number.parseInt(opts.mediaThreshold, 10) : void 0
3290
4832
  });
3291
4833
  } else {
3292
4834
  const selected = await pickDiscoveredServer();
@@ -3299,10 +4841,11 @@ Shortcuts: tl td tc ts rl rr rt rs ru pl pg (see help for details)`
3299
4841
  }
3300
4842
  await startRepl([selected.config.command, ...selected.config.args || []], {
3301
4843
  script: opts.script,
3302
- outDir: opts.outDir
4844
+ outDir: opts.outDir,
4845
+ mediaThresholdKb: opts.mediaThreshold ? Number.parseInt(opts.mediaThreshold, 10) : void 0
3303
4846
  });
3304
4847
  }
3305
4848
  }
3306
4849
  }
3307
4850
  );
3308
- program.parse();
4851
+ program.parse(argvToParse);