run-mcp 1.5.1 → 1.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +23 -0
- package/dist/index.js +1215 -631
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -3,11 +3,136 @@
|
|
|
3
3
|
// src/index.ts
|
|
4
4
|
import { program } from "commander";
|
|
5
5
|
|
|
6
|
-
// src/
|
|
6
|
+
// src/config-scanner.ts
|
|
7
|
+
import { existsSync } from "fs";
|
|
7
8
|
import { readFile } from "fs/promises";
|
|
8
|
-
import {
|
|
9
|
-
import
|
|
10
|
-
import
|
|
9
|
+
import { homedir } from "os";
|
|
10
|
+
import path from "path";
|
|
11
|
+
import process2 from "process";
|
|
12
|
+
import { input, select } from "@inquirer/prompts";
|
|
13
|
+
function getConfigPaths() {
|
|
14
|
+
const home = homedir();
|
|
15
|
+
const cwd = process2.cwd();
|
|
16
|
+
const isWin = process2.platform === "win32";
|
|
17
|
+
const isMac = process2.platform === "darwin";
|
|
18
|
+
const appData = process2.env.APPDATA || path.join(home, "AppData", "Roaming");
|
|
19
|
+
const localAppData = process2.env.LOCALAPPDATA || path.join(home, "AppData", "Local");
|
|
20
|
+
let claudeDesktopGlob;
|
|
21
|
+
if (isWin) {
|
|
22
|
+
claudeDesktopGlob = path.join(appData, "Claude", "claude_desktop_config.json");
|
|
23
|
+
} else if (isMac) {
|
|
24
|
+
claudeDesktopGlob = path.join(
|
|
25
|
+
home,
|
|
26
|
+
"Library",
|
|
27
|
+
"Application Support",
|
|
28
|
+
"Claude",
|
|
29
|
+
"claude_desktop_config.json"
|
|
30
|
+
);
|
|
31
|
+
} else {
|
|
32
|
+
claudeDesktopGlob = path.join(home, ".config", "Claude", "claude_desktop_config.json");
|
|
33
|
+
}
|
|
34
|
+
return [
|
|
35
|
+
{ source: "Cursor (Global)", file: path.join(home, ".cursor", "mcp.json") },
|
|
36
|
+
{ source: "Cursor (Project)", file: path.join(cwd, ".cursor", "mcp.json") },
|
|
37
|
+
{ source: "Windsurf", file: path.join(home, ".codeium", "windsurf", "mcp_config.json") },
|
|
38
|
+
{ source: "Claude Desktop", file: claudeDesktopGlob },
|
|
39
|
+
{ source: "Cline", file: path.join(home, "Documents", "Cline", "MCP", "mcp.json") },
|
|
40
|
+
{ source: "VS Code (Project)", file: path.join(cwd, ".vscode", "mcp.json") },
|
|
41
|
+
{
|
|
42
|
+
source: "VS Code (Global)",
|
|
43
|
+
file: path.join(
|
|
44
|
+
isMac ? path.join(home, "Library", "Application Support") : isWin ? appData : path.join(home, ".config"),
|
|
45
|
+
"Code",
|
|
46
|
+
"User",
|
|
47
|
+
"settings.json"
|
|
48
|
+
)
|
|
49
|
+
},
|
|
50
|
+
{ source: "Copilot CLI (Global)", file: path.join(home, ".copilot", "mcp-config.json") },
|
|
51
|
+
{ source: "Gemini CLI (Global)", file: path.join(home, ".gemini", "settings.json") },
|
|
52
|
+
{ source: "Gemini CLI (Project)", file: path.join(cwd, ".gemini", "settings.json") },
|
|
53
|
+
{ source: "Claude Code (Global)", file: path.join(home, ".claude.json") },
|
|
54
|
+
{ source: "Claude Code (Project)", file: path.join(cwd, ".mcp.json") },
|
|
55
|
+
{ source: "Antigravity", file: path.join(home, ".gemini", "antigravity", "mcp_config.json") }
|
|
56
|
+
];
|
|
57
|
+
}
|
|
58
|
+
async function discoverServers() {
|
|
59
|
+
const servers = [];
|
|
60
|
+
const paths = getConfigPaths();
|
|
61
|
+
for (const { source, file } of paths) {
|
|
62
|
+
if (!existsSync(file)) continue;
|
|
63
|
+
try {
|
|
64
|
+
const content = await readFile(file, "utf8");
|
|
65
|
+
const json = JSON.parse(content);
|
|
66
|
+
let mcpServers;
|
|
67
|
+
if (json.mcpServers && typeof json.mcpServers === "object") {
|
|
68
|
+
mcpServers = json.mcpServers;
|
|
69
|
+
} else if (json.mcp?.servers && typeof json.mcp.servers === "object") {
|
|
70
|
+
mcpServers = json.mcp.servers;
|
|
71
|
+
} else if (json.servers && typeof json.servers === "object") {
|
|
72
|
+
mcpServers = json.servers;
|
|
73
|
+
}
|
|
74
|
+
if (mcpServers) {
|
|
75
|
+
for (const [name, config] of Object.entries(mcpServers)) {
|
|
76
|
+
if (config.command) {
|
|
77
|
+
servers.push({ name, config, source });
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
} catch {
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
return servers;
|
|
85
|
+
}
|
|
86
|
+
async function pickDiscoveredServer() {
|
|
87
|
+
const servers = await discoverServers();
|
|
88
|
+
if (servers.length === 0) {
|
|
89
|
+
return null;
|
|
90
|
+
}
|
|
91
|
+
const uniqueServers = /* @__PURE__ */ new Map();
|
|
92
|
+
for (const s of servers) {
|
|
93
|
+
const key = `${s.name}::${s.config.command}::${(s.config.args || []).join(" ")}`;
|
|
94
|
+
if (!uniqueServers.has(key)) {
|
|
95
|
+
uniqueServers.set(key, s);
|
|
96
|
+
} else {
|
|
97
|
+
if (s.source.includes("Project")) {
|
|
98
|
+
uniqueServers.set(key, s);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
const choices = Array.from(uniqueServers.values()).map((s) => {
|
|
103
|
+
return {
|
|
104
|
+
name: `${s.name} (from ${s.source})`,
|
|
105
|
+
value: s,
|
|
106
|
+
description: `${s.config.command} ${(s.config.args || []).join(" ")}`
|
|
107
|
+
};
|
|
108
|
+
});
|
|
109
|
+
choices.push({
|
|
110
|
+
name: "Enter custom server command...",
|
|
111
|
+
value: "CUSTOM",
|
|
112
|
+
description: "Manually specify a command, e.g. 'npx foo' or 'python server.py'"
|
|
113
|
+
});
|
|
114
|
+
try {
|
|
115
|
+
const answer = await select({
|
|
116
|
+
message: "Select an MCP server to launch:",
|
|
117
|
+
choices,
|
|
118
|
+
pageSize: 15
|
|
119
|
+
});
|
|
120
|
+
if (answer === "CUSTOM") {
|
|
121
|
+
const customCommand = await input({ message: "Command to spawn target MCP server:" });
|
|
122
|
+
if (!customCommand.trim()) return null;
|
|
123
|
+
const parts = customCommand.trim().match(/(?:[^\s"']+|"[^"]*"|'[^']*')+/g)?.map((p) => p.replace(/^["']|["']$/g, ""));
|
|
124
|
+
if (!parts || parts.length === 0) return null;
|
|
125
|
+
return {
|
|
126
|
+
name: "Custom",
|
|
127
|
+
config: { command: parts[0], args: parts.slice(1) },
|
|
128
|
+
source: "Manual"
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
return answer;
|
|
132
|
+
} catch {
|
|
133
|
+
return null;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
11
136
|
|
|
12
137
|
// src/interceptor.ts
|
|
13
138
|
import { mkdir, writeFile } from "fs/promises";
|
|
@@ -33,36 +158,67 @@ var ResponseInterceptor = class {
|
|
|
33
158
|
* with only the content array items modified when interception is needed.
|
|
34
159
|
*/
|
|
35
160
|
async callTool(target, name, args = {}, timeoutMs) {
|
|
161
|
+
const { result } = await this._callToolInternal(target, name, args, timeoutMs);
|
|
162
|
+
return result;
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* Call a tool and return both the result and metadata about interception actions.
|
|
166
|
+
* Used by the agent server when `include_metadata` is requested.
|
|
167
|
+
*/
|
|
168
|
+
async callToolWithMetadata(target, name, args = {}, timeoutMs) {
|
|
169
|
+
return this._callToolInternal(target, name, args, timeoutMs);
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* Internal implementation shared by callTool and callToolWithMetadata.
|
|
173
|
+
*/
|
|
174
|
+
async _callToolInternal(target, name, args = {}, timeoutMs) {
|
|
36
175
|
const timeout = timeoutMs ?? this.defaultTimeoutMs;
|
|
176
|
+
const metadata = {
|
|
177
|
+
truncated: false,
|
|
178
|
+
imagesSaved: 0,
|
|
179
|
+
audioSaved: 0,
|
|
180
|
+
originalSizeBytes: 0
|
|
181
|
+
};
|
|
37
182
|
const targetCall = target.callTool(name, args);
|
|
38
183
|
targetCall.catch(() => {
|
|
39
184
|
});
|
|
40
185
|
const result = await Promise.race([targetCall, this._timeout(timeout, name)]);
|
|
41
186
|
const content = result.content;
|
|
42
187
|
if (Array.isArray(content)) {
|
|
188
|
+
for (const item of content) {
|
|
189
|
+
if (item.type === "text" && item.text) {
|
|
190
|
+
metadata.originalSizeBytes += Buffer.byteLength(item.text, "utf8");
|
|
191
|
+
} else if ((item.type === "image" || item.type === "audio") && item.data) {
|
|
192
|
+
metadata.originalSizeBytes += Buffer.byteLength(item.data, "base64");
|
|
193
|
+
}
|
|
194
|
+
}
|
|
43
195
|
for (let i = 0; i < content.length; i++) {
|
|
44
|
-
content[i] = await this._processItem(content[i]);
|
|
196
|
+
content[i] = await this._processItem(content[i], metadata);
|
|
45
197
|
}
|
|
46
198
|
}
|
|
47
|
-
return result;
|
|
199
|
+
return { result, metadata };
|
|
48
200
|
}
|
|
49
201
|
/**
|
|
50
202
|
* Process a single content item — extract media, truncate text.
|
|
51
203
|
* Preserves all item properties not related to the intercepted data
|
|
52
204
|
* (e.g., annotations, _meta).
|
|
53
205
|
*/
|
|
54
|
-
async _processItem(item) {
|
|
206
|
+
async _processItem(item, metadata) {
|
|
55
207
|
if (item.type === "image" && item.data) {
|
|
208
|
+
metadata.imagesSaved++;
|
|
56
209
|
return this._saveMedia(item.data, item.mimeType ?? "image/png", "image");
|
|
57
210
|
}
|
|
58
211
|
if (item.type === "audio" && item.data) {
|
|
212
|
+
metadata.audioSaved++;
|
|
59
213
|
return this._saveMedia(item.data, item.mimeType ?? "audio/wav", "audio");
|
|
60
214
|
}
|
|
61
215
|
if (item.type === "text" && item.text && BASE64_PATTERN.test(item.text.trim())) {
|
|
216
|
+
metadata.imagesSaved++;
|
|
62
217
|
return this._saveMedia(item.text.trim(), "image/png", "image");
|
|
63
218
|
}
|
|
64
219
|
if (item.type === "text" && item.text && item.text.length > this.maxTextLength) {
|
|
65
220
|
const totalLength = item.text.length;
|
|
221
|
+
metadata.truncated = true;
|
|
66
222
|
return {
|
|
67
223
|
...item,
|
|
68
224
|
text: item.text.slice(0, this.maxTextLength) + `
|
|
@@ -127,219 +283,12 @@ var ResponseInterceptor = class {
|
|
|
127
283
|
"audio/ogg": ".ogg",
|
|
128
284
|
"audio/flac": ".flac",
|
|
129
285
|
"audio/aac": ".aac",
|
|
130
|
-
"audio/webm": ".webm",
|
|
131
|
-
"audio/mp4": ".m4a"
|
|
132
|
-
};
|
|
133
|
-
return map[mimeType] ?? (mimeType.startsWith("audio/") ? ".wav" : ".png");
|
|
134
|
-
}
|
|
135
|
-
};
|
|
136
|
-
|
|
137
|
-
// src/parsing.ts
|
|
138
|
-
function parseCommandLine(input3) {
|
|
139
|
-
const spaceIdx = input3.indexOf(" ");
|
|
140
|
-
if (spaceIdx === -1) {
|
|
141
|
-
return { cmd: input3.toLowerCase(), rest: "" };
|
|
142
|
-
}
|
|
143
|
-
return {
|
|
144
|
-
cmd: input3.slice(0, spaceIdx).toLowerCase(),
|
|
145
|
-
rest: input3.slice(spaceIdx + 1)
|
|
146
|
-
};
|
|
147
|
-
}
|
|
148
|
-
function parseCallArgs(rest) {
|
|
149
|
-
const trimmed = rest.trim();
|
|
150
|
-
if (!trimmed) return { toolName: "", jsonArgs: "" };
|
|
151
|
-
const spaceIdx = trimmed.indexOf(" ");
|
|
152
|
-
if (spaceIdx === -1) {
|
|
153
|
-
return { toolName: trimmed, jsonArgs: "" };
|
|
154
|
-
}
|
|
155
|
-
const toolName = trimmed.slice(0, spaceIdx);
|
|
156
|
-
let remainder = trimmed.slice(spaceIdx + 1).trim();
|
|
157
|
-
let timeoutMs;
|
|
158
|
-
const timeoutMatch = remainder.match(/\s--timeout\s+(\d+)\s*$/);
|
|
159
|
-
if (timeoutMatch) {
|
|
160
|
-
timeoutMs = parseInt(timeoutMatch[1], 10);
|
|
161
|
-
remainder = remainder.slice(0, timeoutMatch.index).trim();
|
|
162
|
-
}
|
|
163
|
-
return { toolName, jsonArgs: remainder, timeoutMs };
|
|
164
|
-
}
|
|
165
|
-
function formatJson(obj, indent = 2) {
|
|
166
|
-
const json = JSON.stringify(obj, null, indent);
|
|
167
|
-
return json.split("\n").map((line) => " ".repeat(indent) + line).join("\n");
|
|
168
|
-
}
|
|
169
|
-
function levenshtein(a, b) {
|
|
170
|
-
const m = a.length;
|
|
171
|
-
const n = b.length;
|
|
172
|
-
const dp = Array.from({ length: m + 1 }, () => Array(n + 1).fill(0));
|
|
173
|
-
for (let i = 0; i <= m; i++) dp[i][0] = i;
|
|
174
|
-
for (let j = 0; j <= n; j++) dp[0][j] = j;
|
|
175
|
-
for (let i = 1; i <= m; i++) {
|
|
176
|
-
for (let j = 1; j <= n; j++) {
|
|
177
|
-
dp[i][j] = a[i - 1] === b[j - 1] ? dp[i - 1][j - 1] : 1 + Math.min(dp[i - 1][j], dp[i][j - 1], dp[i - 1][j - 1]);
|
|
178
|
-
}
|
|
179
|
-
}
|
|
180
|
-
return dp[m][n];
|
|
181
|
-
}
|
|
182
|
-
function suggestCommand(input3, commands, threshold = 0.4) {
|
|
183
|
-
let best = null;
|
|
184
|
-
let bestDist = Infinity;
|
|
185
|
-
for (const cmd of commands) {
|
|
186
|
-
const dist = levenshtein(input3, cmd);
|
|
187
|
-
if (dist < bestDist) {
|
|
188
|
-
bestDist = dist;
|
|
189
|
-
best = cmd;
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
if (best && bestDist <= Math.ceil(input3.length * threshold)) {
|
|
193
|
-
return best;
|
|
194
|
-
}
|
|
195
|
-
return null;
|
|
196
|
-
}
|
|
197
|
-
function scaffoldArgs(schema) {
|
|
198
|
-
return JSON.stringify(scaffoldObject(schema), null, 2);
|
|
199
|
-
}
|
|
200
|
-
function scaffoldValue(prop) {
|
|
201
|
-
switch (prop.type) {
|
|
202
|
-
case "string":
|
|
203
|
-
return "<string>";
|
|
204
|
-
case "number":
|
|
205
|
-
case "integer":
|
|
206
|
-
return "<number>";
|
|
207
|
-
case "boolean":
|
|
208
|
-
return "<boolean>";
|
|
209
|
-
case "array": {
|
|
210
|
-
const items = prop.items;
|
|
211
|
-
return items ? [scaffoldValue(items)] : ["<item>"];
|
|
212
|
-
}
|
|
213
|
-
case "object":
|
|
214
|
-
return scaffoldObject(prop);
|
|
215
|
-
default:
|
|
216
|
-
return `<${prop.type ?? "unknown"}>`;
|
|
217
|
-
}
|
|
218
|
-
}
|
|
219
|
-
function scaffoldObject(schema) {
|
|
220
|
-
const properties = schema.properties;
|
|
221
|
-
if (!properties) return {};
|
|
222
|
-
const result = {};
|
|
223
|
-
for (const [key, prop] of Object.entries(properties)) {
|
|
224
|
-
result[key] = scaffoldValue(prop);
|
|
225
|
-
}
|
|
226
|
-
return result;
|
|
227
|
-
}
|
|
228
|
-
function formatToolDescription(tool) {
|
|
229
|
-
const lines = [];
|
|
230
|
-
lines.push(` ${tool.name}`);
|
|
231
|
-
if (tool.description) {
|
|
232
|
-
lines.push(` ${tool.description}`);
|
|
233
|
-
}
|
|
234
|
-
const schema = tool.inputSchema ?? {};
|
|
235
|
-
const properties = schema.properties;
|
|
236
|
-
const required = schema.required ?? [];
|
|
237
|
-
if (properties && Object.keys(properties).length > 0) {
|
|
238
|
-
lines.push("");
|
|
239
|
-
lines.push(" Arguments:");
|
|
240
|
-
const nameWidth = Math.max(6, ...Object.keys(properties).map((n) => n.length));
|
|
241
|
-
const typeWidth = Math.max(4, ...Object.values(properties).map((p) => typeLabel(p).length));
|
|
242
|
-
for (const [name, prop] of Object.entries(properties)) {
|
|
243
|
-
const type = typeLabel(prop);
|
|
244
|
-
const req = required.includes(name) ? "(required)" : "(optional)";
|
|
245
|
-
const desc = prop.description ?? "";
|
|
246
|
-
lines.push(
|
|
247
|
-
` ${name.padEnd(nameWidth)} ${type.padEnd(typeWidth)} ${req.padEnd(10)} ${desc}`
|
|
248
|
-
);
|
|
249
|
-
}
|
|
250
|
-
} else {
|
|
251
|
-
lines.push("");
|
|
252
|
-
lines.push(" No arguments required.");
|
|
253
|
-
}
|
|
254
|
-
lines.push("");
|
|
255
|
-
lines.push(" Example:");
|
|
256
|
-
if (properties && Object.keys(properties).length > 0) {
|
|
257
|
-
const example = scaffoldObject(schema);
|
|
258
|
-
lines.push(` tools/call ${tool.name} ${JSON.stringify(example)}`);
|
|
259
|
-
} else {
|
|
260
|
-
lines.push(` tools/call ${tool.name}`);
|
|
261
|
-
}
|
|
262
|
-
if (tool.annotations && Object.keys(tool.annotations).length > 0) {
|
|
263
|
-
lines.push("");
|
|
264
|
-
lines.push(" Annotations:");
|
|
265
|
-
const annotationParts = [];
|
|
266
|
-
for (const [key, value] of Object.entries(tool.annotations)) {
|
|
267
|
-
annotationParts.push(`${key}: ${value}`);
|
|
268
|
-
}
|
|
269
|
-
lines.push(` ${annotationParts.join(", ")}`);
|
|
270
|
-
}
|
|
271
|
-
return lines.join("\n");
|
|
272
|
-
}
|
|
273
|
-
function typeLabel(prop) {
|
|
274
|
-
const type = prop.type;
|
|
275
|
-
if (!type) return "any";
|
|
276
|
-
if (type === "array") {
|
|
277
|
-
const items = prop.items;
|
|
278
|
-
return items ? `${typeLabel(items)}[]` : "array";
|
|
279
|
-
}
|
|
280
|
-
return type;
|
|
281
|
-
}
|
|
282
|
-
function groupToolsByPrefix(toolNames) {
|
|
283
|
-
const groups = /* @__PURE__ */ new Map();
|
|
284
|
-
for (const name of toolNames) {
|
|
285
|
-
const underscoreIdx = name.indexOf("_");
|
|
286
|
-
const prefix = underscoreIdx > 0 ? name.slice(0, underscoreIdx) : name;
|
|
287
|
-
const list = groups.get(prefix) ?? [];
|
|
288
|
-
list.push(name);
|
|
289
|
-
groups.set(prefix, list);
|
|
290
|
-
}
|
|
291
|
-
const meaningfulGroups = [...groups.entries()].filter(([, members]) => members.length >= 2);
|
|
292
|
-
if (meaningfulGroups.length < 2) {
|
|
293
|
-
const all = /* @__PURE__ */ new Map();
|
|
294
|
-
all.set("All", [...toolNames]);
|
|
295
|
-
return all;
|
|
296
|
-
}
|
|
297
|
-
const result = /* @__PURE__ */ new Map();
|
|
298
|
-
const other = [];
|
|
299
|
-
for (const [prefix, members] of groups) {
|
|
300
|
-
if (members.length >= 2) {
|
|
301
|
-
const label = prefix.charAt(0).toUpperCase() + prefix.slice(1);
|
|
302
|
-
result.set(label, members);
|
|
303
|
-
} else {
|
|
304
|
-
other.push(...members);
|
|
305
|
-
}
|
|
306
|
-
}
|
|
307
|
-
if (other.length > 0) {
|
|
308
|
-
result.set("Other", other);
|
|
309
|
-
}
|
|
310
|
-
return result;
|
|
311
|
-
}
|
|
312
|
-
var LOG_LEVELS = [
|
|
313
|
-
"debug",
|
|
314
|
-
"info",
|
|
315
|
-
"notice",
|
|
316
|
-
"warning",
|
|
317
|
-
"error",
|
|
318
|
-
"critical",
|
|
319
|
-
"alert",
|
|
320
|
-
"emergency"
|
|
321
|
-
];
|
|
322
|
-
var ALIASES = {
|
|
323
|
-
tl: "tools/list",
|
|
324
|
-
td: "tools/describe",
|
|
325
|
-
tc: "tools/call",
|
|
326
|
-
ts: "tools/scaffold",
|
|
327
|
-
rl: "resources/list",
|
|
328
|
-
rr: "resources/read",
|
|
329
|
-
rt: "resources/templates",
|
|
330
|
-
rs: "resources/subscribe",
|
|
331
|
-
ru: "resources/unsubscribe",
|
|
332
|
-
pl: "prompts/list",
|
|
333
|
-
pg: "prompts/get"
|
|
334
|
-
};
|
|
335
|
-
function resolveAlias(input3) {
|
|
336
|
-
const spaceIdx = input3.indexOf(" ");
|
|
337
|
-
const token = spaceIdx === -1 ? input3 : input3.slice(0, spaceIdx);
|
|
338
|
-
const rest = spaceIdx === -1 ? "" : input3.slice(spaceIdx);
|
|
339
|
-
const expanded = ALIASES[token.toLowerCase()];
|
|
340
|
-
if (!expanded) return null;
|
|
341
|
-
return expanded + rest;
|
|
342
|
-
}
|
|
286
|
+
"audio/webm": ".webm",
|
|
287
|
+
"audio/mp4": ".m4a"
|
|
288
|
+
};
|
|
289
|
+
return map[mimeType] ?? (mimeType.startsWith("audio/") ? ".wav" : ".png");
|
|
290
|
+
}
|
|
291
|
+
};
|
|
343
292
|
|
|
344
293
|
// src/target-manager.ts
|
|
345
294
|
import { EventEmitter } from "events";
|
|
@@ -426,7 +375,7 @@ var TargetManager = class _TargetManager extends EventEmitter {
|
|
|
426
375
|
this.transport = stdioTransport;
|
|
427
376
|
}
|
|
428
377
|
this.client = new Client(
|
|
429
|
-
{ name: "run-mcp", version: "1.
|
|
378
|
+
{ name: "run-mcp", version: "1.6.0" },
|
|
430
379
|
{
|
|
431
380
|
capabilities: {
|
|
432
381
|
roots: { listChanged: true },
|
|
@@ -965,6 +914,445 @@ var TargetManager = class _TargetManager extends EventEmitter {
|
|
|
965
914
|
}
|
|
966
915
|
};
|
|
967
916
|
|
|
917
|
+
// src/headless.ts
|
|
918
|
+
var DEFAULT_HEADLESS_TIMEOUT_MS = 3e4;
|
|
919
|
+
async function runHeadless(targetCommand, operation, opts = {}) {
|
|
920
|
+
const [command, ...args] = targetCommand;
|
|
921
|
+
const target = new TargetManager(command, args);
|
|
922
|
+
const interceptor = new ResponseInterceptor({
|
|
923
|
+
outDir: opts.outDir,
|
|
924
|
+
defaultTimeoutMs: opts.timeoutMs ?? DEFAULT_HEADLESS_TIMEOUT_MS
|
|
925
|
+
});
|
|
926
|
+
target.on("stderr", () => {
|
|
927
|
+
});
|
|
928
|
+
try {
|
|
929
|
+
process.stderr.write(`Connecting to ${targetCommand.join(" ")}...
|
|
930
|
+
`);
|
|
931
|
+
await target.connect();
|
|
932
|
+
const status = target.getStatus();
|
|
933
|
+
process.stderr.write(`Connected (PID: ${status.pid})
|
|
934
|
+
`);
|
|
935
|
+
const result = await executeOperation(target, interceptor, operation, opts);
|
|
936
|
+
process.stdout.write(`${JSON.stringify(result, null, 2)}
|
|
937
|
+
`);
|
|
938
|
+
await target.close();
|
|
939
|
+
process.exit(0);
|
|
940
|
+
} catch (err) {
|
|
941
|
+
const msg = err.message ?? String(err);
|
|
942
|
+
if (msg.includes("ENOENT") || msg.includes("spawn")) {
|
|
943
|
+
process.stderr.write(
|
|
944
|
+
`Error: command "${command}" not found. Check that it is installed and in your PATH.
|
|
945
|
+
`
|
|
946
|
+
);
|
|
947
|
+
} else if (msg.includes("timed out")) {
|
|
948
|
+
process.stderr.write(`Error: ${msg}
|
|
949
|
+
`);
|
|
950
|
+
} else {
|
|
951
|
+
process.stderr.write(`Error: ${msg}
|
|
952
|
+
`);
|
|
953
|
+
}
|
|
954
|
+
await target.close().catch(() => {
|
|
955
|
+
});
|
|
956
|
+
process.exit(1);
|
|
957
|
+
}
|
|
958
|
+
}
|
|
959
|
+
async function executeOperation(target, interceptor, operation, opts) {
|
|
960
|
+
switch (operation.type) {
|
|
961
|
+
case "call": {
|
|
962
|
+
let parsedArgs = {};
|
|
963
|
+
if (operation.args) {
|
|
964
|
+
try {
|
|
965
|
+
parsedArgs = JSON.parse(operation.args);
|
|
966
|
+
} catch (err) {
|
|
967
|
+
process.stderr.write(`Error: Invalid JSON arguments: ${err.message}
|
|
968
|
+
`);
|
|
969
|
+
process.stderr.write(` Received: ${operation.args}
|
|
970
|
+
`);
|
|
971
|
+
process.exit(2);
|
|
972
|
+
}
|
|
973
|
+
}
|
|
974
|
+
const result = await interceptor.callTool(target, operation.tool, parsedArgs);
|
|
975
|
+
if (result.isError) {
|
|
976
|
+
const content = result.content;
|
|
977
|
+
if (Array.isArray(content)) {
|
|
978
|
+
const errorText = content.filter((c) => c.type === "text").map((c) => c.text).join("\n");
|
|
979
|
+
if (errorText) {
|
|
980
|
+
process.stderr.write(`Tool error: ${errorText}
|
|
981
|
+
`);
|
|
982
|
+
}
|
|
983
|
+
}
|
|
984
|
+
if (opts.raw) return result;
|
|
985
|
+
return result.content ?? result;
|
|
986
|
+
}
|
|
987
|
+
if (opts.raw) return result;
|
|
988
|
+
return result.content ?? result;
|
|
989
|
+
}
|
|
990
|
+
case "list-tools": {
|
|
991
|
+
const { tools } = await target.listTools();
|
|
992
|
+
return tools;
|
|
993
|
+
}
|
|
994
|
+
case "list-resources": {
|
|
995
|
+
const { resources } = await target.listResources();
|
|
996
|
+
return resources;
|
|
997
|
+
}
|
|
998
|
+
case "list-prompts": {
|
|
999
|
+
const { prompts } = await target.listPrompts();
|
|
1000
|
+
return prompts;
|
|
1001
|
+
}
|
|
1002
|
+
case "read": {
|
|
1003
|
+
const result = await target.readResource({ uri: operation.uri });
|
|
1004
|
+
return result;
|
|
1005
|
+
}
|
|
1006
|
+
case "describe": {
|
|
1007
|
+
const { tools } = await target.listTools();
|
|
1008
|
+
const tool = tools.find((t) => t.name === operation.tool);
|
|
1009
|
+
if (!tool) {
|
|
1010
|
+
const available = tools.map((t) => t.name).join(", ");
|
|
1011
|
+
process.stderr.write(
|
|
1012
|
+
`Error: Tool "${operation.tool}" not found.
|
|
1013
|
+
Available tools: ${available}
|
|
1014
|
+
`
|
|
1015
|
+
);
|
|
1016
|
+
process.exit(1);
|
|
1017
|
+
}
|
|
1018
|
+
return tool;
|
|
1019
|
+
}
|
|
1020
|
+
case "get-prompt": {
|
|
1021
|
+
let parsedArgs;
|
|
1022
|
+
if (operation.args) {
|
|
1023
|
+
try {
|
|
1024
|
+
parsedArgs = JSON.parse(operation.args);
|
|
1025
|
+
} catch (err) {
|
|
1026
|
+
process.stderr.write(`Error: Invalid JSON arguments: ${err.message}
|
|
1027
|
+
`);
|
|
1028
|
+
process.stderr.write(` Received: ${operation.args}
|
|
1029
|
+
`);
|
|
1030
|
+
process.exit(2);
|
|
1031
|
+
}
|
|
1032
|
+
}
|
|
1033
|
+
const result = await target.getPrompt({
|
|
1034
|
+
name: operation.name,
|
|
1035
|
+
arguments: parsedArgs
|
|
1036
|
+
});
|
|
1037
|
+
return result;
|
|
1038
|
+
}
|
|
1039
|
+
}
|
|
1040
|
+
}
|
|
1041
|
+
|
|
1042
|
+
// src/repl.ts
|
|
1043
|
+
import { readFile as readFile2 } from "fs/promises";
|
|
1044
|
+
import { createInterface } from "readline";
|
|
1045
|
+
import { checkbox, confirm, input as input2, search } from "@inquirer/prompts";
|
|
1046
|
+
import pc2 from "picocolors";
|
|
1047
|
+
|
|
1048
|
+
// src/parsing.ts
|
|
1049
|
+
import pc from "picocolors";
|
|
1050
|
+
function parseCommandLine(input3) {
|
|
1051
|
+
const spaceIdx = input3.indexOf(" ");
|
|
1052
|
+
if (spaceIdx === -1) {
|
|
1053
|
+
return { cmd: input3.toLowerCase(), rest: "" };
|
|
1054
|
+
}
|
|
1055
|
+
return {
|
|
1056
|
+
cmd: input3.slice(0, spaceIdx).toLowerCase(),
|
|
1057
|
+
rest: input3.slice(spaceIdx + 1)
|
|
1058
|
+
};
|
|
1059
|
+
}
|
|
1060
|
+
function parseCallArgs(rest) {
|
|
1061
|
+
const trimmed = rest.trim();
|
|
1062
|
+
if (!trimmed) return { toolName: "", jsonArgs: "" };
|
|
1063
|
+
const spaceIdx = trimmed.indexOf(" ");
|
|
1064
|
+
if (spaceIdx === -1) {
|
|
1065
|
+
return { toolName: trimmed, jsonArgs: "" };
|
|
1066
|
+
}
|
|
1067
|
+
const toolName = trimmed.slice(0, spaceIdx);
|
|
1068
|
+
let remainder = trimmed.slice(spaceIdx + 1).trim();
|
|
1069
|
+
let timeoutMs;
|
|
1070
|
+
const timeoutMatch = remainder.match(/\s--timeout\s+(\d+)\s*$/);
|
|
1071
|
+
if (timeoutMatch) {
|
|
1072
|
+
timeoutMs = parseInt(timeoutMatch[1], 10);
|
|
1073
|
+
remainder = remainder.slice(0, timeoutMatch.index).trim();
|
|
1074
|
+
}
|
|
1075
|
+
return { toolName, jsonArgs: remainder, timeoutMs };
|
|
1076
|
+
}
|
|
1077
|
+
function formatJson(obj, indent = 2, colorize = false) {
|
|
1078
|
+
const json = JSON.stringify(obj, null, indent);
|
|
1079
|
+
const output = colorize ? colorizeJson(json) : json;
|
|
1080
|
+
return output.split("\n").map((line) => " ".repeat(indent) + line).join("\n");
|
|
1081
|
+
}
|
|
1082
|
+
function colorizeJson(json) {
|
|
1083
|
+
const result = [];
|
|
1084
|
+
let i = 0;
|
|
1085
|
+
let expectingValue = false;
|
|
1086
|
+
while (i < json.length) {
|
|
1087
|
+
const ch = json[i];
|
|
1088
|
+
if (ch === '"') {
|
|
1089
|
+
const str = consumeString(json, i);
|
|
1090
|
+
if (expectingValue) {
|
|
1091
|
+
result.push(pc.green(str));
|
|
1092
|
+
expectingValue = false;
|
|
1093
|
+
} else {
|
|
1094
|
+
result.push(pc.cyan(str));
|
|
1095
|
+
}
|
|
1096
|
+
i += str.length;
|
|
1097
|
+
continue;
|
|
1098
|
+
}
|
|
1099
|
+
if (ch === ":") {
|
|
1100
|
+
result.push(ch);
|
|
1101
|
+
expectingValue = true;
|
|
1102
|
+
i++;
|
|
1103
|
+
continue;
|
|
1104
|
+
}
|
|
1105
|
+
if (ch === "," || ch === "}" || ch === "]") {
|
|
1106
|
+
result.push(ch);
|
|
1107
|
+
expectingValue = false;
|
|
1108
|
+
i++;
|
|
1109
|
+
continue;
|
|
1110
|
+
}
|
|
1111
|
+
if (ch === "{" || ch === "[") {
|
|
1112
|
+
result.push(ch);
|
|
1113
|
+
if (ch === "[") expectingValue = true;
|
|
1114
|
+
i++;
|
|
1115
|
+
continue;
|
|
1116
|
+
}
|
|
1117
|
+
if (json.startsWith("true", i)) {
|
|
1118
|
+
result.push(pc.magenta("true"));
|
|
1119
|
+
expectingValue = false;
|
|
1120
|
+
i += 4;
|
|
1121
|
+
continue;
|
|
1122
|
+
}
|
|
1123
|
+
if (json.startsWith("false", i)) {
|
|
1124
|
+
result.push(pc.magenta("false"));
|
|
1125
|
+
expectingValue = false;
|
|
1126
|
+
i += 5;
|
|
1127
|
+
continue;
|
|
1128
|
+
}
|
|
1129
|
+
if (json.startsWith("null", i)) {
|
|
1130
|
+
result.push(pc.dim("null"));
|
|
1131
|
+
expectingValue = false;
|
|
1132
|
+
i += 4;
|
|
1133
|
+
continue;
|
|
1134
|
+
}
|
|
1135
|
+
if (ch === "-" || ch >= "0" && ch <= "9") {
|
|
1136
|
+
let num = "";
|
|
1137
|
+
while (i < json.length && /[0-9.eE+-]/.test(json[i])) {
|
|
1138
|
+
num += json[i];
|
|
1139
|
+
i++;
|
|
1140
|
+
}
|
|
1141
|
+
result.push(pc.yellow(num));
|
|
1142
|
+
expectingValue = false;
|
|
1143
|
+
continue;
|
|
1144
|
+
}
|
|
1145
|
+
result.push(ch);
|
|
1146
|
+
i++;
|
|
1147
|
+
}
|
|
1148
|
+
return result.join("");
|
|
1149
|
+
}
|
|
1150
|
+
function consumeString(json, start) {
|
|
1151
|
+
let i = start + 1;
|
|
1152
|
+
while (i < json.length) {
|
|
1153
|
+
if (json[i] === "\\") {
|
|
1154
|
+
i += 2;
|
|
1155
|
+
continue;
|
|
1156
|
+
}
|
|
1157
|
+
if (json[i] === '"') {
|
|
1158
|
+
return json.slice(start, i + 1);
|
|
1159
|
+
}
|
|
1160
|
+
i++;
|
|
1161
|
+
}
|
|
1162
|
+
return json.slice(start);
|
|
1163
|
+
}
|
|
1164
|
+
function levenshtein(a, b) {
|
|
1165
|
+
const m = a.length;
|
|
1166
|
+
const n = b.length;
|
|
1167
|
+
const dp = Array.from({ length: m + 1 }, () => Array(n + 1).fill(0));
|
|
1168
|
+
for (let i = 0; i <= m; i++) dp[i][0] = i;
|
|
1169
|
+
for (let j = 0; j <= n; j++) dp[0][j] = j;
|
|
1170
|
+
for (let i = 1; i <= m; i++) {
|
|
1171
|
+
for (let j = 1; j <= n; j++) {
|
|
1172
|
+
dp[i][j] = a[i - 1] === b[j - 1] ? dp[i - 1][j - 1] : 1 + Math.min(dp[i - 1][j], dp[i][j - 1], dp[i - 1][j - 1]);
|
|
1173
|
+
}
|
|
1174
|
+
}
|
|
1175
|
+
return dp[m][n];
|
|
1176
|
+
}
|
|
1177
|
+
function suggestCommand(input3, commands, threshold = 0.4) {
|
|
1178
|
+
let best = null;
|
|
1179
|
+
let bestDist = Infinity;
|
|
1180
|
+
for (const cmd of commands) {
|
|
1181
|
+
const dist = levenshtein(input3, cmd);
|
|
1182
|
+
if (dist < bestDist) {
|
|
1183
|
+
bestDist = dist;
|
|
1184
|
+
best = cmd;
|
|
1185
|
+
}
|
|
1186
|
+
}
|
|
1187
|
+
if (best && bestDist <= Math.ceil(input3.length * threshold)) {
|
|
1188
|
+
return best;
|
|
1189
|
+
}
|
|
1190
|
+
return null;
|
|
1191
|
+
}
|
|
1192
|
+
function scaffoldArgs(schema) {
|
|
1193
|
+
return JSON.stringify(scaffoldObject(schema), null, 2);
|
|
1194
|
+
}
|
|
1195
|
+
function scaffoldValue(prop) {
|
|
1196
|
+
if (Array.isArray(prop.enum) && prop.enum.length > 0) {
|
|
1197
|
+
return prop.enum[0];
|
|
1198
|
+
}
|
|
1199
|
+
const variants = prop.anyOf ?? prop.oneOf;
|
|
1200
|
+
if (Array.isArray(variants) && variants.length > 0) {
|
|
1201
|
+
return scaffoldValue(variants[0]);
|
|
1202
|
+
}
|
|
1203
|
+
switch (prop.type) {
|
|
1204
|
+
case "string":
|
|
1205
|
+
return "<string>";
|
|
1206
|
+
case "number":
|
|
1207
|
+
case "integer":
|
|
1208
|
+
return "<number>";
|
|
1209
|
+
case "boolean":
|
|
1210
|
+
return "<boolean>";
|
|
1211
|
+
case "array": {
|
|
1212
|
+
const items = prop.items;
|
|
1213
|
+
return items ? [scaffoldValue(items)] : ["<item>"];
|
|
1214
|
+
}
|
|
1215
|
+
case "object":
|
|
1216
|
+
return scaffoldObject(prop);
|
|
1217
|
+
default:
|
|
1218
|
+
return `<${prop.type ?? "unknown"}>`;
|
|
1219
|
+
}
|
|
1220
|
+
}
|
|
1221
|
+
function scaffoldObject(schema) {
|
|
1222
|
+
const properties = schema.properties;
|
|
1223
|
+
if (properties) {
|
|
1224
|
+
const result = {};
|
|
1225
|
+
for (const [key, prop] of Object.entries(properties)) {
|
|
1226
|
+
result[key] = scaffoldValue(prop);
|
|
1227
|
+
}
|
|
1228
|
+
return result;
|
|
1229
|
+
}
|
|
1230
|
+
const additionalProperties = schema.additionalProperties;
|
|
1231
|
+
if (additionalProperties && typeof additionalProperties === "object") {
|
|
1232
|
+
return { "<key>": scaffoldValue(additionalProperties) };
|
|
1233
|
+
}
|
|
1234
|
+
if (additionalProperties === true || schema.type === "object" && !properties) {
|
|
1235
|
+
return { "<key>": "<value>" };
|
|
1236
|
+
}
|
|
1237
|
+
return {};
|
|
1238
|
+
}
|
|
1239
|
+
function formatToolDescription(tool) {
|
|
1240
|
+
const lines = [];
|
|
1241
|
+
lines.push(` ${tool.name}`);
|
|
1242
|
+
if (tool.description) {
|
|
1243
|
+
lines.push(` ${tool.description}`);
|
|
1244
|
+
}
|
|
1245
|
+
const schema = tool.inputSchema ?? {};
|
|
1246
|
+
const properties = schema.properties;
|
|
1247
|
+
const required = schema.required ?? [];
|
|
1248
|
+
if (properties && Object.keys(properties).length > 0) {
|
|
1249
|
+
lines.push("");
|
|
1250
|
+
lines.push(" Arguments:");
|
|
1251
|
+
const nameWidth = Math.max(6, ...Object.keys(properties).map((n) => n.length));
|
|
1252
|
+
const typeWidth = Math.max(4, ...Object.values(properties).map((p) => typeLabel(p).length));
|
|
1253
|
+
for (const [name, prop] of Object.entries(properties)) {
|
|
1254
|
+
const type = typeLabel(prop);
|
|
1255
|
+
const req = required.includes(name) ? "(required)" : "(optional)";
|
|
1256
|
+
const desc = prop.description ?? "";
|
|
1257
|
+
lines.push(
|
|
1258
|
+
` ${name.padEnd(nameWidth)} ${type.padEnd(typeWidth)} ${req.padEnd(10)} ${desc}`
|
|
1259
|
+
);
|
|
1260
|
+
}
|
|
1261
|
+
} else {
|
|
1262
|
+
lines.push("");
|
|
1263
|
+
lines.push(" No arguments required.");
|
|
1264
|
+
}
|
|
1265
|
+
lines.push("");
|
|
1266
|
+
lines.push(" Example:");
|
|
1267
|
+
if (properties && Object.keys(properties).length > 0) {
|
|
1268
|
+
const example = scaffoldObject(schema);
|
|
1269
|
+
lines.push(` tools/call ${tool.name} ${JSON.stringify(example)}`);
|
|
1270
|
+
} else {
|
|
1271
|
+
lines.push(` tools/call ${tool.name}`);
|
|
1272
|
+
}
|
|
1273
|
+
if (tool.annotations) {
|
|
1274
|
+
const entries = Object.entries(tool.annotations).filter(([key]) => key !== "title");
|
|
1275
|
+
if (entries.length > 0) {
|
|
1276
|
+
lines.push("");
|
|
1277
|
+
lines.push(" Annotations:");
|
|
1278
|
+
for (const [key, value] of entries) {
|
|
1279
|
+
lines.push(` ${key}: ${value}`);
|
|
1280
|
+
}
|
|
1281
|
+
}
|
|
1282
|
+
}
|
|
1283
|
+
return lines.join("\n");
|
|
1284
|
+
}
|
|
1285
|
+
function typeLabel(prop) {
|
|
1286
|
+
const type = prop.type;
|
|
1287
|
+
if (!type) return "any";
|
|
1288
|
+
if (type === "array") {
|
|
1289
|
+
const items = prop.items;
|
|
1290
|
+
return items ? `${typeLabel(items)}[]` : "array";
|
|
1291
|
+
}
|
|
1292
|
+
return type;
|
|
1293
|
+
}
|
|
1294
|
+
function groupToolsByPrefix(toolNames) {
|
|
1295
|
+
const groups = /* @__PURE__ */ new Map();
|
|
1296
|
+
for (const name of toolNames) {
|
|
1297
|
+
const underscoreIdx = name.indexOf("_");
|
|
1298
|
+
const prefix = underscoreIdx > 0 ? name.slice(0, underscoreIdx) : name;
|
|
1299
|
+
const list = groups.get(prefix) ?? [];
|
|
1300
|
+
list.push(name);
|
|
1301
|
+
groups.set(prefix, list);
|
|
1302
|
+
}
|
|
1303
|
+
const meaningfulGroups = [...groups.entries()].filter(([, members]) => members.length >= 2);
|
|
1304
|
+
if (meaningfulGroups.length < 2) {
|
|
1305
|
+
const all = /* @__PURE__ */ new Map();
|
|
1306
|
+
all.set("All", [...toolNames]);
|
|
1307
|
+
return all;
|
|
1308
|
+
}
|
|
1309
|
+
const result = /* @__PURE__ */ new Map();
|
|
1310
|
+
const other = [];
|
|
1311
|
+
for (const [prefix, members] of groups) {
|
|
1312
|
+
if (members.length >= 2) {
|
|
1313
|
+
const label = prefix.charAt(0).toUpperCase() + prefix.slice(1);
|
|
1314
|
+
result.set(label, members);
|
|
1315
|
+
} else {
|
|
1316
|
+
other.push(...members);
|
|
1317
|
+
}
|
|
1318
|
+
}
|
|
1319
|
+
if (other.length > 0) {
|
|
1320
|
+
result.set("Other", other);
|
|
1321
|
+
}
|
|
1322
|
+
return result;
|
|
1323
|
+
}
|
|
1324
|
+
var LOG_LEVELS = [
|
|
1325
|
+
"debug",
|
|
1326
|
+
"info",
|
|
1327
|
+
"notice",
|
|
1328
|
+
"warning",
|
|
1329
|
+
"error",
|
|
1330
|
+
"critical",
|
|
1331
|
+
"alert",
|
|
1332
|
+
"emergency"
|
|
1333
|
+
];
|
|
1334
|
+
var ALIASES = {
|
|
1335
|
+
tl: "tools/list",
|
|
1336
|
+
td: "tools/describe",
|
|
1337
|
+
tc: "tools/call",
|
|
1338
|
+
ts: "tools/scaffold",
|
|
1339
|
+
rl: "resources/list",
|
|
1340
|
+
rr: "resources/read",
|
|
1341
|
+
rt: "resources/templates",
|
|
1342
|
+
rs: "resources/subscribe",
|
|
1343
|
+
ru: "resources/unsubscribe",
|
|
1344
|
+
pl: "prompts/list",
|
|
1345
|
+
pg: "prompts/get"
|
|
1346
|
+
};
|
|
1347
|
+
function resolveAlias(input3) {
|
|
1348
|
+
const spaceIdx = input3.indexOf(" ");
|
|
1349
|
+
const token = spaceIdx === -1 ? input3 : input3.slice(0, spaceIdx);
|
|
1350
|
+
const rest = spaceIdx === -1 ? "" : input3.slice(spaceIdx);
|
|
1351
|
+
const expanded = ALIASES[token.toLowerCase()];
|
|
1352
|
+
if (!expanded) return null;
|
|
1353
|
+
return expanded + rest;
|
|
1354
|
+
}
|
|
1355
|
+
|
|
968
1356
|
// src/repl.ts
|
|
969
1357
|
var KNOWN_COMMANDS = [
|
|
970
1358
|
"explore",
|
|
@@ -994,6 +1382,7 @@ var KNOWN_COMMANDS = [
|
|
|
994
1382
|
"!!",
|
|
995
1383
|
"last",
|
|
996
1384
|
"help",
|
|
1385
|
+
"?",
|
|
997
1386
|
"exit",
|
|
998
1387
|
"quit",
|
|
999
1388
|
// Short aliases
|
|
@@ -1012,8 +1401,22 @@ var KNOWN_COMMANDS = [
|
|
|
1012
1401
|
var cachedToolNames = [];
|
|
1013
1402
|
var cachedResourceUris = [];
|
|
1014
1403
|
var cachedPromptNames = [];
|
|
1404
|
+
var activeCapabilities = null;
|
|
1405
|
+
function getActiveCommands() {
|
|
1406
|
+
let commands = [...KNOWN_COMMANDS];
|
|
1407
|
+
if (!activeCapabilities?.resources) {
|
|
1408
|
+
commands = commands.filter(
|
|
1409
|
+
(c) => !c.startsWith("resources/") && !["rl", "rr", "rt", "rs", "ru"].includes(c)
|
|
1410
|
+
);
|
|
1411
|
+
}
|
|
1412
|
+
if (!activeCapabilities?.prompts) {
|
|
1413
|
+
commands = commands.filter((c) => !c.startsWith("prompts/") && !["pl", "pg"].includes(c));
|
|
1414
|
+
}
|
|
1415
|
+
return commands;
|
|
1416
|
+
}
|
|
1015
1417
|
async function refreshCaches(target) {
|
|
1016
1418
|
const caps = target.getServerCapabilities() ?? {};
|
|
1419
|
+
activeCapabilities = caps;
|
|
1017
1420
|
try {
|
|
1018
1421
|
const { tools } = await target.listTools();
|
|
1019
1422
|
cachedToolNames = tools.map((t) => t.name);
|
|
@@ -1058,7 +1461,7 @@ function computeMatches(line) {
|
|
|
1058
1461
|
const matches2 = cachedPromptNames.filter((n) => n.startsWith(partial));
|
|
1059
1462
|
return [matches2.map((m) => `prompts/get ${m}`), effective];
|
|
1060
1463
|
}
|
|
1061
|
-
const matches =
|
|
1464
|
+
const matches = getActiveCommands().filter((c) => c.startsWith(line));
|
|
1062
1465
|
return [matches, line];
|
|
1063
1466
|
}
|
|
1064
1467
|
var completer = (line) => {
|
|
@@ -1122,34 +1525,36 @@ async function withSuspendedReadline(target, interceptor, fn) {
|
|
|
1122
1525
|
}
|
|
1123
1526
|
}
|
|
1124
1527
|
function getPrompt(target) {
|
|
1125
|
-
if (target.connected) return `${
|
|
1126
|
-
return `${
|
|
1528
|
+
if (target.connected) return `${pc2.green("\u2713")}${pc2.cyan("> ")}`;
|
|
1529
|
+
return `${pc2.red("\u2717")}${pc2.cyan("> ")}`;
|
|
1127
1530
|
}
|
|
1128
1531
|
function printBanner(serverName, serverVersion, toolCount, resourceCount, promptCount) {
|
|
1129
1532
|
const parts = [];
|
|
1130
|
-
parts.push(`${
|
|
1131
|
-
if (resourceCount > 0) parts.push(`${
|
|
1132
|
-
if (promptCount > 0) parts.push(`${
|
|
1133
|
-
const
|
|
1533
|
+
parts.push(`${pc2.bold(toolCount.toString())} tools`);
|
|
1534
|
+
if (resourceCount > 0) parts.push(`${pc2.bold(resourceCount.toString())} resources`);
|
|
1535
|
+
if (promptCount > 0) parts.push(`${pc2.bold(promptCount.toString())} prompts`);
|
|
1536
|
+
const baseTitle = serverVersion ? `${serverName} ${pc2.dim(`v${serverVersion}`)}` : serverName;
|
|
1537
|
+
const isAgentHarness = serverName === "run-mcp";
|
|
1538
|
+
const title = isAgentHarness ? `${baseTitle} ${pc2.bgBlue(pc2.white(" AGENT HARNESS "))}` : baseTitle;
|
|
1134
1539
|
const BOX_WIDTH = 53;
|
|
1135
1540
|
const padLine = (content) => {
|
|
1136
1541
|
const visible = stripAnsi(content).length;
|
|
1137
1542
|
const padding = Math.max(0, BOX_WIDTH - visible);
|
|
1138
|
-
return `${
|
|
1543
|
+
return `${pc2.cyan(" \u2502")}${content}${"".padEnd(padding)}${pc2.cyan("\u2502")}`;
|
|
1139
1544
|
};
|
|
1140
1545
|
const partsStr = ` ${parts.join(" \u2022 ")}`;
|
|
1141
1546
|
console.log();
|
|
1142
|
-
console.log(
|
|
1547
|
+
console.log(pc2.cyan(` \u250C${"\u2500".repeat(BOX_WIDTH)}\u2510`));
|
|
1143
1548
|
console.log(padLine(` ${title}`));
|
|
1144
1549
|
console.log(padLine(partsStr));
|
|
1145
|
-
console.log(
|
|
1550
|
+
console.log(pc2.cyan(` \u251C${"\u2500".repeat(BOX_WIDTH)}\u2524`));
|
|
1146
1551
|
console.log(padLine(" Quick start:"));
|
|
1147
|
-
console.log(padLine(` ${
|
|
1148
|
-
console.log(padLine(` ${
|
|
1149
|
-
console.log(padLine(` ${
|
|
1552
|
+
console.log(padLine(` ${pc2.green("tools/list")} See all tools`));
|
|
1553
|
+
console.log(padLine(` ${pc2.green("tools/call")} ${pc2.dim("<name>")} Call a tool`));
|
|
1554
|
+
console.log(padLine(` ${pc2.green("help")} All commands`));
|
|
1150
1555
|
console.log(padLine(""));
|
|
1151
|
-
console.log(padLine(
|
|
1152
|
-
console.log(
|
|
1556
|
+
console.log(padLine(pc2.dim(" Tab completion is active. Start typing to explore.")));
|
|
1557
|
+
console.log(pc2.cyan(` \u2514${"\u2500".repeat(BOX_WIDTH)}\u2518`));
|
|
1153
1558
|
console.log();
|
|
1154
1559
|
}
|
|
1155
1560
|
function stripAnsi(str) {
|
|
@@ -1162,47 +1567,47 @@ async function startRepl(targetCommand, opts) {
|
|
|
1162
1567
|
isScriptMode = !!opts.script;
|
|
1163
1568
|
target.on("stderr", (text) => {
|
|
1164
1569
|
for (const line of text.split("\n")) {
|
|
1165
|
-
console.error(
|
|
1570
|
+
console.error(pc2.dim(`[server] ${line}`));
|
|
1166
1571
|
}
|
|
1167
1572
|
});
|
|
1168
|
-
console.log(
|
|
1169
|
-
console.log(
|
|
1573
|
+
console.log(pc2.cyan("\u27F3 Connecting to target MCP server..."));
|
|
1574
|
+
console.log(pc2.dim(` Command: ${targetCommand.join(" ")}`));
|
|
1170
1575
|
try {
|
|
1171
1576
|
await target.connect();
|
|
1172
1577
|
} catch (err) {
|
|
1173
1578
|
const msg = err.message ?? String(err);
|
|
1174
1579
|
if (msg.includes("ENOENT") || msg.includes("spawn")) {
|
|
1175
|
-
console.error(
|
|
1176
|
-
console.error(
|
|
1580
|
+
console.error(pc2.red(`\u2717 Failed to start server: command "${command}" not found.`));
|
|
1581
|
+
console.error(pc2.dim(` Check that "${command}" is installed and in your PATH.`));
|
|
1177
1582
|
} else {
|
|
1178
|
-
console.error(
|
|
1179
|
-
console.error(
|
|
1583
|
+
console.error(pc2.red(`\u2717 Failed to connect: ${msg}`));
|
|
1584
|
+
console.error(pc2.dim(` Check that the target command starts a valid MCP server on stdio.`));
|
|
1180
1585
|
}
|
|
1181
1586
|
process.exit(1);
|
|
1182
1587
|
}
|
|
1183
1588
|
const status = target.getStatus();
|
|
1184
|
-
console.log(
|
|
1589
|
+
console.log(pc2.green(`\u2713 Connected (PID: ${status.pid})`));
|
|
1185
1590
|
if (!isScriptMode) {
|
|
1186
1591
|
target.enableAutoReconnect();
|
|
1187
1592
|
target.on(
|
|
1188
1593
|
"reconnecting",
|
|
1189
1594
|
({ attempt, maxAttempts }) => {
|
|
1190
1595
|
console.log(
|
|
1191
|
-
|
|
1596
|
+
pc2.yellow(`
|
|
1192
1597
|
\u27F3 Server disconnected. Reconnecting (${attempt}/${maxAttempts})...`)
|
|
1193
1598
|
);
|
|
1194
1599
|
}
|
|
1195
1600
|
);
|
|
1196
1601
|
target.on("reconnected", async ({ attempt }) => {
|
|
1197
1602
|
const s = target.getStatus();
|
|
1198
|
-
console.log(
|
|
1603
|
+
console.log(pc2.green(`\u2713 Reconnected (PID: ${s.pid}, attempt ${attempt})`));
|
|
1199
1604
|
await refreshCaches(target);
|
|
1200
1605
|
});
|
|
1201
1606
|
target.on("reconnect_failed", ({ reason, message }) => {
|
|
1202
|
-
console.error(
|
|
1607
|
+
console.error(pc2.red(`\u2717 ${message}`));
|
|
1203
1608
|
if (reason === "max_retries") {
|
|
1204
1609
|
console.log(
|
|
1205
|
-
|
|
1610
|
+
pc2.dim(" Use 'exit' to quit or wait for the server to be fixed and restart manually.")
|
|
1206
1611
|
);
|
|
1207
1612
|
}
|
|
1208
1613
|
});
|
|
@@ -1212,38 +1617,38 @@ async function startRepl(targetCommand, opts) {
|
|
|
1212
1617
|
const lvl = notification.params?.level ?? "info";
|
|
1213
1618
|
const data = notification.params?.data ?? "";
|
|
1214
1619
|
const text = typeof data === "string" ? data : JSON.stringify(data);
|
|
1215
|
-
console.log(
|
|
1620
|
+
console.log(pc2.dim(`
|
|
1216
1621
|
[${lvl}] ${text}`));
|
|
1217
1622
|
} else if (method === "notifications/tools/list_changed") {
|
|
1218
|
-
console.log(
|
|
1623
|
+
console.log(pc2.yellow("\n \u27F3 Server tools changed. Run tools/list to see updates."));
|
|
1219
1624
|
refreshCaches(target).catch(() => {
|
|
1220
1625
|
});
|
|
1221
1626
|
} else if (method === "notifications/resources/list_changed") {
|
|
1222
|
-
console.log(
|
|
1627
|
+
console.log(pc2.yellow("\n \u27F3 Server resources changed. Run resources/list to see."));
|
|
1223
1628
|
refreshCaches(target).catch(() => {
|
|
1224
1629
|
});
|
|
1225
1630
|
} else if (method === "notifications/resources/updated") {
|
|
1226
1631
|
const uri = notification.params?.uri ?? "unknown";
|
|
1227
|
-
console.log(
|
|
1632
|
+
console.log(pc2.yellow(`
|
|
1228
1633
|
\u27F3 Resource updated: ${uri}`));
|
|
1229
1634
|
} else if (method === "notifications/prompts/list_changed") {
|
|
1230
|
-
console.log(
|
|
1635
|
+
console.log(pc2.yellow("\n \u27F3 Server prompts changed. Run prompts/list to see."));
|
|
1231
1636
|
refreshCaches(target).catch(() => {
|
|
1232
1637
|
});
|
|
1233
1638
|
}
|
|
1234
1639
|
});
|
|
1235
1640
|
target.on("sampling_request", async ({ request, respond, reject: rejectFn }) => {
|
|
1236
|
-
console.log(
|
|
1641
|
+
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
1642
|
const messages = request?.messages ?? [];
|
|
1238
1643
|
for (const msg of messages) {
|
|
1239
|
-
const role = msg.role === "user" ?
|
|
1644
|
+
const role = msg.role === "user" ? pc2.blue("user") : pc2.magenta("assistant");
|
|
1240
1645
|
const text = msg.content?.text ?? JSON.stringify(msg.content);
|
|
1241
|
-
console.log(
|
|
1646
|
+
console.log(pc2.magenta(` \u2551 ${role}: ${text}`));
|
|
1242
1647
|
}
|
|
1243
|
-
console.log(
|
|
1648
|
+
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
1649
|
if (activeRl) {
|
|
1245
1650
|
try {
|
|
1246
|
-
const answer = await question(activeRl, ` ${
|
|
1651
|
+
const answer = await question(activeRl, ` ${pc2.bold("Approve? [y/N/text]:")} `);
|
|
1247
1652
|
const trimmed = answer.trim().toLowerCase();
|
|
1248
1653
|
if (trimmed === "y" || trimmed === "yes") {
|
|
1249
1654
|
respond({
|
|
@@ -1272,14 +1677,14 @@ async function startRepl(targetCommand, opts) {
|
|
|
1272
1677
|
}
|
|
1273
1678
|
});
|
|
1274
1679
|
target.on("elicitation_request", async ({ request, respond, reject: rejectFn }) => {
|
|
1275
|
-
console.log(
|
|
1276
|
-
console.log(
|
|
1277
|
-
console.log(
|
|
1680
|
+
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"));
|
|
1681
|
+
console.log(pc2.cyan(` \u2551 ${request?.message ?? "Server requests input"}`));
|
|
1682
|
+
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
1683
|
if (activeRl) {
|
|
1279
1684
|
try {
|
|
1280
1685
|
const answer = await question(
|
|
1281
1686
|
activeRl,
|
|
1282
|
-
` ${
|
|
1687
|
+
` ${pc2.bold("Your response (empty to decline):")} `
|
|
1283
1688
|
);
|
|
1284
1689
|
if (answer.trim() === "") {
|
|
1285
1690
|
respond({ action: "decline" });
|
|
@@ -1334,13 +1739,13 @@ async function startRepl(targetCommand, opts) {
|
|
|
1334
1739
|
);
|
|
1335
1740
|
if (!groups.has("All")) {
|
|
1336
1741
|
for (const [label, members] of groups) {
|
|
1337
|
-
console.log(` ${
|
|
1742
|
+
console.log(` ${pc2.bold(label.padEnd(16))} ${pc2.dim(members.join(", "))}`);
|
|
1338
1743
|
}
|
|
1339
1744
|
console.log();
|
|
1340
1745
|
}
|
|
1341
1746
|
}
|
|
1342
1747
|
} catch (err) {
|
|
1343
|
-
console.log(
|
|
1748
|
+
console.log(pc2.yellow(` Warning: Could not list tools: ${err.message}
|
|
1344
1749
|
`));
|
|
1345
1750
|
}
|
|
1346
1751
|
await refreshCaches(target);
|
|
@@ -1352,13 +1757,22 @@ async function startRepl(targetCommand, opts) {
|
|
|
1352
1757
|
try {
|
|
1353
1758
|
await handleCommand(trimmed, target, interceptor);
|
|
1354
1759
|
} catch (err) {
|
|
1355
|
-
|
|
1356
|
-
|
|
1760
|
+
if (err?.message?.includes("-32601") || err?.code === -32601) {
|
|
1761
|
+
let msg = "Server does not support this feature (Method not found)";
|
|
1762
|
+
if (trimmed.startsWith("prompts/")) msg = "This server does not have any prompts.";
|
|
1763
|
+
else if (trimmed.startsWith("resources/"))
|
|
1764
|
+
msg = "This server does not have any resources.";
|
|
1765
|
+
else if (trimmed.startsWith("tools/")) msg = "This server does not have any tools.";
|
|
1766
|
+
console.log(pc2.yellow(` ${msg}`));
|
|
1767
|
+
} else {
|
|
1768
|
+
console.error(pc2.red(`\u2717 Error: ${err.message}`));
|
|
1769
|
+
}
|
|
1770
|
+
console.log(pc2.dim("\nShutting down..."));
|
|
1357
1771
|
await target.close();
|
|
1358
1772
|
process.exit(1);
|
|
1359
1773
|
}
|
|
1360
1774
|
}
|
|
1361
|
-
console.log(
|
|
1775
|
+
console.log(pc2.dim("\nShutting down..."));
|
|
1362
1776
|
await target.close();
|
|
1363
1777
|
process.exit(0);
|
|
1364
1778
|
} else {
|
|
@@ -1400,9 +1814,16 @@ function startReadlineLoop(target, interceptor) {
|
|
|
1400
1814
|
await handleCommand(trimmed, target, interceptor);
|
|
1401
1815
|
} catch (err) {
|
|
1402
1816
|
if (err instanceof AbortFlowError) {
|
|
1403
|
-
console.log(
|
|
1817
|
+
console.log(pc2.yellow(" Aborted."));
|
|
1818
|
+
} else if (err?.message?.includes("-32601") || err?.code === -32601) {
|
|
1819
|
+
let msg = "Server does not support this feature (Method not found)";
|
|
1820
|
+
if (trimmed.startsWith("prompts/")) msg = "This server does not have any prompts.";
|
|
1821
|
+
else if (trimmed.startsWith("resources/"))
|
|
1822
|
+
msg = "This server does not have any resources.";
|
|
1823
|
+
else if (trimmed.startsWith("tools/")) msg = "This server does not have any tools.";
|
|
1824
|
+
console.log(pc2.yellow(` ${msg}`));
|
|
1404
1825
|
} else {
|
|
1405
|
-
console.error(
|
|
1826
|
+
console.error(pc2.red(`\u2717 Error: ${err.message}`));
|
|
1406
1827
|
}
|
|
1407
1828
|
}
|
|
1408
1829
|
if (activeRl) {
|
|
@@ -1431,7 +1852,7 @@ function startReadlineLoop(target, interceptor) {
|
|
|
1431
1852
|
closed = true;
|
|
1432
1853
|
activeRl = null;
|
|
1433
1854
|
if (!globalPauseReadlineClose) {
|
|
1434
|
-
console.log(
|
|
1855
|
+
console.log(pc2.dim("\nShutting down..."));
|
|
1435
1856
|
await target.close();
|
|
1436
1857
|
process.exit(0);
|
|
1437
1858
|
}
|
|
@@ -1448,6 +1869,9 @@ async function handleCommand(input3, target, interceptor) {
|
|
|
1448
1869
|
case "help":
|
|
1449
1870
|
printHelp();
|
|
1450
1871
|
return;
|
|
1872
|
+
case "?":
|
|
1873
|
+
printShortHelp();
|
|
1874
|
+
return;
|
|
1451
1875
|
case "explore":
|
|
1452
1876
|
case "interactive":
|
|
1453
1877
|
await withSuspendedReadline(target, interceptor, async () => {
|
|
@@ -1473,7 +1897,7 @@ async function handleCommand(input3, target, interceptor) {
|
|
|
1473
1897
|
await cmdResourcesList(target);
|
|
1474
1898
|
return;
|
|
1475
1899
|
case "resources/read":
|
|
1476
|
-
await cmdResourcesRead(target, rest);
|
|
1900
|
+
await cmdResourcesRead(target, rest, interceptor);
|
|
1477
1901
|
return;
|
|
1478
1902
|
case "resources/templates":
|
|
1479
1903
|
await cmdResourcesTemplates(target);
|
|
@@ -1482,7 +1906,7 @@ async function handleCommand(input3, target, interceptor) {
|
|
|
1482
1906
|
await cmdPromptsList(target);
|
|
1483
1907
|
return;
|
|
1484
1908
|
case "prompts/get":
|
|
1485
|
-
await cmdPromptsGet(target, rest);
|
|
1909
|
+
await cmdPromptsGet(target, rest, interceptor);
|
|
1486
1910
|
return;
|
|
1487
1911
|
case "timing":
|
|
1488
1912
|
cmdTiming();
|
|
@@ -1520,27 +1944,30 @@ async function handleCommand(input3, target, interceptor) {
|
|
|
1520
1944
|
case "!!":
|
|
1521
1945
|
case "last":
|
|
1522
1946
|
if (lastCommand) {
|
|
1523
|
-
console.log(
|
|
1947
|
+
console.log(pc2.dim(` Re-running: ${lastCommand}`));
|
|
1524
1948
|
await handleCommand(lastCommand, target, interceptor);
|
|
1525
1949
|
} else {
|
|
1526
|
-
console.log(
|
|
1950
|
+
console.log(pc2.yellow("No previous command to re-run."));
|
|
1527
1951
|
}
|
|
1528
1952
|
return;
|
|
1529
1953
|
case "status":
|
|
1530
1954
|
cmdStatus(target);
|
|
1531
1955
|
return;
|
|
1532
1956
|
case "exit":
|
|
1533
|
-
case "quit":
|
|
1534
|
-
|
|
1957
|
+
case "quit": {
|
|
1958
|
+
console.log(pc2.dim("Shutting down..."));
|
|
1959
|
+
await target.close();
|
|
1960
|
+
process.exit(0);
|
|
1535
1961
|
return;
|
|
1962
|
+
}
|
|
1536
1963
|
default: {
|
|
1537
|
-
const suggestion = suggestCommand(cmd,
|
|
1964
|
+
const suggestion = suggestCommand(cmd, getActiveCommands());
|
|
1538
1965
|
if (suggestion) {
|
|
1539
|
-
console.log(
|
|
1966
|
+
console.log(pc2.yellow(`Unknown command: ${cmd}.`));
|
|
1540
1967
|
try {
|
|
1541
1968
|
await withSuspendedReadline(target, interceptor, async () => {
|
|
1542
1969
|
const runIt = await confirm({
|
|
1543
|
-
message: `Did you mean ${
|
|
1970
|
+
message: `Did you mean ${pc2.bold(suggestion)}?`,
|
|
1544
1971
|
default: true
|
|
1545
1972
|
});
|
|
1546
1973
|
if (runIt) {
|
|
@@ -1553,7 +1980,7 @@ async function handleCommand(input3, target, interceptor) {
|
|
|
1553
1980
|
throw new AbortFlowError();
|
|
1554
1981
|
}
|
|
1555
1982
|
} else {
|
|
1556
|
-
console.log(
|
|
1983
|
+
console.log(pc2.yellow(`Unknown command: ${cmd}. Type ${pc2.bold("help")} for usage.`));
|
|
1557
1984
|
}
|
|
1558
1985
|
}
|
|
1559
1986
|
}
|
|
@@ -1561,25 +1988,25 @@ async function handleCommand(input3, target, interceptor) {
|
|
|
1561
1988
|
async function cmdToolsList(target) {
|
|
1562
1989
|
const { tools } = await target.listTools();
|
|
1563
1990
|
if (tools.length === 0) {
|
|
1564
|
-
console.log(
|
|
1991
|
+
console.log(pc2.dim(" No tools available."));
|
|
1565
1992
|
return;
|
|
1566
1993
|
}
|
|
1567
1994
|
const nameWidth = Math.max(8, ...tools.map((t) => t.name.length));
|
|
1568
|
-
console.log(
|
|
1569
|
-
console.log(
|
|
1995
|
+
console.log(pc2.bold(` ${"Name".padEnd(nameWidth)} Description`));
|
|
1996
|
+
console.log(pc2.dim(` ${"\u2500".repeat(nameWidth)} ${"\u2500".repeat(50)}`));
|
|
1570
1997
|
for (const tool of tools) {
|
|
1571
|
-
const desc = tool.description ? tool.description.length > 60 ? `${tool.description.slice(0, 57)}...` : tool.description :
|
|
1572
|
-
console.log(` ${
|
|
1998
|
+
const desc = tool.description ? tool.description.length > 60 ? `${tool.description.slice(0, 57)}...` : tool.description : pc2.dim("(no description)");
|
|
1999
|
+
console.log(` ${pc2.green(tool.name.padEnd(nameWidth))} ${desc}`);
|
|
1573
2000
|
}
|
|
1574
|
-
console.log(
|
|
2001
|
+
console.log(pc2.dim(`
|
|
1575
2002
|
${tools.length} tool(s) total.`));
|
|
1576
2003
|
if (tools.length >= 10) {
|
|
1577
2004
|
const groups = groupToolsByPrefix(tools.map((t) => t.name));
|
|
1578
2005
|
if (!groups.has("All")) {
|
|
1579
2006
|
console.log();
|
|
1580
|
-
console.log(
|
|
2007
|
+
console.log(pc2.bold(" Groups:"));
|
|
1581
2008
|
for (const [label, members] of groups) {
|
|
1582
|
-
console.log(` ${
|
|
2009
|
+
console.log(` ${pc2.cyan(label.padEnd(16))} ${pc2.dim(members.join(", "))}`);
|
|
1583
2010
|
}
|
|
1584
2011
|
}
|
|
1585
2012
|
}
|
|
@@ -1587,28 +2014,28 @@ async function cmdToolsList(target) {
|
|
|
1587
2014
|
async function cmdToolsDescribe(target, rest) {
|
|
1588
2015
|
const name = rest.trim();
|
|
1589
2016
|
if (!name) {
|
|
1590
|
-
console.log(
|
|
2017
|
+
console.log(pc2.yellow(" Usage: tools/describe <name>"));
|
|
1591
2018
|
if (cachedToolNames.length > 0) {
|
|
1592
2019
|
const preview = cachedToolNames.slice(0, 6);
|
|
1593
2020
|
const more = cachedToolNames.length > 6 ? `, ... (${cachedToolNames.length} total)` : "";
|
|
1594
|
-
console.log(
|
|
2021
|
+
console.log(pc2.dim(`
|
|
1595
2022
|
Available tools: ${preview.join(", ")}${more}`));
|
|
1596
|
-
console.log(
|
|
2023
|
+
console.log(pc2.dim(` Type ${pc2.bold("tools/list")} for all.`));
|
|
1597
2024
|
}
|
|
1598
2025
|
return;
|
|
1599
2026
|
}
|
|
1600
2027
|
const { tools } = await target.listTools();
|
|
1601
2028
|
const tool = tools.find((t) => t.name === name);
|
|
1602
2029
|
if (!tool) {
|
|
1603
|
-
console.log(
|
|
2030
|
+
console.log(pc2.red(`Tool "${name}" not found.`));
|
|
1604
2031
|
const suggestion = suggestCommand(
|
|
1605
2032
|
name,
|
|
1606
2033
|
tools.map((t) => t.name)
|
|
1607
2034
|
);
|
|
1608
2035
|
if (suggestion) {
|
|
1609
|
-
console.log(
|
|
2036
|
+
console.log(pc2.yellow(`Did you mean ${pc2.bold(suggestion)}?`));
|
|
1610
2037
|
} else {
|
|
1611
|
-
console.log(
|
|
2038
|
+
console.log(pc2.dim(`Available: ${tools.map((t) => t.name).join(", ")}`));
|
|
1612
2039
|
}
|
|
1613
2040
|
return;
|
|
1614
2041
|
}
|
|
@@ -1628,14 +2055,25 @@ async function cmdToolsCall(target, interceptor, rest) {
|
|
|
1628
2055
|
const cleanedRest = clearPrevious ? rest.replace(/\s*--clear/, "").trim() : rest;
|
|
1629
2056
|
const { toolName, jsonArgs, timeoutMs } = parseCallArgs(cleanedRest);
|
|
1630
2057
|
if (!toolName) {
|
|
1631
|
-
|
|
2058
|
+
if (!isScriptMode && cachedToolNames.length > 0 && process.stdin.isTTY) {
|
|
2059
|
+
const picked = await withSuspendedReadline(target, interceptor, async () => {
|
|
2060
|
+
const tools = await target.listTools();
|
|
2061
|
+
return pickInteractive(
|
|
2062
|
+
tools.tools.map((t) => ({ name: t.name, description: t.description })),
|
|
2063
|
+
"Pick a tool to call:"
|
|
2064
|
+
);
|
|
2065
|
+
});
|
|
2066
|
+
if (!picked) return;
|
|
2067
|
+
return cmdToolsCall(target, interceptor, picked);
|
|
2068
|
+
}
|
|
2069
|
+
console.log(pc2.yellow(" Usage: tools/call <name> [json_args] [--timeout <ms>]"));
|
|
1632
2070
|
if (cachedToolNames.length > 0) {
|
|
1633
2071
|
const preview = cachedToolNames.slice(0, 6);
|
|
1634
2072
|
const more = cachedToolNames.length > 6 ? `, ... (${cachedToolNames.length} total)` : "";
|
|
1635
|
-
console.log(
|
|
2073
|
+
console.log(pc2.dim(`
|
|
1636
2074
|
Available tools: ${preview.join(", ")}${more}`));
|
|
1637
2075
|
console.log(
|
|
1638
|
-
|
|
2076
|
+
pc2.dim(` Run without args for ${pc2.bold("interactive mode")}: tools/call <name>`)
|
|
1639
2077
|
);
|
|
1640
2078
|
}
|
|
1641
2079
|
return;
|
|
@@ -1645,8 +2083,8 @@ async function cmdToolsCall(target, interceptor, rest) {
|
|
|
1645
2083
|
try {
|
|
1646
2084
|
args = JSON.parse(jsonArgs);
|
|
1647
2085
|
} catch (err) {
|
|
1648
|
-
console.error(
|
|
1649
|
-
console.log(
|
|
2086
|
+
console.error(pc2.red(`Invalid JSON: ${err.message}`));
|
|
2087
|
+
console.log(pc2.dim(` Received: ${jsonArgs}`));
|
|
1650
2088
|
return;
|
|
1651
2089
|
}
|
|
1652
2090
|
const { tools } = await target.listTools();
|
|
@@ -1656,14 +2094,14 @@ async function cmdToolsCall(target, interceptor, rest) {
|
|
|
1656
2094
|
const required = schema.required ?? [];
|
|
1657
2095
|
const missing = required.filter((r) => !(r in args));
|
|
1658
2096
|
if (missing.length > 0) {
|
|
1659
|
-
console.log(
|
|
2097
|
+
console.log(pc2.yellow(`
|
|
1660
2098
|
Missing required arguments: ${missing.join(", ")}`));
|
|
1661
2099
|
console.log();
|
|
1662
2100
|
const scaffolded = scaffoldArgs(schema);
|
|
1663
|
-
console.log(
|
|
2101
|
+
console.log(pc2.dim(" Try:"));
|
|
1664
2102
|
console.log(` tools/call ${toolName} ${scaffolded}`);
|
|
1665
2103
|
console.log();
|
|
1666
|
-
console.log(
|
|
2104
|
+
console.log(pc2.dim(" Or run without args for interactive mode:"));
|
|
1667
2105
|
console.log(` tools/call ${toolName}`);
|
|
1668
2106
|
console.log();
|
|
1669
2107
|
return;
|
|
@@ -1681,56 +2119,68 @@ async function cmdToolsCall(target, interceptor, rest) {
|
|
|
1681
2119
|
}
|
|
1682
2120
|
}
|
|
1683
2121
|
}
|
|
1684
|
-
console.log(
|
|
2122
|
+
console.log(pc2.dim(` Calling ${toolName}...`));
|
|
1685
2123
|
const startTime = Date.now();
|
|
1686
2124
|
const result = await interceptor.callTool(target, toolName, args, timeoutMs);
|
|
1687
2125
|
const elapsed = Date.now() - startTime;
|
|
1688
2126
|
callHistory.push({ toolName, durationMs: elapsed, timestamp: startTime });
|
|
1689
2127
|
lastToolArgsMap.set(toolName, { ...args });
|
|
1690
|
-
const width = 60;
|
|
1691
2128
|
const isError = result.isError === true;
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
2129
|
+
console.log();
|
|
2130
|
+
printResultBlock({
|
|
2131
|
+
label: isError ? "Error" : "Result",
|
|
2132
|
+
labelColor: isError ? "red" : "green",
|
|
2133
|
+
elapsed,
|
|
2134
|
+
toolName
|
|
2135
|
+
});
|
|
1697
2136
|
const content = result.content;
|
|
1698
2137
|
if (Array.isArray(content)) {
|
|
1699
2138
|
for (const item of content) {
|
|
1700
2139
|
if (item.type === "text") {
|
|
1701
|
-
|
|
2140
|
+
if (isError) {
|
|
2141
|
+
console.log(pc2.red(` \u2717 ${item.text}`));
|
|
2142
|
+
} else {
|
|
2143
|
+
try {
|
|
2144
|
+
const parsed = JSON.parse(item.text);
|
|
2145
|
+
if (typeof parsed === "object" && parsed !== null) {
|
|
2146
|
+
console.log(formatJson(parsed, 2, true));
|
|
2147
|
+
} else {
|
|
2148
|
+
console.log(pc2.yellow(` ${item.text}`));
|
|
2149
|
+
}
|
|
2150
|
+
} catch {
|
|
2151
|
+
console.log(pc2.yellow(` ${item.text}`));
|
|
2152
|
+
}
|
|
2153
|
+
}
|
|
1702
2154
|
} else {
|
|
1703
|
-
console.log(formatJson(item, 2));
|
|
2155
|
+
console.log(formatJson(item, 2, true));
|
|
1704
2156
|
}
|
|
1705
2157
|
}
|
|
1706
2158
|
} else {
|
|
1707
|
-
console.log(formatJson(result, 2));
|
|
2159
|
+
console.log(formatJson(result, 2, true));
|
|
1708
2160
|
}
|
|
1709
2161
|
if (isError) {
|
|
1710
2162
|
console.log(
|
|
1711
|
-
|
|
2163
|
+
pc2.yellow(
|
|
1712
2164
|
` \u{1F4A1} Tip: Check the tool arguments via 'tools/describe ${toolName}'
|
|
1713
2165
|
or view the raw server stderr above.`
|
|
1714
2166
|
)
|
|
1715
2167
|
);
|
|
1716
2168
|
}
|
|
1717
|
-
|
|
1718
|
-
const bottomPads = Math.max(0, width - 4 - elapsedStr.length);
|
|
1719
|
-
console.log(` ${pc.dim("\u2500".repeat(bottomPads))} ${pc.dim(elapsedStr)} ${pc.dim("\u2500\u2500")}`);
|
|
2169
|
+
console.log();
|
|
1720
2170
|
}
|
|
1721
2171
|
async function interactiveArgPrompt(target, interceptor, toolName, clearPrevious = false) {
|
|
1722
2172
|
const { tools } = await target.listTools();
|
|
1723
2173
|
const tool = tools.find((t) => t.name === toolName);
|
|
1724
2174
|
if (!tool) {
|
|
1725
|
-
console.log(
|
|
2175
|
+
console.log(pc2.red(`Tool "${toolName}" not found.`));
|
|
1726
2176
|
const suggestion = suggestCommand(
|
|
1727
2177
|
toolName,
|
|
1728
2178
|
tools.map((t) => t.name)
|
|
1729
2179
|
);
|
|
1730
2180
|
if (suggestion) {
|
|
1731
|
-
console.log(
|
|
2181
|
+
console.log(pc2.yellow(`Did you mean ${pc2.bold(suggestion)}?`));
|
|
1732
2182
|
} else {
|
|
1733
|
-
console.log(
|
|
2183
|
+
console.log(pc2.dim(`Available: ${tools.map((t) => t.name).join(", ")}`));
|
|
1734
2184
|
}
|
|
1735
2185
|
return null;
|
|
1736
2186
|
}
|
|
@@ -1740,9 +2190,9 @@ async function interactiveArgPrompt(target, interceptor, toolName, clearPrevious
|
|
|
1740
2190
|
return {};
|
|
1741
2191
|
}
|
|
1742
2192
|
if (isScriptMode) {
|
|
1743
|
-
console.log(
|
|
2193
|
+
console.log(pc2.yellow(` Tool "${toolName}" requires arguments.`));
|
|
1744
2194
|
const scaffolded = scaffoldArgs(schema);
|
|
1745
|
-
console.log(
|
|
2195
|
+
console.log(pc2.dim(` Usage: tools/call ${toolName} ${scaffolded}`));
|
|
1746
2196
|
return null;
|
|
1747
2197
|
}
|
|
1748
2198
|
const required = schema.required ?? [];
|
|
@@ -1751,10 +2201,10 @@ async function interactiveArgPrompt(target, interceptor, toolName, clearPrevious
|
|
|
1751
2201
|
const optionalProps = allProps.filter(([name]) => !required.includes(name));
|
|
1752
2202
|
const previousArgs = clearPrevious ? void 0 : lastToolArgsMap.get(toolName);
|
|
1753
2203
|
console.log();
|
|
1754
|
-
console.log(` ${
|
|
2204
|
+
console.log(` ${pc2.bold(tool.name)}${tool.description ? pc2.dim(` \u2014 ${tool.description}`) : ""}`);
|
|
1755
2205
|
if (previousArgs) {
|
|
1756
|
-
console.log(
|
|
1757
|
-
console.log(
|
|
2206
|
+
console.log(pc2.dim(` Previous: ${JSON.stringify(previousArgs)}`));
|
|
2207
|
+
console.log(pc2.dim(" Press Enter to reuse values, or type to override."));
|
|
1758
2208
|
}
|
|
1759
2209
|
console.log();
|
|
1760
2210
|
const collectedArgs = {};
|
|
@@ -1772,9 +2222,21 @@ async function interactiveArgPrompt(target, interceptor, toolName, clearPrevious
|
|
|
1772
2222
|
const typeStr = prop.type ?? "any";
|
|
1773
2223
|
const desc = prop.description ?? "";
|
|
1774
2224
|
const prevVal = previousArgs?.[name];
|
|
1775
|
-
const label = desc ? `${name} ${
|
|
1776
|
-
const answerStr = await
|
|
1777
|
-
{
|
|
2225
|
+
const label = desc ? `${name} ${pc2.dim(`(${typeStr})`)} ${pc2.dim(desc)}` : `${name} ${pc2.dim(`(${typeStr})`)}`;
|
|
2226
|
+
const answerStr = await input2(
|
|
2227
|
+
{
|
|
2228
|
+
message: label,
|
|
2229
|
+
default: prevVal !== void 0 ? String(prevVal) : void 0,
|
|
2230
|
+
validate: (val) => {
|
|
2231
|
+
if (!val && typeStr !== "string") {
|
|
2232
|
+
return "This argument is required and cannot be empty.";
|
|
2233
|
+
}
|
|
2234
|
+
if (val && (typeStr === "number" || typeStr === "integer") && Number.isNaN(Number(val))) {
|
|
2235
|
+
return "Must be a valid number.";
|
|
2236
|
+
}
|
|
2237
|
+
return true;
|
|
2238
|
+
}
|
|
2239
|
+
},
|
|
1778
2240
|
{ signal: abortController.signal }
|
|
1779
2241
|
);
|
|
1780
2242
|
collectedArgs[name] = coerceValue(answerStr, typeStr);
|
|
@@ -1802,16 +2264,25 @@ async function interactiveArgPrompt(target, interceptor, toolName, clearPrevious
|
|
|
1802
2264
|
const typeStr = prop.type ?? "any";
|
|
1803
2265
|
const desc = prop.description ?? "";
|
|
1804
2266
|
const prevVal = previousArgs?.[name];
|
|
1805
|
-
const label = desc ? `${name} ${
|
|
1806
|
-
const answerStr = await
|
|
1807
|
-
{
|
|
2267
|
+
const label = desc ? `${name} ${pc2.dim(`(${typeStr})`)} ${pc2.dim(desc)}` : `${name} ${pc2.dim(`(${typeStr})`)}`;
|
|
2268
|
+
const answerStr = await input2(
|
|
2269
|
+
{
|
|
2270
|
+
message: label,
|
|
2271
|
+
default: prevVal !== void 0 ? String(prevVal) : void 0,
|
|
2272
|
+
validate: (val) => {
|
|
2273
|
+
if (val && (typeStr === "number" || typeStr === "integer") && Number.isNaN(Number(val))) {
|
|
2274
|
+
return "Must be a valid number.";
|
|
2275
|
+
}
|
|
2276
|
+
return true;
|
|
2277
|
+
}
|
|
2278
|
+
},
|
|
1808
2279
|
{ signal: abortController.signal }
|
|
1809
2280
|
);
|
|
1810
2281
|
collectedArgs[name] = coerceValue(answerStr, typeStr);
|
|
1811
2282
|
}
|
|
1812
2283
|
}
|
|
1813
2284
|
console.log();
|
|
1814
|
-
console.log(
|
|
2285
|
+
console.log(formatJson(collectedArgs, 2, true));
|
|
1815
2286
|
const shouldExecute = await confirm(
|
|
1816
2287
|
{ message: "Execute?", default: true },
|
|
1817
2288
|
{ signal: abortController.signal }
|
|
@@ -1832,14 +2303,14 @@ function printJsonTemplate(filled, allProps, currentProp) {
|
|
|
1832
2303
|
const parts = [];
|
|
1833
2304
|
for (const [name] of allProps) {
|
|
1834
2305
|
if (name in filled) {
|
|
1835
|
-
parts.push(`"${name}": ${
|
|
2306
|
+
parts.push(`"${name}": ${pc2.green(JSON.stringify(filled[name]))}`);
|
|
1836
2307
|
} else if (name === currentProp) {
|
|
1837
|
-
parts.push(`"${name}": ${
|
|
2308
|
+
parts.push(`"${name}": ${pc2.yellow("\u2592")}`);
|
|
1838
2309
|
} else {
|
|
1839
|
-
parts.push(`"${name}": ${
|
|
2310
|
+
parts.push(`"${name}": ${pc2.dim("\u2592")}`);
|
|
1840
2311
|
}
|
|
1841
2312
|
}
|
|
1842
|
-
console.log(
|
|
2313
|
+
console.log(pc2.dim(" { ") + parts.join(pc2.dim(", ")) + pc2.dim(" }"));
|
|
1843
2314
|
console.log();
|
|
1844
2315
|
}
|
|
1845
2316
|
function coerceValue(input3, type) {
|
|
@@ -1888,11 +2359,11 @@ function question(rl, prompt) {
|
|
|
1888
2359
|
async function cmdToolsScaffold(target, rest) {
|
|
1889
2360
|
const name = rest.trim();
|
|
1890
2361
|
if (!name) {
|
|
1891
|
-
console.log(
|
|
2362
|
+
console.log(pc2.yellow(" Usage: tools/scaffold <name>"));
|
|
1892
2363
|
if (cachedToolNames.length > 0) {
|
|
1893
2364
|
const preview = cachedToolNames.slice(0, 6);
|
|
1894
2365
|
const more = cachedToolNames.length > 6 ? `, ... (${cachedToolNames.length} total)` : "";
|
|
1895
|
-
console.log(
|
|
2366
|
+
console.log(pc2.dim(`
|
|
1896
2367
|
Available tools: ${preview.join(", ")}${more}`));
|
|
1897
2368
|
}
|
|
1898
2369
|
return;
|
|
@@ -1900,107 +2371,147 @@ async function cmdToolsScaffold(target, rest) {
|
|
|
1900
2371
|
const { tools } = await target.listTools();
|
|
1901
2372
|
const tool = tools.find((t) => t.name === name);
|
|
1902
2373
|
if (!tool) {
|
|
1903
|
-
console.log(
|
|
2374
|
+
console.log(pc2.red(`Tool "${name}" not found.`));
|
|
1904
2375
|
const suggestion = suggestCommand(
|
|
1905
2376
|
name,
|
|
1906
2377
|
tools.map((t) => t.name)
|
|
1907
2378
|
);
|
|
1908
2379
|
if (suggestion) {
|
|
1909
|
-
console.log(
|
|
2380
|
+
console.log(pc2.yellow(`Did you mean ${pc2.bold(suggestion)}?`));
|
|
1910
2381
|
} else {
|
|
1911
|
-
console.log(
|
|
2382
|
+
console.log(pc2.dim(`Available: ${tools.map((t) => t.name).join(", ")}`));
|
|
1912
2383
|
}
|
|
1913
2384
|
return;
|
|
1914
2385
|
}
|
|
1915
2386
|
const scaffolded = scaffoldArgs(tool.inputSchema);
|
|
1916
|
-
console.log(
|
|
2387
|
+
console.log(pc2.cyan("\n Ready-to-paste command:"));
|
|
1917
2388
|
console.log(` tools/call ${name} ${scaffolded}
|
|
1918
2389
|
`);
|
|
1919
2390
|
}
|
|
1920
2391
|
async function cmdResourcesList(target) {
|
|
1921
2392
|
const { resources } = await target.listResources();
|
|
1922
2393
|
if (resources.length === 0) {
|
|
1923
|
-
console.log(
|
|
2394
|
+
console.log(pc2.dim(" No resources available."));
|
|
1924
2395
|
return;
|
|
1925
2396
|
}
|
|
1926
2397
|
const uriWidth = Math.max(6, ...resources.map((r) => r.uri.length));
|
|
1927
|
-
console.log(
|
|
1928
|
-
console.log(
|
|
2398
|
+
console.log(pc2.bold(` ${"URI".padEnd(uriWidth)} Name`));
|
|
2399
|
+
console.log(pc2.dim(` ${"\u2500".repeat(uriWidth)} ${"\u2500".repeat(40)}`));
|
|
1929
2400
|
for (const r of resources) {
|
|
1930
2401
|
const uri = r.uri;
|
|
1931
|
-
const name = r.name ??
|
|
1932
|
-
console.log(` ${
|
|
2402
|
+
const name = r.name ?? pc2.dim("(unnamed)");
|
|
2403
|
+
console.log(` ${pc2.green(uri.padEnd(uriWidth))} ${name}`);
|
|
1933
2404
|
}
|
|
1934
|
-
console.log(
|
|
2405
|
+
console.log(pc2.dim(`
|
|
1935
2406
|
${resources.length} resource(s) total.`));
|
|
1936
2407
|
}
|
|
1937
|
-
async function cmdResourcesRead(target, rest) {
|
|
2408
|
+
async function cmdResourcesRead(target, rest, interceptor) {
|
|
1938
2409
|
const uri = rest.trim();
|
|
1939
2410
|
if (!uri) {
|
|
1940
|
-
|
|
2411
|
+
if (!isScriptMode && cachedResourceUris.length > 0 && process.stdin.isTTY && interceptor) {
|
|
2412
|
+
const picked = await withSuspendedReadline(target, interceptor, async () => {
|
|
2413
|
+
const { resources } = await target.listResources();
|
|
2414
|
+
return pickInteractive(
|
|
2415
|
+
resources.map((r) => ({
|
|
2416
|
+
name: r.uri,
|
|
2417
|
+
description: r.description || r.name
|
|
2418
|
+
})),
|
|
2419
|
+
"Pick a resource to read:"
|
|
2420
|
+
);
|
|
2421
|
+
});
|
|
2422
|
+
if (!picked) return;
|
|
2423
|
+
return cmdResourcesRead(target, picked, interceptor);
|
|
2424
|
+
}
|
|
2425
|
+
console.log(pc2.yellow(" Usage: resources/read <uri>"));
|
|
1941
2426
|
if (cachedResourceUris.length > 0) {
|
|
1942
2427
|
const preview = cachedResourceUris.slice(0, 5);
|
|
1943
2428
|
const more = cachedResourceUris.length > 5 ? `, ... (${cachedResourceUris.length} total)` : "";
|
|
1944
|
-
console.log(
|
|
2429
|
+
console.log(pc2.dim(`
|
|
1945
2430
|
Available resources: ${preview.join(", ")}${more}`));
|
|
1946
2431
|
}
|
|
1947
2432
|
return;
|
|
1948
2433
|
}
|
|
2434
|
+
const startTime = Date.now();
|
|
1949
2435
|
const result = await target.readResource({ uri });
|
|
2436
|
+
const elapsed = Date.now() - startTime;
|
|
2437
|
+
console.log();
|
|
2438
|
+
printResultBlock({ label: "Resource", labelColor: "cyan", elapsed, detail: uri });
|
|
1950
2439
|
for (const item of result.contents) {
|
|
1951
2440
|
if (item.text !== void 0) {
|
|
1952
|
-
|
|
2441
|
+
const text = item.text;
|
|
2442
|
+
try {
|
|
2443
|
+
const parsed = JSON.parse(text);
|
|
2444
|
+
if (typeof parsed === "object" && parsed !== null) {
|
|
2445
|
+
console.log(formatJson(parsed, 2, true));
|
|
2446
|
+
} else {
|
|
2447
|
+
console.log(pc2.yellow(` ${text}`));
|
|
2448
|
+
}
|
|
2449
|
+
} catch {
|
|
2450
|
+
console.log(pc2.yellow(` ${text}`));
|
|
2451
|
+
}
|
|
1953
2452
|
} else if (item.blob !== void 0) {
|
|
1954
2453
|
const mimeType = item.mimeType ?? "application/octet-stream";
|
|
1955
2454
|
const sizeBytes = Buffer.from(item.blob, "base64").length;
|
|
1956
|
-
console.log(
|
|
2455
|
+
console.log(pc2.dim(` [Binary: ${mimeType}, ${sizeBytes} bytes]`));
|
|
1957
2456
|
} else {
|
|
1958
|
-
console.log(formatJson(item, 2));
|
|
2457
|
+
console.log(formatJson(item, 2, true));
|
|
1959
2458
|
}
|
|
1960
2459
|
}
|
|
2460
|
+
console.log();
|
|
1961
2461
|
}
|
|
1962
2462
|
async function cmdResourcesTemplates(target) {
|
|
1963
2463
|
const { resourceTemplates } = await target.listResourceTemplates();
|
|
1964
2464
|
if (resourceTemplates.length === 0) {
|
|
1965
|
-
console.log(
|
|
2465
|
+
console.log(pc2.dim(" No resource templates available."));
|
|
1966
2466
|
return;
|
|
1967
2467
|
}
|
|
1968
2468
|
const uriWidth = Math.max(
|
|
1969
2469
|
12,
|
|
1970
2470
|
...resourceTemplates.map((t) => t.uriTemplate.length)
|
|
1971
2471
|
);
|
|
1972
|
-
console.log(
|
|
1973
|
-
console.log(
|
|
2472
|
+
console.log(pc2.bold(` ${"URI Template".padEnd(uriWidth)} Name`));
|
|
2473
|
+
console.log(pc2.dim(` ${"\u2500".repeat(uriWidth)} ${"\u2500".repeat(40)}`));
|
|
1974
2474
|
for (const t of resourceTemplates) {
|
|
1975
2475
|
const uriTemplate = t.uriTemplate;
|
|
1976
|
-
const name = t.name ??
|
|
1977
|
-
console.log(` ${
|
|
2476
|
+
const name = t.name ?? pc2.dim("(unnamed)");
|
|
2477
|
+
console.log(` ${pc2.green(uriTemplate.padEnd(uriWidth))} ${name}`);
|
|
1978
2478
|
}
|
|
1979
|
-
console.log(
|
|
2479
|
+
console.log(pc2.dim(`
|
|
1980
2480
|
${resourceTemplates.length} template(s) total.`));
|
|
1981
2481
|
}
|
|
1982
2482
|
async function cmdPromptsList(target) {
|
|
1983
2483
|
const { prompts } = await target.listPrompts();
|
|
1984
2484
|
if (prompts.length === 0) {
|
|
1985
|
-
console.log(
|
|
2485
|
+
console.log(pc2.dim(" No prompts available."));
|
|
1986
2486
|
return;
|
|
1987
2487
|
}
|
|
1988
2488
|
const nameWidth = Math.max(8, ...prompts.map((p) => p.name.length));
|
|
1989
|
-
console.log(
|
|
1990
|
-
console.log(
|
|
2489
|
+
console.log(pc2.bold(` ${"Name".padEnd(nameWidth)} Description`));
|
|
2490
|
+
console.log(pc2.dim(` ${"\u2500".repeat(nameWidth)} ${"\u2500".repeat(50)}`));
|
|
1991
2491
|
for (const p of prompts) {
|
|
1992
|
-
const desc = p.description ? p.description.length > 60 ? `${p.description.slice(0, 57)}...` : p.description :
|
|
1993
|
-
console.log(` ${
|
|
2492
|
+
const desc = p.description ? p.description.length > 60 ? `${p.description.slice(0, 57)}...` : p.description : pc2.dim("(no description)");
|
|
2493
|
+
console.log(` ${pc2.green(p.name.padEnd(nameWidth))} ${desc}`);
|
|
1994
2494
|
}
|
|
1995
|
-
console.log(
|
|
2495
|
+
console.log(pc2.dim(`
|
|
1996
2496
|
${prompts.length} prompt(s) total.`));
|
|
1997
2497
|
}
|
|
1998
|
-
async function cmdPromptsGet(target, rest) {
|
|
2498
|
+
async function cmdPromptsGet(target, rest, interceptor) {
|
|
1999
2499
|
const { toolName: promptName, jsonArgs } = parseCallArgs(rest);
|
|
2000
2500
|
if (!promptName) {
|
|
2001
|
-
|
|
2501
|
+
if (!isScriptMode && cachedPromptNames.length > 0 && process.stdin.isTTY && interceptor) {
|
|
2502
|
+
const picked = await withSuspendedReadline(target, interceptor, async () => {
|
|
2503
|
+
const { prompts } = await target.listPrompts();
|
|
2504
|
+
return pickInteractive(
|
|
2505
|
+
prompts.map((p) => ({ name: p.name, description: p.description })),
|
|
2506
|
+
"Pick a prompt to get:"
|
|
2507
|
+
);
|
|
2508
|
+
});
|
|
2509
|
+
if (!picked) return;
|
|
2510
|
+
return cmdPromptsGet(target, picked);
|
|
2511
|
+
}
|
|
2512
|
+
console.log(pc2.yellow(" Usage: prompts/get <name> [json_args]"));
|
|
2002
2513
|
if (cachedPromptNames.length > 0) {
|
|
2003
|
-
console.log(
|
|
2514
|
+
console.log(pc2.dim(`
|
|
2004
2515
|
Available prompts: ${cachedPromptNames.join(", ")}`));
|
|
2005
2516
|
}
|
|
2006
2517
|
return;
|
|
@@ -2010,48 +2521,71 @@ async function cmdPromptsGet(target, rest) {
|
|
|
2010
2521
|
try {
|
|
2011
2522
|
promptArgs = JSON.parse(jsonArgs);
|
|
2012
2523
|
} catch (err) {
|
|
2013
|
-
console.error(
|
|
2014
|
-
console.log(
|
|
2524
|
+
console.error(pc2.red(`Invalid JSON: ${err.message}`));
|
|
2525
|
+
console.log(pc2.dim(` Received: ${jsonArgs}`));
|
|
2015
2526
|
return;
|
|
2016
2527
|
}
|
|
2017
2528
|
}
|
|
2529
|
+
const startTime = Date.now();
|
|
2018
2530
|
const result = await target.getPrompt({ name: promptName, arguments: promptArgs });
|
|
2531
|
+
const elapsed = Date.now() - startTime;
|
|
2532
|
+
if (result.messages.length === 0) {
|
|
2533
|
+
console.log(pc2.dim(" No messages returned."));
|
|
2534
|
+
return;
|
|
2535
|
+
}
|
|
2536
|
+
console.log();
|
|
2537
|
+
printResultBlock({ label: "Prompt", labelColor: "blue", elapsed, detail: promptName });
|
|
2019
2538
|
for (const msg of result.messages) {
|
|
2020
|
-
const role = msg.role === "user" ?
|
|
2539
|
+
const role = msg.role === "user" ? pc2.blue("user") : pc2.magenta("assistant");
|
|
2021
2540
|
const text = msg.content.text ?? JSON.stringify(msg.content);
|
|
2022
|
-
|
|
2541
|
+
try {
|
|
2542
|
+
const parsed = JSON.parse(text);
|
|
2543
|
+
if (typeof parsed === "object" && parsed !== null) {
|
|
2544
|
+
console.log(` ${pc2.bold(role)}:`);
|
|
2545
|
+
console.log(formatJson(parsed, 4, true));
|
|
2546
|
+
} else {
|
|
2547
|
+
console.log(` ${pc2.bold(role)}: ${pc2.yellow(text)}`);
|
|
2548
|
+
}
|
|
2549
|
+
} catch {
|
|
2550
|
+
console.log(` ${pc2.bold(role)}: ${pc2.yellow(text)}`);
|
|
2551
|
+
}
|
|
2023
2552
|
}
|
|
2553
|
+
console.log();
|
|
2024
2554
|
}
|
|
2025
2555
|
async function cmdPing(target) {
|
|
2026
2556
|
try {
|
|
2027
2557
|
const elapsed = await target.ping();
|
|
2028
|
-
console.log(
|
|
2558
|
+
console.log();
|
|
2559
|
+
console.log(pc2.green(` \u2713 Pong! Round-trip: ${elapsed}ms`));
|
|
2560
|
+
console.log();
|
|
2029
2561
|
} catch (err) {
|
|
2030
|
-
console.
|
|
2562
|
+
console.log();
|
|
2563
|
+
console.error(pc2.red(` \u2717 Ping failed: ${err.message}`));
|
|
2564
|
+
console.log();
|
|
2031
2565
|
}
|
|
2032
2566
|
}
|
|
2033
2567
|
async function cmdLogLevel(target, rest) {
|
|
2034
2568
|
const level = rest.trim().toLowerCase();
|
|
2035
2569
|
if (!level) {
|
|
2036
|
-
console.log(
|
|
2037
|
-
console.log(
|
|
2570
|
+
console.log(pc2.yellow(" Usage: log-level <level>"));
|
|
2571
|
+
console.log(pc2.dim(` Valid levels: ${LOG_LEVELS.join(", ")}`));
|
|
2038
2572
|
return;
|
|
2039
2573
|
}
|
|
2040
2574
|
if (!LOG_LEVELS.includes(level)) {
|
|
2041
2575
|
const suggestion = suggestCommand(level, [...LOG_LEVELS]);
|
|
2042
2576
|
const hint = suggestion ? ` Did you mean "${suggestion}"?` : "";
|
|
2043
|
-
console.log(
|
|
2044
|
-
console.log(
|
|
2577
|
+
console.log(pc2.red(` Unknown log level: "${level}".${hint}`));
|
|
2578
|
+
console.log(pc2.dim(` Valid levels: ${LOG_LEVELS.join(", ")}`));
|
|
2045
2579
|
return;
|
|
2046
2580
|
}
|
|
2047
2581
|
try {
|
|
2048
2582
|
await target.setLoggingLevel(level);
|
|
2049
|
-
console.log(
|
|
2583
|
+
console.log(pc2.green(` \u2713 Logging level set to: ${level}`));
|
|
2050
2584
|
} catch (err) {
|
|
2051
|
-
console.error(
|
|
2585
|
+
console.error(pc2.red(` \u2717 Failed to set log level: ${err.message}`));
|
|
2052
2586
|
const caps = target.getServerCapabilities();
|
|
2053
2587
|
if (!caps?.logging) {
|
|
2054
|
-
console.log(
|
|
2588
|
+
console.log(pc2.dim(" The server does not advertise logging support."));
|
|
2055
2589
|
}
|
|
2056
2590
|
}
|
|
2057
2591
|
}
|
|
@@ -2059,28 +2593,28 @@ function cmdHistory(target, rest) {
|
|
|
2059
2593
|
const arg = rest.trim();
|
|
2060
2594
|
if (arg === "clear") {
|
|
2061
2595
|
target.clearHistory();
|
|
2062
|
-
console.log(
|
|
2596
|
+
console.log(pc2.dim(" History cleared."));
|
|
2063
2597
|
return;
|
|
2064
2598
|
}
|
|
2065
2599
|
const count = arg ? Number.parseInt(arg, 10) : 20;
|
|
2066
2600
|
const records = target.getHistory(Number.isNaN(count) ? 20 : count);
|
|
2067
2601
|
if (records.length === 0) {
|
|
2068
|
-
console.log(
|
|
2602
|
+
console.log(pc2.dim(" No request history yet."));
|
|
2069
2603
|
return;
|
|
2070
2604
|
}
|
|
2071
|
-
console.log(
|
|
2072
|
-
console.log(
|
|
2605
|
+
console.log(pc2.bold("\n Request History"));
|
|
2606
|
+
console.log(pc2.dim(` ${"\u2500".repeat(70)}`));
|
|
2073
2607
|
for (const rec of records) {
|
|
2074
2608
|
const time = new Date(rec.timestamp).toLocaleTimeString();
|
|
2075
2609
|
const dur = `${rec.durationMs}ms`;
|
|
2076
|
-
const hasError = rec.error ?
|
|
2610
|
+
const hasError = rec.error ? pc2.red(" \u2717") : "";
|
|
2077
2611
|
console.log(
|
|
2078
|
-
` ${
|
|
2612
|
+
` ${pc2.dim(`#${rec.id}`)} ${pc2.dim(time)} ${pc2.green(rec.method.padEnd(30))} ${pc2.cyan(dur.padStart(8))}${hasError}`
|
|
2079
2613
|
);
|
|
2080
2614
|
}
|
|
2081
2615
|
const total = target.getHistory().length;
|
|
2082
2616
|
console.log(
|
|
2083
|
-
|
|
2617
|
+
pc2.dim(`
|
|
2084
2618
|
Showing ${records.length} of ${total} total. Use "history clear" to reset.`)
|
|
2085
2619
|
);
|
|
2086
2620
|
console.log();
|
|
@@ -2089,26 +2623,26 @@ function cmdNotifications(target, rest) {
|
|
|
2089
2623
|
const arg = rest.trim();
|
|
2090
2624
|
if (arg === "clear") {
|
|
2091
2625
|
target.clearNotifications();
|
|
2092
|
-
console.log(
|
|
2626
|
+
console.log(pc2.dim(" Notifications cleared."));
|
|
2093
2627
|
return;
|
|
2094
2628
|
}
|
|
2095
2629
|
const count = arg ? Number.parseInt(arg, 10) : 20;
|
|
2096
2630
|
const records = target.getNotifications(Number.isNaN(count) ? 20 : count);
|
|
2097
2631
|
if (records.length === 0) {
|
|
2098
|
-
console.log(
|
|
2099
|
-
console.log(
|
|
2632
|
+
console.log(pc2.dim(" No notifications received yet."));
|
|
2633
|
+
console.log(pc2.dim(" Notifications appear inline as they arrive from the server."));
|
|
2100
2634
|
return;
|
|
2101
2635
|
}
|
|
2102
|
-
console.log(
|
|
2103
|
-
console.log(
|
|
2636
|
+
console.log(pc2.bold("\n Server Notifications"));
|
|
2637
|
+
console.log(pc2.dim(` ${"\u2500".repeat(70)}`));
|
|
2104
2638
|
for (const n of records) {
|
|
2105
2639
|
const time = new Date(n.timestamp).toLocaleTimeString();
|
|
2106
|
-
const params = n.params ? ` ${
|
|
2107
|
-
console.log(` ${
|
|
2640
|
+
const params = n.params ? ` ${pc2.dim(JSON.stringify(n.params))}` : "";
|
|
2641
|
+
console.log(` ${pc2.dim(time)} ${pc2.yellow(n.method)}${params}`);
|
|
2108
2642
|
}
|
|
2109
2643
|
const total = target.getNotifications().length;
|
|
2110
2644
|
console.log(
|
|
2111
|
-
|
|
2645
|
+
pc2.dim(`
|
|
2112
2646
|
Showing ${records.length} of ${total} total. Use "notifications clear" to reset.`)
|
|
2113
2647
|
);
|
|
2114
2648
|
console.log();
|
|
@@ -2116,44 +2650,44 @@ function cmdNotifications(target, rest) {
|
|
|
2116
2650
|
async function cmdResourcesSubscribe(target, rest) {
|
|
2117
2651
|
const uri = rest.trim();
|
|
2118
2652
|
if (!uri) {
|
|
2119
|
-
console.log(
|
|
2653
|
+
console.log(pc2.yellow(" Usage: resources/subscribe <uri>"));
|
|
2120
2654
|
if (cachedResourceUris.length > 0) {
|
|
2121
|
-
console.log(
|
|
2655
|
+
console.log(pc2.dim(` Available: ${cachedResourceUris.join(", ")}`));
|
|
2122
2656
|
}
|
|
2123
2657
|
return;
|
|
2124
2658
|
}
|
|
2125
2659
|
try {
|
|
2126
2660
|
await target.subscribeResource({ uri });
|
|
2127
|
-
console.log(
|
|
2128
|
-
console.log(
|
|
2661
|
+
console.log(pc2.green(` \u2713 Subscribed to: ${uri}`));
|
|
2662
|
+
console.log(pc2.dim(" You'll see notifications when this resource changes."));
|
|
2129
2663
|
} catch (err) {
|
|
2130
|
-
console.error(
|
|
2664
|
+
console.error(pc2.red(` \u2717 Subscribe failed: ${err.message}`));
|
|
2131
2665
|
}
|
|
2132
2666
|
}
|
|
2133
2667
|
async function cmdResourcesUnsubscribe(target, rest) {
|
|
2134
2668
|
const uri = rest.trim();
|
|
2135
2669
|
if (!uri) {
|
|
2136
|
-
console.log(
|
|
2670
|
+
console.log(pc2.yellow(" Usage: resources/unsubscribe <uri>"));
|
|
2137
2671
|
return;
|
|
2138
2672
|
}
|
|
2139
2673
|
try {
|
|
2140
2674
|
await target.unsubscribeResource({ uri });
|
|
2141
|
-
console.log(
|
|
2675
|
+
console.log(pc2.green(` \u2713 Unsubscribed from: ${uri}`));
|
|
2142
2676
|
} catch (err) {
|
|
2143
|
-
console.error(
|
|
2677
|
+
console.error(pc2.red(` \u2717 Unsubscribe failed: ${err.message}`));
|
|
2144
2678
|
}
|
|
2145
2679
|
}
|
|
2146
2680
|
function cmdRootsList(target) {
|
|
2147
2681
|
const roots = target.getRoots();
|
|
2148
2682
|
if (roots.length === 0) {
|
|
2149
|
-
console.log(
|
|
2150
|
-
console.log(
|
|
2683
|
+
console.log(pc2.dim(" No roots configured."));
|
|
2684
|
+
console.log(pc2.dim(" Use roots/add <uri> [name] to add one."));
|
|
2151
2685
|
return;
|
|
2152
2686
|
}
|
|
2153
|
-
console.log(
|
|
2687
|
+
console.log(pc2.bold("\n Client Roots"));
|
|
2154
2688
|
for (const r of roots) {
|
|
2155
2689
|
const name = r.name ? ` (${r.name})` : "";
|
|
2156
|
-
console.log(` ${
|
|
2690
|
+
console.log(` ${pc2.green(r.uri)}${pc2.dim(name)}`);
|
|
2157
2691
|
}
|
|
2158
2692
|
console.log();
|
|
2159
2693
|
}
|
|
@@ -2162,53 +2696,53 @@ async function cmdRootsAdd(target, rest) {
|
|
|
2162
2696
|
const uri = parts[0];
|
|
2163
2697
|
const name = parts.slice(1).join(" ") || void 0;
|
|
2164
2698
|
if (!uri) {
|
|
2165
|
-
console.log(
|
|
2166
|
-
console.log(
|
|
2699
|
+
console.log(pc2.yellow(" Usage: roots/add <uri> [name]"));
|
|
2700
|
+
console.log(pc2.dim(' Example: roots/add file:///Users/me/project "My Project"'));
|
|
2167
2701
|
return;
|
|
2168
2702
|
}
|
|
2169
2703
|
await target.addRoot({ uri, name });
|
|
2170
|
-
console.log(
|
|
2171
|
-
console.log(
|
|
2704
|
+
console.log(pc2.green(` \u2713 Root added: ${uri}`));
|
|
2705
|
+
console.log(pc2.dim(" Server has been notified of the change."));
|
|
2172
2706
|
}
|
|
2173
2707
|
async function cmdRootsRemove(target, rest) {
|
|
2174
2708
|
const uri = rest.trim();
|
|
2175
2709
|
if (!uri) {
|
|
2176
|
-
console.log(
|
|
2710
|
+
console.log(pc2.yellow(" Usage: roots/remove <uri>"));
|
|
2177
2711
|
const roots = target.getRoots();
|
|
2178
2712
|
if (roots.length > 0) {
|
|
2179
|
-
console.log(
|
|
2713
|
+
console.log(pc2.dim(` Current roots: ${roots.map((r) => r.uri).join(", ")}`));
|
|
2180
2714
|
}
|
|
2181
2715
|
return;
|
|
2182
2716
|
}
|
|
2183
2717
|
const removed = await target.removeRoot(uri);
|
|
2184
2718
|
if (removed) {
|
|
2185
|
-
console.log(
|
|
2719
|
+
console.log(pc2.green(` \u2713 Root removed: ${uri}`));
|
|
2186
2720
|
} else {
|
|
2187
|
-
console.log(
|
|
2721
|
+
console.log(pc2.yellow(` Root not found: ${uri}`));
|
|
2188
2722
|
}
|
|
2189
2723
|
}
|
|
2190
2724
|
async function cmdReconnect(target) {
|
|
2191
|
-
console.log(
|
|
2725
|
+
console.log(pc2.cyan("\u27F3 Disconnecting..."));
|
|
2192
2726
|
await target.close();
|
|
2193
2727
|
await new Promise((resolve) => setTimeout(resolve, 200));
|
|
2194
|
-
console.log(
|
|
2728
|
+
console.log(pc2.cyan("\u27F3 Reconnecting..."));
|
|
2195
2729
|
const { command, args } = target.getStatus();
|
|
2196
|
-
console.log(
|
|
2730
|
+
console.log(pc2.dim(` Command: ${command} ${args.join(" ")}`));
|
|
2197
2731
|
try {
|
|
2198
2732
|
await target.connect();
|
|
2199
2733
|
const s = target.getStatus();
|
|
2200
|
-
console.log(
|
|
2734
|
+
console.log(pc2.green(`\u2713 Reconnected (PID: ${s.pid})`));
|
|
2201
2735
|
const { tools } = await target.listTools();
|
|
2202
|
-
console.log(
|
|
2736
|
+
console.log(pc2.cyan(` ${tools.length} tool(s) available.
|
|
2203
2737
|
`));
|
|
2204
2738
|
await refreshCaches(target);
|
|
2205
2739
|
} catch (err) {
|
|
2206
|
-
console.error(
|
|
2740
|
+
console.error(pc2.red(`\u2717 Failed to reconnect: ${err.message}`));
|
|
2207
2741
|
}
|
|
2208
2742
|
}
|
|
2209
2743
|
function cmdTiming() {
|
|
2210
2744
|
if (callHistory.length === 0) {
|
|
2211
|
-
console.log(
|
|
2745
|
+
console.log(pc2.dim(" No tool calls recorded yet."));
|
|
2212
2746
|
return;
|
|
2213
2747
|
}
|
|
2214
2748
|
const groups = /* @__PURE__ */ new Map();
|
|
@@ -2218,8 +2752,8 @@ function cmdTiming() {
|
|
|
2218
2752
|
groups.set(record.toolName, list);
|
|
2219
2753
|
}
|
|
2220
2754
|
const nameWidth = Math.max(8, ...[...groups.keys()].map((n) => n.length));
|
|
2221
|
-
console.log(
|
|
2222
|
-
console.log(
|
|
2755
|
+
console.log(pc2.bold("\n Tool Call Performance"));
|
|
2756
|
+
console.log(pc2.dim(` ${"\u2500".repeat(nameWidth + 50)}`));
|
|
2223
2757
|
let totalCalls = 0;
|
|
2224
2758
|
let totalMs = 0;
|
|
2225
2759
|
let slowestName = "";
|
|
@@ -2237,12 +2771,12 @@ function cmdTiming() {
|
|
|
2237
2771
|
slowestName = name;
|
|
2238
2772
|
}
|
|
2239
2773
|
console.log(
|
|
2240
|
-
` ${
|
|
2774
|
+
` ${pc2.green(name.padEnd(nameWidth))} \xD7 ${count} avg: ${avg}ms p95: ${p95}ms max: ${max}ms`
|
|
2241
2775
|
);
|
|
2242
2776
|
}
|
|
2243
2777
|
const overallAvg = Math.round(totalMs / totalCalls);
|
|
2244
2778
|
console.log(
|
|
2245
|
-
|
|
2779
|
+
pc2.dim(
|
|
2246
2780
|
`
|
|
2247
2781
|
Total: ${totalCalls} call(s), avg ${overallAvg}ms, slowest: ${slowestName} (${slowestMs}ms)`
|
|
2248
2782
|
)
|
|
@@ -2253,79 +2787,122 @@ function cmdStatus(target) {
|
|
|
2253
2787
|
const s = target.getStatus();
|
|
2254
2788
|
const uptimeStr = s.uptime >= 60 ? `${Math.floor(s.uptime / 60)}m ${(s.uptime % 60).toFixed(0)}s` : `${s.uptime.toFixed(1)}s`;
|
|
2255
2789
|
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(` ${
|
|
2790
|
+
console.log(pc2.bold("\n Target Server Status"));
|
|
2791
|
+
console.log(` ${pc2.dim("Connected:")} ${s.connected ? pc2.green("yes") : pc2.red("no")}`);
|
|
2792
|
+
console.log(` ${pc2.dim("PID:")} ${s.pid ?? "N/A"}`);
|
|
2793
|
+
console.log(` ${pc2.dim("Uptime:")} ${uptimeStr}`);
|
|
2794
|
+
console.log(` ${pc2.dim("Last response:")} ${lastRespStr}`);
|
|
2795
|
+
console.log(` ${pc2.dim("Stderr lines:")} ${s.stderrLineCount.toLocaleString()}`);
|
|
2796
|
+
console.log(` ${pc2.dim("Reconnects:")} ${s.reconnectAttempts}/${s.maxReconnectAttempts}`);
|
|
2797
|
+
console.log(` ${pc2.dim("Command:")} ${s.command} ${s.args.join(" ")}`);
|
|
2264
2798
|
console.log();
|
|
2265
2799
|
}
|
|
2800
|
+
function printResultBlock(opts) {
|
|
2801
|
+
const colorFn = pc2[opts.labelColor];
|
|
2802
|
+
const elapsedStr = opts.elapsed < 1e3 ? `${opts.elapsed}ms` : `${(opts.elapsed / 1e3).toFixed(1)}s`;
|
|
2803
|
+
const detail = opts.detail ?? opts.toolName ?? "";
|
|
2804
|
+
console.log(` ${colorFn(opts.label)} ${pc2.dim(detail)} ${pc2.dim(`(${elapsedStr})`)}`);
|
|
2805
|
+
console.log(pc2.dim(` ${"\u2500".repeat(60)}`));
|
|
2806
|
+
}
|
|
2807
|
+
function printShortHelp() {
|
|
2808
|
+
const hasTools = !!activeCapabilities?.tools;
|
|
2809
|
+
const hasResources = !!activeCapabilities?.resources;
|
|
2810
|
+
const hasPrompts = !!activeCapabilities?.prompts;
|
|
2811
|
+
const tC = hasTools ? pc2.green : pc2.dim;
|
|
2812
|
+
const rC = hasResources ? pc2.green : pc2.dim;
|
|
2813
|
+
const pC = hasPrompts ? pc2.green : pc2.dim;
|
|
2814
|
+
console.log(`
|
|
2815
|
+
${pc2.bold("Quick Reference:")}
|
|
2816
|
+
|
|
2817
|
+
${tC("tl")} ${tC("tools/list")} ${rC("rl")} ${rC("resources/list")} ${pC("pl")} ${pC("prompts/list")}
|
|
2818
|
+
${tC("td")} ${tC("tools/describe")} ${rC("rr")} ${rC("resources/read")} ${pC("pg")} ${pC("prompts/get")}
|
|
2819
|
+
${tC("tc")} ${tC("tools/call")} ${rC("rt")} ${rC("resources/templates")}
|
|
2820
|
+
${tC("ts")} ${tC("tools/scaffold")} ${rC("rs")} ${rC("resources/subscribe")}
|
|
2821
|
+
${rC("ru")} ${rC("resources/unsubscribe")}
|
|
2822
|
+
|
|
2823
|
+
${pc2.green("ping")} ${pc2.green("status")} ${pc2.green("timing")} ${pc2.green("history")} ${pc2.green("!!")} ${pc2.green("explore")} ${pc2.green("reconnect")}
|
|
2824
|
+
|
|
2825
|
+
${pc2.dim("Type 'help' for full command reference.")}
|
|
2826
|
+
`);
|
|
2827
|
+
}
|
|
2266
2828
|
function printHelp() {
|
|
2829
|
+
const hasTools = !!activeCapabilities?.tools;
|
|
2830
|
+
const hasResources = !!activeCapabilities?.resources;
|
|
2831
|
+
const hasPrompts = !!activeCapabilities?.prompts;
|
|
2832
|
+
const hasLogging = !!activeCapabilities?.logging;
|
|
2833
|
+
const tC = hasTools ? pc2.green : pc2.dim;
|
|
2834
|
+
const rC = hasResources ? pc2.green : pc2.dim;
|
|
2835
|
+
const pC = hasPrompts ? pc2.green : pc2.dim;
|
|
2836
|
+
const lC = hasLogging ? pc2.green : pc2.dim;
|
|
2837
|
+
const tD = hasTools ? (s) => s : pc2.dim;
|
|
2838
|
+
const rD = hasResources ? (s) => s : pc2.dim;
|
|
2839
|
+
const pD = hasPrompts ? (s) => s : pc2.dim;
|
|
2840
|
+
const lD = hasLogging ? (s) => s : pc2.dim;
|
|
2841
|
+
const tH = hasTools ? pc2.bold("Tool Commands:") : pc2.dim(pc2.bold("Tool Commands:")) + pc2.dim(" (Unsupported)");
|
|
2842
|
+
const rH = hasResources ? pc2.bold("Resource Commands:") : pc2.dim(pc2.bold("Resource Commands:")) + pc2.dim(" (Unsupported)");
|
|
2843
|
+
const pH = hasPrompts ? pc2.bold("Prompt Commands:") : pc2.dim(pc2.bold("Prompt Commands:")) + pc2.dim(" (Unsupported)");
|
|
2267
2844
|
console.log(`
|
|
2268
|
-
${
|
|
2845
|
+
${tH}
|
|
2269
2846
|
|
|
2270
|
-
${
|
|
2271
|
-
${
|
|
2272
|
-
${
|
|
2273
|
-
Options: ${
|
|
2274
|
-
${
|
|
2275
|
-
${
|
|
2276
|
-
${
|
|
2847
|
+
${tC("tools/list")} ${tD("List all available tools")}
|
|
2848
|
+
${tC("tools/describe")} <name> ${tD("Show a tool's input schema")}
|
|
2849
|
+
${tC("tools/call")} <name> [json] [opts] ${tD("Call a tool (interactive if no json)")}
|
|
2850
|
+
${tD("Options:")} ${pc2.dim("--timeout <ms>")} ${tD("Override default timeout (60s)")}
|
|
2851
|
+
${pc2.dim("--clear")} ${tD("Ignore remembered argument defaults")}
|
|
2852
|
+
${tC("tools/scaffold")} <name> ${tD("Generate a template for a tool's arguments")}
|
|
2853
|
+
${tC("tools/forget")} [name] ${tD("Clear remembered interactive defaults")}
|
|
2277
2854
|
|
|
2278
|
-
${
|
|
2855
|
+
${rH}
|
|
2279
2856
|
|
|
2280
|
-
${
|
|
2281
|
-
${
|
|
2282
|
-
${
|
|
2283
|
-
${
|
|
2284
|
-
${
|
|
2857
|
+
${rC("resources/list")} ${rD("List all available resources")}
|
|
2858
|
+
${rC("resources/read")} <uri> ${rD("Read a resource by URI")}
|
|
2859
|
+
${rC("resources/templates")} ${rD("List resource templates")}
|
|
2860
|
+
${rC("resources/subscribe")} <uri> ${rD("Subscribe to resource changes")}
|
|
2861
|
+
${rC("resources/unsubscribe")} <uri> ${rD("Unsubscribe from resource changes")}
|
|
2285
2862
|
|
|
2286
|
-
${
|
|
2863
|
+
${pH}
|
|
2287
2864
|
|
|
2288
|
-
${
|
|
2289
|
-
${
|
|
2865
|
+
${pC("prompts/list")} ${pD("List all available prompts")}
|
|
2866
|
+
${pC("prompts/get")} <name> [json_args] ${pD("Get a prompt with arguments")}
|
|
2290
2867
|
|
|
2291
|
-
${
|
|
2868
|
+
${pc2.bold("Protocol Commands:")}
|
|
2292
2869
|
|
|
2293
|
-
${
|
|
2294
|
-
${
|
|
2295
|
-
${
|
|
2296
|
-
${
|
|
2870
|
+
${pc2.green("ping")} Verify connection, show round-trip time
|
|
2871
|
+
${lC("log-level")} <level> ${lD("Set server logging verbosity")}${hasLogging ? "" : pc2.dim(" (Unsupported)")}
|
|
2872
|
+
${pc2.green("history")} [count|clear] Show request/response history
|
|
2873
|
+
${pc2.green("notifications")} [count|clear] Show server notifications
|
|
2297
2874
|
|
|
2298
|
-
${
|
|
2875
|
+
${pc2.bold("Roots Management:")}
|
|
2299
2876
|
|
|
2300
|
-
${
|
|
2301
|
-
${
|
|
2302
|
-
${
|
|
2877
|
+
${pc2.green("roots/list")} Show configured client roots
|
|
2878
|
+
${pc2.green("roots/add")} <uri> [name] Add a root directory
|
|
2879
|
+
${pc2.green("roots/remove")} <uri> Remove a root directory
|
|
2303
2880
|
|
|
2304
|
-
${
|
|
2881
|
+
${pc2.bold("Session Commands:")}
|
|
2305
2882
|
|
|
2306
|
-
${
|
|
2307
|
-
${
|
|
2308
|
-
${
|
|
2309
|
-
${
|
|
2310
|
-
${
|
|
2311
|
-
${
|
|
2883
|
+
${pc2.green("!!")} / ${pc2.green("last")} Re-run the last command
|
|
2884
|
+
${pc2.green("reconnect")} Disconnect and reconnect to the server
|
|
2885
|
+
${pc2.green("timing")} Show tool call performance stats
|
|
2886
|
+
${pc2.green("status")} Show target server status
|
|
2887
|
+
${pc2.green("help")} Show this help
|
|
2888
|
+
${pc2.green("exit")} / ${pc2.green("quit")} Disconnect and exit
|
|
2312
2889
|
|
|
2313
|
-
${
|
|
2890
|
+
${pc2.bold("Shortcuts:")}
|
|
2314
2891
|
|
|
2315
|
-
${
|
|
2316
|
-
${
|
|
2317
|
-
${
|
|
2318
|
-
${
|
|
2319
|
-
${
|
|
2892
|
+
${tC("tl")} ${tC("tools/list")} ${rC("rl")} ${rC("resources/list")} ${pC("pl")} ${pC("prompts/list")}
|
|
2893
|
+
${tC("td")} ${tC("tools/describe")} ${rC("rr")} ${rC("resources/read")} ${pC("pg")} ${pC("prompts/get")}
|
|
2894
|
+
${tC("tc")} ${tC("tools/call")} ${rC("rt")} ${rC("resources/templates")}
|
|
2895
|
+
${tC("ts")} ${tC("tools/scaffold")} ${rC("rs")} ${rC("resources/subscribe")}
|
|
2896
|
+
${rC("ru")} ${rC("resources/unsubscribe")}
|
|
2320
2897
|
|
|
2321
|
-
${
|
|
2322
|
-
${
|
|
2323
|
-
${
|
|
2324
|
-
${
|
|
2898
|
+
${pc2.dim("Lines starting with # are treated as comments.")}
|
|
2899
|
+
${pc2.dim('JSON arguments can contain spaces: tools/call say {"message": "hello world"}')}
|
|
2900
|
+
${pc2.dim("Run tools/call <name> without JSON for interactive argument prompting.")}
|
|
2901
|
+
${pc2.dim("Use tools/call <name> --clear to ignore remembered defaults.")}
|
|
2325
2902
|
`);
|
|
2326
2903
|
}
|
|
2327
2904
|
async function readScriptLines(filepath) {
|
|
2328
|
-
const content = await
|
|
2905
|
+
const content = await readFile2(filepath, "utf-8");
|
|
2329
2906
|
return content.split("\n");
|
|
2330
2907
|
}
|
|
2331
2908
|
function cmdToolsForget(rest) {
|
|
@@ -2333,14 +2910,39 @@ function cmdToolsForget(rest) {
|
|
|
2333
2910
|
if (toolName) {
|
|
2334
2911
|
if (lastToolArgsMap.has(toolName)) {
|
|
2335
2912
|
lastToolArgsMap.delete(toolName);
|
|
2336
|
-
console.log(
|
|
2913
|
+
console.log(pc2.green(` Cleared remembered args for ${pc2.bold(toolName)}.`));
|
|
2337
2914
|
} else {
|
|
2338
|
-
console.log(
|
|
2915
|
+
console.log(pc2.yellow(` No remembered args for "${toolName}".`));
|
|
2339
2916
|
}
|
|
2340
2917
|
} else {
|
|
2341
2918
|
const count = lastToolArgsMap.size;
|
|
2342
2919
|
lastToolArgsMap.clear();
|
|
2343
|
-
console.log(
|
|
2920
|
+
console.log(pc2.green(` Cleared remembered args for ${count} tool${count === 1 ? "" : "s"}.`));
|
|
2921
|
+
}
|
|
2922
|
+
}
|
|
2923
|
+
async function pickInteractive(items, message) {
|
|
2924
|
+
if (items.length === 0) return null;
|
|
2925
|
+
if (!process.stdin.isTTY) return null;
|
|
2926
|
+
const choices = items.map((item) => ({
|
|
2927
|
+
name: item.name,
|
|
2928
|
+
value: item.name,
|
|
2929
|
+
description: item.description || ""
|
|
2930
|
+
}));
|
|
2931
|
+
try {
|
|
2932
|
+
const picked = await search({
|
|
2933
|
+
message,
|
|
2934
|
+
source: async (term) => {
|
|
2935
|
+
if (!term) return choices;
|
|
2936
|
+
const lower = term.toLowerCase();
|
|
2937
|
+
return choices.filter(
|
|
2938
|
+
(c) => c.name.toLowerCase().includes(lower) || c.description.toLowerCase().includes(lower)
|
|
2939
|
+
);
|
|
2940
|
+
}
|
|
2941
|
+
});
|
|
2942
|
+
return picked;
|
|
2943
|
+
} catch (err) {
|
|
2944
|
+
if (isAbortError(err)) return null;
|
|
2945
|
+
throw err;
|
|
2344
2946
|
}
|
|
2345
2947
|
}
|
|
2346
2948
|
async function cmdExplore(target, interceptor) {
|
|
@@ -2384,19 +2986,19 @@ async function cmdExplore(target, interceptor) {
|
|
|
2384
2986
|
}
|
|
2385
2987
|
}
|
|
2386
2988
|
if (choices.length === 0) {
|
|
2387
|
-
console.log(
|
|
2989
|
+
console.log(pc2.yellow("No tools, resources, or prompts found."));
|
|
2388
2990
|
return;
|
|
2389
2991
|
}
|
|
2390
2992
|
if (!process.stdin.isTTY) {
|
|
2391
|
-
console.log(
|
|
2993
|
+
console.log(pc2.bold("\n Server Capabilities\n"));
|
|
2392
2994
|
for (let i = 0; i < choices.length; i++) {
|
|
2393
2995
|
console.log(
|
|
2394
|
-
` ${
|
|
2996
|
+
` ${pc2.cyan(String(i + 1).padStart(2))}. ${choices[i].name} ${pc2.dim(choices[i].description)}`
|
|
2395
2997
|
);
|
|
2396
2998
|
}
|
|
2397
2999
|
console.log();
|
|
2398
3000
|
console.log(
|
|
2399
|
-
|
|
3001
|
+
pc2.dim(" Non-interactive mode \u2014 use specific commands instead (e.g., tools/call <name>).")
|
|
2400
3002
|
);
|
|
2401
3003
|
return;
|
|
2402
3004
|
}
|
|
@@ -2430,139 +3032,6 @@ import { createHash } from "crypto";
|
|
|
2430
3032
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2431
3033
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
2432
3034
|
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
|
|
2547
|
-
});
|
|
2548
|
-
if (answer === "CUSTOM") {
|
|
2549
|
-
const customCommand = await input2({ message: "Command to spawn target MCP server:" });
|
|
2550
|
-
if (!customCommand.trim()) return null;
|
|
2551
|
-
const parts = customCommand.trim().match(/(?:[^\s"']+|"[^"]*"|'[^']*')+/g)?.map((p) => p.replace(/^["']|["']$/g, ""));
|
|
2552
|
-
if (!parts || parts.length === 0) return null;
|
|
2553
|
-
return {
|
|
2554
|
-
name: "Custom",
|
|
2555
|
-
config: { command: parts[0], args: parts.slice(1) },
|
|
2556
|
-
source: "Manual"
|
|
2557
|
-
};
|
|
2558
|
-
}
|
|
2559
|
-
return answer;
|
|
2560
|
-
} catch {
|
|
2561
|
-
return null;
|
|
2562
|
-
}
|
|
2563
|
-
}
|
|
2564
|
-
|
|
2565
|
-
// src/server.ts
|
|
2566
3035
|
function hashDefinition(obj) {
|
|
2567
3036
|
return createHash("md5").update(JSON.stringify(obj)).digest("hex").slice(0, 12);
|
|
2568
3037
|
}
|
|
@@ -2612,7 +3081,7 @@ async function startServer(opts) {
|
|
|
2612
3081
|
maxTextLength: opts.maxTextLength
|
|
2613
3082
|
});
|
|
2614
3083
|
const mcpServer = new McpServer(
|
|
2615
|
-
{ name: "run-mcp", version: "1.
|
|
3084
|
+
{ name: "run-mcp", version: "1.6.0" },
|
|
2616
3085
|
{
|
|
2617
3086
|
capabilities: {
|
|
2618
3087
|
tools: {}
|
|
@@ -2997,7 +3466,12 @@ Available: ${available}`
|
|
|
2997
3466
|
const servers = await discoverServers();
|
|
2998
3467
|
if (servers.length === 0) {
|
|
2999
3468
|
return {
|
|
3000
|
-
content: [
|
|
3469
|
+
content: [
|
|
3470
|
+
{
|
|
3471
|
+
type: "text",
|
|
3472
|
+
text: "No configured MCP servers discovered in common locations."
|
|
3473
|
+
}
|
|
3474
|
+
]
|
|
3001
3475
|
};
|
|
3002
3476
|
}
|
|
3003
3477
|
const lines = ["Discovered the following MCP server configurations:"];
|
|
@@ -3044,7 +3518,10 @@ Available: ${available}`
|
|
|
3044
3518
|
env: z.record(z.string()).optional().describe("Extra environment variables for the server process"),
|
|
3045
3519
|
// Lifecycle
|
|
3046
3520
|
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')")
|
|
3521
|
+
timeout_ms: z.number().optional().describe("Timeout in ms (only applies to type: 'tool')"),
|
|
3522
|
+
include_metadata: z.boolean().optional().describe(
|
|
3523
|
+
"Include a structured metadata content item with latency, interception info, and content statistics. Useful for programmatic consumption."
|
|
3524
|
+
)
|
|
3048
3525
|
}
|
|
3049
3526
|
},
|
|
3050
3527
|
async ({
|
|
@@ -3055,7 +3532,8 @@ Available: ${available}`
|
|
|
3055
3532
|
args,
|
|
3056
3533
|
env,
|
|
3057
3534
|
disconnect_after,
|
|
3058
|
-
timeout_ms
|
|
3535
|
+
timeout_ms,
|
|
3536
|
+
include_metadata
|
|
3059
3537
|
}) => {
|
|
3060
3538
|
try {
|
|
3061
3539
|
const connectError = await ensureConnected(command, args, env);
|
|
@@ -3118,20 +3596,50 @@ Available tools: ${toolNames.join(", ")}`
|
|
|
3118
3596
|
} catch {
|
|
3119
3597
|
}
|
|
3120
3598
|
const startMs = Date.now();
|
|
3121
|
-
|
|
3122
|
-
|
|
3123
|
-
|
|
3124
|
-
|
|
3125
|
-
|
|
3126
|
-
|
|
3599
|
+
let interceptionMeta;
|
|
3600
|
+
if (include_metadata) {
|
|
3601
|
+
const { result: toolResult, metadata } = await interceptor.callToolWithMetadata(
|
|
3602
|
+
target,
|
|
3603
|
+
name,
|
|
3604
|
+
callArgs ?? {},
|
|
3605
|
+
timeout_ms
|
|
3606
|
+
);
|
|
3607
|
+
result = toolResult;
|
|
3608
|
+
interceptionMeta = metadata;
|
|
3609
|
+
} else {
|
|
3610
|
+
result = await interceptor.callTool(
|
|
3611
|
+
target,
|
|
3612
|
+
name,
|
|
3613
|
+
callArgs ?? {},
|
|
3614
|
+
timeout_ms
|
|
3615
|
+
);
|
|
3616
|
+
}
|
|
3127
3617
|
const elapsedMs = Date.now() - startMs;
|
|
3128
3618
|
const resultContent = result.content;
|
|
3129
|
-
if (Array.isArray(resultContent) && resultContent.length > 0) {
|
|
3619
|
+
if (!include_metadata && Array.isArray(resultContent) && resultContent.length > 0) {
|
|
3130
3620
|
const lastItem = resultContent[resultContent.length - 1];
|
|
3131
3621
|
if (lastItem.type === "text") {
|
|
3132
3622
|
lastItem.text += ` (${elapsedMs}ms)`;
|
|
3133
3623
|
}
|
|
3134
3624
|
}
|
|
3625
|
+
if (include_metadata && Array.isArray(resultContent)) {
|
|
3626
|
+
const meta = {
|
|
3627
|
+
latency_ms: elapsedMs,
|
|
3628
|
+
content_items: resultContent.length,
|
|
3629
|
+
is_error: result.isError === true
|
|
3630
|
+
};
|
|
3631
|
+
if (interceptionMeta) {
|
|
3632
|
+
meta.truncated = interceptionMeta.truncated;
|
|
3633
|
+
meta.images_saved = interceptionMeta.imagesSaved;
|
|
3634
|
+
meta.audio_saved = interceptionMeta.audioSaved;
|
|
3635
|
+
meta.original_size_bytes = interceptionMeta.originalSizeBytes;
|
|
3636
|
+
}
|
|
3637
|
+
resultContent.unshift({
|
|
3638
|
+
type: "text",
|
|
3639
|
+
text: `--- metadata ---
|
|
3640
|
+
${JSON.stringify(meta)}`
|
|
3641
|
+
});
|
|
3642
|
+
}
|
|
3135
3643
|
break;
|
|
3136
3644
|
}
|
|
3137
3645
|
case "resource": {
|
|
@@ -3214,7 +3722,74 @@ Available tools: ${toolNames.join(", ")}`
|
|
|
3214
3722
|
}
|
|
3215
3723
|
|
|
3216
3724
|
// src/index.ts
|
|
3217
|
-
|
|
3725
|
+
function extractTargetCommand(targetCommand) {
|
|
3726
|
+
return targetCommand.filter((a) => a !== "--");
|
|
3727
|
+
}
|
|
3728
|
+
function requireTargetCommand(targetCommand, subcommandUsage) {
|
|
3729
|
+
const target = extractTargetCommand(targetCommand);
|
|
3730
|
+
if (target.length === 0) {
|
|
3731
|
+
process.stderr.write(`Error: No target server command provided after '--'.
|
|
3732
|
+
`);
|
|
3733
|
+
process.stderr.write(`Usage: ${subcommandUsage}
|
|
3734
|
+
`);
|
|
3735
|
+
process.exit(2);
|
|
3736
|
+
}
|
|
3737
|
+
return target;
|
|
3738
|
+
}
|
|
3739
|
+
function parseHeadlessOpts(opts) {
|
|
3740
|
+
return {
|
|
3741
|
+
outDir: opts.outDir,
|
|
3742
|
+
timeoutMs: opts.timeout ? Number.parseInt(opts.timeout, 10) : void 0,
|
|
3743
|
+
raw: opts.raw
|
|
3744
|
+
};
|
|
3745
|
+
}
|
|
3746
|
+
program.enablePositionalOptions();
|
|
3747
|
+
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("--raw", "Print the full result object including metadata").allowUnknownOption().action(
|
|
3748
|
+
async (tool, jsonArgs, targetCommand, opts) => {
|
|
3749
|
+
const target = requireTargetCommand(
|
|
3750
|
+
targetCommand,
|
|
3751
|
+
"run-mcp call <tool> [json_args] -- <server_command...>"
|
|
3752
|
+
);
|
|
3753
|
+
await runHeadless(target, { type: "call", tool, args: jsonArgs }, parseHeadlessOpts(opts));
|
|
3754
|
+
}
|
|
3755
|
+
);
|
|
3756
|
+
program.command("list-tools").argument("[target_command...]", "Target server command (after --)").description("List all tools on a target MCP server as JSON").allowUnknownOption().action(async (targetCommand) => {
|
|
3757
|
+
const target = requireTargetCommand(targetCommand, "run-mcp list-tools -- <server_command...>");
|
|
3758
|
+
await runHeadless(target, { type: "list-tools" });
|
|
3759
|
+
});
|
|
3760
|
+
program.command("list-resources").argument("[target_command...]", "Target server command (after --)").description("List all resources on a target MCP server as JSON").allowUnknownOption().action(async (targetCommand) => {
|
|
3761
|
+
const target = requireTargetCommand(
|
|
3762
|
+
targetCommand,
|
|
3763
|
+
"run-mcp list-resources -- <server_command...>"
|
|
3764
|
+
);
|
|
3765
|
+
await runHeadless(target, { type: "list-resources" });
|
|
3766
|
+
});
|
|
3767
|
+
program.command("list-prompts").argument("[target_command...]", "Target server command (after --)").description("List all prompts on a target MCP server as JSON").allowUnknownOption().action(async (targetCommand) => {
|
|
3768
|
+
const target = requireTargetCommand(
|
|
3769
|
+
targetCommand,
|
|
3770
|
+
"run-mcp list-prompts -- <server_command...>"
|
|
3771
|
+
);
|
|
3772
|
+
await runHeadless(target, { type: "list-prompts" });
|
|
3773
|
+
});
|
|
3774
|
+
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").allowUnknownOption().action(async (uri, targetCommand) => {
|
|
3775
|
+
const target = requireTargetCommand(targetCommand, "run-mcp read <uri> -- <server_command...>");
|
|
3776
|
+
await runHeadless(target, { type: "read", uri });
|
|
3777
|
+
});
|
|
3778
|
+
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").allowUnknownOption().action(async (tool, targetCommand) => {
|
|
3779
|
+
const target = requireTargetCommand(
|
|
3780
|
+
targetCommand,
|
|
3781
|
+
"run-mcp describe <tool> -- <server_command...>"
|
|
3782
|
+
);
|
|
3783
|
+
await runHeadless(target, { type: "describe", tool });
|
|
3784
|
+
});
|
|
3785
|
+
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").allowUnknownOption().action(async (name, jsonArgs, targetCommand) => {
|
|
3786
|
+
const target = requireTargetCommand(
|
|
3787
|
+
targetCommand,
|
|
3788
|
+
"run-mcp get-prompt <name> [json_args] -- <server_command...>"
|
|
3789
|
+
);
|
|
3790
|
+
await runHeadless(target, { type: "get-prompt", name, args: jsonArgs });
|
|
3791
|
+
});
|
|
3792
|
+
program.name("run-mcp").description("A smart interactive REPL and live test harness for MCP servers").version("1.6.0").passThroughOptions().allowUnknownOption().argument(
|
|
3218
3793
|
"[target_command...]",
|
|
3219
3794
|
"Command to spawn the target MCP server (starts REPL if provided, Agent server otherwise)"
|
|
3220
3795
|
).option("-o, --out-dir <path>", "Directory to save intercepted images and audio").option(
|
|
@@ -3234,6 +3809,15 @@ Examples:
|
|
|
3234
3809
|
$ run-mcp --out-dir ./test-output # Agent mode with options
|
|
3235
3810
|
$ run-mcp --out-dir ./screenshots node srv.js # REPL mode with options
|
|
3236
3811
|
|
|
3812
|
+
Headless Commands (pipe-friendly, JSON output):
|
|
3813
|
+
$ run-mcp call echo '{"text":"hi"}' -- node my-server.js
|
|
3814
|
+
$ run-mcp list-tools -- node my-server.js | jq '.[].name'
|
|
3815
|
+
$ run-mcp list-resources -- node my-server.js
|
|
3816
|
+
$ run-mcp list-prompts -- node my-server.js
|
|
3817
|
+
$ run-mcp read docs://readme -- node my-server.js
|
|
3818
|
+
$ run-mcp describe echo -- node my-server.js
|
|
3819
|
+
$ run-mcp get-prompt greeting '{"name":"Ada"}' -- node my-server.js
|
|
3820
|
+
|
|
3237
3821
|
Agent Mode Configuration (mcp.json):
|
|
3238
3822
|
{
|
|
3239
3823
|
"mcpServers": {
|