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.
- package/README.md +121 -58
- package/dist/index.js +2223 -680
- 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/
|
|
12
|
+
// src/config-scanner.ts
|
|
13
|
+
import { existsSync } from "fs";
|
|
7
14
|
import { readFile } from "fs/promises";
|
|
8
|
-
import {
|
|
9
|
-
import
|
|
10
|
-
import
|
|
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
|
-
|
|
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,
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
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 (
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
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
|
-
|
|
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
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
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
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
const
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
this._stderrLines
|
|
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.
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
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/
|
|
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/
|
|
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
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
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
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
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
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
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("
|
|
532
|
-
this.
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
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 =
|
|
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 `${
|
|
1126
|
-
return `${
|
|
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(`${
|
|
1131
|
-
if (resourceCount > 0) parts.push(`${
|
|
1132
|
-
if (promptCount > 0) parts.push(`${
|
|
1133
|
-
const
|
|
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 `${
|
|
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(
|
|
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(
|
|
1815
|
+
console.log(pc2.cyan(` \u251C${"\u2500".repeat(BOX_WIDTH)}\u2524`));
|
|
1146
1816
|
console.log(padLine(" Quick start:"));
|
|
1147
|
-
console.log(padLine(` ${
|
|
1148
|
-
console.log(padLine(` ${
|
|
1149
|
-
console.log(padLine(` ${
|
|
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(
|
|
1152
|
-
console.log(
|
|
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({
|
|
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(
|
|
1838
|
+
console.error(pc2.dim(`[server] ${line}`));
|
|
1166
1839
|
}
|
|
1167
1840
|
});
|
|
1168
|
-
console.log(
|
|
1169
|
-
console.log(
|
|
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(
|
|
1176
|
-
console.error(
|
|
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(
|
|
1179
|
-
console.error(
|
|
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(
|
|
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
|
-
|
|
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(
|
|
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(
|
|
1875
|
+
console.error(pc2.red(`\u2717 ${message}`));
|
|
1203
1876
|
if (reason === "max_retries") {
|
|
1204
1877
|
console.log(
|
|
1205
|
-
|
|
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(
|
|
1888
|
+
console.log(pc2.dim(`
|
|
1216
1889
|
[${lvl}] ${text}`));
|
|
1217
1890
|
} else if (method === "notifications/tools/list_changed") {
|
|
1218
|
-
console.log(
|
|
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(
|
|
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(
|
|
1900
|
+
console.log(pc2.yellow(`
|
|
1228
1901
|
\u27F3 Resource updated: ${uri}`));
|
|
1229
1902
|
} else if (method === "notifications/prompts/list_changed") {
|
|
1230
|
-
console.log(
|
|
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(
|
|
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" ?
|
|
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(
|
|
1914
|
+
console.log(pc2.magenta(` \u2551 ${role}: ${text}`));
|
|
1242
1915
|
}
|
|
1243
|
-
console.log(
|
|
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, ` ${
|
|
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(
|
|
1276
|
-
console.log(
|
|
1277
|
-
console.log(
|
|
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
|
-
` ${
|
|
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
|
|
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(` ${
|
|
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(
|
|
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
|
-
|
|
1351
|
-
if (!trimmed || trimmed.startsWith("#"))
|
|
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(
|
|
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
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
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 ${
|
|
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(
|
|
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(
|
|
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(
|
|
1569
|
-
console.log(
|
|
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 :
|
|
1572
|
-
console.log(` ${
|
|
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(
|
|
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(
|
|
2309
|
+
console.log(pc2.bold(" Groups:"));
|
|
1581
2310
|
for (const [label, members] of groups) {
|
|
1582
|
-
console.log(` ${
|
|
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(
|
|
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(
|
|
2323
|
+
console.log(pc2.dim(`
|
|
1595
2324
|
Available tools: ${preview.join(", ")}${more}`));
|
|
1596
|
-
console.log(
|
|
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(
|
|
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(
|
|
2338
|
+
console.log(pc2.yellow(`Did you mean ${pc2.bold(suggestion)}?`));
|
|
1610
2339
|
} else {
|
|
1611
|
-
console.log(
|
|
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
|
-
|
|
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(
|
|
2375
|
+
console.log(pc2.dim(`
|
|
1636
2376
|
Available tools: ${preview.join(", ")}${more}`));
|
|
1637
2377
|
console.log(
|
|
1638
|
-
|
|
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
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
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
|
-
|
|
1656
|
-
|
|
1657
|
-
const
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
const
|
|
1663
|
-
|
|
1664
|
-
console.log(`
|
|
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(
|
|
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
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
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
|
-
|
|
1718
|
-
|
|
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(
|
|
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(
|
|
2509
|
+
console.log(pc2.yellow(`Did you mean ${pc2.bold(suggestion)}?`));
|
|
1732
2510
|
} else {
|
|
1733
|
-
console.log(
|
|
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(
|
|
2521
|
+
console.log(pc2.yellow(` Tool "${toolName}" requires arguments.`));
|
|
1744
2522
|
const scaffolded = scaffoldArgs(schema);
|
|
1745
|
-
console.log(
|
|
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(` ${
|
|
2532
|
+
console.log(` ${pc2.bold(tool.name)}${tool.description ? pc2.dim(` \u2014 ${tool.description}`) : ""}`);
|
|
1755
2533
|
if (previousArgs) {
|
|
1756
|
-
console.log(
|
|
1757
|
-
console.log(
|
|
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} ${
|
|
1776
|
-
const answerStr = await
|
|
1777
|
-
{
|
|
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} ${
|
|
1806
|
-
const answerStr = await
|
|
1807
|
-
{
|
|
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(
|
|
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}": ${
|
|
2634
|
+
parts.push(`"${name}": ${pc2.green(JSON.stringify(filled[name]))}`);
|
|
1836
2635
|
} else if (name === currentProp) {
|
|
1837
|
-
parts.push(`"${name}": ${
|
|
2636
|
+
parts.push(`"${name}": ${pc2.yellow("\u2592")}`);
|
|
1838
2637
|
} else {
|
|
1839
|
-
parts.push(`"${name}": ${
|
|
2638
|
+
parts.push(`"${name}": ${pc2.dim("\u2592")}`);
|
|
1840
2639
|
}
|
|
1841
2640
|
}
|
|
1842
|
-
console.log(
|
|
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((
|
|
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
|
-
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
2708
|
+
console.log(pc2.yellow(`Did you mean ${pc2.bold(suggestion)}?`));
|
|
1910
2709
|
} else {
|
|
1911
|
-
console.log(
|
|
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(
|
|
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(
|
|
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(
|
|
1928
|
-
console.log(
|
|
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 ??
|
|
1932
|
-
console.log(` ${
|
|
2730
|
+
const name = r.name ?? pc2.dim("(unnamed)");
|
|
2731
|
+
console.log(` ${pc2.green(uri.padEnd(uriWidth))} ${name}`);
|
|
1933
2732
|
}
|
|
1934
|
-
console.log(
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
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(
|
|
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(
|
|
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(
|
|
1973
|
-
console.log(
|
|
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 ??
|
|
1977
|
-
console.log(` ${
|
|
2805
|
+
const name = t.name ?? pc2.dim("(unnamed)");
|
|
2806
|
+
console.log(` ${pc2.green(uriTemplate.padEnd(uriWidth))} ${name}`);
|
|
1978
2807
|
}
|
|
1979
|
-
console.log(
|
|
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(
|
|
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(
|
|
1990
|
-
console.log(
|
|
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 :
|
|
1993
|
-
console.log(` ${
|
|
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(
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
2011
|
-
|
|
2012
|
-
|
|
2013
|
-
|
|
2014
|
-
|
|
2015
|
-
|
|
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" ?
|
|
2892
|
+
const role = msg.role === "user" ? pc2.blue("user") : pc2.magenta("assistant");
|
|
2021
2893
|
const text = msg.content.text ?? JSON.stringify(msg.content);
|
|
2022
|
-
|
|
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(
|
|
2912
|
+
console.log();
|
|
2913
|
+
console.log(pc2.green(` \u2713 Pong! Round-trip: ${elapsed}ms`));
|
|
2914
|
+
console.log();
|
|
2029
2915
|
} catch (err) {
|
|
2030
|
-
console.
|
|
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(
|
|
2037
|
-
console.log(
|
|
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(
|
|
2044
|
-
console.log(
|
|
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(
|
|
2937
|
+
console.log(pc2.green(` \u2713 Logging level set to: ${level}`));
|
|
2050
2938
|
} catch (err) {
|
|
2051
|
-
console.error(
|
|
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(
|
|
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(
|
|
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(
|
|
2956
|
+
console.log(pc2.dim(" No request history yet."));
|
|
2069
2957
|
return;
|
|
2070
2958
|
}
|
|
2071
|
-
console.log(
|
|
2072
|
-
console.log(
|
|
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 ?
|
|
2964
|
+
const hasError = rec.error ? pc2.red(" \u2717") : "";
|
|
2077
2965
|
console.log(
|
|
2078
|
-
` ${
|
|
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
|
-
|
|
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(
|
|
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(
|
|
2099
|
-
console.log(
|
|
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(
|
|
2103
|
-
console.log(
|
|
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 ? ` ${
|
|
2107
|
-
console.log(` ${
|
|
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
|
-
|
|
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(
|
|
3007
|
+
console.log(pc2.yellow(" Usage: resources/subscribe <uri>"));
|
|
2120
3008
|
if (cachedResourceUris.length > 0) {
|
|
2121
|
-
console.log(
|
|
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(
|
|
2128
|
-
console.log(
|
|
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(
|
|
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(
|
|
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(
|
|
3029
|
+
console.log(pc2.green(` \u2713 Unsubscribed from: ${uri}`));
|
|
2142
3030
|
} catch (err) {
|
|
2143
|
-
console.error(
|
|
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(
|
|
2150
|
-
console.log(
|
|
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(
|
|
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(` ${
|
|
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(
|
|
2166
|
-
console.log(
|
|
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(
|
|
2171
|
-
console.log(
|
|
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(
|
|
3064
|
+
console.log(pc2.yellow(" Usage: roots/remove <uri>"));
|
|
2177
3065
|
const roots = target.getRoots();
|
|
2178
3066
|
if (roots.length > 0) {
|
|
2179
|
-
console.log(
|
|
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(
|
|
3073
|
+
console.log(pc2.green(` \u2713 Root removed: ${uri}`));
|
|
2186
3074
|
} else {
|
|
2187
|
-
console.log(
|
|
3075
|
+
console.log(pc2.yellow(` Root not found: ${uri}`));
|
|
2188
3076
|
}
|
|
2189
3077
|
}
|
|
2190
3078
|
async function cmdReconnect(target) {
|
|
2191
|
-
console.log(
|
|
3079
|
+
console.log(pc2.cyan("\u27F3 Disconnecting..."));
|
|
2192
3080
|
await target.close();
|
|
2193
|
-
await new Promise((
|
|
2194
|
-
console.log(
|
|
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(
|
|
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(
|
|
3088
|
+
console.log(pc2.green(`\u2713 Reconnected (PID: ${s.pid})`));
|
|
2201
3089
|
const { tools } = await target.listTools();
|
|
2202
|
-
console.log(
|
|
3090
|
+
console.log(pc2.cyan(` ${tools.length} tool(s) available.
|
|
2203
3091
|
`));
|
|
2204
3092
|
await refreshCaches(target);
|
|
2205
3093
|
} catch (err) {
|
|
2206
|
-
console.error(
|
|
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(
|
|
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(
|
|
2222
|
-
console.log(
|
|
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
|
-
` ${
|
|
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
|
-
|
|
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(
|
|
2257
|
-
console.log(` ${
|
|
2258
|
-
console.log(` ${
|
|
2259
|
-
console.log(` ${
|
|
2260
|
-
console.log(` ${
|
|
2261
|
-
console.log(` ${
|
|
2262
|
-
console.log(` ${
|
|
2263
|
-
console.log(` ${
|
|
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
|
-
${
|
|
3199
|
+
${tH}
|
|
2269
3200
|
|
|
2270
|
-
${
|
|
2271
|
-
${
|
|
2272
|
-
${
|
|
2273
|
-
Options: ${
|
|
2274
|
-
${
|
|
2275
|
-
${
|
|
2276
|
-
${
|
|
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
|
-
${
|
|
3209
|
+
${rH}
|
|
2279
3210
|
|
|
2280
|
-
${
|
|
2281
|
-
${
|
|
2282
|
-
${
|
|
2283
|
-
${
|
|
2284
|
-
${
|
|
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
|
-
${
|
|
3217
|
+
${pH}
|
|
2287
3218
|
|
|
2288
|
-
${
|
|
2289
|
-
${
|
|
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
|
-
${
|
|
3222
|
+
${pc2.bold("Protocol Commands:")}
|
|
2292
3223
|
|
|
2293
|
-
${
|
|
2294
|
-
${
|
|
2295
|
-
${
|
|
2296
|
-
${
|
|
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
|
-
${
|
|
3229
|
+
${pc2.bold("Roots Management:")}
|
|
2299
3230
|
|
|
2300
|
-
${
|
|
2301
|
-
${
|
|
2302
|
-
${
|
|
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
|
-
${
|
|
3235
|
+
${pc2.bold("Session Commands:")}
|
|
2305
3236
|
|
|
2306
|
-
${
|
|
2307
|
-
${
|
|
2308
|
-
${
|
|
2309
|
-
${
|
|
2310
|
-
${
|
|
2311
|
-
${
|
|
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
|
-
${
|
|
3244
|
+
${pc2.bold("Shortcuts:")}
|
|
2314
3245
|
|
|
2315
|
-
${
|
|
2316
|
-
${
|
|
2317
|
-
${
|
|
2318
|
-
${
|
|
2319
|
-
${
|
|
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
|
-
${
|
|
2322
|
-
${
|
|
2323
|
-
${
|
|
2324
|
-
${
|
|
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
|
|
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(
|
|
3267
|
+
console.log(pc2.green(` Cleared remembered args for ${pc2.bold(toolName)}.`));
|
|
2337
3268
|
} else {
|
|
2338
|
-
console.log(
|
|
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(
|
|
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(
|
|
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(
|
|
3347
|
+
console.log(pc2.bold("\n Server Capabilities\n"));
|
|
2392
3348
|
for (let i = 0; i < choices.length; i++) {
|
|
2393
3349
|
console.log(
|
|
2394
|
-
` ${
|
|
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
|
-
|
|
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 === "
|
|
2549
|
-
|
|
2550
|
-
|
|
2551
|
-
|
|
2552
|
-
|
|
2553
|
-
|
|
2554
|
-
|
|
2555
|
-
|
|
2556
|
-
|
|
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.
|
|
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
|
-
|
|
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 (
|
|
2696
|
-
for (const [key, value] of Object.entries(
|
|
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(
|
|
2701
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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: [
|
|
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
|
-
|
|
3041
|
-
"Command to spawn the server (e.g. 'node').
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
3122
|
-
|
|
3123
|
-
|
|
3124
|
-
|
|
3125
|
-
|
|
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)
|
|
3130
|
-
const
|
|
3131
|
-
|
|
3132
|
-
|
|
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
|
|
3139
|
-
|
|
3140
|
-
|
|
3141
|
-
|
|
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
|
-
|
|
3148
|
-
|
|
3149
|
-
|
|
3150
|
-
|
|
3151
|
-
|
|
3152
|
-
|
|
3153
|
-
|
|
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
|
-
|
|
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
|
|
3232
|
-
$ run-mcp node my-server.js
|
|
3233
|
-
$ run-mcp npx -y some-mcp-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
|
|
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
|
-
|
|
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);
|