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