run-mcp 1.6.2 → 1.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +75 -0
- package/dist/index.js +2133 -860
- package/package.json +9 -3
package/dist/index.js
CHANGED
|
@@ -3,10 +3,10 @@
|
|
|
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
|
|
@@ -143,6 +143,7 @@ async function pickDiscoveredServer() {
|
|
|
143
143
|
import { mkdir, writeFile } from "fs/promises";
|
|
144
144
|
import { tmpdir } from "os";
|
|
145
145
|
import { join } from "path";
|
|
146
|
+
import { exec } from "child_process";
|
|
146
147
|
var BASE64_PATTERN = /^[A-Za-z0-9+/]{1000,}={0,2}$/;
|
|
147
148
|
var DEFAULT_TIMEOUT_MS = 3e5;
|
|
148
149
|
var DEFAULT_MAX_TEXT_LENGTH = 5e4;
|
|
@@ -151,12 +152,14 @@ var ResponseInterceptor = class {
|
|
|
151
152
|
defaultTimeoutMs;
|
|
152
153
|
maxTextLength;
|
|
153
154
|
mediaThresholdKb;
|
|
155
|
+
openMedia;
|
|
154
156
|
fileCounter = 0;
|
|
155
157
|
constructor(opts = {}) {
|
|
156
158
|
this.outDir = opts.outDir ?? join(tmpdir(), "run-mcp");
|
|
157
159
|
this.defaultTimeoutMs = opts.defaultTimeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
158
160
|
this.maxTextLength = opts.maxTextLength ?? DEFAULT_MAX_TEXT_LENGTH;
|
|
159
161
|
this.mediaThresholdKb = opts.mediaThresholdKb ?? 0;
|
|
162
|
+
this.openMedia = opts.openMedia ?? false;
|
|
160
163
|
}
|
|
161
164
|
/**
|
|
162
165
|
* Call a tool on the target, applying timeout, media extraction, and truncation.
|
|
@@ -373,6 +376,13 @@ var ResponseInterceptor = class {
|
|
|
373
376
|
const filepath = join(this.outDir, filename);
|
|
374
377
|
const buffer = Buffer.from(base64Data, "base64");
|
|
375
378
|
await writeFile(filepath, buffer);
|
|
379
|
+
if (this.openMedia) {
|
|
380
|
+
const isMac = process.platform === "darwin";
|
|
381
|
+
const isWin = process.platform === "win32";
|
|
382
|
+
const cmd = isMac ? "open" : isWin ? "start" : "xdg-open";
|
|
383
|
+
exec(`${cmd} "${filepath}"`, () => {
|
|
384
|
+
});
|
|
385
|
+
}
|
|
376
386
|
const sizeKB = (buffer.length / 1024).toFixed(1);
|
|
377
387
|
const label = mediaType === "audio" ? "Audio" : "Image";
|
|
378
388
|
return {
|
|
@@ -423,8 +433,41 @@ var ResponseInterceptor = class {
|
|
|
423
433
|
}
|
|
424
434
|
};
|
|
425
435
|
|
|
426
|
-
// src/
|
|
436
|
+
// src/colors.ts
|
|
427
437
|
import pc from "picocolors";
|
|
438
|
+
function shouldEnableColor() {
|
|
439
|
+
const dashDashIdx = process.argv.indexOf("--");
|
|
440
|
+
const argsToCheck = dashDashIdx === -1 ? process.argv : process.argv.slice(0, dashDashIdx);
|
|
441
|
+
for (let i = 0; i < argsToCheck.length; i++) {
|
|
442
|
+
const arg = argsToCheck[i];
|
|
443
|
+
if (arg.startsWith("--color=")) {
|
|
444
|
+
const val = arg.split("=")[1];
|
|
445
|
+
if (val === "always") return true;
|
|
446
|
+
if (val === "never") return false;
|
|
447
|
+
} else if (arg === "--color") {
|
|
448
|
+
const val = argsToCheck[i + 1];
|
|
449
|
+
if (val === "always") return true;
|
|
450
|
+
if (val === "never") return false;
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
if (process.env.CLICOLOR_FORCE !== void 0 && process.env.CLICOLOR_FORCE !== "0") {
|
|
454
|
+
return true;
|
|
455
|
+
}
|
|
456
|
+
if (process.env.NO_COLOR) {
|
|
457
|
+
return false;
|
|
458
|
+
}
|
|
459
|
+
if (process.env.CLICOLOR === "0") {
|
|
460
|
+
return false;
|
|
461
|
+
}
|
|
462
|
+
const isatty = !!(process.stdout?.isTTY || process.stderr?.isTTY);
|
|
463
|
+
if (!isatty) {
|
|
464
|
+
return false;
|
|
465
|
+
}
|
|
466
|
+
return true;
|
|
467
|
+
}
|
|
468
|
+
var colors = pc.createColors(shouldEnableColor());
|
|
469
|
+
|
|
470
|
+
// src/parsing.ts
|
|
428
471
|
function parseCommandLine(input3) {
|
|
429
472
|
const spaceIdx = input3.indexOf(" ");
|
|
430
473
|
if (spaceIdx === -1) {
|
|
@@ -466,10 +509,10 @@ function colorizeJson(json) {
|
|
|
466
509
|
if (ch === '"') {
|
|
467
510
|
const str = consumeString(json, i);
|
|
468
511
|
if (expectingValue) {
|
|
469
|
-
result.push(
|
|
512
|
+
result.push(colors.green(str));
|
|
470
513
|
expectingValue = false;
|
|
471
514
|
} else {
|
|
472
|
-
result.push(
|
|
515
|
+
result.push(colors.cyan(str));
|
|
473
516
|
}
|
|
474
517
|
i += str.length;
|
|
475
518
|
continue;
|
|
@@ -493,19 +536,19 @@ function colorizeJson(json) {
|
|
|
493
536
|
continue;
|
|
494
537
|
}
|
|
495
538
|
if (json.startsWith("true", i)) {
|
|
496
|
-
result.push(
|
|
539
|
+
result.push(colors.magenta("true"));
|
|
497
540
|
expectingValue = false;
|
|
498
541
|
i += 4;
|
|
499
542
|
continue;
|
|
500
543
|
}
|
|
501
544
|
if (json.startsWith("false", i)) {
|
|
502
|
-
result.push(
|
|
545
|
+
result.push(colors.magenta("false"));
|
|
503
546
|
expectingValue = false;
|
|
504
547
|
i += 5;
|
|
505
548
|
continue;
|
|
506
549
|
}
|
|
507
550
|
if (json.startsWith("null", i)) {
|
|
508
|
-
result.push(
|
|
551
|
+
result.push(colors.dim("null"));
|
|
509
552
|
expectingValue = false;
|
|
510
553
|
i += 4;
|
|
511
554
|
continue;
|
|
@@ -516,7 +559,7 @@ function colorizeJson(json) {
|
|
|
516
559
|
num += json[i];
|
|
517
560
|
i++;
|
|
518
561
|
}
|
|
519
|
-
result.push(
|
|
562
|
+
result.push(colors.yellow(num));
|
|
520
563
|
expectingValue = false;
|
|
521
564
|
continue;
|
|
522
565
|
}
|
|
@@ -834,7 +877,425 @@ function interpolateString(input3, context) {
|
|
|
834
877
|
|
|
835
878
|
// src/target-manager.ts
|
|
836
879
|
import { EventEmitter } from "events";
|
|
880
|
+
import { execSync } from "child_process";
|
|
881
|
+
import { writeFileSync, rmSync, existsSync as existsSync3 } from "fs";
|
|
882
|
+
import { join as join3 } from "path";
|
|
883
|
+
import { tmpdir as tmpdir2 } from "os";
|
|
837
884
|
import treeKill from "tree-kill";
|
|
885
|
+
|
|
886
|
+
// src/settings.ts
|
|
887
|
+
import { existsSync as existsSync2, readFileSync } from "fs";
|
|
888
|
+
import { dirname, join as join2, resolve, isAbsolute } from "path";
|
|
889
|
+
import { homedir as homedir2 } from "os";
|
|
890
|
+
function resolvePath(p, settingsFileDir) {
|
|
891
|
+
if (p.startsWith("~")) {
|
|
892
|
+
return resolve(homedir2(), p.slice(1).replace(/^[/\\]/, ""));
|
|
893
|
+
}
|
|
894
|
+
if (p.startsWith("$HOME")) {
|
|
895
|
+
return resolve(homedir2(), p.slice(5).replace(/^[/\\]/, ""));
|
|
896
|
+
}
|
|
897
|
+
if (isAbsolute(p)) {
|
|
898
|
+
return resolve(p);
|
|
899
|
+
}
|
|
900
|
+
return resolve(settingsFileDir, p);
|
|
901
|
+
}
|
|
902
|
+
function matchDomain(host, pattern) {
|
|
903
|
+
if (pattern === "*") return true;
|
|
904
|
+
const [hostName, hostPort] = host.split(":");
|
|
905
|
+
const [patternName, patternPort] = pattern.split(":");
|
|
906
|
+
if (patternPort && patternPort !== "*") {
|
|
907
|
+
if (hostPort !== patternPort) return false;
|
|
908
|
+
}
|
|
909
|
+
const regexPattern = patternName.replace(/\./g, "\\.").replace(/\*/g, ".*");
|
|
910
|
+
const regex = new RegExp(`^${regexPattern}$`, "i");
|
|
911
|
+
return regex.test(hostName);
|
|
912
|
+
}
|
|
913
|
+
var SandboxPolicy = class {
|
|
914
|
+
// Allowlists (Absolute paths or patterns)
|
|
915
|
+
fileReadAllow = /* @__PURE__ */ new Set();
|
|
916
|
+
fileWriteAllow = /* @__PURE__ */ new Set();
|
|
917
|
+
networkAllow = /* @__PURE__ */ new Set();
|
|
918
|
+
// Denylists (Absolute paths or patterns)
|
|
919
|
+
fileReadDeny = /* @__PURE__ */ new Set();
|
|
920
|
+
fileWriteDeny = /* @__PURE__ */ new Set();
|
|
921
|
+
networkDeny = /* @__PURE__ */ new Set();
|
|
922
|
+
constructor() {
|
|
923
|
+
}
|
|
924
|
+
/**
|
|
925
|
+
* Merge a SandboxConfig block into the policy.
|
|
926
|
+
* Path rules are resolved relative to the settingsFileDir.
|
|
927
|
+
*/
|
|
928
|
+
mergeConfig(config, settingsFileDir) {
|
|
929
|
+
if (config.file?.allow?.read) {
|
|
930
|
+
for (const p of config.file.allow.read) {
|
|
931
|
+
this.fileReadAllow.add(resolvePath(p, settingsFileDir));
|
|
932
|
+
}
|
|
933
|
+
}
|
|
934
|
+
if (config.file?.allow?.write) {
|
|
935
|
+
for (const p of config.file.allow.write) {
|
|
936
|
+
this.fileWriteAllow.add(resolvePath(p, settingsFileDir));
|
|
937
|
+
}
|
|
938
|
+
}
|
|
939
|
+
if (config.file?.deny?.read) {
|
|
940
|
+
for (const p of config.file.deny.read) {
|
|
941
|
+
this.fileReadDeny.add(resolvePath(p, settingsFileDir));
|
|
942
|
+
}
|
|
943
|
+
}
|
|
944
|
+
if (config.file?.deny?.write) {
|
|
945
|
+
for (const p of config.file.deny.write) {
|
|
946
|
+
this.fileWriteDeny.add(resolvePath(p, settingsFileDir));
|
|
947
|
+
}
|
|
948
|
+
}
|
|
949
|
+
if (config.network?.allow) {
|
|
950
|
+
for (const host of config.network.allow) {
|
|
951
|
+
this.networkAllow.add(host);
|
|
952
|
+
}
|
|
953
|
+
}
|
|
954
|
+
if (config.network?.deny) {
|
|
955
|
+
for (const host of config.network.deny) {
|
|
956
|
+
this.networkDeny.add(host);
|
|
957
|
+
}
|
|
958
|
+
}
|
|
959
|
+
}
|
|
960
|
+
/**
|
|
961
|
+
* Merge raw command line overrides.
|
|
962
|
+
* CLI overrides are resolved relative to the process CWD.
|
|
963
|
+
*/
|
|
964
|
+
mergeCliOverrides(options) {
|
|
965
|
+
const cwd = process.cwd();
|
|
966
|
+
if (options.allowRead) {
|
|
967
|
+
for (const p of options.allowRead) this.fileReadAllow.add(resolvePath(p, cwd));
|
|
968
|
+
}
|
|
969
|
+
if (options.allowWrite) {
|
|
970
|
+
for (const p of options.allowWrite) this.fileWriteAllow.add(resolvePath(p, cwd));
|
|
971
|
+
}
|
|
972
|
+
if (options.allowNet) {
|
|
973
|
+
for (const host of options.allowNet) this.networkAllow.add(host);
|
|
974
|
+
}
|
|
975
|
+
if (options.denyRead) {
|
|
976
|
+
for (const p of options.denyRead) this.fileReadDeny.add(resolvePath(p, cwd));
|
|
977
|
+
}
|
|
978
|
+
if (options.denyWrite) {
|
|
979
|
+
for (const p of options.denyWrite) this.fileWriteDeny.add(resolvePath(p, cwd));
|
|
980
|
+
}
|
|
981
|
+
if (options.denyNet) {
|
|
982
|
+
for (const host of options.denyNet) this.networkDeny.add(host);
|
|
983
|
+
}
|
|
984
|
+
}
|
|
985
|
+
/**
|
|
986
|
+
* Automatically deny reading sensitive credential directories if network outbound is allowed.
|
|
987
|
+
*/
|
|
988
|
+
applyCredentialProtections() {
|
|
989
|
+
if (this.networkAllow.size > 0 && !this.networkDeny.has("*")) {
|
|
990
|
+
const sensitivePatterns = [
|
|
991
|
+
"~/.ssh",
|
|
992
|
+
"~/.aws",
|
|
993
|
+
"~/.kube",
|
|
994
|
+
"~/.config/gcloud",
|
|
995
|
+
"~/.netrc",
|
|
996
|
+
"~/.npmrc"
|
|
997
|
+
];
|
|
998
|
+
const cwd = process.cwd();
|
|
999
|
+
for (const pattern of sensitivePatterns) {
|
|
1000
|
+
const resolved = resolvePath(pattern, cwd);
|
|
1001
|
+
if (!this.fileReadAllow.has(resolved)) {
|
|
1002
|
+
this.fileReadDeny.add(resolved);
|
|
1003
|
+
}
|
|
1004
|
+
}
|
|
1005
|
+
}
|
|
1006
|
+
}
|
|
1007
|
+
/**
|
|
1008
|
+
* Evaluates if a file read path is allowed, under Deny-Wins precedence.
|
|
1009
|
+
*/
|
|
1010
|
+
isFileReadAllowed(p) {
|
|
1011
|
+
const absPath = resolve(p);
|
|
1012
|
+
for (const denyPath of this.fileReadDeny) {
|
|
1013
|
+
if (absPath === denyPath || absPath.startsWith(denyPath + "/")) {
|
|
1014
|
+
return false;
|
|
1015
|
+
}
|
|
1016
|
+
}
|
|
1017
|
+
for (const allowPath of this.fileReadAllow) {
|
|
1018
|
+
if (absPath === allowPath || absPath.startsWith(allowPath + "/")) {
|
|
1019
|
+
return true;
|
|
1020
|
+
}
|
|
1021
|
+
}
|
|
1022
|
+
return false;
|
|
1023
|
+
}
|
|
1024
|
+
/**
|
|
1025
|
+
* Evaluates if a file write path is allowed, under Deny-Wins precedence.
|
|
1026
|
+
*/
|
|
1027
|
+
isFileWriteAllowed(p) {
|
|
1028
|
+
const absPath = resolve(p);
|
|
1029
|
+
for (const denyPath of this.fileWriteDeny) {
|
|
1030
|
+
if (absPath === denyPath || absPath.startsWith(denyPath + "/")) {
|
|
1031
|
+
return false;
|
|
1032
|
+
}
|
|
1033
|
+
}
|
|
1034
|
+
for (const allowPath of this.fileWriteAllow) {
|
|
1035
|
+
if (absPath === allowPath || absPath.startsWith(allowPath + "/")) {
|
|
1036
|
+
return true;
|
|
1037
|
+
}
|
|
1038
|
+
}
|
|
1039
|
+
return false;
|
|
1040
|
+
}
|
|
1041
|
+
/**
|
|
1042
|
+
* Evaluates if network access to a host (hostname:port or hostname) is allowed.
|
|
1043
|
+
*/
|
|
1044
|
+
isNetworkAllowed(host) {
|
|
1045
|
+
for (const denyPattern of this.networkDeny) {
|
|
1046
|
+
if (matchDomain(host, denyPattern)) {
|
|
1047
|
+
return false;
|
|
1048
|
+
}
|
|
1049
|
+
}
|
|
1050
|
+
for (const allowPattern of this.networkAllow) {
|
|
1051
|
+
if (matchDomain(host, allowPattern)) {
|
|
1052
|
+
return true;
|
|
1053
|
+
}
|
|
1054
|
+
}
|
|
1055
|
+
return false;
|
|
1056
|
+
}
|
|
1057
|
+
/**
|
|
1058
|
+
* Generate Scheme-based macOS Seatbelt profile.
|
|
1059
|
+
*/
|
|
1060
|
+
getSeatbeltProfile(options) {
|
|
1061
|
+
if (options.audit) {
|
|
1062
|
+
return `(version 1)
|
|
1063
|
+
(allow default)
|
|
1064
|
+
(deny network*)
|
|
1065
|
+
(deny file-write*)
|
|
1066
|
+
(deny process-fork)
|
|
1067
|
+
(deny process-exec*)
|
|
1068
|
+
(allow process-exec*
|
|
1069
|
+
(subpath "/System")
|
|
1070
|
+
(subpath "/usr")
|
|
1071
|
+
(subpath "/bin")
|
|
1072
|
+
(subpath "${options.nodeBinDir}")
|
|
1073
|
+
)
|
|
1074
|
+
(deny file-read*)
|
|
1075
|
+
(allow file-read*
|
|
1076
|
+
(literal "/")
|
|
1077
|
+
(subpath "/System")
|
|
1078
|
+
(subpath "/usr")
|
|
1079
|
+
(subpath "/dev")
|
|
1080
|
+
(subpath "/private/var")
|
|
1081
|
+
(subpath "/var")
|
|
1082
|
+
(subpath "/private/etc")
|
|
1083
|
+
(subpath "/etc")
|
|
1084
|
+
(subpath "${options.nodeBinDir}")
|
|
1085
|
+
(subpath "${options.nodeInstallDir}")
|
|
1086
|
+
(subpath "${options.cwd}")
|
|
1087
|
+
(path-ancestors "${options.cwd}")
|
|
1088
|
+
(path-ancestors "${options.nodeBinDir}")
|
|
1089
|
+
)
|
|
1090
|
+
`;
|
|
1091
|
+
}
|
|
1092
|
+
const readDirectives = Array.from(this.fileReadAllow).map((p) => ` (subpath "${p}")`).join("\n");
|
|
1093
|
+
const writeDirectives = Array.from(this.fileWriteAllow).map((p) => ` (subpath "${p}")`).join("\n");
|
|
1094
|
+
const readDenyDirectives = Array.from(this.fileReadDeny).map((p) => ` (subpath "${p}")`).join("\n");
|
|
1095
|
+
const writeDenyDirectives = Array.from(this.fileWriteDeny).map((p) => ` (subpath "${p}")`).join("\n");
|
|
1096
|
+
let netRules;
|
|
1097
|
+
if (this.networkAllow.size > 0 && !this.networkDeny.has("*")) {
|
|
1098
|
+
netRules = `(allow network-outbound)
|
|
1099
|
+
`;
|
|
1100
|
+
if (this.networkDeny.size > 0) {
|
|
1101
|
+
for (const pattern of this.networkDeny) {
|
|
1102
|
+
if (pattern !== "*") {
|
|
1103
|
+
netRules += `(deny network-outbound (remote ip "${pattern}"))
|
|
1104
|
+
`;
|
|
1105
|
+
}
|
|
1106
|
+
}
|
|
1107
|
+
}
|
|
1108
|
+
} else {
|
|
1109
|
+
netRules = `(deny network*)
|
|
1110
|
+
`;
|
|
1111
|
+
}
|
|
1112
|
+
return `(version 1)
|
|
1113
|
+
(allow default)
|
|
1114
|
+
${netRules}
|
|
1115
|
+
(deny file-write*)
|
|
1116
|
+
(allow file-write*
|
|
1117
|
+
(subpath "${options.tmp}")
|
|
1118
|
+
(subpath "/private/tmp")
|
|
1119
|
+
(subpath "/tmp")
|
|
1120
|
+
${writeDirectives}
|
|
1121
|
+
)
|
|
1122
|
+
${writeDenyDirectives ? `(deny file-write*
|
|
1123
|
+
${writeDenyDirectives}
|
|
1124
|
+
)
|
|
1125
|
+
` : ""}
|
|
1126
|
+
(deny file-read*)
|
|
1127
|
+
(allow file-read*
|
|
1128
|
+
(literal "/")
|
|
1129
|
+
(subpath "/System")
|
|
1130
|
+
(subpath "/usr")
|
|
1131
|
+
(subpath "/dev")
|
|
1132
|
+
(subpath "/private/var")
|
|
1133
|
+
(subpath "/var")
|
|
1134
|
+
(subpath "/private/etc")
|
|
1135
|
+
(subpath "/etc")
|
|
1136
|
+
(subpath "/private/tmp")
|
|
1137
|
+
(subpath "/tmp")
|
|
1138
|
+
(subpath "${options.nodeBinDir}")
|
|
1139
|
+
(subpath "${options.nodeInstallDir}")
|
|
1140
|
+
(subpath "${options.cwd}")
|
|
1141
|
+
(subpath "${options.tmp}")
|
|
1142
|
+
(path-ancestors "${options.cwd}")
|
|
1143
|
+
(path-ancestors "${options.nodeBinDir}")
|
|
1144
|
+
${readDirectives}
|
|
1145
|
+
)
|
|
1146
|
+
${readDenyDirectives ? `(deny file-read*
|
|
1147
|
+
${readDenyDirectives}
|
|
1148
|
+
)
|
|
1149
|
+
` : ""}
|
|
1150
|
+
`;
|
|
1151
|
+
}
|
|
1152
|
+
};
|
|
1153
|
+
function loadSettings(cwd = process.cwd()) {
|
|
1154
|
+
const settings = { sandbox: {} };
|
|
1155
|
+
const loadedConfigs = [];
|
|
1156
|
+
let managedPath;
|
|
1157
|
+
if (process.platform === "darwin") {
|
|
1158
|
+
managedPath = "/Library/Application Support/run-mcp/settings.json";
|
|
1159
|
+
} else if (process.platform === "win32") {
|
|
1160
|
+
managedPath = join2(process.env.ProgramFiles || "C:\\Program Files", "run-mcp", "settings.json");
|
|
1161
|
+
} else {
|
|
1162
|
+
managedPath = "/etc/run-mcp/settings.json";
|
|
1163
|
+
}
|
|
1164
|
+
if (existsSync2(managedPath)) {
|
|
1165
|
+
try {
|
|
1166
|
+
const config = JSON.parse(readFileSync(managedPath, "utf8"));
|
|
1167
|
+
if (config.sandbox) loadedConfigs.push({ config: config.sandbox, dir: dirname(managedPath) });
|
|
1168
|
+
} catch {
|
|
1169
|
+
}
|
|
1170
|
+
}
|
|
1171
|
+
const userPath = join2(homedir2(), ".run-mcp", "settings.json");
|
|
1172
|
+
if (existsSync2(userPath)) {
|
|
1173
|
+
try {
|
|
1174
|
+
const config = JSON.parse(readFileSync(userPath, "utf8"));
|
|
1175
|
+
if (config.sandbox) loadedConfigs.push({ config: config.sandbox, dir: dirname(userPath) });
|
|
1176
|
+
} catch {
|
|
1177
|
+
}
|
|
1178
|
+
}
|
|
1179
|
+
const projectPath = join2(cwd, ".run-mcp", "settings.json");
|
|
1180
|
+
if (existsSync2(projectPath)) {
|
|
1181
|
+
try {
|
|
1182
|
+
const config = JSON.parse(readFileSync(projectPath, "utf8"));
|
|
1183
|
+
if (config.sandbox) loadedConfigs.push({ config: config.sandbox, dir: dirname(projectPath) });
|
|
1184
|
+
} catch {
|
|
1185
|
+
}
|
|
1186
|
+
}
|
|
1187
|
+
const localPath = join2(cwd, ".run-mcp", "settings.local.json");
|
|
1188
|
+
if (existsSync2(localPath)) {
|
|
1189
|
+
try {
|
|
1190
|
+
const config = JSON.parse(readFileSync(localPath, "utf8"));
|
|
1191
|
+
if (config.sandbox) loadedConfigs.push({ config: config.sandbox, dir: dirname(localPath) });
|
|
1192
|
+
} catch {
|
|
1193
|
+
}
|
|
1194
|
+
}
|
|
1195
|
+
const policy = new SandboxPolicy();
|
|
1196
|
+
for (const item of loadedConfigs) {
|
|
1197
|
+
policy.mergeConfig(item.config, item.dir);
|
|
1198
|
+
}
|
|
1199
|
+
settings.sandbox = {
|
|
1200
|
+
file: {
|
|
1201
|
+
allow: {
|
|
1202
|
+
read: Array.from(policy.fileReadAllow),
|
|
1203
|
+
write: Array.from(policy.fileWriteAllow)
|
|
1204
|
+
},
|
|
1205
|
+
deny: {
|
|
1206
|
+
read: Array.from(policy.fileReadDeny),
|
|
1207
|
+
write: Array.from(policy.fileWriteDeny)
|
|
1208
|
+
}
|
|
1209
|
+
},
|
|
1210
|
+
network: {
|
|
1211
|
+
allow: Array.from(policy.networkAllow),
|
|
1212
|
+
deny: Array.from(policy.networkDeny)
|
|
1213
|
+
}
|
|
1214
|
+
};
|
|
1215
|
+
return settings;
|
|
1216
|
+
}
|
|
1217
|
+
|
|
1218
|
+
// src/proxy-audit.ts
|
|
1219
|
+
import http from "http";
|
|
1220
|
+
import net from "net";
|
|
1221
|
+
var NetworkAuditProxy = class {
|
|
1222
|
+
server;
|
|
1223
|
+
port = 0;
|
|
1224
|
+
constructor() {
|
|
1225
|
+
this.server = http.createServer((req, res) => {
|
|
1226
|
+
const url = req.url || "";
|
|
1227
|
+
console.error(`\x1B[36m\u{1F310} [NETWORK AUDIT] HTTP request to: ${url}\x1B[0m`);
|
|
1228
|
+
try {
|
|
1229
|
+
const parsedUrl = new URL(url);
|
|
1230
|
+
const options = {
|
|
1231
|
+
hostname: parsedUrl.hostname,
|
|
1232
|
+
port: parsedUrl.port || 80,
|
|
1233
|
+
path: parsedUrl.pathname + parsedUrl.search,
|
|
1234
|
+
method: req.method,
|
|
1235
|
+
headers: req.headers
|
|
1236
|
+
};
|
|
1237
|
+
const proxyReq = http.request(options, (proxyRes) => {
|
|
1238
|
+
res.writeHead(proxyRes.statusCode || 200, proxyRes.headers);
|
|
1239
|
+
proxyRes.pipe(res);
|
|
1240
|
+
});
|
|
1241
|
+
proxyReq.on("error", (err) => {
|
|
1242
|
+
res.writeHead(502);
|
|
1243
|
+
res.end(`Proxy Error: ${err.message}`);
|
|
1244
|
+
});
|
|
1245
|
+
req.pipe(proxyReq);
|
|
1246
|
+
} catch (err) {
|
|
1247
|
+
res.writeHead(400);
|
|
1248
|
+
res.end(`Invalid URL: ${err.message}`);
|
|
1249
|
+
}
|
|
1250
|
+
});
|
|
1251
|
+
this.server.on("connect", (req, clientSocket, head) => {
|
|
1252
|
+
const parts = req.url ? req.url.split(":") : [];
|
|
1253
|
+
const hostname = parts[0];
|
|
1254
|
+
const port = parts[1] ? Number.parseInt(parts[1], 10) : 443;
|
|
1255
|
+
console.error(
|
|
1256
|
+
`\x1B[36m\u{1F310} [NETWORK AUDIT] HTTPS connection established to: ${hostname}:${port}\x1B[0m`
|
|
1257
|
+
);
|
|
1258
|
+
const serverSocket = net.connect(port, hostname, () => {
|
|
1259
|
+
clientSocket.write("HTTP/1.1 200 Connection Established\r\n\r\n");
|
|
1260
|
+
serverSocket.write(head);
|
|
1261
|
+
serverSocket.pipe(clientSocket);
|
|
1262
|
+
clientSocket.pipe(serverSocket);
|
|
1263
|
+
});
|
|
1264
|
+
serverSocket.on("error", (err) => {
|
|
1265
|
+
clientSocket.end(`HTTP/1.1 502 Bad Gateway\r
|
|
1266
|
+
\r
|
|
1267
|
+
Proxy Connection Error: ${err.message}`);
|
|
1268
|
+
});
|
|
1269
|
+
clientSocket.on("error", () => {
|
|
1270
|
+
serverSocket.end();
|
|
1271
|
+
});
|
|
1272
|
+
});
|
|
1273
|
+
}
|
|
1274
|
+
async start() {
|
|
1275
|
+
return new Promise((resolve3, reject) => {
|
|
1276
|
+
this.server.on("error", (err) => {
|
|
1277
|
+
reject(err);
|
|
1278
|
+
});
|
|
1279
|
+
this.server.listen(0, "127.0.0.1", () => {
|
|
1280
|
+
const addr = this.server.address();
|
|
1281
|
+
if (addr && typeof addr === "object") {
|
|
1282
|
+
this.port = addr.port;
|
|
1283
|
+
}
|
|
1284
|
+
resolve3(this.port);
|
|
1285
|
+
});
|
|
1286
|
+
});
|
|
1287
|
+
}
|
|
1288
|
+
async close() {
|
|
1289
|
+
return new Promise((resolve3) => {
|
|
1290
|
+
this.server.close(() => resolve3());
|
|
1291
|
+
});
|
|
1292
|
+
}
|
|
1293
|
+
getPort() {
|
|
1294
|
+
return this.port;
|
|
1295
|
+
}
|
|
1296
|
+
};
|
|
1297
|
+
|
|
1298
|
+
// src/target-manager.ts
|
|
838
1299
|
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
839
1300
|
import { SSEClientTransport } from "@modelcontextprotocol/sdk/client/sse.js";
|
|
840
1301
|
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
|
|
@@ -852,11 +1313,29 @@ var MIN_UPTIME_FOR_RESTART_MS = 5e3;
|
|
|
852
1313
|
var MAX_RECONNECT_ATTEMPTS = 3;
|
|
853
1314
|
var STABLE_CONNECTION_RESET_MS = 6e4;
|
|
854
1315
|
var MAX_HISTORY = 100;
|
|
1316
|
+
function isCommandAvailable(cmd) {
|
|
1317
|
+
try {
|
|
1318
|
+
const checkCmd = process.platform === "win32" ? `where ${cmd}` : `command -v ${cmd}`;
|
|
1319
|
+
execSync(checkCmd, { stdio: "ignore" });
|
|
1320
|
+
return true;
|
|
1321
|
+
} catch {
|
|
1322
|
+
return false;
|
|
1323
|
+
}
|
|
1324
|
+
}
|
|
855
1325
|
var TargetManager = class _TargetManager extends EventEmitter {
|
|
856
|
-
constructor(command, args) {
|
|
1326
|
+
constructor(command, args, options = {}) {
|
|
857
1327
|
super();
|
|
858
1328
|
this.command = command;
|
|
859
1329
|
this.args = args;
|
|
1330
|
+
this.sandboxMode = options.sandbox ?? "none";
|
|
1331
|
+
this.sandboxOptions = {
|
|
1332
|
+
allowRead: options.allowRead,
|
|
1333
|
+
allowWrite: options.allowWrite,
|
|
1334
|
+
allowNet: options.allowNet,
|
|
1335
|
+
denyRead: options.denyRead,
|
|
1336
|
+
denyWrite: options.denyWrite,
|
|
1337
|
+
denyNet: options.denyNet
|
|
1338
|
+
};
|
|
860
1339
|
}
|
|
861
1340
|
client = null;
|
|
862
1341
|
transport = null;
|
|
@@ -883,6 +1362,12 @@ var TargetManager = class _TargetManager extends EventEmitter {
|
|
|
883
1362
|
static MAX_NOTIFICATIONS = 200;
|
|
884
1363
|
// Roots
|
|
885
1364
|
_roots = [];
|
|
1365
|
+
_proxy = null;
|
|
1366
|
+
_proxyPort = 0;
|
|
1367
|
+
sandboxMode;
|
|
1368
|
+
_tempSbPath = null;
|
|
1369
|
+
_useWindowsMxc = false;
|
|
1370
|
+
sandboxOptions;
|
|
886
1371
|
/**
|
|
887
1372
|
* Enable auto-reconnect behavior.
|
|
888
1373
|
* Only applies to interactive REPL mode — proxy mode manages its own lifecycle.
|
|
@@ -901,11 +1386,27 @@ var TargetManager = class _TargetManager extends EventEmitter {
|
|
|
901
1386
|
if (this.command.startsWith("http://") || this.command.startsWith("https://")) {
|
|
902
1387
|
this.transport = new SSEClientTransport(new URL(this.command));
|
|
903
1388
|
} else {
|
|
1389
|
+
const policy = new SandboxPolicy();
|
|
1390
|
+
const fileSettings = loadSettings();
|
|
1391
|
+
if (fileSettings.sandbox) {
|
|
1392
|
+
policy.mergeConfig(fileSettings.sandbox, process.cwd());
|
|
1393
|
+
}
|
|
1394
|
+
policy.mergeCliOverrides(this.sandboxOptions);
|
|
1395
|
+
policy.applyCredentialProtections();
|
|
1396
|
+
if (this.sandboxMode !== "none" && this.sandboxMode !== "audit" && policy.networkAllow.size > 0 && !policy.networkDeny.has("*")) {
|
|
1397
|
+
this._proxy = new NetworkAuditProxy();
|
|
1398
|
+
this._proxyPort = await this._proxy.start();
|
|
1399
|
+
}
|
|
1400
|
+
const { command: finalCommand, args: finalArgs } = await this._maybeWrapCommand(policy);
|
|
904
1401
|
const stdioTransport = new StdioClientTransport({
|
|
905
|
-
command:
|
|
906
|
-
args:
|
|
907
|
-
stderr: "pipe"
|
|
1402
|
+
command: finalCommand,
|
|
1403
|
+
args: finalArgs,
|
|
1404
|
+
stderr: "pipe",
|
|
1405
|
+
env: this._getDefaultEnvironment()
|
|
908
1406
|
});
|
|
1407
|
+
if (this._useWindowsMxc) {
|
|
1408
|
+
await this._applyWindowsMxcOverride(stdioTransport, finalCommand, finalArgs, policy);
|
|
1409
|
+
}
|
|
909
1410
|
stdioTransport.stderr?.on("data", (chunk) => {
|
|
910
1411
|
const text = chunk.toString().trimEnd();
|
|
911
1412
|
if (text) {
|
|
@@ -915,13 +1416,21 @@ var TargetManager = class _TargetManager extends EventEmitter {
|
|
|
915
1416
|
if (this._stderrLines.length > _TargetManager.MAX_STDERR_LINES) {
|
|
916
1417
|
this._stderrLines = this._stderrLines.slice(-_TargetManager.MAX_STDERR_LINES);
|
|
917
1418
|
}
|
|
1419
|
+
if (this.sandboxMode === "audit") {
|
|
1420
|
+
const lowerText = text.toLowerCase();
|
|
1421
|
+
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")) {
|
|
1422
|
+
console.error(
|
|
1423
|
+
`\x1B[31m\u26A0\uFE0F [SANDBOX AUDIT] Blocked unauthorized side-effect: ${text}\x1B[0m`
|
|
1424
|
+
);
|
|
1425
|
+
}
|
|
1426
|
+
}
|
|
918
1427
|
this.emit("stderr", text);
|
|
919
1428
|
}
|
|
920
1429
|
});
|
|
921
1430
|
this.transport = stdioTransport;
|
|
922
1431
|
}
|
|
923
1432
|
this.client = new Client(
|
|
924
|
-
{ name: "run-mcp", version: "1.
|
|
1433
|
+
{ name: "run-mcp", version: "1.7.0" },
|
|
925
1434
|
{
|
|
926
1435
|
capabilities: {
|
|
927
1436
|
roots: { listChanged: true },
|
|
@@ -979,7 +1488,7 @@ var TargetManager = class _TargetManager extends EventEmitter {
|
|
|
979
1488
|
this.emit("notification", record);
|
|
980
1489
|
});
|
|
981
1490
|
this.client.setRequestHandler(CreateMessageRequestSchema, async (request) => {
|
|
982
|
-
return new Promise((
|
|
1491
|
+
return new Promise((resolve3, reject) => {
|
|
983
1492
|
const timeout = setTimeout(() => {
|
|
984
1493
|
reject(new Error("Sampling request timed out (no response from user in 5 minutes)"));
|
|
985
1494
|
}, 3e5);
|
|
@@ -987,7 +1496,7 @@ var TargetManager = class _TargetManager extends EventEmitter {
|
|
|
987
1496
|
request: request.params,
|
|
988
1497
|
respond: (result) => {
|
|
989
1498
|
clearTimeout(timeout);
|
|
990
|
-
|
|
1499
|
+
resolve3(result);
|
|
991
1500
|
},
|
|
992
1501
|
reject: (err) => {
|
|
993
1502
|
clearTimeout(timeout);
|
|
@@ -997,7 +1506,7 @@ var TargetManager = class _TargetManager extends EventEmitter {
|
|
|
997
1506
|
});
|
|
998
1507
|
});
|
|
999
1508
|
this.client.setRequestHandler(ElicitRequestSchema, async (request) => {
|
|
1000
|
-
return new Promise((
|
|
1509
|
+
return new Promise((resolve3, reject) => {
|
|
1001
1510
|
const timeout = setTimeout(() => {
|
|
1002
1511
|
reject(new Error("Elicitation request timed out (no response from user in 5 minutes)"));
|
|
1003
1512
|
}, 3e5);
|
|
@@ -1005,7 +1514,7 @@ var TargetManager = class _TargetManager extends EventEmitter {
|
|
|
1005
1514
|
request: request.params,
|
|
1006
1515
|
respond: (result) => {
|
|
1007
1516
|
clearTimeout(timeout);
|
|
1008
|
-
|
|
1517
|
+
resolve3(result);
|
|
1009
1518
|
},
|
|
1010
1519
|
reject: (err) => {
|
|
1011
1520
|
clearTimeout(timeout);
|
|
@@ -1343,7 +1852,8 @@ var TargetManager = class _TargetManager extends EventEmitter {
|
|
|
1343
1852
|
lastResponseTime: this._lastResponseTime,
|
|
1344
1853
|
stderrLineCount: this._stderrLineCount,
|
|
1345
1854
|
reconnectAttempts: this._reconnectAttempts,
|
|
1346
|
-
maxReconnectAttempts: MAX_RECONNECT_ATTEMPTS
|
|
1855
|
+
maxReconnectAttempts: MAX_RECONNECT_ATTEMPTS,
|
|
1856
|
+
sandbox: this.sandboxMode
|
|
1347
1857
|
};
|
|
1348
1858
|
}
|
|
1349
1859
|
/**
|
|
@@ -1368,10 +1878,25 @@ var TargetManager = class _TargetManager extends EventEmitter {
|
|
|
1368
1878
|
this.transport = null;
|
|
1369
1879
|
}
|
|
1370
1880
|
if (pidToKill) {
|
|
1371
|
-
await new Promise((
|
|
1372
|
-
treeKill(pidToKill, "SIGKILL", () =>
|
|
1881
|
+
await new Promise((resolve3) => {
|
|
1882
|
+
treeKill(pidToKill, "SIGKILL", () => resolve3());
|
|
1373
1883
|
});
|
|
1374
1884
|
}
|
|
1885
|
+
if (this._tempSbPath && existsSync3(this._tempSbPath)) {
|
|
1886
|
+
try {
|
|
1887
|
+
rmSync(this._tempSbPath, { force: true });
|
|
1888
|
+
} catch {
|
|
1889
|
+
}
|
|
1890
|
+
this._tempSbPath = null;
|
|
1891
|
+
}
|
|
1892
|
+
if (this._proxy) {
|
|
1893
|
+
try {
|
|
1894
|
+
await this._proxy.close();
|
|
1895
|
+
} catch {
|
|
1896
|
+
}
|
|
1897
|
+
this._proxy = null;
|
|
1898
|
+
this._proxyPort = 0;
|
|
1899
|
+
}
|
|
1375
1900
|
this._connected = false;
|
|
1376
1901
|
this.childPid = null;
|
|
1377
1902
|
}
|
|
@@ -1443,6 +1968,264 @@ var TargetManager = class _TargetManager extends EventEmitter {
|
|
|
1443
1968
|
}
|
|
1444
1969
|
}
|
|
1445
1970
|
// ─── Internal helpers ──────────────────────────────────────────────────────
|
|
1971
|
+
async _maybeWrapCommand(policy) {
|
|
1972
|
+
let command = this.command;
|
|
1973
|
+
let args = [...this.args];
|
|
1974
|
+
let modeToUse = this.sandboxMode;
|
|
1975
|
+
if (modeToUse === "auto") {
|
|
1976
|
+
if (process.platform === "darwin") {
|
|
1977
|
+
if (isCommandAvailable("sandbox-exec")) {
|
|
1978
|
+
modeToUse = "native";
|
|
1979
|
+
} else {
|
|
1980
|
+
process.stderr.write("Warning: sandbox-exec is not available. Sandboxing disabled.\n");
|
|
1981
|
+
modeToUse = "none";
|
|
1982
|
+
}
|
|
1983
|
+
} else if (process.platform === "linux") {
|
|
1984
|
+
if (isCommandAvailable("bwrap")) {
|
|
1985
|
+
modeToUse = "native";
|
|
1986
|
+
} else {
|
|
1987
|
+
process.stderr.write("Warning: bwrap is not available. Sandboxing disabled.\n");
|
|
1988
|
+
modeToUse = "none";
|
|
1989
|
+
}
|
|
1990
|
+
} else if (process.platform === "win32") {
|
|
1991
|
+
let hasMxc = false;
|
|
1992
|
+
try {
|
|
1993
|
+
const mxcModule = "@microsoft/mxc-sdk";
|
|
1994
|
+
await import(mxcModule);
|
|
1995
|
+
hasMxc = true;
|
|
1996
|
+
} catch {
|
|
1997
|
+
}
|
|
1998
|
+
if (hasMxc) {
|
|
1999
|
+
modeToUse = "native";
|
|
2000
|
+
} else {
|
|
2001
|
+
process.stderr.write(
|
|
2002
|
+
"Warning: @microsoft/mxc-sdk is not available. Sandboxing disabled.\n"
|
|
2003
|
+
);
|
|
2004
|
+
modeToUse = "none";
|
|
2005
|
+
}
|
|
2006
|
+
} else {
|
|
2007
|
+
process.stderr.write(
|
|
2008
|
+
`Warning: Sandboxing not supported on platform "${process.platform}". Sandboxing disabled.
|
|
2009
|
+
`
|
|
2010
|
+
);
|
|
2011
|
+
modeToUse = "none";
|
|
2012
|
+
}
|
|
2013
|
+
}
|
|
2014
|
+
if (modeToUse === "docker") {
|
|
2015
|
+
let image = "node:20";
|
|
2016
|
+
if (command.includes("python")) {
|
|
2017
|
+
image = "python:3";
|
|
2018
|
+
}
|
|
2019
|
+
const cwd = process.cwd();
|
|
2020
|
+
args = [
|
|
2021
|
+
"run",
|
|
2022
|
+
"-i",
|
|
2023
|
+
"--rm",
|
|
2024
|
+
"--net=none",
|
|
2025
|
+
"-v",
|
|
2026
|
+
`${cwd}:/workspace`,
|
|
2027
|
+
"-w",
|
|
2028
|
+
"/workspace",
|
|
2029
|
+
image,
|
|
2030
|
+
command,
|
|
2031
|
+
...args
|
|
2032
|
+
];
|
|
2033
|
+
command = "docker";
|
|
2034
|
+
} else if (modeToUse === "native" || modeToUse === "audit") {
|
|
2035
|
+
const isAudit = modeToUse === "audit";
|
|
2036
|
+
if (process.platform === "darwin") {
|
|
2037
|
+
if (!isCommandAvailable("sandbox-exec")) {
|
|
2038
|
+
throw new Error(
|
|
2039
|
+
"sandbox-exec not found. Native sandboxing is not available on this macOS host."
|
|
2040
|
+
);
|
|
2041
|
+
}
|
|
2042
|
+
const cwd = process.cwd();
|
|
2043
|
+
const tmp = tmpdir2();
|
|
2044
|
+
const nodeBinDir = join3(process.execPath, "..");
|
|
2045
|
+
const nodeInstallDir = join3(nodeBinDir, "..");
|
|
2046
|
+
const profile = policy.getSeatbeltProfile({
|
|
2047
|
+
tmp,
|
|
2048
|
+
cwd,
|
|
2049
|
+
nodeBinDir,
|
|
2050
|
+
nodeInstallDir,
|
|
2051
|
+
audit: isAudit
|
|
2052
|
+
});
|
|
2053
|
+
const tempSbPath = join3(
|
|
2054
|
+
tmp,
|
|
2055
|
+
`run-mcp-sandbox-${Date.now()}-${Math.random().toString(36).slice(2)}.sb`
|
|
2056
|
+
);
|
|
2057
|
+
writeFileSync(tempSbPath, profile, "utf8");
|
|
2058
|
+
this._tempSbPath = tempSbPath;
|
|
2059
|
+
args = ["-f", tempSbPath, command, ...args];
|
|
2060
|
+
command = "sandbox-exec";
|
|
2061
|
+
} else if (process.platform === "linux") {
|
|
2062
|
+
if (!isCommandAvailable("bwrap")) {
|
|
2063
|
+
throw new Error(
|
|
2064
|
+
"bwrap not found. Native sandboxing is not available on this Linux host."
|
|
2065
|
+
);
|
|
2066
|
+
}
|
|
2067
|
+
const cwd = process.cwd();
|
|
2068
|
+
const tmp = tmpdir2();
|
|
2069
|
+
const bwrapArgs = [
|
|
2070
|
+
"--ro-bind",
|
|
2071
|
+
"/usr",
|
|
2072
|
+
"/usr",
|
|
2073
|
+
"--ro-bind",
|
|
2074
|
+
"/lib",
|
|
2075
|
+
"/lib",
|
|
2076
|
+
"--ro-bind-try",
|
|
2077
|
+
"/lib64",
|
|
2078
|
+
"/lib64",
|
|
2079
|
+
"--ro-bind-try",
|
|
2080
|
+
"/bin",
|
|
2081
|
+
"/bin",
|
|
2082
|
+
"--ro-bind-try",
|
|
2083
|
+
"/sbin",
|
|
2084
|
+
"/sbin",
|
|
2085
|
+
"--ro-bind-try",
|
|
2086
|
+
"/etc",
|
|
2087
|
+
"/etc",
|
|
2088
|
+
"--ro-bind",
|
|
2089
|
+
cwd,
|
|
2090
|
+
cwd,
|
|
2091
|
+
"--dev",
|
|
2092
|
+
"/dev",
|
|
2093
|
+
"--proc",
|
|
2094
|
+
"/proc",
|
|
2095
|
+
"--unshare-pid",
|
|
2096
|
+
"--unshare-user",
|
|
2097
|
+
"--unshare-ipc"
|
|
2098
|
+
];
|
|
2099
|
+
if (isAudit) {
|
|
2100
|
+
bwrapArgs.push("--unshare-net");
|
|
2101
|
+
bwrapArgs.push("--ro-bind", tmp, tmp);
|
|
2102
|
+
} else {
|
|
2103
|
+
bwrapArgs.push("--bind", tmp, tmp);
|
|
2104
|
+
if (policy.networkAllow.size === 0 || policy.networkDeny.has("*")) {
|
|
2105
|
+
bwrapArgs.push("--unshare-net");
|
|
2106
|
+
}
|
|
2107
|
+
for (const p of policy.fileReadAllow) {
|
|
2108
|
+
if (existsSync3(p)) {
|
|
2109
|
+
bwrapArgs.push("--ro-bind", p, p);
|
|
2110
|
+
}
|
|
2111
|
+
}
|
|
2112
|
+
for (const p of policy.fileWriteAllow) {
|
|
2113
|
+
if (existsSync3(p)) {
|
|
2114
|
+
bwrapArgs.push("--bind", p, p);
|
|
2115
|
+
}
|
|
2116
|
+
}
|
|
2117
|
+
}
|
|
2118
|
+
args = [...bwrapArgs, command, ...args];
|
|
2119
|
+
command = "bwrap";
|
|
2120
|
+
} else if (process.platform === "win32") {
|
|
2121
|
+
let hasMxc = false;
|
|
2122
|
+
try {
|
|
2123
|
+
const mxcModule = "@microsoft/mxc-sdk";
|
|
2124
|
+
await import(mxcModule);
|
|
2125
|
+
hasMxc = true;
|
|
2126
|
+
} catch {
|
|
2127
|
+
}
|
|
2128
|
+
if (!hasMxc) {
|
|
2129
|
+
throw new Error(
|
|
2130
|
+
"@microsoft/mxc-sdk not found. Native sandboxing is not available on this Windows host."
|
|
2131
|
+
);
|
|
2132
|
+
}
|
|
2133
|
+
this._useWindowsMxc = true;
|
|
2134
|
+
} else {
|
|
2135
|
+
throw new Error(`Native sandboxing not supported on platform "${process.platform}"`);
|
|
2136
|
+
}
|
|
2137
|
+
}
|
|
2138
|
+
return { command, args };
|
|
2139
|
+
}
|
|
2140
|
+
async _applyWindowsMxcOverride(transport, command, args, policyObj) {
|
|
2141
|
+
const mxcModule = "@microsoft/mxc-sdk";
|
|
2142
|
+
const mxcSdk = await import(mxcModule);
|
|
2143
|
+
const isAudit = this.sandboxMode === "audit";
|
|
2144
|
+
const readPaths = isAudit ? [process.cwd()] : [process.cwd(), tmpdir2(), ...Array.from(policyObj.fileReadAllow)];
|
|
2145
|
+
const writePaths = isAudit ? [] : [tmpdir2(), ...Array.from(policyObj.fileWriteAllow)];
|
|
2146
|
+
const policy = {
|
|
2147
|
+
filesystem: {
|
|
2148
|
+
read: readPaths,
|
|
2149
|
+
write: writePaths
|
|
2150
|
+
},
|
|
2151
|
+
network: {
|
|
2152
|
+
outbound: isAudit ? "block" : policyObj.networkAllow.size > 0 ? "allow" : "block"
|
|
2153
|
+
}
|
|
2154
|
+
};
|
|
2155
|
+
const config = mxcSdk.createConfigFromPolicy(policy);
|
|
2156
|
+
transport.start = async () => {
|
|
2157
|
+
if (transport._process) {
|
|
2158
|
+
throw new Error("StdioClientTransport already started!");
|
|
2159
|
+
}
|
|
2160
|
+
return new Promise((resolve3, reject) => {
|
|
2161
|
+
try {
|
|
2162
|
+
const env = {
|
|
2163
|
+
...this._getDefaultEnvironment(),
|
|
2164
|
+
...transport._serverParams.env
|
|
2165
|
+
};
|
|
2166
|
+
transport._process = mxcSdk.spawnSandboxFromConfig(config, command, args, {
|
|
2167
|
+
env,
|
|
2168
|
+
stdio: ["pipe", "pipe", transport._serverParams.stderr ?? "inherit"],
|
|
2169
|
+
cwd: transport._serverParams.cwd
|
|
2170
|
+
});
|
|
2171
|
+
transport._process.on("error", (error) => {
|
|
2172
|
+
reject(error);
|
|
2173
|
+
transport.onerror?.(error);
|
|
2174
|
+
});
|
|
2175
|
+
transport._process.on("spawn", () => {
|
|
2176
|
+
resolve3();
|
|
2177
|
+
});
|
|
2178
|
+
transport._process.on("close", () => {
|
|
2179
|
+
transport._process = void 0;
|
|
2180
|
+
transport.onclose?.();
|
|
2181
|
+
});
|
|
2182
|
+
transport._process.stdin?.on("error", (error) => {
|
|
2183
|
+
transport.onerror?.(error);
|
|
2184
|
+
});
|
|
2185
|
+
transport._process.stdout?.on("data", (chunk) => {
|
|
2186
|
+
transport._readBuffer.append(chunk);
|
|
2187
|
+
transport.processReadBuffer();
|
|
2188
|
+
});
|
|
2189
|
+
transport._process.stdout?.on("error", (error) => {
|
|
2190
|
+
transport.onerror?.(error);
|
|
2191
|
+
});
|
|
2192
|
+
if (transport._stderrStream && transport._process.stderr) {
|
|
2193
|
+
transport._process.stderr.pipe(transport._stderrStream);
|
|
2194
|
+
}
|
|
2195
|
+
} catch (err) {
|
|
2196
|
+
reject(err);
|
|
2197
|
+
}
|
|
2198
|
+
});
|
|
2199
|
+
};
|
|
2200
|
+
}
|
|
2201
|
+
_getDefaultEnvironment() {
|
|
2202
|
+
const env = {};
|
|
2203
|
+
const safeVars = process.platform === "win32" ? [
|
|
2204
|
+
"APPDATA",
|
|
2205
|
+
"HOMEDRIVE",
|
|
2206
|
+
"HOMEPATH",
|
|
2207
|
+
"LOCALAPPDATA",
|
|
2208
|
+
"PATH",
|
|
2209
|
+
"TEMP",
|
|
2210
|
+
"USERPROFILE",
|
|
2211
|
+
"SYSTEMROOT"
|
|
2212
|
+
] : ["HOME", "PATH", "SHELL", "USER"];
|
|
2213
|
+
for (const key of safeVars) {
|
|
2214
|
+
if (process.env[key]) {
|
|
2215
|
+
env[key] = process.env[key];
|
|
2216
|
+
}
|
|
2217
|
+
}
|
|
2218
|
+
if (this._proxyPort) {
|
|
2219
|
+
const proxyUrl = `http://127.0.0.1:${this._proxyPort}`;
|
|
2220
|
+
env["http_proxy"] = proxyUrl;
|
|
2221
|
+
env["https_proxy"] = proxyUrl;
|
|
2222
|
+
env["HTTP_PROXY"] = proxyUrl;
|
|
2223
|
+
env["HTTPS_PROXY"] = proxyUrl;
|
|
2224
|
+
env["all_proxy"] = proxyUrl;
|
|
2225
|
+
env["ALL_PROXY"] = proxyUrl;
|
|
2226
|
+
}
|
|
2227
|
+
return env;
|
|
2228
|
+
}
|
|
1446
2229
|
_assertConnected() {
|
|
1447
2230
|
if (!this._connected || !this.client) {
|
|
1448
2231
|
throw new Error("Not connected to target MCP server");
|
|
@@ -1476,7 +2259,15 @@ var TargetManager = class _TargetManager extends EventEmitter {
|
|
|
1476
2259
|
var DEFAULT_HEADLESS_TIMEOUT_MS = 3e4;
|
|
1477
2260
|
async function runHeadless(targetCommand, operation, opts = {}) {
|
|
1478
2261
|
const [command, ...args] = targetCommand;
|
|
1479
|
-
const target = new TargetManager(command, args
|
|
2262
|
+
const target = new TargetManager(command, args, {
|
|
2263
|
+
sandbox: opts.sandbox,
|
|
2264
|
+
allowRead: opts.allowRead,
|
|
2265
|
+
allowWrite: opts.allowWrite,
|
|
2266
|
+
allowNet: opts.allowNet,
|
|
2267
|
+
denyRead: opts.denyRead,
|
|
2268
|
+
denyWrite: opts.denyWrite,
|
|
2269
|
+
denyNet: opts.denyNet
|
|
2270
|
+
});
|
|
1480
2271
|
const interceptor = new ResponseInterceptor({
|
|
1481
2272
|
outDir: opts.outDir,
|
|
1482
2273
|
defaultTimeoutMs: opts.timeoutMs ?? DEFAULT_HEADLESS_TIMEOUT_MS
|
|
@@ -1504,21 +2295,21 @@ async function runHeadless(targetCommand, operation, opts = {}) {
|
|
|
1504
2295
|
process.exit(hasError ? 1 : 0);
|
|
1505
2296
|
} catch (err) {
|
|
1506
2297
|
const msg = err.message ?? String(err);
|
|
2298
|
+
let exitCode;
|
|
1507
2299
|
if (msg.includes("ENOENT") || msg.includes("spawn")) {
|
|
1508
2300
|
process.stderr.write(
|
|
1509
2301
|
`Error: command "${command}" not found. Check that it is installed and in your PATH.
|
|
1510
2302
|
`
|
|
1511
2303
|
);
|
|
1512
|
-
|
|
1513
|
-
process.stderr.write(`Error: ${msg}
|
|
1514
|
-
`);
|
|
2304
|
+
exitCode = 66;
|
|
1515
2305
|
} else {
|
|
1516
2306
|
process.stderr.write(`Error: ${msg}
|
|
1517
2307
|
`);
|
|
2308
|
+
exitCode = 69;
|
|
1518
2309
|
}
|
|
1519
2310
|
await target.close().catch(() => {
|
|
1520
2311
|
});
|
|
1521
|
-
process.exit(
|
|
2312
|
+
process.exit(exitCode);
|
|
1522
2313
|
}
|
|
1523
2314
|
}
|
|
1524
2315
|
async function executeOperation(target, interceptor, operation, opts) {
|
|
@@ -1535,7 +2326,7 @@ async function executeOperation(target, interceptor, operation, opts) {
|
|
|
1535
2326
|
`);
|
|
1536
2327
|
process.stderr.write(` Received: ${operation.args}
|
|
1537
2328
|
`);
|
|
1538
|
-
process.exit(
|
|
2329
|
+
process.exit(65);
|
|
1539
2330
|
}
|
|
1540
2331
|
} else {
|
|
1541
2332
|
parsedArgs = parseHttpieArgs(trimmed);
|
|
@@ -1583,7 +2374,7 @@ async function executeOperation(target, interceptor, operation, opts) {
|
|
|
1583
2374
|
Available tools: ${available}
|
|
1584
2375
|
`
|
|
1585
2376
|
);
|
|
1586
|
-
process.exit(
|
|
2377
|
+
process.exit(64);
|
|
1587
2378
|
}
|
|
1588
2379
|
return { result: tool, hasError: false };
|
|
1589
2380
|
}
|
|
@@ -1599,7 +2390,7 @@ Available tools: ${available}
|
|
|
1599
2390
|
`);
|
|
1600
2391
|
process.stderr.write(` Received: ${operation.args}
|
|
1601
2392
|
`);
|
|
1602
|
-
process.exit(
|
|
2393
|
+
process.exit(65);
|
|
1603
2394
|
}
|
|
1604
2395
|
} else {
|
|
1605
2396
|
parsedArgs = parseHttpieArgs(trimmed);
|
|
@@ -1614,14 +2405,16 @@ Available tools: ${available}
|
|
|
1614
2405
|
}
|
|
1615
2406
|
}
|
|
1616
2407
|
|
|
1617
|
-
// src/repl.ts
|
|
1618
|
-
import { readFile as
|
|
2408
|
+
// src/repl/index.ts
|
|
2409
|
+
import { readFile as readFile4 } from "fs/promises";
|
|
1619
2410
|
import { createInterface } from "readline";
|
|
1620
|
-
|
|
1621
|
-
|
|
2411
|
+
|
|
2412
|
+
// src/repl/state.ts
|
|
1622
2413
|
var KNOWN_COMMANDS = [
|
|
2414
|
+
"menu",
|
|
1623
2415
|
"explore",
|
|
1624
2416
|
"interactive",
|
|
2417
|
+
"view",
|
|
1625
2418
|
"tools/list",
|
|
1626
2419
|
"tools/describe",
|
|
1627
2420
|
"tools/call",
|
|
@@ -1663,48 +2456,259 @@ var KNOWN_COMMANDS = [
|
|
|
1663
2456
|
"pl",
|
|
1664
2457
|
"pg"
|
|
1665
2458
|
];
|
|
2459
|
+
var activeRl = null;
|
|
2460
|
+
function setActiveRl(rl) {
|
|
2461
|
+
activeRl = rl;
|
|
2462
|
+
}
|
|
2463
|
+
var closed = false;
|
|
2464
|
+
function setClosed(val) {
|
|
2465
|
+
closed = val;
|
|
2466
|
+
}
|
|
2467
|
+
var isScriptMode = false;
|
|
2468
|
+
function setIsScriptMode(val) {
|
|
2469
|
+
isScriptMode = val;
|
|
2470
|
+
}
|
|
2471
|
+
var globalPauseReadlineClose = false;
|
|
2472
|
+
function setGlobalPauseReadlineClose(val) {
|
|
2473
|
+
globalPauseReadlineClose = val;
|
|
2474
|
+
}
|
|
2475
|
+
var deferNextPrompt = false;
|
|
2476
|
+
function setDeferNextPrompt(val) {
|
|
2477
|
+
deferNextPrompt = val;
|
|
2478
|
+
}
|
|
1666
2479
|
var cachedToolNames = [];
|
|
2480
|
+
function setCachedToolNames(names) {
|
|
2481
|
+
cachedToolNames = names;
|
|
2482
|
+
}
|
|
1667
2483
|
var cachedResourceUris = [];
|
|
2484
|
+
function setCachedResourceUris(uris) {
|
|
2485
|
+
cachedResourceUris = uris;
|
|
2486
|
+
}
|
|
1668
2487
|
var cachedPromptNames = [];
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
let commands = [...KNOWN_COMMANDS];
|
|
1672
|
-
if (!activeCapabilities?.resources) {
|
|
1673
|
-
commands = commands.filter(
|
|
1674
|
-
(c) => !c.startsWith("resources/") && !["rl", "rr", "rt", "rs", "ru"].includes(c)
|
|
1675
|
-
);
|
|
1676
|
-
}
|
|
1677
|
-
if (!activeCapabilities?.prompts) {
|
|
1678
|
-
commands = commands.filter((c) => !c.startsWith("prompts/") && !["pl", "pg"].includes(c));
|
|
1679
|
-
}
|
|
1680
|
-
return commands;
|
|
2488
|
+
function setCachedPromptNames(names) {
|
|
2489
|
+
cachedPromptNames = names;
|
|
1681
2490
|
}
|
|
1682
|
-
|
|
1683
|
-
|
|
2491
|
+
var activeCapabilities = null;
|
|
2492
|
+
function setActiveCapabilities(caps) {
|
|
1684
2493
|
activeCapabilities = caps;
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
2494
|
+
}
|
|
2495
|
+
var tabCycleState = null;
|
|
2496
|
+
function setTabCycleState(state) {
|
|
2497
|
+
tabCycleState = state;
|
|
2498
|
+
}
|
|
2499
|
+
var callHistory = [];
|
|
2500
|
+
var lastToolArgsMap = /* @__PURE__ */ new Map();
|
|
2501
|
+
|
|
2502
|
+
// src/repl/history.ts
|
|
2503
|
+
import { readFile as readFile2, mkdir as mkdir2, appendFile } from "fs/promises";
|
|
2504
|
+
import { existsSync as existsSync4 } from "fs";
|
|
2505
|
+
import { join as join4 } from "path";
|
|
2506
|
+
import { homedir as homedir3 } from "os";
|
|
2507
|
+
var RUN_MCP_DIR = join4(homedir3(), ".run-mcp");
|
|
2508
|
+
var HISTORY_FILE = join4(RUN_MCP_DIR, "history");
|
|
2509
|
+
var replHistory = [];
|
|
2510
|
+
async function ensureRunMcpDir() {
|
|
2511
|
+
try {
|
|
2512
|
+
await mkdir2(RUN_MCP_DIR, { recursive: true });
|
|
2513
|
+
} catch {
|
|
2514
|
+
}
|
|
2515
|
+
}
|
|
2516
|
+
async function loadHistory() {
|
|
2517
|
+
await ensureRunMcpDir();
|
|
2518
|
+
if (existsSync4(HISTORY_FILE)) {
|
|
1691
2519
|
try {
|
|
1692
|
-
const
|
|
1693
|
-
|
|
2520
|
+
const content = await readFile2(HISTORY_FILE, "utf8");
|
|
2521
|
+
const lines = content.split("\n").map((l) => l.trim()).filter(Boolean);
|
|
2522
|
+
replHistory.length = 0;
|
|
2523
|
+
replHistory.push(...lines);
|
|
1694
2524
|
} catch {
|
|
1695
2525
|
}
|
|
1696
2526
|
}
|
|
1697
|
-
|
|
2527
|
+
}
|
|
2528
|
+
async function appendToHistoryFile(line) {
|
|
2529
|
+
await ensureRunMcpDir();
|
|
2530
|
+
try {
|
|
2531
|
+
await appendFile(HISTORY_FILE, line + "\n", "utf8");
|
|
2532
|
+
} catch {
|
|
2533
|
+
}
|
|
2534
|
+
}
|
|
2535
|
+
|
|
2536
|
+
// src/repl/wizard.ts
|
|
2537
|
+
import { readFile as readFile3, writeFile as writeFile2 } from "fs/promises";
|
|
2538
|
+
import { existsSync as existsSync5 } from "fs";
|
|
2539
|
+
import { join as join5 } from "path";
|
|
2540
|
+
var WIZARD_DEFAULTS_FILE = join5(RUN_MCP_DIR, "wizard_defaults.json");
|
|
2541
|
+
async function loadWizardDefaults() {
|
|
2542
|
+
await ensureRunMcpDir();
|
|
2543
|
+
if (existsSync5(WIZARD_DEFAULTS_FILE)) {
|
|
1698
2544
|
try {
|
|
1699
|
-
const
|
|
1700
|
-
|
|
2545
|
+
const content = await readFile3(WIZARD_DEFAULTS_FILE, "utf8");
|
|
2546
|
+
const parsed = JSON.parse(content);
|
|
2547
|
+
lastToolArgsMap.clear();
|
|
2548
|
+
for (const [key, val] of Object.entries(parsed)) {
|
|
2549
|
+
lastToolArgsMap.set(key, val);
|
|
2550
|
+
}
|
|
1701
2551
|
} catch {
|
|
1702
2552
|
}
|
|
1703
2553
|
}
|
|
1704
2554
|
}
|
|
1705
|
-
|
|
2555
|
+
async function saveWizardDefaults() {
|
|
2556
|
+
await ensureRunMcpDir();
|
|
2557
|
+
try {
|
|
2558
|
+
const obj = Object.fromEntries(lastToolArgsMap.entries());
|
|
2559
|
+
await writeFile2(WIZARD_DEFAULTS_FILE, JSON.stringify(obj, null, 2), "utf8");
|
|
2560
|
+
} catch {
|
|
2561
|
+
}
|
|
2562
|
+
}
|
|
2563
|
+
|
|
2564
|
+
// src/repl/ui.ts
|
|
2565
|
+
function printResultBlock(opts) {
|
|
2566
|
+
const colorFn = colors[opts.labelColor];
|
|
2567
|
+
const elapsedStr = opts.elapsed < 1e3 ? `${opts.elapsed}ms` : `${(opts.elapsed / 1e3).toFixed(1)}s`;
|
|
2568
|
+
const detail = opts.detail ?? opts.toolName ?? "";
|
|
2569
|
+
console.log(` ${colorFn(opts.label)} ${colors.dim(detail)} ${colors.dim(`(${elapsedStr})`)}`);
|
|
2570
|
+
console.log(colors.dim(` ${"\u2500".repeat(60)}`));
|
|
2571
|
+
}
|
|
2572
|
+
function printShortHelp() {
|
|
2573
|
+
const hasTools = !!activeCapabilities?.tools;
|
|
2574
|
+
const hasResources = !!activeCapabilities?.resources;
|
|
2575
|
+
const hasPrompts = !!activeCapabilities?.prompts;
|
|
2576
|
+
const tC = hasTools ? colors.green : colors.dim;
|
|
2577
|
+
const rC = hasResources ? colors.green : colors.dim;
|
|
2578
|
+
const pC = hasPrompts ? colors.green : colors.dim;
|
|
2579
|
+
console.log(`
|
|
2580
|
+
${colors.bold("Quick Reference:")}
|
|
2581
|
+
|
|
2582
|
+
${tC("tl")} ${tC("tools/list")} ${rC("rl")} ${rC("resources/list")} ${pC("pl")} ${pC("prompts/list")}
|
|
2583
|
+
${tC("td")} ${tC("tools/describe")} ${rC("rr")} ${rC("resources/read")} ${pC("pg")} ${pC("prompts/get")}
|
|
2584
|
+
${tC("tc")} ${tC("tools/call")} ${rC("rt")} ${rC("resources/templates")}
|
|
2585
|
+
${tC("ts")} ${tC("tools/scaffold")} ${rC("rs")} ${rC("resources/subscribe")}
|
|
2586
|
+
${rC("ru")} ${rC("resources/unsubscribe")}
|
|
2587
|
+
|
|
2588
|
+
${colors.green("ping")} ${colors.green("status")} ${colors.green("timing")} ${colors.green("history")} ${colors.green("!!")} ${colors.green("explore")} ${colors.green("reconnect")}
|
|
2589
|
+
|
|
2590
|
+
${colors.dim("Type 'help' for full command reference.")}
|
|
2591
|
+
`);
|
|
2592
|
+
}
|
|
2593
|
+
function printHelp() {
|
|
2594
|
+
const hasTools = !!activeCapabilities?.tools;
|
|
2595
|
+
const hasResources = !!activeCapabilities?.resources;
|
|
2596
|
+
const hasPrompts = !!activeCapabilities?.prompts;
|
|
2597
|
+
const hasLogging = !!activeCapabilities?.logging;
|
|
2598
|
+
const tC = hasTools ? colors.green : colors.dim;
|
|
2599
|
+
const rC = hasResources ? colors.green : colors.dim;
|
|
2600
|
+
const pC = hasPrompts ? colors.green : colors.dim;
|
|
2601
|
+
const lC = hasLogging ? colors.green : colors.dim;
|
|
2602
|
+
const tD = hasTools ? (s) => s : colors.dim;
|
|
2603
|
+
const rD = hasResources ? (s) => s : colors.dim;
|
|
2604
|
+
const pD = hasPrompts ? (s) => s : colors.dim;
|
|
2605
|
+
const lD = hasLogging ? (s) => s : colors.dim;
|
|
2606
|
+
const tH = hasTools ? colors.bold("Tool Commands:") : colors.dim(colors.bold("Tool Commands:")) + colors.dim(" (Unsupported)");
|
|
2607
|
+
const rH = hasResources ? colors.bold("Resource Commands:") : colors.dim(colors.bold("Resource Commands:")) + colors.dim(" (Unsupported)");
|
|
2608
|
+
const pH = hasPrompts ? colors.bold("Prompt Commands:") : colors.dim(colors.bold("Prompt Commands:")) + colors.dim(" (Unsupported)");
|
|
2609
|
+
console.log(`
|
|
2610
|
+
${tH}
|
|
2611
|
+
|
|
2612
|
+
${tC("tools/list")} ${tD("List all available tools")}
|
|
2613
|
+
${tC("tools/describe")} <name> ${tD("Show a tool's input schema")}
|
|
2614
|
+
${tC("tools/call")} <name> [json] [opts] ${tD("Call a tool (interactive if no json)")}
|
|
2615
|
+
${tD("Options:")} ${colors.dim("--timeout <ms>")} ${tD("Override default timeout (60s)")}
|
|
2616
|
+
${colors.dim("--clear")} ${tD("Ignore remembered argument defaults")}
|
|
2617
|
+
${tC("tools/scaffold")} <name> ${tD("Generate a template for a tool's arguments")}
|
|
2618
|
+
${tC("tools/forget")} [name] ${tD("Clear remembered interactive defaults")}
|
|
2619
|
+
|
|
2620
|
+
${rH}
|
|
2621
|
+
|
|
2622
|
+
${rC("resources/list")} ${rD("List all available resources")}
|
|
2623
|
+
${rC("resources/read")} <uri> ${rD("Read a resource by URI")}
|
|
2624
|
+
${rC("resources/templates")} ${rD("List resource templates")}
|
|
2625
|
+
${rC("resources/subscribe")} <uri> ${rD("Subscribe to resource changes")}
|
|
2626
|
+
${rC("resources/unsubscribe")} <uri> ${rD("Unsubscribe from resource changes")}
|
|
2627
|
+
|
|
2628
|
+
${pH}
|
|
2629
|
+
|
|
2630
|
+
${pC("prompts/list")} ${pD("List all available prompts")}
|
|
2631
|
+
${pC("prompts/get")} <name> [json_args] ${pD("Get a prompt with arguments")}
|
|
2632
|
+
|
|
2633
|
+
${colors.bold("Protocol Commands:")}
|
|
2634
|
+
|
|
2635
|
+
${colors.green("ping")} Verify connection, show round-trip time
|
|
2636
|
+
${lC("log-level")} <level> ${lD("Set server logging verbosity")}${hasLogging ? "" : colors.dim(" (Unsupported)")}
|
|
2637
|
+
${colors.green("history")} [count|clear] Show request/response history
|
|
2638
|
+
${colors.green("notifications")} [count|clear] Show server notifications
|
|
2639
|
+
|
|
2640
|
+
${colors.bold("Roots Management:")}
|
|
2641
|
+
|
|
2642
|
+
${colors.green("roots/list")} Show configured client roots
|
|
2643
|
+
${colors.green("roots/add")} <uri> [name] Add a root directory
|
|
2644
|
+
${colors.green("roots/remove")} <uri> Remove a root directory
|
|
2645
|
+
|
|
2646
|
+
${colors.bold("Session Commands:")}
|
|
2647
|
+
|
|
2648
|
+
${colors.green("!!")} / ${colors.green("last")} Re-run the last command
|
|
2649
|
+
${colors.green("reconnect")} Disconnect and reconnect to the server
|
|
2650
|
+
${colors.green("timing")} Show tool call performance stats
|
|
2651
|
+
${colors.green("status")} Show target server status
|
|
2652
|
+
${colors.green("help")} Show this help
|
|
2653
|
+
${colors.green("exit")} / ${colors.green("quit")} Disconnect and exit
|
|
2654
|
+
|
|
2655
|
+
${colors.bold("Shortcuts:")}
|
|
2656
|
+
|
|
2657
|
+
${tC("tl")} ${tC("tools/list")} ${rC("rl")} ${rC("resources/list")} ${pC("pl")} ${pC("prompts/list")}
|
|
2658
|
+
${tC("td")} ${tC("tools/describe")} ${rC("rr")} ${rC("resources/read")} ${pC("pg")} ${pC("prompts/get")}
|
|
2659
|
+
${tC("tc")} ${tC("tools/call")} ${rC("rt")} ${rC("resources/templates")}
|
|
2660
|
+
${tC("ts")} ${tC("tools/scaffold")} ${rC("rs")} ${rC("resources/subscribe")}
|
|
2661
|
+
${rC("ru")} ${rC("resources/unsubscribe")}
|
|
2662
|
+
|
|
2663
|
+
${colors.dim("Lines starting with # are treated as comments.")}
|
|
2664
|
+
${colors.dim('JSON arguments can contain spaces: tools/call say {"message": "hello world"}')}
|
|
2665
|
+
${colors.dim("Run tools/call <name> without JSON for interactive argument prompting.")}
|
|
2666
|
+
${colors.dim("Use tools/call <name> --clear to ignore remembered defaults.")}
|
|
2667
|
+
`);
|
|
2668
|
+
}
|
|
2669
|
+
function stripAnsi(str) {
|
|
2670
|
+
return str.replace(/\x1B\[\d+m/g, "");
|
|
2671
|
+
}
|
|
2672
|
+
var BOX_WIDTH = 58;
|
|
2673
|
+
function padLine(content) {
|
|
2674
|
+
const clean = stripAnsi(content);
|
|
2675
|
+
const padding = Math.max(0, BOX_WIDTH - clean.length);
|
|
2676
|
+
return `${colors.cyan(" \u2502")}${content}${"".padEnd(padding)}${colors.cyan("\u2502")}`;
|
|
2677
|
+
}
|
|
2678
|
+
function printBanner(serverName, serverVersion, toolCount, resourceCount, promptCount, harnessMode) {
|
|
2679
|
+
const parts = [];
|
|
2680
|
+
parts.push(`${colors.bold(toolCount.toString())} tools`);
|
|
2681
|
+
if (resourceCount > 0) parts.push(`${colors.bold(resourceCount.toString())} resources`);
|
|
2682
|
+
if (promptCount > 0) parts.push(`${colors.bold(promptCount.toString())} prompts`);
|
|
2683
|
+
const baseTitle = serverVersion ? `${serverName} ${colors.dim(`v${serverVersion}`)}` : serverName;
|
|
2684
|
+
const title = harnessMode ? `${baseTitle} ${colors.bgBlue(colors.white(" AGENT HARNESS "))}` : baseTitle;
|
|
2685
|
+
console.log(colors.cyan(` \u250C${"\u2500".repeat(BOX_WIDTH)}\u2510`));
|
|
2686
|
+
console.log(padLine(` Connected to ${title}`));
|
|
2687
|
+
console.log(padLine(` Discovered ${parts.join(", ")}`));
|
|
2688
|
+
console.log(colors.cyan(` \u251C${"\u2500".repeat(BOX_WIDTH)}\u2524`));
|
|
2689
|
+
console.log(padLine(` ${colors.green("tools/list")} See all tools`));
|
|
2690
|
+
console.log(padLine(` ${colors.green("tools/call")} ${colors.dim("<name>")} Call a tool`));
|
|
2691
|
+
console.log(padLine(` ${colors.green("help")} All commands`));
|
|
2692
|
+
console.log(padLine(""));
|
|
2693
|
+
console.log(padLine(colors.dim(" Tab completion is active. Start typing to explore.")));
|
|
2694
|
+
console.log(colors.cyan(` \u2514${"\u2500".repeat(BOX_WIDTH)}\u2518`));
|
|
2695
|
+
}
|
|
2696
|
+
|
|
2697
|
+
// src/repl/completer.ts
|
|
1706
2698
|
function resetTabCycle() {
|
|
1707
|
-
|
|
2699
|
+
setTabCycleState(null);
|
|
2700
|
+
}
|
|
2701
|
+
function getActiveCommands() {
|
|
2702
|
+
let commands = [...KNOWN_COMMANDS];
|
|
2703
|
+
if (!activeCapabilities?.resources) {
|
|
2704
|
+
commands = commands.filter(
|
|
2705
|
+
(c) => !c.startsWith("resources/") && !["rl", "rr", "rt", "rs", "ru"].includes(c)
|
|
2706
|
+
);
|
|
2707
|
+
}
|
|
2708
|
+
if (!activeCapabilities?.prompts) {
|
|
2709
|
+
commands = commands.filter((c) => !c.startsWith("prompts/") && !["pl", "pg"].includes(c));
|
|
2710
|
+
}
|
|
2711
|
+
return commands;
|
|
1708
2712
|
}
|
|
1709
2713
|
function computeMatches(line) {
|
|
1710
2714
|
const expanded = resolveAlias(line);
|
|
@@ -1733,8 +2737,12 @@ var completer = (line) => {
|
|
|
1733
2737
|
if (tabCycleState) {
|
|
1734
2738
|
const inCycle = line === tabCycleState.original || tabCycleState.matches.includes(line);
|
|
1735
2739
|
if (inCycle) {
|
|
1736
|
-
|
|
1737
|
-
|
|
2740
|
+
const nextIndex = (tabCycleState.index + 1) % tabCycleState.matches.length;
|
|
2741
|
+
setTabCycleState({
|
|
2742
|
+
...tabCycleState,
|
|
2743
|
+
index: nextIndex
|
|
2744
|
+
});
|
|
2745
|
+
const next = tabCycleState.matches[nextIndex];
|
|
1738
2746
|
setImmediate(() => {
|
|
1739
2747
|
if (activeRl) {
|
|
1740
2748
|
activeRl.line = next;
|
|
@@ -1744,17 +2752,41 @@ var completer = (line) => {
|
|
|
1744
2752
|
});
|
|
1745
2753
|
return [[], ""];
|
|
1746
2754
|
}
|
|
1747
|
-
|
|
2755
|
+
setTabCycleState(null);
|
|
1748
2756
|
}
|
|
1749
2757
|
const [matches, matchLine] = computeMatches(line);
|
|
1750
2758
|
if (matches.length > 1) {
|
|
1751
|
-
|
|
2759
|
+
setTabCycleState({ matches, index: -1, original: line });
|
|
1752
2760
|
}
|
|
1753
2761
|
return [matches, matchLine];
|
|
1754
2762
|
};
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
|
|
2763
|
+
async function refreshCaches(target) {
|
|
2764
|
+
const caps = target.getServerCapabilities() ?? {};
|
|
2765
|
+
setActiveCapabilities(caps);
|
|
2766
|
+
try {
|
|
2767
|
+
const { tools } = await target.listTools();
|
|
2768
|
+
setCachedToolNames(tools.map((t) => t.name));
|
|
2769
|
+
} catch {
|
|
2770
|
+
}
|
|
2771
|
+
if (caps.resources) {
|
|
2772
|
+
try {
|
|
2773
|
+
const { resources } = await target.listResources();
|
|
2774
|
+
setCachedResourceUris(resources.map((r) => r.uri));
|
|
2775
|
+
} catch {
|
|
2776
|
+
}
|
|
2777
|
+
}
|
|
2778
|
+
if (caps.prompts) {
|
|
2779
|
+
try {
|
|
2780
|
+
const { prompts } = await target.listPrompts();
|
|
2781
|
+
setCachedPromptNames(prompts.map((p) => p.name));
|
|
2782
|
+
} catch {
|
|
2783
|
+
}
|
|
2784
|
+
}
|
|
2785
|
+
}
|
|
2786
|
+
|
|
2787
|
+
// src/repl/commands.ts
|
|
2788
|
+
import { exec as exec2 } from "child_process";
|
|
2789
|
+
import { checkbox, confirm, input as input2, search } from "@inquirer/prompts";
|
|
1758
2790
|
var lastCommand = null;
|
|
1759
2791
|
var AbortFlowError = class extends Error {
|
|
1760
2792
|
constructor() {
|
|
@@ -1766,419 +2798,67 @@ function isAbortError(err) {
|
|
|
1766
2798
|
if (!err) return false;
|
|
1767
2799
|
return err.name === "ExitPromptError" || err.name === "AbortError" || err.message === "Prompt was aborted" || typeof err.message === "string" && err.message.includes("User force closed");
|
|
1768
2800
|
}
|
|
1769
|
-
var activeRl = null;
|
|
1770
|
-
var isScriptMode = false;
|
|
1771
|
-
var globalPauseReadlineClose = false;
|
|
1772
|
-
var deferNextPrompt = false;
|
|
1773
2801
|
async function withSuspendedReadline(target, interceptor, fn) {
|
|
1774
2802
|
const wasActive = !!activeRl;
|
|
1775
2803
|
if (wasActive) {
|
|
1776
|
-
|
|
2804
|
+
setGlobalPauseReadlineClose(true);
|
|
1777
2805
|
activeRl.close();
|
|
1778
|
-
|
|
2806
|
+
setActiveRl(null);
|
|
1779
2807
|
}
|
|
1780
2808
|
try {
|
|
1781
2809
|
return await fn();
|
|
1782
2810
|
} finally {
|
|
1783
2811
|
if (wasActive) {
|
|
1784
|
-
|
|
2812
|
+
setGlobalPauseReadlineClose(false);
|
|
1785
2813
|
if (!isScriptMode) {
|
|
1786
|
-
|
|
2814
|
+
setDeferNextPrompt(true);
|
|
1787
2815
|
startReadlineLoop(target, interceptor);
|
|
1788
2816
|
}
|
|
1789
2817
|
}
|
|
1790
2818
|
}
|
|
1791
2819
|
}
|
|
1792
|
-
function
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
}
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
parts.push(`${pc2.bold(toolCount.toString())} tools`);
|
|
1799
|
-
if (resourceCount > 0) parts.push(`${pc2.bold(resourceCount.toString())} resources`);
|
|
1800
|
-
if (promptCount > 0) parts.push(`${pc2.bold(promptCount.toString())} prompts`);
|
|
1801
|
-
const baseTitle = serverVersion ? `${serverName} ${pc2.dim(`v${serverVersion}`)}` : serverName;
|
|
1802
|
-
const isAgentHarness = serverName === "run-mcp";
|
|
1803
|
-
const title = isAgentHarness ? `${baseTitle} ${pc2.bgBlue(pc2.white(" AGENT HARNESS "))}` : baseTitle;
|
|
1804
|
-
const BOX_WIDTH = 53;
|
|
1805
|
-
const padLine = (content) => {
|
|
1806
|
-
const visible = stripAnsi(content).length;
|
|
1807
|
-
const padding = Math.max(0, BOX_WIDTH - visible);
|
|
1808
|
-
return `${pc2.cyan(" \u2502")}${content}${"".padEnd(padding)}${pc2.cyan("\u2502")}`;
|
|
1809
|
-
};
|
|
1810
|
-
const partsStr = ` ${parts.join(" \u2022 ")}`;
|
|
1811
|
-
console.log();
|
|
1812
|
-
console.log(pc2.cyan(` \u250C${"\u2500".repeat(BOX_WIDTH)}\u2510`));
|
|
1813
|
-
console.log(padLine(` ${title}`));
|
|
1814
|
-
console.log(padLine(partsStr));
|
|
1815
|
-
console.log(pc2.cyan(` \u251C${"\u2500".repeat(BOX_WIDTH)}\u2524`));
|
|
1816
|
-
console.log(padLine(" Quick start:"));
|
|
1817
|
-
console.log(padLine(` ${pc2.green("tools/list")} See all tools`));
|
|
1818
|
-
console.log(padLine(` ${pc2.green("tools/call")} ${pc2.dim("<name>")} Call a tool`));
|
|
1819
|
-
console.log(padLine(` ${pc2.green("help")} All commands`));
|
|
1820
|
-
console.log(padLine(""));
|
|
1821
|
-
console.log(padLine(pc2.dim(" Tab completion is active. Start typing to explore.")));
|
|
1822
|
-
console.log(pc2.cyan(` \u2514${"\u2500".repeat(BOX_WIDTH)}\u2518`));
|
|
1823
|
-
console.log();
|
|
1824
|
-
}
|
|
1825
|
-
function stripAnsi(str) {
|
|
1826
|
-
return str.replace(/\x1b\[[0-9;]*m/g, "");
|
|
1827
|
-
}
|
|
1828
|
-
async function startRepl(targetCommand, opts) {
|
|
1829
|
-
const [command, ...args] = targetCommand;
|
|
1830
|
-
const target = new TargetManager(command, args);
|
|
1831
|
-
const interceptor = new ResponseInterceptor({
|
|
1832
|
-
outDir: opts.outDir,
|
|
1833
|
-
mediaThresholdKb: opts.mediaThresholdKb
|
|
1834
|
-
});
|
|
1835
|
-
isScriptMode = !!opts.script;
|
|
1836
|
-
target.on("stderr", (text) => {
|
|
1837
|
-
for (const line of text.split("\n")) {
|
|
1838
|
-
console.error(pc2.dim(`[server] ${line}`));
|
|
1839
|
-
}
|
|
1840
|
-
});
|
|
1841
|
-
console.log(pc2.cyan("\u27F3 Connecting to target MCP server..."));
|
|
1842
|
-
console.log(pc2.dim(` Command: ${targetCommand.join(" ")}`));
|
|
1843
|
-
try {
|
|
1844
|
-
await target.connect();
|
|
1845
|
-
} catch (err) {
|
|
1846
|
-
const msg = err.message ?? String(err);
|
|
1847
|
-
if (msg.includes("ENOENT") || msg.includes("spawn")) {
|
|
1848
|
-
console.error(pc2.red(`\u2717 Failed to start server: command "${command}" not found.`));
|
|
1849
|
-
console.error(pc2.dim(` Check that "${command}" is installed and in your PATH.`));
|
|
1850
|
-
} else {
|
|
1851
|
-
console.error(pc2.red(`\u2717 Failed to connect: ${msg}`));
|
|
1852
|
-
console.error(pc2.dim(` Check that the target command starts a valid MCP server on stdio.`));
|
|
1853
|
-
}
|
|
1854
|
-
process.exit(1);
|
|
2820
|
+
async function handleCommand(input3, target, interceptor) {
|
|
2821
|
+
const expanded = resolveAlias(input3);
|
|
2822
|
+
const effective = expanded ?? input3;
|
|
2823
|
+
const { cmd, rest } = parseCommandLine(effective);
|
|
2824
|
+
if (cmd !== "!!" && cmd !== "last") {
|
|
2825
|
+
lastCommand = input3;
|
|
1855
2826
|
}
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
);
|
|
1867
|
-
|
|
1868
|
-
);
|
|
1869
|
-
target.on("reconnected", async ({ attempt }) => {
|
|
1870
|
-
const s = target.getStatus();
|
|
1871
|
-
console.log(pc2.green(`\u2713 Reconnected (PID: ${s.pid}, attempt ${attempt})`));
|
|
1872
|
-
await refreshCaches(target);
|
|
1873
|
-
});
|
|
1874
|
-
target.on("reconnect_failed", ({ reason, message }) => {
|
|
1875
|
-
console.error(pc2.red(`\u2717 ${message}`));
|
|
1876
|
-
if (reason === "max_retries") {
|
|
1877
|
-
console.log(
|
|
1878
|
-
pc2.dim(" Use 'exit' to quit or wait for the server to be fixed and restart manually.")
|
|
1879
|
-
);
|
|
1880
|
-
}
|
|
1881
|
-
});
|
|
1882
|
-
target.on("notification", (notification) => {
|
|
1883
|
-
const method = notification.method;
|
|
1884
|
-
if (method === "notifications/message") {
|
|
1885
|
-
const lvl = notification.params?.level ?? "info";
|
|
1886
|
-
const data = notification.params?.data ?? "";
|
|
1887
|
-
const text = typeof data === "string" ? data : JSON.stringify(data);
|
|
1888
|
-
console.log(pc2.dim(`
|
|
1889
|
-
[${lvl}] ${text}`));
|
|
1890
|
-
} else if (method === "notifications/tools/list_changed") {
|
|
1891
|
-
console.log(pc2.yellow("\n \u27F3 Server tools changed. Run tools/list to see updates."));
|
|
1892
|
-
refreshCaches(target).catch(() => {
|
|
1893
|
-
});
|
|
1894
|
-
} else if (method === "notifications/resources/list_changed") {
|
|
1895
|
-
console.log(pc2.yellow("\n \u27F3 Server resources changed. Run resources/list to see."));
|
|
1896
|
-
refreshCaches(target).catch(() => {
|
|
1897
|
-
});
|
|
1898
|
-
} else if (method === "notifications/resources/updated") {
|
|
1899
|
-
const uri = notification.params?.uri ?? "unknown";
|
|
1900
|
-
console.log(pc2.yellow(`
|
|
1901
|
-
\u27F3 Resource updated: ${uri}`));
|
|
1902
|
-
} else if (method === "notifications/prompts/list_changed") {
|
|
1903
|
-
console.log(pc2.yellow("\n \u27F3 Server prompts changed. Run prompts/list to see."));
|
|
1904
|
-
refreshCaches(target).catch(() => {
|
|
1905
|
-
});
|
|
1906
|
-
}
|
|
1907
|
-
});
|
|
1908
|
-
target.on("sampling_request", async ({ request, respond, reject: rejectFn }) => {
|
|
1909
|
-
console.log(pc2.magenta("\n \u2554\u2550\u2550 Sampling Request \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550"));
|
|
1910
|
-
const messages = request?.messages ?? [];
|
|
1911
|
-
for (const msg of messages) {
|
|
1912
|
-
const role = msg.role === "user" ? pc2.blue("user") : pc2.magenta("assistant");
|
|
1913
|
-
const text = msg.content?.text ?? JSON.stringify(msg.content);
|
|
1914
|
-
console.log(pc2.magenta(` \u2551 ${role}: ${text}`));
|
|
2827
|
+
switch (cmd) {
|
|
2828
|
+
case "help":
|
|
2829
|
+
printHelp();
|
|
2830
|
+
return;
|
|
2831
|
+
case "?":
|
|
2832
|
+
printShortHelp();
|
|
2833
|
+
return;
|
|
2834
|
+
case "view": {
|
|
2835
|
+
const filepath = rest.trim();
|
|
2836
|
+
if (!filepath) {
|
|
2837
|
+
console.error(colors.red("Error: Please specify a file path to view."));
|
|
2838
|
+
return;
|
|
1915
2839
|
}
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
|
|
1923
|
-
|
|
1924
|
-
role: "assistant",
|
|
1925
|
-
content: { type: "text", text: "Approved by user." }
|
|
1926
|
-
});
|
|
1927
|
-
} else if (trimmed === "n" || trimmed === "no" || trimmed === "") {
|
|
1928
|
-
rejectFn(new Error("Sampling request rejected by user"));
|
|
1929
|
-
} else {
|
|
1930
|
-
respond({
|
|
1931
|
-
model: "user-provided",
|
|
1932
|
-
role: "assistant",
|
|
1933
|
-
content: { type: "text", text: answer.trim() }
|
|
1934
|
-
});
|
|
1935
|
-
}
|
|
1936
|
-
} catch (err) {
|
|
1937
|
-
if (err instanceof AbortFlowError) {
|
|
1938
|
-
rejectFn(new Error("Sampling request rejected by user"));
|
|
1939
|
-
} else {
|
|
1940
|
-
throw err;
|
|
1941
|
-
}
|
|
2840
|
+
const isMac = process.platform === "darwin";
|
|
2841
|
+
const isWin = process.platform === "win32";
|
|
2842
|
+
const cmdToRun = isMac ? "open" : isWin ? "start" : "xdg-open";
|
|
2843
|
+
exec2(`${cmdToRun} "${filepath}"`, (err) => {
|
|
2844
|
+
if (err) {
|
|
2845
|
+
console.error(colors.red(`Error opening file: ${err.message}`));
|
|
2846
|
+
} else {
|
|
2847
|
+
console.log(colors.green(`Opened ${filepath}`));
|
|
1942
2848
|
}
|
|
1943
|
-
}
|
|
1944
|
-
rejectFn(new Error("No interactive terminal available for sampling approval"));
|
|
1945
|
-
}
|
|
1946
|
-
});
|
|
1947
|
-
target.on("elicitation_request", async ({ request, respond, reject: rejectFn }) => {
|
|
1948
|
-
console.log(pc2.cyan("\n \u2554\u2550\u2550 Elicitation Request \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550"));
|
|
1949
|
-
console.log(pc2.cyan(` \u2551 ${request?.message ?? "Server requests input"}`));
|
|
1950
|
-
console.log(pc2.cyan(" \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550"));
|
|
1951
|
-
if (activeRl) {
|
|
1952
|
-
try {
|
|
1953
|
-
const answer = await question(
|
|
1954
|
-
activeRl,
|
|
1955
|
-
` ${pc2.bold("Your response (empty to decline):")} `
|
|
1956
|
-
);
|
|
1957
|
-
if (answer.trim() === "") {
|
|
1958
|
-
respond({ action: "decline" });
|
|
1959
|
-
} else {
|
|
1960
|
-
try {
|
|
1961
|
-
const parsed = JSON.parse(answer.trim());
|
|
1962
|
-
respond({ action: "accept", content: parsed });
|
|
1963
|
-
} catch {
|
|
1964
|
-
respond({ action: "accept", content: { value: answer.trim() } });
|
|
1965
|
-
}
|
|
1966
|
-
}
|
|
1967
|
-
} catch (err) {
|
|
1968
|
-
if (err instanceof AbortFlowError) {
|
|
1969
|
-
respond({ action: "decline" });
|
|
1970
|
-
} else {
|
|
1971
|
-
throw err;
|
|
1972
|
-
}
|
|
1973
|
-
}
|
|
1974
|
-
} else {
|
|
1975
|
-
rejectFn(new Error("No interactive terminal available for elicitation"));
|
|
1976
|
-
}
|
|
1977
|
-
});
|
|
1978
|
-
}
|
|
1979
|
-
let toolCount;
|
|
1980
|
-
let resourceCount = 0;
|
|
1981
|
-
let promptCount = 0;
|
|
1982
|
-
try {
|
|
1983
|
-
const { tools } = await target.listTools();
|
|
1984
|
-
toolCount = tools.length;
|
|
1985
|
-
const caps = target.getServerCapabilities() ?? {};
|
|
1986
|
-
if (caps.resources) {
|
|
1987
|
-
try {
|
|
1988
|
-
const { resources } = await target.listResources();
|
|
1989
|
-
resourceCount = resources.length;
|
|
1990
|
-
} catch {
|
|
1991
|
-
}
|
|
1992
|
-
}
|
|
1993
|
-
if (caps.prompts) {
|
|
1994
|
-
try {
|
|
1995
|
-
const { prompts } = await target.listPrompts();
|
|
1996
|
-
promptCount = prompts.length;
|
|
1997
|
-
} catch {
|
|
1998
|
-
}
|
|
1999
|
-
}
|
|
2000
|
-
const serverInfo = target.getServerVersion();
|
|
2001
|
-
const serverName = serverInfo?.name ?? "MCP Server";
|
|
2002
|
-
const serverVersion = serverInfo?.version ?? "";
|
|
2003
|
-
printBanner(serverName, serverVersion, toolCount, resourceCount, promptCount);
|
|
2004
|
-
if (toolCount >= 10) {
|
|
2005
|
-
const groups = groupToolsByPrefix(
|
|
2006
|
-
cachedToolNames.length > 0 ? cachedToolNames : tools.map((t) => t.name)
|
|
2007
|
-
);
|
|
2008
|
-
if (!groups.has("All")) {
|
|
2009
|
-
for (const [label, members] of groups) {
|
|
2010
|
-
console.log(` ${pc2.bold(label.padEnd(16))} ${pc2.dim(members.join(", "))}`);
|
|
2011
|
-
}
|
|
2012
|
-
console.log();
|
|
2013
|
-
}
|
|
2014
|
-
}
|
|
2015
|
-
} catch (err) {
|
|
2016
|
-
console.log(pc2.yellow(` Warning: Could not list tools: ${err.message}
|
|
2017
|
-
`));
|
|
2018
|
-
}
|
|
2019
|
-
await refreshCaches(target);
|
|
2020
|
-
if (isScriptMode) {
|
|
2021
|
-
const lines = await readScriptLines(opts.script);
|
|
2022
|
-
const scriptContext = {};
|
|
2023
|
-
let expectError = false;
|
|
2024
|
-
for (const line of lines) {
|
|
2025
|
-
let trimmed = line.trim();
|
|
2026
|
-
if (!trimmed || trimmed.startsWith("#")) {
|
|
2027
|
-
if (trimmed === "# @expect-error") {
|
|
2028
|
-
expectError = true;
|
|
2029
|
-
}
|
|
2030
|
-
continue;
|
|
2031
|
-
}
|
|
2032
|
-
if (trimmed.endsWith("# @expect-error")) {
|
|
2033
|
-
expectError = true;
|
|
2034
|
-
trimmed = trimmed.replace(/\s*#\s*@expect-error$/, "");
|
|
2035
|
-
}
|
|
2036
|
-
const interpolated = interpolateString(trimmed, scriptContext);
|
|
2037
|
-
try {
|
|
2038
|
-
const res = await handleCommand(interpolated, target, interceptor);
|
|
2039
|
-
if (res !== void 0) {
|
|
2040
|
-
scriptContext.LAST = res;
|
|
2041
|
-
}
|
|
2042
|
-
const isErrorRes = res && typeof res === "object" && res.isError === true;
|
|
2043
|
-
if (expectError && !isErrorRes) {
|
|
2044
|
-
console.error(pc2.red(`\u2717 Expected an error but the command succeeded.`));
|
|
2045
|
-
await target.close();
|
|
2046
|
-
process.exit(1);
|
|
2047
|
-
}
|
|
2048
|
-
if (!expectError && isErrorRes) {
|
|
2049
|
-
console.error(pc2.red(`\u2717 Command failed unexpectedly.`));
|
|
2050
|
-
await target.close();
|
|
2051
|
-
process.exit(1);
|
|
2052
|
-
}
|
|
2053
|
-
if (expectError && isErrorRes) {
|
|
2054
|
-
console.log(pc2.yellow(` \u2713 Expected error caught: tool returned isError: true`));
|
|
2055
|
-
}
|
|
2056
|
-
} catch (err) {
|
|
2057
|
-
if (expectError) {
|
|
2058
|
-
console.log(pc2.yellow(` \u2713 Expected error caught: ${err.message}`));
|
|
2059
|
-
} else {
|
|
2060
|
-
if (err?.message?.includes("-32601") || err?.code === -32601) {
|
|
2061
|
-
let msg = "Server does not support this feature (Method not found)";
|
|
2062
|
-
if (trimmed.startsWith("prompts/")) msg = "This server does not have any prompts.";
|
|
2063
|
-
else if (trimmed.startsWith("resources/"))
|
|
2064
|
-
msg = "This server does not have any resources.";
|
|
2065
|
-
else if (trimmed.startsWith("tools/")) msg = "This server does not have any tools.";
|
|
2066
|
-
console.log(pc2.yellow(` ${msg}`));
|
|
2067
|
-
} else {
|
|
2068
|
-
console.error(pc2.red(`\u2717 Error: ${err.message}`));
|
|
2069
|
-
}
|
|
2070
|
-
console.log(pc2.dim("\nShutting down..."));
|
|
2071
|
-
await target.close();
|
|
2072
|
-
process.exit(1);
|
|
2073
|
-
}
|
|
2074
|
-
}
|
|
2075
|
-
expectError = false;
|
|
2076
|
-
}
|
|
2077
|
-
console.log(pc2.dim("\nShutting down..."));
|
|
2078
|
-
await target.close();
|
|
2079
|
-
process.exit(0);
|
|
2080
|
-
} else {
|
|
2081
|
-
startReadlineLoop(target, interceptor);
|
|
2082
|
-
}
|
|
2083
|
-
}
|
|
2084
|
-
function startReadlineLoop(target, interceptor) {
|
|
2085
|
-
if (isScriptMode || activeRl) return;
|
|
2086
|
-
const rl = createInterface({
|
|
2087
|
-
input: process.stdin,
|
|
2088
|
-
output: process.stdout,
|
|
2089
|
-
prompt: getPrompt(target),
|
|
2090
|
-
terminal: true,
|
|
2091
|
-
completer,
|
|
2092
|
-
history: [...replHistory].reverse()
|
|
2093
|
-
// Node's readline history expects newest first
|
|
2094
|
-
});
|
|
2095
|
-
activeRl = rl;
|
|
2096
|
-
if (process.stdin.isTTY) {
|
|
2097
|
-
process.stdin.on("keypress", (_str, key) => {
|
|
2098
|
-
if (!key || key.name !== "tab") {
|
|
2099
|
-
resetTabCycle();
|
|
2100
|
-
}
|
|
2101
|
-
});
|
|
2102
|
-
}
|
|
2103
|
-
if (!deferNextPrompt) {
|
|
2104
|
-
rl.prompt();
|
|
2105
|
-
}
|
|
2106
|
-
deferNextPrompt = false;
|
|
2107
|
-
let processing = false;
|
|
2108
|
-
let closed = false;
|
|
2109
|
-
const queue = [];
|
|
2110
|
-
const processQueue = async () => {
|
|
2111
|
-
if (processing) return;
|
|
2112
|
-
processing = true;
|
|
2113
|
-
while (queue.length > 0) {
|
|
2114
|
-
const trimmed = queue.shift();
|
|
2115
|
-
try {
|
|
2116
|
-
await handleCommand(trimmed, target, interceptor);
|
|
2117
|
-
} catch (err) {
|
|
2118
|
-
if (err instanceof AbortFlowError) {
|
|
2119
|
-
console.log(pc2.yellow(" Aborted."));
|
|
2120
|
-
} else if (err?.message?.includes("-32601") || err?.code === -32601) {
|
|
2121
|
-
let msg = "Server does not support this feature (Method not found)";
|
|
2122
|
-
if (trimmed.startsWith("prompts/")) msg = "This server does not have any prompts.";
|
|
2123
|
-
else if (trimmed.startsWith("resources/"))
|
|
2124
|
-
msg = "This server does not have any resources.";
|
|
2125
|
-
else if (trimmed.startsWith("tools/")) msg = "This server does not have any tools.";
|
|
2126
|
-
console.log(pc2.yellow(` ${msg}`));
|
|
2127
|
-
} else {
|
|
2128
|
-
console.error(pc2.red(`\u2717 Error: ${err.message}`));
|
|
2129
|
-
}
|
|
2130
|
-
}
|
|
2131
|
-
if (activeRl) {
|
|
2132
|
-
setImmediate(() => {
|
|
2133
|
-
if (activeRl) {
|
|
2134
|
-
console.log();
|
|
2135
|
-
activeRl.setPrompt(getPrompt(target));
|
|
2136
|
-
activeRl.prompt();
|
|
2137
|
-
}
|
|
2138
|
-
});
|
|
2139
|
-
}
|
|
2140
|
-
}
|
|
2141
|
-
processing = false;
|
|
2142
|
-
};
|
|
2143
|
-
rl.on("line", (line) => {
|
|
2144
|
-
const trimmed = line.trim();
|
|
2145
|
-
if (!trimmed || trimmed.startsWith("#")) {
|
|
2146
|
-
if (!closed && activeRl) activeRl.prompt();
|
|
2849
|
+
});
|
|
2147
2850
|
return;
|
|
2148
2851
|
}
|
|
2149
|
-
|
|
2150
|
-
queue.push(trimmed);
|
|
2151
|
-
processQueue();
|
|
2152
|
-
});
|
|
2153
|
-
rl.on("close", async () => {
|
|
2154
|
-
closed = true;
|
|
2155
|
-
activeRl = null;
|
|
2156
|
-
if (!globalPauseReadlineClose) {
|
|
2157
|
-
console.log(pc2.dim("\nShutting down..."));
|
|
2158
|
-
await target.close();
|
|
2159
|
-
process.exit(0);
|
|
2160
|
-
}
|
|
2161
|
-
});
|
|
2162
|
-
}
|
|
2163
|
-
async function handleCommand(input3, target, interceptor) {
|
|
2164
|
-
const expanded = resolveAlias(input3);
|
|
2165
|
-
const effective = expanded ?? input3;
|
|
2166
|
-
const { cmd, rest } = parseCommandLine(effective);
|
|
2167
|
-
if (cmd !== "!!" && cmd !== "last") {
|
|
2168
|
-
lastCommand = input3;
|
|
2169
|
-
}
|
|
2170
|
-
switch (cmd) {
|
|
2171
|
-
case "help":
|
|
2172
|
-
printHelp();
|
|
2173
|
-
return;
|
|
2174
|
-
case "?":
|
|
2175
|
-
printShortHelp();
|
|
2176
|
-
return;
|
|
2852
|
+
case "menu":
|
|
2177
2853
|
case "explore":
|
|
2178
2854
|
case "interactive":
|
|
2179
|
-
|
|
2180
|
-
|
|
2181
|
-
|
|
2855
|
+
if (activeRl) {
|
|
2856
|
+
setGlobalPauseReadlineClose(true);
|
|
2857
|
+
activeRl.close();
|
|
2858
|
+
setActiveRl(null);
|
|
2859
|
+
setGlobalPauseReadlineClose(false);
|
|
2860
|
+
}
|
|
2861
|
+
mainMenuLoop(target, interceptor);
|
|
2182
2862
|
return;
|
|
2183
2863
|
case "tools/list":
|
|
2184
2864
|
await cmdToolsList(target);
|
|
@@ -2243,10 +2923,10 @@ async function handleCommand(input3, target, interceptor) {
|
|
|
2243
2923
|
case "!!":
|
|
2244
2924
|
case "last":
|
|
2245
2925
|
if (lastCommand) {
|
|
2246
|
-
console.log(
|
|
2926
|
+
console.log(colors.dim(` Re-running: ${lastCommand}`));
|
|
2247
2927
|
return await handleCommand(lastCommand, target, interceptor);
|
|
2248
2928
|
} else {
|
|
2249
|
-
console.log(
|
|
2929
|
+
console.log(colors.yellow("No previous command to re-run."));
|
|
2250
2930
|
}
|
|
2251
2931
|
return;
|
|
2252
2932
|
case "status":
|
|
@@ -2254,7 +2934,7 @@ async function handleCommand(input3, target, interceptor) {
|
|
|
2254
2934
|
return;
|
|
2255
2935
|
case "exit":
|
|
2256
2936
|
case "quit": {
|
|
2257
|
-
console.log(
|
|
2937
|
+
console.log(colors.dim("Shutting down..."));
|
|
2258
2938
|
await target.close();
|
|
2259
2939
|
process.exit(0);
|
|
2260
2940
|
return;
|
|
@@ -2265,11 +2945,11 @@ async function handleCommand(input3, target, interceptor) {
|
|
|
2265
2945
|
}
|
|
2266
2946
|
const suggestion = suggestCommand(cmd, getActiveCommands());
|
|
2267
2947
|
if (suggestion) {
|
|
2268
|
-
console.log(
|
|
2948
|
+
console.log(colors.yellow(`Unknown command: ${cmd}.`));
|
|
2269
2949
|
try {
|
|
2270
2950
|
await withSuspendedReadline(target, interceptor, async () => {
|
|
2271
2951
|
const runIt = await confirm({
|
|
2272
|
-
message: `Did you mean ${
|
|
2952
|
+
message: `Did you mean ${colors.bold(suggestion)}?`,
|
|
2273
2953
|
default: true
|
|
2274
2954
|
});
|
|
2275
2955
|
if (runIt) {
|
|
@@ -2282,7 +2962,7 @@ async function handleCommand(input3, target, interceptor) {
|
|
|
2282
2962
|
throw new AbortFlowError();
|
|
2283
2963
|
}
|
|
2284
2964
|
} else {
|
|
2285
|
-
console.log(
|
|
2965
|
+
console.log(colors.yellow(`Unknown command: ${cmd}. Type ${colors.bold("help")} for usage.`));
|
|
2286
2966
|
}
|
|
2287
2967
|
}
|
|
2288
2968
|
}
|
|
@@ -2290,25 +2970,25 @@ async function handleCommand(input3, target, interceptor) {
|
|
|
2290
2970
|
async function cmdToolsList(target) {
|
|
2291
2971
|
const { tools } = await target.listTools();
|
|
2292
2972
|
if (tools.length === 0) {
|
|
2293
|
-
console.log(
|
|
2973
|
+
console.log(colors.dim(" No tools available."));
|
|
2294
2974
|
return;
|
|
2295
2975
|
}
|
|
2296
2976
|
const nameWidth = Math.max(8, ...tools.map((t) => t.name.length));
|
|
2297
|
-
console.log(
|
|
2298
|
-
console.log(
|
|
2977
|
+
console.log(colors.bold(` ${"Name".padEnd(nameWidth)} Description`));
|
|
2978
|
+
console.log(colors.dim(` ${"\u2500".repeat(nameWidth)} ${"\u2500".repeat(50)}`));
|
|
2299
2979
|
for (const tool of tools) {
|
|
2300
|
-
const desc = tool.description ? tool.description.length > 60 ? `${tool.description.slice(0, 57)}...` : tool.description :
|
|
2301
|
-
console.log(` ${
|
|
2980
|
+
const desc = tool.description ? tool.description.length > 60 ? `${tool.description.slice(0, 57)}...` : tool.description : colors.dim("(no description)");
|
|
2981
|
+
console.log(` ${colors.green(tool.name.padEnd(nameWidth))} ${desc}`);
|
|
2302
2982
|
}
|
|
2303
|
-
console.log(
|
|
2983
|
+
console.log(colors.dim(`
|
|
2304
2984
|
${tools.length} tool(s) total.`));
|
|
2305
2985
|
if (tools.length >= 10) {
|
|
2306
2986
|
const groups = groupToolsByPrefix(tools.map((t) => t.name));
|
|
2307
2987
|
if (!groups.has("All")) {
|
|
2308
2988
|
console.log();
|
|
2309
|
-
console.log(
|
|
2989
|
+
console.log(colors.bold(" Groups:"));
|
|
2310
2990
|
for (const [label, members] of groups) {
|
|
2311
|
-
console.log(` ${
|
|
2991
|
+
console.log(` ${colors.cyan(label.padEnd(16))} ${colors.dim(members.join(", "))}`);
|
|
2312
2992
|
}
|
|
2313
2993
|
}
|
|
2314
2994
|
}
|
|
@@ -2316,28 +2996,28 @@ async function cmdToolsList(target) {
|
|
|
2316
2996
|
async function cmdToolsDescribe(target, rest) {
|
|
2317
2997
|
const name = rest.trim();
|
|
2318
2998
|
if (!name) {
|
|
2319
|
-
console.log(
|
|
2999
|
+
console.log(colors.yellow(" Usage: tools/describe <name>"));
|
|
2320
3000
|
if (cachedToolNames.length > 0) {
|
|
2321
3001
|
const preview = cachedToolNames.slice(0, 6);
|
|
2322
3002
|
const more = cachedToolNames.length > 6 ? `, ... (${cachedToolNames.length} total)` : "";
|
|
2323
|
-
console.log(
|
|
3003
|
+
console.log(colors.dim(`
|
|
2324
3004
|
Available tools: ${preview.join(", ")}${more}`));
|
|
2325
|
-
console.log(
|
|
3005
|
+
console.log(colors.dim(` Type ${colors.bold("tools/list")} for all.`));
|
|
2326
3006
|
}
|
|
2327
3007
|
return;
|
|
2328
3008
|
}
|
|
2329
3009
|
const { tools } = await target.listTools();
|
|
2330
3010
|
const tool = tools.find((t) => t.name === name);
|
|
2331
3011
|
if (!tool) {
|
|
2332
|
-
console.log(
|
|
3012
|
+
console.log(colors.red(`Tool "${name}" not found.`));
|
|
2333
3013
|
const suggestion = suggestCommand(
|
|
2334
3014
|
name,
|
|
2335
3015
|
tools.map((t) => t.name)
|
|
2336
3016
|
);
|
|
2337
3017
|
if (suggestion) {
|
|
2338
|
-
console.log(
|
|
3018
|
+
console.log(colors.yellow(`Did you mean ${colors.bold(suggestion)}?`));
|
|
2339
3019
|
} else {
|
|
2340
|
-
console.log(
|
|
3020
|
+
console.log(colors.dim(`Available: ${tools.map((t) => t.name).join(", ")}`));
|
|
2341
3021
|
}
|
|
2342
3022
|
return;
|
|
2343
3023
|
}
|
|
@@ -2368,14 +3048,14 @@ async function cmdToolsCall(target, interceptor, rest) {
|
|
|
2368
3048
|
if (!picked) return;
|
|
2369
3049
|
return cmdToolsCall(target, interceptor, picked);
|
|
2370
3050
|
}
|
|
2371
|
-
console.log(
|
|
3051
|
+
console.log(colors.yellow(" Usage: tools/call <name> [json_args] [--timeout <ms>]"));
|
|
2372
3052
|
if (cachedToolNames.length > 0) {
|
|
2373
3053
|
const preview = cachedToolNames.slice(0, 6);
|
|
2374
3054
|
const more = cachedToolNames.length > 6 ? `, ... (${cachedToolNames.length} total)` : "";
|
|
2375
|
-
console.log(
|
|
3055
|
+
console.log(colors.dim(`
|
|
2376
3056
|
Available tools: ${preview.join(", ")}${more}`));
|
|
2377
3057
|
console.log(
|
|
2378
|
-
|
|
3058
|
+
colors.dim(` Run without args for ${colors.bold("interactive mode")}: tools/call <name>`)
|
|
2379
3059
|
);
|
|
2380
3060
|
}
|
|
2381
3061
|
return;
|
|
@@ -2387,31 +3067,31 @@ async function cmdToolsCall(target, interceptor, rest) {
|
|
|
2387
3067
|
try {
|
|
2388
3068
|
args = JSON.parse(trimmed);
|
|
2389
3069
|
} catch (err) {
|
|
2390
|
-
console.error(
|
|
2391
|
-
console.log(
|
|
3070
|
+
console.error(colors.red(`Invalid JSON: ${err.message}`));
|
|
3071
|
+
console.log(colors.dim(` Received: ${jsonArgs}`));
|
|
2392
3072
|
return;
|
|
2393
3073
|
}
|
|
2394
3074
|
} else {
|
|
2395
3075
|
try {
|
|
2396
3076
|
args = parseHttpieArgs(trimmed);
|
|
2397
3077
|
} catch (err) {
|
|
2398
|
-
console.error(
|
|
3078
|
+
console.error(colors.red(`Invalid shorthand arguments: ${err.message}`));
|
|
2399
3079
|
return;
|
|
2400
3080
|
}
|
|
2401
3081
|
}
|
|
2402
3082
|
const { tools } = await target.listTools();
|
|
2403
3083
|
const tool = tools.find((t) => t.name === toolName);
|
|
2404
3084
|
if (!tool) {
|
|
2405
|
-
console.log(
|
|
3085
|
+
console.log(colors.red(`
|
|
2406
3086
|
\u2717 Tool "${toolName}" not found.`));
|
|
2407
3087
|
const toolNames = tools.map((t) => t.name);
|
|
2408
3088
|
const suggestion = suggestCommand(toolName, toolNames);
|
|
2409
3089
|
if (suggestion) {
|
|
2410
|
-
console.log(
|
|
3090
|
+
console.log(colors.yellow(` \u{1F4A1} Did you mean "${suggestion}"?`));
|
|
2411
3091
|
} else {
|
|
2412
3092
|
const preview = toolNames.slice(0, 6);
|
|
2413
3093
|
const more = toolNames.length > 6 ? `, ... (${toolNames.length} total)` : "";
|
|
2414
|
-
console.log(
|
|
3094
|
+
console.log(colors.dim(` Available tools: ${preview.join(", ")}${more}`));
|
|
2415
3095
|
}
|
|
2416
3096
|
return { isError: true, content: [{ type: "text", text: `Tool not found: ${toolName}` }] };
|
|
2417
3097
|
}
|
|
@@ -2419,14 +3099,14 @@ async function cmdToolsCall(target, interceptor, rest) {
|
|
|
2419
3099
|
const required = schema.required ?? [];
|
|
2420
3100
|
const missing = required.filter((r) => !(r in args));
|
|
2421
3101
|
if (missing.length > 0) {
|
|
2422
|
-
console.log(
|
|
3102
|
+
console.log(colors.yellow(`
|
|
2423
3103
|
Missing required arguments: ${missing.join(", ")}`));
|
|
2424
3104
|
console.log();
|
|
2425
3105
|
const scaffolded = scaffoldArgs(schema);
|
|
2426
|
-
console.log(
|
|
3106
|
+
console.log(colors.dim(" Try:"));
|
|
2427
3107
|
console.log(` tools/call ${toolName} ${scaffolded}`);
|
|
2428
3108
|
console.log();
|
|
2429
|
-
console.log(
|
|
3109
|
+
console.log(colors.dim(" Or run without args for interactive mode:"));
|
|
2430
3110
|
console.log(` tools/call ${toolName}`);
|
|
2431
3111
|
console.log();
|
|
2432
3112
|
return;
|
|
@@ -2438,17 +3118,21 @@ async function cmdToolsCall(target, interceptor, rest) {
|
|
|
2438
3118
|
if (!isScriptMode) {
|
|
2439
3119
|
const fullCmd = `tools/call ${toolName} ${JSON.stringify(args)}`;
|
|
2440
3120
|
replHistory.push(fullCmd);
|
|
3121
|
+
appendToHistoryFile(fullCmd).catch(() => {
|
|
3122
|
+
});
|
|
2441
3123
|
if (activeRl) {
|
|
2442
3124
|
activeRl.history.unshift(fullCmd);
|
|
2443
3125
|
}
|
|
2444
3126
|
}
|
|
2445
3127
|
}
|
|
2446
|
-
console.log(
|
|
3128
|
+
console.log(colors.dim(` Calling ${toolName}...`));
|
|
2447
3129
|
const startTime = Date.now();
|
|
2448
3130
|
const result = await interceptor.callTool(target, toolName, args, timeoutMs);
|
|
2449
3131
|
const elapsed = Date.now() - startTime;
|
|
2450
3132
|
callHistory.push({ toolName, durationMs: elapsed, timestamp: startTime });
|
|
2451
3133
|
lastToolArgsMap.set(toolName, { ...args });
|
|
3134
|
+
saveWizardDefaults().catch(() => {
|
|
3135
|
+
});
|
|
2452
3136
|
const isError = result.isError === true;
|
|
2453
3137
|
console.log();
|
|
2454
3138
|
printResultBlock({
|
|
@@ -2462,17 +3146,17 @@ async function cmdToolsCall(target, interceptor, rest) {
|
|
|
2462
3146
|
for (const item of content) {
|
|
2463
3147
|
if (item.type === "text") {
|
|
2464
3148
|
if (isError) {
|
|
2465
|
-
console.log(
|
|
3149
|
+
console.log(colors.red(` \u2717 ${item.text}`));
|
|
2466
3150
|
} else {
|
|
2467
3151
|
try {
|
|
2468
3152
|
const parsed = JSON.parse(item.text);
|
|
2469
3153
|
if (typeof parsed === "object" && parsed !== null) {
|
|
2470
3154
|
console.log(formatJson(parsed, 2, true));
|
|
2471
3155
|
} else {
|
|
2472
|
-
console.log(
|
|
3156
|
+
console.log(colors.yellow(` ${item.text}`));
|
|
2473
3157
|
}
|
|
2474
3158
|
} catch {
|
|
2475
|
-
console.log(
|
|
3159
|
+
console.log(colors.yellow(` ${item.text}`));
|
|
2476
3160
|
}
|
|
2477
3161
|
}
|
|
2478
3162
|
} else {
|
|
@@ -2486,7 +3170,7 @@ async function cmdToolsCall(target, interceptor, rest) {
|
|
|
2486
3170
|
const errText = Array.isArray(content) ? content.map((c) => c.text || "").join(" ").toLowerCase() : typeof content === "object" ? (content.text || "").toLowerCase() : "";
|
|
2487
3171
|
if (errText.includes("argument") || errText.includes("validation") || errText.includes("schema") || errText.includes("missing") || errText.includes("invalid")) {
|
|
2488
3172
|
console.log(
|
|
2489
|
-
|
|
3173
|
+
colors.yellow(
|
|
2490
3174
|
` \u{1F4A1} Tip: Check the tool arguments via 'tools/describe ${toolName}'
|
|
2491
3175
|
or view the raw server stderr above.`
|
|
2492
3176
|
)
|
|
@@ -2500,15 +3184,15 @@ async function interactiveArgPrompt(target, interceptor, toolName, clearPrevious
|
|
|
2500
3184
|
const { tools } = await target.listTools();
|
|
2501
3185
|
const tool = tools.find((t) => t.name === toolName);
|
|
2502
3186
|
if (!tool) {
|
|
2503
|
-
console.log(
|
|
3187
|
+
console.log(colors.red(`Tool "${toolName}" not found.`));
|
|
2504
3188
|
const suggestion = suggestCommand(
|
|
2505
3189
|
toolName,
|
|
2506
3190
|
tools.map((t) => t.name)
|
|
2507
3191
|
);
|
|
2508
3192
|
if (suggestion) {
|
|
2509
|
-
console.log(
|
|
3193
|
+
console.log(colors.yellow(`Did you mean ${colors.bold(suggestion)}?`));
|
|
2510
3194
|
} else {
|
|
2511
|
-
console.log(
|
|
3195
|
+
console.log(colors.dim(`Available: ${tools.map((t) => t.name).join(", ")}`));
|
|
2512
3196
|
}
|
|
2513
3197
|
return null;
|
|
2514
3198
|
}
|
|
@@ -2518,9 +3202,9 @@ async function interactiveArgPrompt(target, interceptor, toolName, clearPrevious
|
|
|
2518
3202
|
return {};
|
|
2519
3203
|
}
|
|
2520
3204
|
if (isScriptMode) {
|
|
2521
|
-
console.log(
|
|
3205
|
+
console.log(colors.yellow(` Tool "${toolName}" requires arguments.`));
|
|
2522
3206
|
const scaffolded = scaffoldArgs(schema);
|
|
2523
|
-
console.log(
|
|
3207
|
+
console.log(colors.dim(` Usage: tools/call ${toolName} ${scaffolded}`));
|
|
2524
3208
|
return null;
|
|
2525
3209
|
}
|
|
2526
3210
|
const required = schema.required ?? [];
|
|
@@ -2529,10 +3213,10 @@ async function interactiveArgPrompt(target, interceptor, toolName, clearPrevious
|
|
|
2529
3213
|
const optionalProps = allProps.filter(([name]) => !required.includes(name));
|
|
2530
3214
|
const previousArgs = clearPrevious ? void 0 : lastToolArgsMap.get(toolName);
|
|
2531
3215
|
console.log();
|
|
2532
|
-
console.log(` ${
|
|
3216
|
+
console.log(` ${colors.bold(tool.name)}${tool.description ? colors.dim(` \u2014 ${tool.description}`) : ""}`);
|
|
2533
3217
|
if (previousArgs) {
|
|
2534
|
-
console.log(
|
|
2535
|
-
console.log(
|
|
3218
|
+
console.log(colors.dim(` Previous: ${JSON.stringify(previousArgs)}`));
|
|
3219
|
+
console.log(colors.dim(" Press Enter to reuse values, or type to override."));
|
|
2536
3220
|
}
|
|
2537
3221
|
console.log();
|
|
2538
3222
|
const collectedArgs = {};
|
|
@@ -2550,7 +3234,7 @@ async function interactiveArgPrompt(target, interceptor, toolName, clearPrevious
|
|
|
2550
3234
|
const typeStr = prop.type ?? "any";
|
|
2551
3235
|
const desc = prop.description ?? "";
|
|
2552
3236
|
const prevVal = previousArgs?.[name];
|
|
2553
|
-
const label = desc ? `${name} ${
|
|
3237
|
+
const label = desc ? `${name} ${colors.dim(`(${typeStr})`)} ${colors.dim(desc)}` : `${name} ${colors.dim(`(${typeStr})`)}`;
|
|
2554
3238
|
const answerStr = await input2(
|
|
2555
3239
|
{
|
|
2556
3240
|
message: label,
|
|
@@ -2592,7 +3276,7 @@ async function interactiveArgPrompt(target, interceptor, toolName, clearPrevious
|
|
|
2592
3276
|
const typeStr = prop.type ?? "any";
|
|
2593
3277
|
const desc = prop.description ?? "";
|
|
2594
3278
|
const prevVal = previousArgs?.[name];
|
|
2595
|
-
const label = desc ? `${name} ${
|
|
3279
|
+
const label = desc ? `${name} ${colors.dim(`(${typeStr})`)} ${colors.dim(desc)}` : `${name} ${colors.dim(`(${typeStr})`)}`;
|
|
2596
3280
|
const answerStr = await input2(
|
|
2597
3281
|
{
|
|
2598
3282
|
message: label,
|
|
@@ -2631,14 +3315,14 @@ function printJsonTemplate(filled, allProps, currentProp) {
|
|
|
2631
3315
|
const parts = [];
|
|
2632
3316
|
for (const [name] of allProps) {
|
|
2633
3317
|
if (name in filled) {
|
|
2634
|
-
parts.push(`"${name}": ${
|
|
3318
|
+
parts.push(`"${name}": ${colors.green(JSON.stringify(filled[name]))}`);
|
|
2635
3319
|
} else if (name === currentProp) {
|
|
2636
|
-
parts.push(`"${name}": ${
|
|
3320
|
+
parts.push(`"${name}": ${colors.yellow("\u2592")}`);
|
|
2637
3321
|
} else {
|
|
2638
|
-
parts.push(`"${name}": ${
|
|
3322
|
+
parts.push(`"${name}": ${colors.dim("\u2592")}`);
|
|
2639
3323
|
}
|
|
2640
3324
|
}
|
|
2641
|
-
console.log(
|
|
3325
|
+
console.log(colors.dim(" { ") + parts.join(colors.dim(", ")) + colors.dim(" }"));
|
|
2642
3326
|
console.log();
|
|
2643
3327
|
}
|
|
2644
3328
|
function coerceValue(input3, type) {
|
|
@@ -2655,7 +3339,7 @@ function coerceValue(input3, type) {
|
|
|
2655
3339
|
}
|
|
2656
3340
|
}
|
|
2657
3341
|
function question(rl, prompt) {
|
|
2658
|
-
return new Promise((
|
|
3342
|
+
return new Promise((resolve3, reject) => {
|
|
2659
3343
|
let aborted = false;
|
|
2660
3344
|
const onKeypress = (_str, key) => {
|
|
2661
3345
|
if (key && key.name === "escape") {
|
|
@@ -2679,7 +3363,7 @@ function question(rl, prompt) {
|
|
|
2679
3363
|
if (aborted) {
|
|
2680
3364
|
reject(new AbortFlowError());
|
|
2681
3365
|
} else {
|
|
2682
|
-
|
|
3366
|
+
resolve3(answer);
|
|
2683
3367
|
}
|
|
2684
3368
|
});
|
|
2685
3369
|
});
|
|
@@ -2687,11 +3371,11 @@ function question(rl, prompt) {
|
|
|
2687
3371
|
async function cmdToolsScaffold(target, rest) {
|
|
2688
3372
|
const name = rest.trim();
|
|
2689
3373
|
if (!name) {
|
|
2690
|
-
console.log(
|
|
3374
|
+
console.log(colors.yellow(" Usage: tools/scaffold <name>"));
|
|
2691
3375
|
if (cachedToolNames.length > 0) {
|
|
2692
3376
|
const preview = cachedToolNames.slice(0, 6);
|
|
2693
3377
|
const more = cachedToolNames.length > 6 ? `, ... (${cachedToolNames.length} total)` : "";
|
|
2694
|
-
console.log(
|
|
3378
|
+
console.log(colors.dim(`
|
|
2695
3379
|
Available tools: ${preview.join(", ")}${more}`));
|
|
2696
3380
|
}
|
|
2697
3381
|
return;
|
|
@@ -2699,38 +3383,38 @@ async function cmdToolsScaffold(target, rest) {
|
|
|
2699
3383
|
const { tools } = await target.listTools();
|
|
2700
3384
|
const tool = tools.find((t) => t.name === name);
|
|
2701
3385
|
if (!tool) {
|
|
2702
|
-
console.log(
|
|
3386
|
+
console.log(colors.red(`Tool "${name}" not found.`));
|
|
2703
3387
|
const suggestion = suggestCommand(
|
|
2704
3388
|
name,
|
|
2705
3389
|
tools.map((t) => t.name)
|
|
2706
3390
|
);
|
|
2707
3391
|
if (suggestion) {
|
|
2708
|
-
console.log(
|
|
3392
|
+
console.log(colors.yellow(`Did you mean ${colors.bold(suggestion)}?`));
|
|
2709
3393
|
} else {
|
|
2710
|
-
console.log(
|
|
3394
|
+
console.log(colors.dim(`Available: ${tools.map((t) => t.name).join(", ")}`));
|
|
2711
3395
|
}
|
|
2712
3396
|
return;
|
|
2713
3397
|
}
|
|
2714
3398
|
const scaffolded = scaffoldArgs(tool.inputSchema);
|
|
2715
|
-
console.log(
|
|
3399
|
+
console.log(colors.cyan("\n Ready-to-paste command:"));
|
|
2716
3400
|
console.log(` tools/call ${name} ${scaffolded}
|
|
2717
3401
|
`);
|
|
2718
3402
|
}
|
|
2719
3403
|
async function cmdResourcesList(target) {
|
|
2720
3404
|
const { resources } = await target.listResources();
|
|
2721
3405
|
if (resources.length === 0) {
|
|
2722
|
-
console.log(
|
|
3406
|
+
console.log(colors.dim(" No resources available."));
|
|
2723
3407
|
return;
|
|
2724
3408
|
}
|
|
2725
3409
|
const uriWidth = Math.max(6, ...resources.map((r) => r.uri.length));
|
|
2726
|
-
console.log(
|
|
2727
|
-
console.log(
|
|
3410
|
+
console.log(colors.bold(` ${"URI".padEnd(uriWidth)} Name`));
|
|
3411
|
+
console.log(colors.dim(` ${"\u2500".repeat(uriWidth)} ${"\u2500".repeat(40)}`));
|
|
2728
3412
|
for (const r of resources) {
|
|
2729
3413
|
const uri = r.uri;
|
|
2730
|
-
const name = r.name ??
|
|
2731
|
-
console.log(` ${
|
|
3414
|
+
const name = r.name ?? colors.dim("(unnamed)");
|
|
3415
|
+
console.log(` ${colors.green(uri.padEnd(uriWidth))} ${name}`);
|
|
2732
3416
|
}
|
|
2733
|
-
console.log(
|
|
3417
|
+
console.log(colors.dim(`
|
|
2734
3418
|
${resources.length} resource(s) total.`));
|
|
2735
3419
|
}
|
|
2736
3420
|
async function cmdResourcesRead(target, rest, interceptor) {
|
|
@@ -2750,11 +3434,11 @@ async function cmdResourcesRead(target, rest, interceptor) {
|
|
|
2750
3434
|
if (!picked) return;
|
|
2751
3435
|
return cmdResourcesRead(target, picked, interceptor);
|
|
2752
3436
|
}
|
|
2753
|
-
console.log(
|
|
3437
|
+
console.log(colors.yellow(" Usage: resources/read <uri>"));
|
|
2754
3438
|
if (cachedResourceUris.length > 0) {
|
|
2755
3439
|
const preview = cachedResourceUris.slice(0, 5);
|
|
2756
3440
|
const more = cachedResourceUris.length > 5 ? `, ... (${cachedResourceUris.length} total)` : "";
|
|
2757
|
-
console.log(
|
|
3441
|
+
console.log(colors.dim(`
|
|
2758
3442
|
Available resources: ${preview.join(", ")}${more}`));
|
|
2759
3443
|
}
|
|
2760
3444
|
return;
|
|
@@ -2772,15 +3456,15 @@ async function cmdResourcesRead(target, rest, interceptor) {
|
|
|
2772
3456
|
if (typeof parsed === "object" && parsed !== null) {
|
|
2773
3457
|
console.log(formatJson(parsed, 2, true));
|
|
2774
3458
|
} else {
|
|
2775
|
-
console.log(
|
|
3459
|
+
console.log(colors.yellow(` ${text}`));
|
|
2776
3460
|
}
|
|
2777
3461
|
} catch {
|
|
2778
|
-
console.log(
|
|
3462
|
+
console.log(colors.yellow(` ${text}`));
|
|
2779
3463
|
}
|
|
2780
3464
|
} else if (item.blob !== void 0) {
|
|
2781
3465
|
const mimeType = item.mimeType ?? "application/octet-stream";
|
|
2782
3466
|
const sizeBytes = Buffer.from(item.blob, "base64").length;
|
|
2783
|
-
console.log(
|
|
3467
|
+
console.log(colors.dim(` [Binary: ${mimeType}, ${sizeBytes} bytes]`));
|
|
2784
3468
|
} else {
|
|
2785
3469
|
console.log(formatJson(item, 2, true));
|
|
2786
3470
|
}
|
|
@@ -2791,37 +3475,37 @@ async function cmdResourcesRead(target, rest, interceptor) {
|
|
|
2791
3475
|
async function cmdResourcesTemplates(target) {
|
|
2792
3476
|
const { resourceTemplates } = await target.listResourceTemplates();
|
|
2793
3477
|
if (resourceTemplates.length === 0) {
|
|
2794
|
-
console.log(
|
|
3478
|
+
console.log(colors.dim(" No resource templates available."));
|
|
2795
3479
|
return;
|
|
2796
3480
|
}
|
|
2797
3481
|
const uriWidth = Math.max(
|
|
2798
3482
|
12,
|
|
2799
3483
|
...resourceTemplates.map((t) => t.uriTemplate.length)
|
|
2800
3484
|
);
|
|
2801
|
-
console.log(
|
|
2802
|
-
console.log(
|
|
3485
|
+
console.log(colors.bold(` ${"URI Template".padEnd(uriWidth)} Name`));
|
|
3486
|
+
console.log(colors.dim(` ${"\u2500".repeat(uriWidth)} ${"\u2500".repeat(40)}`));
|
|
2803
3487
|
for (const t of resourceTemplates) {
|
|
2804
3488
|
const uriTemplate = t.uriTemplate;
|
|
2805
|
-
const name = t.name ??
|
|
2806
|
-
console.log(` ${
|
|
3489
|
+
const name = t.name ?? colors.dim("(unnamed)");
|
|
3490
|
+
console.log(` ${colors.green(uriTemplate.padEnd(uriWidth))} ${name}`);
|
|
2807
3491
|
}
|
|
2808
|
-
console.log(
|
|
3492
|
+
console.log(colors.dim(`
|
|
2809
3493
|
${resourceTemplates.length} template(s) total.`));
|
|
2810
3494
|
}
|
|
2811
3495
|
async function cmdPromptsList(target) {
|
|
2812
3496
|
const { prompts } = await target.listPrompts();
|
|
2813
3497
|
if (prompts.length === 0) {
|
|
2814
|
-
console.log(
|
|
3498
|
+
console.log(colors.dim(" No prompts available."));
|
|
2815
3499
|
return;
|
|
2816
3500
|
}
|
|
2817
3501
|
const nameWidth = Math.max(8, ...prompts.map((p) => p.name.length));
|
|
2818
|
-
console.log(
|
|
2819
|
-
console.log(
|
|
3502
|
+
console.log(colors.bold(` ${"Name".padEnd(nameWidth)} Description`));
|
|
3503
|
+
console.log(colors.dim(` ${"\u2500".repeat(nameWidth)} ${"\u2500".repeat(50)}`));
|
|
2820
3504
|
for (const p of prompts) {
|
|
2821
|
-
const desc = p.description ? p.description.length > 60 ? `${p.description.slice(0, 57)}...` : p.description :
|
|
2822
|
-
console.log(` ${
|
|
3505
|
+
const desc = p.description ? p.description.length > 60 ? `${p.description.slice(0, 57)}...` : p.description : colors.dim("(no description)");
|
|
3506
|
+
console.log(` ${colors.green(p.name.padEnd(nameWidth))} ${desc}`);
|
|
2823
3507
|
}
|
|
2824
|
-
console.log(
|
|
3508
|
+
console.log(colors.dim(`
|
|
2825
3509
|
${prompts.length} prompt(s) total.`));
|
|
2826
3510
|
}
|
|
2827
3511
|
async function cmdPromptsGet(target, rest, interceptor) {
|
|
@@ -2838,9 +3522,9 @@ async function cmdPromptsGet(target, rest, interceptor) {
|
|
|
2838
3522
|
if (!picked) return;
|
|
2839
3523
|
return cmdPromptsGet(target, picked);
|
|
2840
3524
|
}
|
|
2841
|
-
console.log(
|
|
3525
|
+
console.log(colors.yellow(" Usage: prompts/get <name> [json_args]"));
|
|
2842
3526
|
if (cachedPromptNames.length > 0) {
|
|
2843
|
-
console.log(
|
|
3527
|
+
console.log(colors.dim(`
|
|
2844
3528
|
Available prompts: ${cachedPromptNames.join(", ")}`));
|
|
2845
3529
|
}
|
|
2846
3530
|
return;
|
|
@@ -2852,15 +3536,15 @@ async function cmdPromptsGet(target, rest, interceptor) {
|
|
|
2852
3536
|
try {
|
|
2853
3537
|
promptArgs = JSON.parse(trimmed);
|
|
2854
3538
|
} catch (err) {
|
|
2855
|
-
console.error(
|
|
2856
|
-
console.log(
|
|
3539
|
+
console.error(colors.red(`Invalid JSON: ${err.message}`));
|
|
3540
|
+
console.log(colors.dim(` Received: ${jsonArgs}`));
|
|
2857
3541
|
return;
|
|
2858
3542
|
}
|
|
2859
3543
|
} else {
|
|
2860
3544
|
try {
|
|
2861
3545
|
promptArgs = parseHttpieArgs(trimmed);
|
|
2862
3546
|
} catch (err) {
|
|
2863
|
-
console.error(
|
|
3547
|
+
console.error(colors.red(`Invalid shorthand arguments: ${err.message}`));
|
|
2864
3548
|
return;
|
|
2865
3549
|
}
|
|
2866
3550
|
}
|
|
@@ -2868,14 +3552,14 @@ async function cmdPromptsGet(target, rest, interceptor) {
|
|
|
2868
3552
|
const { prompts } = await target.listPrompts();
|
|
2869
3553
|
const prompt = prompts.find((p) => p.name === promptName);
|
|
2870
3554
|
if (!prompt) {
|
|
2871
|
-
console.log(
|
|
3555
|
+
console.log(colors.red(`
|
|
2872
3556
|
\u2717 Prompt "${promptName}" not found.`));
|
|
2873
3557
|
const promptNames = prompts.map((p) => p.name);
|
|
2874
3558
|
const suggestion = suggestCommand(promptName, promptNames);
|
|
2875
3559
|
if (suggestion) {
|
|
2876
|
-
console.log(
|
|
3560
|
+
console.log(colors.yellow(` \u{1F4A1} Did you mean "${suggestion}"?`));
|
|
2877
3561
|
} else {
|
|
2878
|
-
console.log(
|
|
3562
|
+
console.log(colors.dim(` Available prompts: ${promptNames.join(", ")}`));
|
|
2879
3563
|
}
|
|
2880
3564
|
return { isError: true, content: [{ type: "text", text: `Prompt not found: ${promptName}` }] };
|
|
2881
3565
|
}
|
|
@@ -2883,24 +3567,24 @@ async function cmdPromptsGet(target, rest, interceptor) {
|
|
|
2883
3567
|
const result = await target.getPrompt({ name: promptName, arguments: promptArgs });
|
|
2884
3568
|
const elapsed = Date.now() - startTime;
|
|
2885
3569
|
if (result.messages.length === 0) {
|
|
2886
|
-
console.log(
|
|
3570
|
+
console.log(colors.dim(" No messages returned."));
|
|
2887
3571
|
return;
|
|
2888
3572
|
}
|
|
2889
3573
|
console.log();
|
|
2890
3574
|
printResultBlock({ label: "Prompt", labelColor: "blue", elapsed, detail: promptName });
|
|
2891
3575
|
for (const msg of result.messages) {
|
|
2892
|
-
const role = msg.role === "user" ?
|
|
3576
|
+
const role = msg.role === "user" ? colors.blue("user") : colors.magenta("assistant");
|
|
2893
3577
|
const text = msg.content.text ?? JSON.stringify(msg.content);
|
|
2894
3578
|
try {
|
|
2895
3579
|
const parsed = JSON.parse(text);
|
|
2896
3580
|
if (typeof parsed === "object" && parsed !== null) {
|
|
2897
|
-
console.log(` ${
|
|
3581
|
+
console.log(` ${colors.bold(role)}:`);
|
|
2898
3582
|
console.log(formatJson(parsed, 4, true));
|
|
2899
3583
|
} else {
|
|
2900
|
-
console.log(` ${
|
|
3584
|
+
console.log(` ${colors.bold(role)}: ${colors.yellow(text)}`);
|
|
2901
3585
|
}
|
|
2902
3586
|
} catch {
|
|
2903
|
-
console.log(` ${
|
|
3587
|
+
console.log(` ${colors.bold(role)}: ${colors.yellow(text)}`);
|
|
2904
3588
|
}
|
|
2905
3589
|
}
|
|
2906
3590
|
console.log();
|
|
@@ -2910,36 +3594,36 @@ async function cmdPing(target) {
|
|
|
2910
3594
|
try {
|
|
2911
3595
|
const elapsed = await target.ping();
|
|
2912
3596
|
console.log();
|
|
2913
|
-
console.log(
|
|
3597
|
+
console.log(colors.green(` \u2713 Pong! Round-trip: ${elapsed}ms`));
|
|
2914
3598
|
console.log();
|
|
2915
3599
|
} catch (err) {
|
|
2916
3600
|
console.log();
|
|
2917
|
-
console.error(
|
|
3601
|
+
console.error(colors.red(` \u2717 Ping failed: ${err.message}`));
|
|
2918
3602
|
console.log();
|
|
2919
3603
|
}
|
|
2920
3604
|
}
|
|
2921
3605
|
async function cmdLogLevel(target, rest) {
|
|
2922
3606
|
const level = rest.trim().toLowerCase();
|
|
2923
3607
|
if (!level) {
|
|
2924
|
-
console.log(
|
|
2925
|
-
console.log(
|
|
3608
|
+
console.log(colors.yellow(" Usage: log-level <level>"));
|
|
3609
|
+
console.log(colors.dim(` Valid levels: ${LOG_LEVELS.join(", ")}`));
|
|
2926
3610
|
return;
|
|
2927
3611
|
}
|
|
2928
3612
|
if (!LOG_LEVELS.includes(level)) {
|
|
2929
3613
|
const suggestion = suggestCommand(level, [...LOG_LEVELS]);
|
|
2930
3614
|
const hint = suggestion ? ` Did you mean "${suggestion}"?` : "";
|
|
2931
|
-
console.log(
|
|
2932
|
-
console.log(
|
|
3615
|
+
console.log(colors.red(` Unknown log level: "${level}".${hint}`));
|
|
3616
|
+
console.log(colors.dim(` Valid levels: ${LOG_LEVELS.join(", ")}`));
|
|
2933
3617
|
return;
|
|
2934
3618
|
}
|
|
2935
3619
|
try {
|
|
2936
3620
|
await target.setLoggingLevel(level);
|
|
2937
|
-
console.log(
|
|
3621
|
+
console.log(colors.green(` \u2713 Logging level set to: ${level}`));
|
|
2938
3622
|
} catch (err) {
|
|
2939
|
-
console.error(
|
|
3623
|
+
console.error(colors.red(` \u2717 Failed to set log level: ${err.message}`));
|
|
2940
3624
|
const caps = target.getServerCapabilities();
|
|
2941
3625
|
if (!caps?.logging) {
|
|
2942
|
-
console.log(
|
|
3626
|
+
console.log(colors.dim(" The server does not advertise logging support."));
|
|
2943
3627
|
}
|
|
2944
3628
|
}
|
|
2945
3629
|
}
|
|
@@ -2947,28 +3631,28 @@ function cmdHistory(target, rest) {
|
|
|
2947
3631
|
const arg = rest.trim();
|
|
2948
3632
|
if (arg === "clear") {
|
|
2949
3633
|
target.clearHistory();
|
|
2950
|
-
console.log(
|
|
3634
|
+
console.log(colors.dim(" History cleared."));
|
|
2951
3635
|
return;
|
|
2952
3636
|
}
|
|
2953
3637
|
const count = arg ? Number.parseInt(arg, 10) : 20;
|
|
2954
3638
|
const records = target.getHistory(Number.isNaN(count) ? 20 : count);
|
|
2955
3639
|
if (records.length === 0) {
|
|
2956
|
-
console.log(
|
|
3640
|
+
console.log(colors.dim(" No request history yet."));
|
|
2957
3641
|
return;
|
|
2958
3642
|
}
|
|
2959
|
-
console.log(
|
|
2960
|
-
console.log(
|
|
3643
|
+
console.log(colors.bold("\n Request History"));
|
|
3644
|
+
console.log(colors.dim(` ${"\u2500".repeat(70)}`));
|
|
2961
3645
|
for (const rec of records) {
|
|
2962
3646
|
const time = new Date(rec.timestamp).toLocaleTimeString();
|
|
2963
3647
|
const dur = `${rec.durationMs}ms`;
|
|
2964
|
-
const hasError = rec.error ?
|
|
3648
|
+
const hasError = rec.error ? colors.red(" \u2717") : "";
|
|
2965
3649
|
console.log(
|
|
2966
|
-
` ${
|
|
3650
|
+
` ${colors.dim(`#${rec.id}`)} ${colors.dim(time)} ${colors.green(rec.method.padEnd(30))} ${colors.cyan(dur.padStart(8))}${hasError}`
|
|
2967
3651
|
);
|
|
2968
3652
|
}
|
|
2969
3653
|
const total = target.getHistory().length;
|
|
2970
3654
|
console.log(
|
|
2971
|
-
|
|
3655
|
+
colors.dim(`
|
|
2972
3656
|
Showing ${records.length} of ${total} total. Use "history clear" to reset.`)
|
|
2973
3657
|
);
|
|
2974
3658
|
console.log();
|
|
@@ -2977,26 +3661,26 @@ function cmdNotifications(target, rest) {
|
|
|
2977
3661
|
const arg = rest.trim();
|
|
2978
3662
|
if (arg === "clear") {
|
|
2979
3663
|
target.clearNotifications();
|
|
2980
|
-
console.log(
|
|
3664
|
+
console.log(colors.dim(" Notifications cleared."));
|
|
2981
3665
|
return;
|
|
2982
3666
|
}
|
|
2983
3667
|
const count = arg ? Number.parseInt(arg, 10) : 20;
|
|
2984
3668
|
const records = target.getNotifications(Number.isNaN(count) ? 20 : count);
|
|
2985
3669
|
if (records.length === 0) {
|
|
2986
|
-
console.log(
|
|
2987
|
-
console.log(
|
|
3670
|
+
console.log(colors.dim(" No notifications received yet."));
|
|
3671
|
+
console.log(colors.dim(" Notifications appear inline as they arrive from the server."));
|
|
2988
3672
|
return;
|
|
2989
3673
|
}
|
|
2990
|
-
console.log(
|
|
2991
|
-
console.log(
|
|
3674
|
+
console.log(colors.bold("\n Server Notifications"));
|
|
3675
|
+
console.log(colors.dim(` ${"\u2500".repeat(70)}`));
|
|
2992
3676
|
for (const n of records) {
|
|
2993
3677
|
const time = new Date(n.timestamp).toLocaleTimeString();
|
|
2994
|
-
const params = n.params ? ` ${
|
|
2995
|
-
console.log(` ${
|
|
3678
|
+
const params = n.params ? ` ${colors.dim(JSON.stringify(n.params))}` : "";
|
|
3679
|
+
console.log(` ${colors.dim(time)} ${colors.yellow(n.method)}${params}`);
|
|
2996
3680
|
}
|
|
2997
3681
|
const total = target.getNotifications().length;
|
|
2998
3682
|
console.log(
|
|
2999
|
-
|
|
3683
|
+
colors.dim(`
|
|
3000
3684
|
Showing ${records.length} of ${total} total. Use "notifications clear" to reset.`)
|
|
3001
3685
|
);
|
|
3002
3686
|
console.log();
|
|
@@ -3004,44 +3688,44 @@ function cmdNotifications(target, rest) {
|
|
|
3004
3688
|
async function cmdResourcesSubscribe(target, rest) {
|
|
3005
3689
|
const uri = rest.trim();
|
|
3006
3690
|
if (!uri) {
|
|
3007
|
-
console.log(
|
|
3691
|
+
console.log(colors.yellow(" Usage: resources/subscribe <uri>"));
|
|
3008
3692
|
if (cachedResourceUris.length > 0) {
|
|
3009
|
-
console.log(
|
|
3693
|
+
console.log(colors.dim(` Available: ${cachedResourceUris.join(", ")}`));
|
|
3010
3694
|
}
|
|
3011
3695
|
return;
|
|
3012
3696
|
}
|
|
3013
3697
|
try {
|
|
3014
3698
|
await target.subscribeResource({ uri });
|
|
3015
|
-
console.log(
|
|
3016
|
-
console.log(
|
|
3699
|
+
console.log(colors.green(` \u2713 Subscribed to: ${uri}`));
|
|
3700
|
+
console.log(colors.dim(" You'll see notifications when this resource changes."));
|
|
3017
3701
|
} catch (err) {
|
|
3018
|
-
console.error(
|
|
3702
|
+
console.error(colors.red(` \u2717 Subscribe failed: ${err.message}`));
|
|
3019
3703
|
}
|
|
3020
3704
|
}
|
|
3021
3705
|
async function cmdResourcesUnsubscribe(target, rest) {
|
|
3022
3706
|
const uri = rest.trim();
|
|
3023
3707
|
if (!uri) {
|
|
3024
|
-
console.log(
|
|
3708
|
+
console.log(colors.yellow(" Usage: resources/unsubscribe <uri>"));
|
|
3025
3709
|
return;
|
|
3026
3710
|
}
|
|
3027
3711
|
try {
|
|
3028
3712
|
await target.unsubscribeResource({ uri });
|
|
3029
|
-
console.log(
|
|
3713
|
+
console.log(colors.green(` \u2713 Unsubscribed from: ${uri}`));
|
|
3030
3714
|
} catch (err) {
|
|
3031
|
-
console.error(
|
|
3715
|
+
console.error(colors.red(` \u2717 Unsubscribe failed: ${err.message}`));
|
|
3032
3716
|
}
|
|
3033
3717
|
}
|
|
3034
3718
|
function cmdRootsList(target) {
|
|
3035
3719
|
const roots = target.getRoots();
|
|
3036
3720
|
if (roots.length === 0) {
|
|
3037
|
-
console.log(
|
|
3038
|
-
console.log(
|
|
3721
|
+
console.log(colors.dim(" No roots configured."));
|
|
3722
|
+
console.log(colors.dim(" Use roots/add <uri> [name] to add one."));
|
|
3039
3723
|
return;
|
|
3040
3724
|
}
|
|
3041
|
-
console.log(
|
|
3725
|
+
console.log(colors.bold("\n Client Roots"));
|
|
3042
3726
|
for (const r of roots) {
|
|
3043
3727
|
const name = r.name ? ` (${r.name})` : "";
|
|
3044
|
-
console.log(` ${
|
|
3728
|
+
console.log(` ${colors.green(r.uri)}${colors.dim(name)}`);
|
|
3045
3729
|
}
|
|
3046
3730
|
console.log();
|
|
3047
3731
|
}
|
|
@@ -3050,53 +3734,53 @@ async function cmdRootsAdd(target, rest) {
|
|
|
3050
3734
|
const uri = parts[0];
|
|
3051
3735
|
const name = parts.slice(1).join(" ") || void 0;
|
|
3052
3736
|
if (!uri) {
|
|
3053
|
-
console.log(
|
|
3054
|
-
console.log(
|
|
3737
|
+
console.log(colors.yellow(" Usage: roots/add <uri> [name]"));
|
|
3738
|
+
console.log(colors.dim(' Example: roots/add file:///Users/me/project "My Project"'));
|
|
3055
3739
|
return;
|
|
3056
3740
|
}
|
|
3057
3741
|
await target.addRoot({ uri, name });
|
|
3058
|
-
console.log(
|
|
3059
|
-
console.log(
|
|
3742
|
+
console.log(colors.green(` \u2713 Root added: ${uri}`));
|
|
3743
|
+
console.log(colors.dim(" Server has been notified of the change."));
|
|
3060
3744
|
}
|
|
3061
3745
|
async function cmdRootsRemove(target, rest) {
|
|
3062
3746
|
const uri = rest.trim();
|
|
3063
3747
|
if (!uri) {
|
|
3064
|
-
console.log(
|
|
3748
|
+
console.log(colors.yellow(" Usage: roots/remove <uri>"));
|
|
3065
3749
|
const roots = target.getRoots();
|
|
3066
3750
|
if (roots.length > 0) {
|
|
3067
|
-
console.log(
|
|
3751
|
+
console.log(colors.dim(` Current roots: ${roots.map((r) => r.uri).join(", ")}`));
|
|
3068
3752
|
}
|
|
3069
3753
|
return;
|
|
3070
3754
|
}
|
|
3071
3755
|
const removed = await target.removeRoot(uri);
|
|
3072
3756
|
if (removed) {
|
|
3073
|
-
console.log(
|
|
3757
|
+
console.log(colors.green(` \u2713 Root removed: ${uri}`));
|
|
3074
3758
|
} else {
|
|
3075
|
-
console.log(
|
|
3759
|
+
console.log(colors.yellow(` Root not found: ${uri}`));
|
|
3076
3760
|
}
|
|
3077
3761
|
}
|
|
3078
3762
|
async function cmdReconnect(target) {
|
|
3079
|
-
console.log(
|
|
3763
|
+
console.log(colors.cyan("\u27F3 Disconnecting..."));
|
|
3080
3764
|
await target.close();
|
|
3081
|
-
await new Promise((
|
|
3082
|
-
console.log(
|
|
3765
|
+
await new Promise((resolve3) => setTimeout(resolve3, 200));
|
|
3766
|
+
console.log(colors.cyan("\u27F3 Reconnecting..."));
|
|
3083
3767
|
const { command, args } = target.getStatus();
|
|
3084
|
-
console.log(
|
|
3768
|
+
console.log(colors.dim(` Command: ${command} ${args.join(" ")}`));
|
|
3085
3769
|
try {
|
|
3086
3770
|
await target.connect();
|
|
3087
3771
|
const s = target.getStatus();
|
|
3088
|
-
console.log(
|
|
3772
|
+
console.log(colors.green(`\u2713 Reconnected (PID: ${s.pid})`));
|
|
3089
3773
|
const { tools } = await target.listTools();
|
|
3090
|
-
console.log(
|
|
3774
|
+
console.log(colors.cyan(` ${tools.length} tool(s) available.
|
|
3091
3775
|
`));
|
|
3092
3776
|
await refreshCaches(target);
|
|
3093
3777
|
} catch (err) {
|
|
3094
|
-
console.error(
|
|
3778
|
+
console.error(colors.red(`\u2717 Failed to reconnect: ${err.message}`));
|
|
3095
3779
|
}
|
|
3096
3780
|
}
|
|
3097
3781
|
function cmdTiming() {
|
|
3098
3782
|
if (callHistory.length === 0) {
|
|
3099
|
-
console.log(
|
|
3783
|
+
console.log(colors.dim(" No tool calls recorded yet."));
|
|
3100
3784
|
return;
|
|
3101
3785
|
}
|
|
3102
3786
|
const groups = /* @__PURE__ */ new Map();
|
|
@@ -3106,8 +3790,8 @@ function cmdTiming() {
|
|
|
3106
3790
|
groups.set(record.toolName, list);
|
|
3107
3791
|
}
|
|
3108
3792
|
const nameWidth = Math.max(8, ...[...groups.keys()].map((n) => n.length));
|
|
3109
|
-
console.log(
|
|
3110
|
-
console.log(
|
|
3793
|
+
console.log(colors.bold("\n Tool Call Performance"));
|
|
3794
|
+
console.log(colors.dim(` ${"\u2500".repeat(nameWidth + 50)}`));
|
|
3111
3795
|
let totalCalls = 0;
|
|
3112
3796
|
let totalMs = 0;
|
|
3113
3797
|
let slowestName = "";
|
|
@@ -3125,12 +3809,12 @@ function cmdTiming() {
|
|
|
3125
3809
|
slowestName = name;
|
|
3126
3810
|
}
|
|
3127
3811
|
console.log(
|
|
3128
|
-
` ${
|
|
3812
|
+
` ${colors.green(name.padEnd(nameWidth))} \xD7 ${count} avg: ${avg}ms p95: ${p95}ms max: ${max}ms`
|
|
3129
3813
|
);
|
|
3130
3814
|
}
|
|
3131
3815
|
const overallAvg = Math.round(totalMs / totalCalls);
|
|
3132
3816
|
console.log(
|
|
3133
|
-
|
|
3817
|
+
colors.dim(
|
|
3134
3818
|
`
|
|
3135
3819
|
Total: ${totalCalls} call(s), avg ${overallAvg}ms, slowest: ${slowestName} (${slowestMs}ms)`
|
|
3136
3820
|
)
|
|
@@ -3141,138 +3825,34 @@ function cmdStatus(target) {
|
|
|
3141
3825
|
const s = target.getStatus();
|
|
3142
3826
|
const uptimeStr = s.uptime >= 60 ? `${Math.floor(s.uptime / 60)}m ${(s.uptime % 60).toFixed(0)}s` : `${s.uptime.toFixed(1)}s`;
|
|
3143
3827
|
const lastRespStr = s.lastResponseTime ? `${((Date.now() - s.lastResponseTime) / 1e3).toFixed(1)}s ago` : "never";
|
|
3144
|
-
console.log(
|
|
3145
|
-
console.log(` ${
|
|
3146
|
-
console.log(` ${
|
|
3147
|
-
console.log(` ${
|
|
3148
|
-
console.log(` ${
|
|
3149
|
-
console.log(` ${
|
|
3150
|
-
console.log(` ${
|
|
3151
|
-
console.log(` ${
|
|
3828
|
+
console.log(colors.bold("\n Target Server Status"));
|
|
3829
|
+
console.log(` ${colors.dim("Connected:")} ${s.connected ? colors.green("yes") : colors.red("no")}`);
|
|
3830
|
+
console.log(` ${colors.dim("PID:")} ${s.pid ?? "N/A"}`);
|
|
3831
|
+
console.log(` ${colors.dim("Uptime:")} ${uptimeStr}`);
|
|
3832
|
+
console.log(` ${colors.dim("Last response:")} ${lastRespStr}`);
|
|
3833
|
+
console.log(` ${colors.dim("Stderr lines:")} ${s.stderrLineCount.toLocaleString()}`);
|
|
3834
|
+
console.log(` ${colors.dim("Reconnects:")} ${s.reconnectAttempts}/${s.maxReconnectAttempts}`);
|
|
3835
|
+
console.log(` ${colors.dim("Command:")} ${s.command} ${s.args.join(" ")}`);
|
|
3152
3836
|
console.log();
|
|
3153
3837
|
}
|
|
3154
|
-
function
|
|
3155
|
-
const
|
|
3156
|
-
|
|
3157
|
-
|
|
3158
|
-
|
|
3159
|
-
|
|
3160
|
-
}
|
|
3161
|
-
|
|
3162
|
-
|
|
3163
|
-
|
|
3164
|
-
|
|
3165
|
-
|
|
3166
|
-
|
|
3167
|
-
|
|
3168
|
-
|
|
3169
|
-
|
|
3170
|
-
|
|
3171
|
-
|
|
3172
|
-
${tC("td")} ${tC("tools/describe")} ${rC("rr")} ${rC("resources/read")} ${pC("pg")} ${pC("prompts/get")}
|
|
3173
|
-
${tC("tc")} ${tC("tools/call")} ${rC("rt")} ${rC("resources/templates")}
|
|
3174
|
-
${tC("ts")} ${tC("tools/scaffold")} ${rC("rs")} ${rC("resources/subscribe")}
|
|
3175
|
-
${rC("ru")} ${rC("resources/unsubscribe")}
|
|
3176
|
-
|
|
3177
|
-
${pc2.green("ping")} ${pc2.green("status")} ${pc2.green("timing")} ${pc2.green("history")} ${pc2.green("!!")} ${pc2.green("explore")} ${pc2.green("reconnect")}
|
|
3178
|
-
|
|
3179
|
-
${pc2.dim("Type 'help' for full command reference.")}
|
|
3180
|
-
`);
|
|
3181
|
-
}
|
|
3182
|
-
function printHelp() {
|
|
3183
|
-
const hasTools = !!activeCapabilities?.tools;
|
|
3184
|
-
const hasResources = !!activeCapabilities?.resources;
|
|
3185
|
-
const hasPrompts = !!activeCapabilities?.prompts;
|
|
3186
|
-
const hasLogging = !!activeCapabilities?.logging;
|
|
3187
|
-
const tC = hasTools ? pc2.green : pc2.dim;
|
|
3188
|
-
const rC = hasResources ? pc2.green : pc2.dim;
|
|
3189
|
-
const pC = hasPrompts ? pc2.green : pc2.dim;
|
|
3190
|
-
const lC = hasLogging ? pc2.green : pc2.dim;
|
|
3191
|
-
const tD = hasTools ? (s) => s : pc2.dim;
|
|
3192
|
-
const rD = hasResources ? (s) => s : pc2.dim;
|
|
3193
|
-
const pD = hasPrompts ? (s) => s : pc2.dim;
|
|
3194
|
-
const lD = hasLogging ? (s) => s : pc2.dim;
|
|
3195
|
-
const tH = hasTools ? pc2.bold("Tool Commands:") : pc2.dim(pc2.bold("Tool Commands:")) + pc2.dim(" (Unsupported)");
|
|
3196
|
-
const rH = hasResources ? pc2.bold("Resource Commands:") : pc2.dim(pc2.bold("Resource Commands:")) + pc2.dim(" (Unsupported)");
|
|
3197
|
-
const pH = hasPrompts ? pc2.bold("Prompt Commands:") : pc2.dim(pc2.bold("Prompt Commands:")) + pc2.dim(" (Unsupported)");
|
|
3198
|
-
console.log(`
|
|
3199
|
-
${tH}
|
|
3200
|
-
|
|
3201
|
-
${tC("tools/list")} ${tD("List all available tools")}
|
|
3202
|
-
${tC("tools/describe")} <name> ${tD("Show a tool's input schema")}
|
|
3203
|
-
${tC("tools/call")} <name> [json] [opts] ${tD("Call a tool (interactive if no json)")}
|
|
3204
|
-
${tD("Options:")} ${pc2.dim("--timeout <ms>")} ${tD("Override default timeout (60s)")}
|
|
3205
|
-
${pc2.dim("--clear")} ${tD("Ignore remembered argument defaults")}
|
|
3206
|
-
${tC("tools/scaffold")} <name> ${tD("Generate a template for a tool's arguments")}
|
|
3207
|
-
${tC("tools/forget")} [name] ${tD("Clear remembered interactive defaults")}
|
|
3208
|
-
|
|
3209
|
-
${rH}
|
|
3210
|
-
|
|
3211
|
-
${rC("resources/list")} ${rD("List all available resources")}
|
|
3212
|
-
${rC("resources/read")} <uri> ${rD("Read a resource by URI")}
|
|
3213
|
-
${rC("resources/templates")} ${rD("List resource templates")}
|
|
3214
|
-
${rC("resources/subscribe")} <uri> ${rD("Subscribe to resource changes")}
|
|
3215
|
-
${rC("resources/unsubscribe")} <uri> ${rD("Unsubscribe from resource changes")}
|
|
3216
|
-
|
|
3217
|
-
${pH}
|
|
3218
|
-
|
|
3219
|
-
${pC("prompts/list")} ${pD("List all available prompts")}
|
|
3220
|
-
${pC("prompts/get")} <name> [json_args] ${pD("Get a prompt with arguments")}
|
|
3221
|
-
|
|
3222
|
-
${pc2.bold("Protocol Commands:")}
|
|
3223
|
-
|
|
3224
|
-
${pc2.green("ping")} Verify connection, show round-trip time
|
|
3225
|
-
${lC("log-level")} <level> ${lD("Set server logging verbosity")}${hasLogging ? "" : pc2.dim(" (Unsupported)")}
|
|
3226
|
-
${pc2.green("history")} [count|clear] Show request/response history
|
|
3227
|
-
${pc2.green("notifications")} [count|clear] Show server notifications
|
|
3228
|
-
|
|
3229
|
-
${pc2.bold("Roots Management:")}
|
|
3230
|
-
|
|
3231
|
-
${pc2.green("roots/list")} Show configured client roots
|
|
3232
|
-
${pc2.green("roots/add")} <uri> [name] Add a root directory
|
|
3233
|
-
${pc2.green("roots/remove")} <uri> Remove a root directory
|
|
3234
|
-
|
|
3235
|
-
${pc2.bold("Session Commands:")}
|
|
3236
|
-
|
|
3237
|
-
${pc2.green("!!")} / ${pc2.green("last")} Re-run the last command
|
|
3238
|
-
${pc2.green("reconnect")} Disconnect and reconnect to the server
|
|
3239
|
-
${pc2.green("timing")} Show tool call performance stats
|
|
3240
|
-
${pc2.green("status")} Show target server status
|
|
3241
|
-
${pc2.green("help")} Show this help
|
|
3242
|
-
${pc2.green("exit")} / ${pc2.green("quit")} Disconnect and exit
|
|
3243
|
-
|
|
3244
|
-
${pc2.bold("Shortcuts:")}
|
|
3245
|
-
|
|
3246
|
-
${tC("tl")} ${tC("tools/list")} ${rC("rl")} ${rC("resources/list")} ${pC("pl")} ${pC("prompts/list")}
|
|
3247
|
-
${tC("td")} ${tC("tools/describe")} ${rC("rr")} ${rC("resources/read")} ${pC("pg")} ${pC("prompts/get")}
|
|
3248
|
-
${tC("tc")} ${tC("tools/call")} ${rC("rt")} ${rC("resources/templates")}
|
|
3249
|
-
${tC("ts")} ${tC("tools/scaffold")} ${rC("rs")} ${rC("resources/subscribe")}
|
|
3250
|
-
${rC("ru")} ${rC("resources/unsubscribe")}
|
|
3251
|
-
|
|
3252
|
-
${pc2.dim("Lines starting with # are treated as comments.")}
|
|
3253
|
-
${pc2.dim('JSON arguments can contain spaces: tools/call say {"message": "hello world"}')}
|
|
3254
|
-
${pc2.dim("Run tools/call <name> without JSON for interactive argument prompting.")}
|
|
3255
|
-
${pc2.dim("Use tools/call <name> --clear to ignore remembered defaults.")}
|
|
3256
|
-
`);
|
|
3257
|
-
}
|
|
3258
|
-
async function readScriptLines(filepath) {
|
|
3259
|
-
const content = await readFile2(filepath, "utf-8");
|
|
3260
|
-
return content.split("\n");
|
|
3261
|
-
}
|
|
3262
|
-
function cmdToolsForget(rest) {
|
|
3263
|
-
const toolName = rest.trim();
|
|
3264
|
-
if (toolName) {
|
|
3265
|
-
if (lastToolArgsMap.has(toolName)) {
|
|
3266
|
-
lastToolArgsMap.delete(toolName);
|
|
3267
|
-
console.log(pc2.green(` Cleared remembered args for ${pc2.bold(toolName)}.`));
|
|
3268
|
-
} else {
|
|
3269
|
-
console.log(pc2.yellow(` No remembered args for "${toolName}".`));
|
|
3270
|
-
}
|
|
3271
|
-
} else {
|
|
3272
|
-
const count = lastToolArgsMap.size;
|
|
3273
|
-
lastToolArgsMap.clear();
|
|
3274
|
-
console.log(pc2.green(` Cleared remembered args for ${count} tool${count === 1 ? "" : "s"}.`));
|
|
3275
|
-
}
|
|
3838
|
+
function cmdToolsForget(rest) {
|
|
3839
|
+
const toolName = rest.trim();
|
|
3840
|
+
if (toolName) {
|
|
3841
|
+
if (lastToolArgsMap.has(toolName)) {
|
|
3842
|
+
lastToolArgsMap.delete(toolName);
|
|
3843
|
+
saveWizardDefaults().catch(() => {
|
|
3844
|
+
});
|
|
3845
|
+
console.log(colors.green(` Cleared remembered args for ${colors.bold(toolName)}.`));
|
|
3846
|
+
} else {
|
|
3847
|
+
console.log(colors.yellow(` No remembered args for "${toolName}".`));
|
|
3848
|
+
}
|
|
3849
|
+
} else {
|
|
3850
|
+
const count = lastToolArgsMap.size;
|
|
3851
|
+
lastToolArgsMap.clear();
|
|
3852
|
+
saveWizardDefaults().catch(() => {
|
|
3853
|
+
});
|
|
3854
|
+
console.log(colors.green(` Cleared remembered args for ${count} tool${count === 1 ? "" : "s"}.`));
|
|
3855
|
+
}
|
|
3276
3856
|
}
|
|
3277
3857
|
async function pickInteractive(items, message) {
|
|
3278
3858
|
if (items.length === 0) return null;
|
|
@@ -3298,88 +3878,489 @@ async function pickInteractive(items, message) {
|
|
|
3298
3878
|
if (isAbortError(err)) return null;
|
|
3299
3879
|
throw err;
|
|
3300
3880
|
}
|
|
3301
|
-
}
|
|
3302
|
-
async function
|
|
3303
|
-
const choices = [];
|
|
3304
|
-
const caps = target.getServerCapabilities() ?? {};
|
|
3881
|
+
}
|
|
3882
|
+
async function showMainMenu(target, interceptor) {
|
|
3883
|
+
const choices = [];
|
|
3884
|
+
const caps = target.getServerCapabilities() ?? {};
|
|
3885
|
+
try {
|
|
3886
|
+
const { tools } = await target.listTools();
|
|
3887
|
+
for (const t of tools) {
|
|
3888
|
+
choices.push({
|
|
3889
|
+
name: `\u{1F6E0}\uFE0F Tool: ${t.name}`,
|
|
3890
|
+
value: { type: "tool", name: t.name },
|
|
3891
|
+
description: t.description || `Call the ${t.name} tool`
|
|
3892
|
+
});
|
|
3893
|
+
}
|
|
3894
|
+
} catch {
|
|
3895
|
+
}
|
|
3896
|
+
if (caps.resources) {
|
|
3897
|
+
try {
|
|
3898
|
+
const { resources } = await target.listResources();
|
|
3899
|
+
for (const r of resources) {
|
|
3900
|
+
choices.push({
|
|
3901
|
+
name: `\u{1F4C4} Resource: ${r.name || r.uri}`,
|
|
3902
|
+
value: { type: "resource", uri: r.uri },
|
|
3903
|
+
description: r.description || `Read resource ${r.uri}`
|
|
3904
|
+
});
|
|
3905
|
+
}
|
|
3906
|
+
} catch {
|
|
3907
|
+
}
|
|
3908
|
+
}
|
|
3909
|
+
if (caps.prompts) {
|
|
3910
|
+
try {
|
|
3911
|
+
const { prompts } = await target.listPrompts();
|
|
3912
|
+
for (const p of prompts) {
|
|
3913
|
+
choices.push({
|
|
3914
|
+
name: `\u{1F4AC} Prompt: ${p.name}`,
|
|
3915
|
+
value: { type: "prompt", name: p.name },
|
|
3916
|
+
description: p.description || `Get the ${p.name} prompt`
|
|
3917
|
+
});
|
|
3918
|
+
}
|
|
3919
|
+
} catch {
|
|
3920
|
+
}
|
|
3921
|
+
}
|
|
3922
|
+
choices.push({
|
|
3923
|
+
name: "\u2699\uFE0F Check Server Status",
|
|
3924
|
+
value: { type: "command", name: "status" },
|
|
3925
|
+
description: "View connection status and server info"
|
|
3926
|
+
});
|
|
3927
|
+
choices.push({
|
|
3928
|
+
name: "\u{1F504} Reconnect Server",
|
|
3929
|
+
value: { type: "command", name: "reconnect" },
|
|
3930
|
+
description: "Restart the target MCP server"
|
|
3931
|
+
});
|
|
3932
|
+
choices.push({
|
|
3933
|
+
name: "\u2328\uFE0F Type a raw command",
|
|
3934
|
+
value: { type: "raw" },
|
|
3935
|
+
description: "Drop to the traditional REPL prompt (use 'menu' to return)"
|
|
3936
|
+
});
|
|
3937
|
+
choices.push({
|
|
3938
|
+
name: "\u{1F6AA} Exit",
|
|
3939
|
+
value: { type: "command", name: "exit" },
|
|
3940
|
+
description: "Close connection and exit"
|
|
3941
|
+
});
|
|
3942
|
+
if (!process.stdin.isTTY) return false;
|
|
3943
|
+
try {
|
|
3944
|
+
const answer = await search({
|
|
3945
|
+
message: "Select an action (start typing to search):",
|
|
3946
|
+
source: async (term) => {
|
|
3947
|
+
if (!term) return choices;
|
|
3948
|
+
const lower = term.toLowerCase();
|
|
3949
|
+
return choices.filter(
|
|
3950
|
+
(c) => c.name.toLowerCase().includes(lower) || c.description.toLowerCase().includes(lower)
|
|
3951
|
+
);
|
|
3952
|
+
}
|
|
3953
|
+
});
|
|
3954
|
+
if (answer.type === "tool") {
|
|
3955
|
+
await cmdToolsCall(target, interceptor, answer.name);
|
|
3956
|
+
} else if (answer.type === "resource") {
|
|
3957
|
+
await cmdResourcesRead(target, answer.uri);
|
|
3958
|
+
} else if (answer.type === "prompt") {
|
|
3959
|
+
await cmdPromptsGet(target, answer.name);
|
|
3960
|
+
} else if (answer.type === "command") {
|
|
3961
|
+
if (answer.name === "status") {
|
|
3962
|
+
cmdStatus(target);
|
|
3963
|
+
} else if (answer.name === "reconnect") {
|
|
3964
|
+
await cmdReconnect(target);
|
|
3965
|
+
} else if (answer.name === "exit") {
|
|
3966
|
+
console.log(colors.dim("\nShutting down..."));
|
|
3967
|
+
await target.close();
|
|
3968
|
+
process.exit(0);
|
|
3969
|
+
}
|
|
3970
|
+
} else if (answer.type === "raw") {
|
|
3971
|
+
return true;
|
|
3972
|
+
}
|
|
3973
|
+
return false;
|
|
3974
|
+
} catch (err) {
|
|
3975
|
+
if (isAbortError(err)) {
|
|
3976
|
+
console.log(colors.dim("\nShutting down..."));
|
|
3977
|
+
await target.close();
|
|
3978
|
+
process.exit(0);
|
|
3979
|
+
}
|
|
3980
|
+
throw err;
|
|
3981
|
+
}
|
|
3982
|
+
}
|
|
3983
|
+
async function mainMenuLoop(target, interceptor) {
|
|
3984
|
+
if (isScriptMode || !process.stdin.isTTY) {
|
|
3985
|
+
startReadlineLoop(target, interceptor);
|
|
3986
|
+
return;
|
|
3987
|
+
}
|
|
3988
|
+
while (true) {
|
|
3989
|
+
try {
|
|
3990
|
+
const dropToRaw = await showMainMenu(target, interceptor);
|
|
3991
|
+
if (dropToRaw) {
|
|
3992
|
+
console.log(colors.dim(" Entering raw command mode. Type 'menu' to return."));
|
|
3993
|
+
startReadlineLoop(target, interceptor);
|
|
3994
|
+
break;
|
|
3995
|
+
}
|
|
3996
|
+
} catch (err) {
|
|
3997
|
+
console.error(colors.red(`Error in menu: ${err.message}`));
|
|
3998
|
+
startReadlineLoop(target, interceptor);
|
|
3999
|
+
break;
|
|
4000
|
+
}
|
|
4001
|
+
}
|
|
4002
|
+
}
|
|
4003
|
+
|
|
4004
|
+
// src/repl/index.ts
|
|
4005
|
+
function getPrompt(target) {
|
|
4006
|
+
if (target.connected) return `${colors.green("\u2713")}${colors.cyan("> ")}`;
|
|
4007
|
+
return `${colors.red("\u2717")}${colors.cyan("> ")}`;
|
|
4008
|
+
}
|
|
4009
|
+
function startReadlineLoop(target, interceptor) {
|
|
4010
|
+
if (isScriptMode || activeRl) return;
|
|
4011
|
+
const rl = createInterface({
|
|
4012
|
+
input: process.stdin,
|
|
4013
|
+
output: process.stdout,
|
|
4014
|
+
prompt: getPrompt(target),
|
|
4015
|
+
terminal: true,
|
|
4016
|
+
completer,
|
|
4017
|
+
history: [...replHistory].reverse()
|
|
4018
|
+
// Node's readline history expects newest first
|
|
4019
|
+
});
|
|
4020
|
+
setActiveRl(rl);
|
|
4021
|
+
setClosed(false);
|
|
4022
|
+
if (process.stdin.isTTY) {
|
|
4023
|
+
process.stdin.on("keypress", (_str, key) => {
|
|
4024
|
+
if (!key || key.name !== "tab") {
|
|
4025
|
+
resetTabCycle();
|
|
4026
|
+
}
|
|
4027
|
+
});
|
|
4028
|
+
}
|
|
4029
|
+
if (!deferNextPrompt) {
|
|
4030
|
+
rl.prompt();
|
|
4031
|
+
}
|
|
4032
|
+
setDeferNextPrompt(false);
|
|
4033
|
+
let processing = false;
|
|
4034
|
+
const queue = [];
|
|
4035
|
+
const processQueue = async () => {
|
|
4036
|
+
if (processing) return;
|
|
4037
|
+
processing = true;
|
|
4038
|
+
while (queue.length > 0) {
|
|
4039
|
+
const trimmed = queue.shift();
|
|
4040
|
+
try {
|
|
4041
|
+
await handleCommand(trimmed, target, interceptor);
|
|
4042
|
+
} catch (err) {
|
|
4043
|
+
if (err instanceof AbortFlowError) {
|
|
4044
|
+
console.log(colors.yellow(" Aborted."));
|
|
4045
|
+
} else if (err?.message?.includes("-32601") || err?.code === -32601) {
|
|
4046
|
+
let msg = "Server does not support this feature (Method not found)";
|
|
4047
|
+
if (trimmed.startsWith("prompts/")) msg = "This server does not have any prompts.";
|
|
4048
|
+
else if (trimmed.startsWith("resources/"))
|
|
4049
|
+
msg = "This server does not have any resources.";
|
|
4050
|
+
else if (trimmed.startsWith("tools/")) msg = "This server does not have any tools.";
|
|
4051
|
+
console.log(colors.yellow(` ${msg}`));
|
|
4052
|
+
} else {
|
|
4053
|
+
console.error(colors.red(`\u2717 Error: ${err.message}`));
|
|
4054
|
+
}
|
|
4055
|
+
}
|
|
4056
|
+
if (activeRl) {
|
|
4057
|
+
setImmediate(() => {
|
|
4058
|
+
if (activeRl) {
|
|
4059
|
+
console.log();
|
|
4060
|
+
activeRl.setPrompt(getPrompt(target));
|
|
4061
|
+
activeRl.prompt();
|
|
4062
|
+
}
|
|
4063
|
+
});
|
|
4064
|
+
}
|
|
4065
|
+
}
|
|
4066
|
+
processing = false;
|
|
4067
|
+
};
|
|
4068
|
+
rl.on("line", (line) => {
|
|
4069
|
+
const trimmed = line.trim();
|
|
4070
|
+
if (!trimmed || trimmed.startsWith("#")) {
|
|
4071
|
+
if (!closed && activeRl) activeRl.prompt();
|
|
4072
|
+
return;
|
|
4073
|
+
}
|
|
4074
|
+
replHistory.push(trimmed);
|
|
4075
|
+
appendToHistoryFile(trimmed).catch(() => {
|
|
4076
|
+
});
|
|
4077
|
+
queue.push(trimmed);
|
|
4078
|
+
processQueue();
|
|
4079
|
+
});
|
|
4080
|
+
rl.on("close", async () => {
|
|
4081
|
+
setClosed(true);
|
|
4082
|
+
setActiveRl(null);
|
|
4083
|
+
if (!globalPauseReadlineClose) {
|
|
4084
|
+
console.log(colors.dim("\nShutting down..."));
|
|
4085
|
+
await target.close();
|
|
4086
|
+
process.exit(0);
|
|
4087
|
+
}
|
|
4088
|
+
});
|
|
4089
|
+
}
|
|
4090
|
+
async function startRepl(targetCommand, opts) {
|
|
4091
|
+
const [command, ...args] = targetCommand;
|
|
4092
|
+
const target = new TargetManager(command, args, {
|
|
4093
|
+
sandbox: opts.sandbox,
|
|
4094
|
+
allowRead: opts.allowRead,
|
|
4095
|
+
allowWrite: opts.allowWrite,
|
|
4096
|
+
allowNet: opts.allowNet,
|
|
4097
|
+
denyRead: opts.denyRead,
|
|
4098
|
+
denyWrite: opts.denyWrite,
|
|
4099
|
+
denyNet: opts.denyNet
|
|
4100
|
+
});
|
|
4101
|
+
const interceptor = new ResponseInterceptor({
|
|
4102
|
+
outDir: opts.outDir,
|
|
4103
|
+
mediaThresholdKb: opts.mediaThresholdKb,
|
|
4104
|
+
openMedia: opts.openMedia
|
|
4105
|
+
});
|
|
4106
|
+
setIsScriptMode(!!opts.script);
|
|
4107
|
+
if (!isScriptMode) {
|
|
4108
|
+
await loadHistory();
|
|
4109
|
+
await loadWizardDefaults();
|
|
4110
|
+
}
|
|
4111
|
+
target.on("stderr", (text) => {
|
|
4112
|
+
for (const line of text.split("\n")) {
|
|
4113
|
+
console.error(colors.dim(`[server] ${line}`));
|
|
4114
|
+
}
|
|
4115
|
+
});
|
|
4116
|
+
console.log(colors.cyan("\u27F3 Connecting to target MCP server..."));
|
|
4117
|
+
console.log(colors.dim(` Command: ${targetCommand.join(" ")}`));
|
|
4118
|
+
try {
|
|
4119
|
+
await target.connect();
|
|
4120
|
+
} catch (err) {
|
|
4121
|
+
const msg = err.message ?? String(err);
|
|
4122
|
+
if (msg.includes("ENOENT") || msg.includes("spawn")) {
|
|
4123
|
+
console.error(colors.red(`\u2717 Failed to start server: command "${command}" not found.`));
|
|
4124
|
+
console.error(colors.dim(` Check that "${command}" is installed and in your PATH.`));
|
|
4125
|
+
} else {
|
|
4126
|
+
console.error(colors.red(`\u2717 Failed to connect: ${msg}`));
|
|
4127
|
+
console.error(colors.dim(` Check that the target command starts a valid MCP server on stdio.`));
|
|
4128
|
+
}
|
|
4129
|
+
process.exit(1);
|
|
4130
|
+
}
|
|
4131
|
+
const status = target.getStatus();
|
|
4132
|
+
console.log(colors.green(`\u2713 Connected (PID: ${status.pid})`));
|
|
4133
|
+
if (!isScriptMode) {
|
|
4134
|
+
target.enableAutoReconnect();
|
|
4135
|
+
target.on(
|
|
4136
|
+
"reconnecting",
|
|
4137
|
+
({ attempt, maxAttempts }) => {
|
|
4138
|
+
console.log(
|
|
4139
|
+
colors.yellow(`
|
|
4140
|
+
\u27F3 Server disconnected. Reconnecting (${attempt}/${maxAttempts})...`)
|
|
4141
|
+
);
|
|
4142
|
+
}
|
|
4143
|
+
);
|
|
4144
|
+
target.on("reconnected", async ({ attempt }) => {
|
|
4145
|
+
const s = target.getStatus();
|
|
4146
|
+
console.log(colors.green(`\u2713 Reconnected (PID: ${s.pid}, attempt ${attempt})`));
|
|
4147
|
+
await refreshCaches(target);
|
|
4148
|
+
});
|
|
4149
|
+
target.on("reconnect_failed", ({ reason, message }) => {
|
|
4150
|
+
console.error(colors.red(`\u2717 ${message}`));
|
|
4151
|
+
if (reason === "max_retries") {
|
|
4152
|
+
console.log(
|
|
4153
|
+
colors.dim(" Use 'exit' to quit or wait for the server to be fixed and restart manually.")
|
|
4154
|
+
);
|
|
4155
|
+
}
|
|
4156
|
+
});
|
|
4157
|
+
target.on("notification", (notification) => {
|
|
4158
|
+
const method = notification.method;
|
|
4159
|
+
if (method === "notifications/message") {
|
|
4160
|
+
const lvl = notification.params?.level ?? "info";
|
|
4161
|
+
const data = notification.params?.data ?? "";
|
|
4162
|
+
const text = typeof data === "string" ? data : JSON.stringify(data);
|
|
4163
|
+
console.log(colors.dim(`
|
|
4164
|
+
[${lvl}] ${text}`));
|
|
4165
|
+
} else if (method === "notifications/tools/list_changed") {
|
|
4166
|
+
console.log(colors.yellow("\n \u27F3 Server tools changed. Run tools/list to see updates."));
|
|
4167
|
+
refreshCaches(target).catch(() => {
|
|
4168
|
+
});
|
|
4169
|
+
} else if (method === "notifications/resources/list_changed") {
|
|
4170
|
+
console.log(colors.yellow("\n \u27F3 Server resources changed. Run resources/list to see."));
|
|
4171
|
+
refreshCaches(target).catch(() => {
|
|
4172
|
+
});
|
|
4173
|
+
} else if (method === "notifications/resources/updated") {
|
|
4174
|
+
const uri = notification.params?.uri ?? "unknown";
|
|
4175
|
+
console.log(colors.yellow(`
|
|
4176
|
+
\u27F3 Resource updated: ${uri}`));
|
|
4177
|
+
} else if (method === "notifications/prompts/list_changed") {
|
|
4178
|
+
console.log(colors.yellow("\n \u27F3 Server prompts changed. Run prompts/list to see."));
|
|
4179
|
+
refreshCaches(target).catch(() => {
|
|
4180
|
+
});
|
|
4181
|
+
}
|
|
4182
|
+
});
|
|
4183
|
+
target.on("sampling_request", async ({ request, respond, reject: rejectFn }) => {
|
|
4184
|
+
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"));
|
|
4185
|
+
const messages = request?.messages ?? [];
|
|
4186
|
+
for (const msg of messages) {
|
|
4187
|
+
const role = msg.role === "user" ? colors.blue("user") : colors.magenta("assistant");
|
|
4188
|
+
const text = msg.content?.text ?? JSON.stringify(msg.content);
|
|
4189
|
+
console.log(colors.magenta(` \u2551 ${role}: ${text}`));
|
|
4190
|
+
}
|
|
4191
|
+
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"));
|
|
4192
|
+
if (activeRl) {
|
|
4193
|
+
try {
|
|
4194
|
+
const answer = await question(activeRl, ` ${colors.bold("Approve? [y/N/text]:")} `);
|
|
4195
|
+
const trimmed = answer.trim().toLowerCase();
|
|
4196
|
+
if (trimmed === "y" || trimmed === "yes") {
|
|
4197
|
+
respond({
|
|
4198
|
+
model: "user-approved",
|
|
4199
|
+
role: "assistant",
|
|
4200
|
+
content: { type: "text", text: "Approved by user." }
|
|
4201
|
+
});
|
|
4202
|
+
} else if (trimmed === "n" || trimmed === "no" || trimmed === "") {
|
|
4203
|
+
rejectFn(new Error("Sampling request rejected by user"));
|
|
4204
|
+
} else {
|
|
4205
|
+
respond({
|
|
4206
|
+
model: "user-provided",
|
|
4207
|
+
role: "assistant",
|
|
4208
|
+
content: { type: "text", text: answer.trim() }
|
|
4209
|
+
});
|
|
4210
|
+
}
|
|
4211
|
+
} catch (err) {
|
|
4212
|
+
if (err instanceof AbortFlowError) {
|
|
4213
|
+
rejectFn(new Error("Sampling request rejected by user"));
|
|
4214
|
+
} else {
|
|
4215
|
+
throw err;
|
|
4216
|
+
}
|
|
4217
|
+
}
|
|
4218
|
+
} else {
|
|
4219
|
+
rejectFn(new Error("No interactive terminal available for sampling approval"));
|
|
4220
|
+
}
|
|
4221
|
+
});
|
|
4222
|
+
target.on("elicitation_request", async ({ request, respond, reject: rejectFn }) => {
|
|
4223
|
+
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"));
|
|
4224
|
+
console.log(colors.cyan(` \u2551 ${request?.message ?? "Server requests input"}`));
|
|
4225
|
+
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"));
|
|
4226
|
+
if (activeRl) {
|
|
4227
|
+
try {
|
|
4228
|
+
const answer = await question(
|
|
4229
|
+
activeRl,
|
|
4230
|
+
` ${colors.bold("Your response (empty to decline):")} `
|
|
4231
|
+
);
|
|
4232
|
+
if (answer.trim() === "") {
|
|
4233
|
+
respond({ action: "decline" });
|
|
4234
|
+
} else {
|
|
4235
|
+
try {
|
|
4236
|
+
const parsed = JSON.parse(answer.trim());
|
|
4237
|
+
respond({ action: "accept", content: parsed });
|
|
4238
|
+
} catch {
|
|
4239
|
+
respond({ action: "accept", content: { value: answer.trim() } });
|
|
4240
|
+
}
|
|
4241
|
+
}
|
|
4242
|
+
} catch (err) {
|
|
4243
|
+
if (err instanceof AbortFlowError) {
|
|
4244
|
+
respond({ action: "decline" });
|
|
4245
|
+
} else {
|
|
4246
|
+
throw err;
|
|
4247
|
+
}
|
|
4248
|
+
}
|
|
4249
|
+
} else {
|
|
4250
|
+
rejectFn(new Error("No interactive terminal available for elicitation"));
|
|
4251
|
+
}
|
|
4252
|
+
});
|
|
4253
|
+
}
|
|
4254
|
+
let toolCount;
|
|
4255
|
+
let resourceCount = 0;
|
|
4256
|
+
let promptCount = 0;
|
|
3305
4257
|
try {
|
|
3306
4258
|
const { tools } = await target.listTools();
|
|
3307
|
-
|
|
3308
|
-
|
|
3309
|
-
|
|
3310
|
-
|
|
3311
|
-
|
|
3312
|
-
|
|
3313
|
-
|
|
3314
|
-
} catch {
|
|
3315
|
-
}
|
|
3316
|
-
if (caps.resources) {
|
|
3317
|
-
try {
|
|
3318
|
-
const { resources } = await target.listResources();
|
|
3319
|
-
for (const r of resources) {
|
|
3320
|
-
choices.push({
|
|
3321
|
-
name: `\u{1F4C4} Resource: ${r.name || r.uri}`,
|
|
3322
|
-
value: { type: "resource", uri: r.uri },
|
|
3323
|
-
description: r.description || `Read resource ${r.uri}`
|
|
3324
|
-
});
|
|
4259
|
+
toolCount = tools.length;
|
|
4260
|
+
const caps = target.getServerCapabilities() ?? {};
|
|
4261
|
+
if (caps.resources) {
|
|
4262
|
+
try {
|
|
4263
|
+
const { resources } = await target.listResources();
|
|
4264
|
+
resourceCount = resources.length;
|
|
4265
|
+
} catch {
|
|
3325
4266
|
}
|
|
3326
|
-
} catch {
|
|
3327
4267
|
}
|
|
3328
|
-
|
|
3329
|
-
|
|
3330
|
-
|
|
3331
|
-
|
|
3332
|
-
|
|
3333
|
-
choices.push({
|
|
3334
|
-
name: `\u{1F4AC} Prompt: ${p.name}`,
|
|
3335
|
-
value: { type: "prompt", name: p.name },
|
|
3336
|
-
description: p.description || `Get the ${p.name} prompt`
|
|
3337
|
-
});
|
|
4268
|
+
if (caps.prompts) {
|
|
4269
|
+
try {
|
|
4270
|
+
const { prompts } = await target.listPrompts();
|
|
4271
|
+
promptCount = prompts.length;
|
|
4272
|
+
} catch {
|
|
3338
4273
|
}
|
|
3339
|
-
} catch {
|
|
3340
4274
|
}
|
|
3341
|
-
|
|
3342
|
-
|
|
3343
|
-
|
|
3344
|
-
|
|
3345
|
-
|
|
3346
|
-
|
|
3347
|
-
|
|
3348
|
-
|
|
3349
|
-
console.log(
|
|
3350
|
-
` ${pc2.cyan(String(i + 1).padStart(2))}. ${choices[i].name} ${pc2.dim(choices[i].description)}`
|
|
4275
|
+
const serverInfo = target.getServerVersion();
|
|
4276
|
+
const serverName = serverInfo?.name ?? "MCP Server";
|
|
4277
|
+
const serverVersion = serverInfo?.version ?? "";
|
|
4278
|
+
const isHarness = serverName === "run-mcp";
|
|
4279
|
+
printBanner(serverName, serverVersion, toolCount, resourceCount, promptCount, isHarness);
|
|
4280
|
+
if (toolCount >= 10) {
|
|
4281
|
+
const groups = groupToolsByPrefix(
|
|
4282
|
+
cachedToolNames.length > 0 ? cachedToolNames : tools.map((t) => t.name)
|
|
3351
4283
|
);
|
|
3352
|
-
|
|
3353
|
-
|
|
3354
|
-
|
|
3355
|
-
|
|
3356
|
-
|
|
3357
|
-
return;
|
|
3358
|
-
}
|
|
3359
|
-
try {
|
|
3360
|
-
const answer = await search({
|
|
3361
|
-
message: "Explore server capabilities:",
|
|
3362
|
-
source: async (term) => {
|
|
3363
|
-
if (!term) return choices;
|
|
3364
|
-
const lower = term.toLowerCase();
|
|
3365
|
-
return choices.filter(
|
|
3366
|
-
(c) => c.name.toLowerCase().includes(lower) || c.description.toLowerCase().includes(lower)
|
|
3367
|
-
);
|
|
4284
|
+
if (!groups.has("All")) {
|
|
4285
|
+
for (const [label, members] of groups) {
|
|
4286
|
+
console.log(` ${colors.bold(label.padEnd(16))} ${colors.dim(members.join(", "))}`);
|
|
4287
|
+
}
|
|
4288
|
+
console.log();
|
|
3368
4289
|
}
|
|
3369
|
-
});
|
|
3370
|
-
if (answer.type === "tool") {
|
|
3371
|
-
await cmdToolsCall(target, interceptor, answer.name);
|
|
3372
|
-
} else if (answer.type === "resource") {
|
|
3373
|
-
await cmdResourcesRead(target, answer.uri);
|
|
3374
|
-
} else if (answer.type === "prompt") {
|
|
3375
|
-
await cmdPromptsGet(target, answer.name);
|
|
3376
4290
|
}
|
|
3377
4291
|
} catch (err) {
|
|
3378
|
-
|
|
3379
|
-
|
|
4292
|
+
console.log(colors.yellow(` Warning: Could not list tools: ${err.message}
|
|
4293
|
+
`));
|
|
4294
|
+
}
|
|
4295
|
+
await refreshCaches(target);
|
|
4296
|
+
if (isScriptMode) {
|
|
4297
|
+
const lines = await readScriptLines(opts.script);
|
|
4298
|
+
const scriptContext = {};
|
|
4299
|
+
let expectError = false;
|
|
4300
|
+
for (const line of lines) {
|
|
4301
|
+
let trimmed = line.trim();
|
|
4302
|
+
if (!trimmed || trimmed.startsWith("#")) {
|
|
4303
|
+
if (trimmed === "# @expect-error") {
|
|
4304
|
+
expectError = true;
|
|
4305
|
+
}
|
|
4306
|
+
continue;
|
|
4307
|
+
}
|
|
4308
|
+
if (trimmed.endsWith("# @expect-error")) {
|
|
4309
|
+
expectError = true;
|
|
4310
|
+
trimmed = trimmed.replace(/\s*#\s*@expect-error$/, "");
|
|
4311
|
+
}
|
|
4312
|
+
const interpolated = interpolateString(trimmed, scriptContext);
|
|
4313
|
+
try {
|
|
4314
|
+
const res = await handleCommand(interpolated, target, interceptor);
|
|
4315
|
+
if (res !== void 0) {
|
|
4316
|
+
scriptContext.LAST = res;
|
|
4317
|
+
}
|
|
4318
|
+
const isErrorRes = res && typeof res === "object" && res.isError === true;
|
|
4319
|
+
if (expectError && !isErrorRes) {
|
|
4320
|
+
console.error(colors.red(`\u2717 Expected an error but the command succeeded.`));
|
|
4321
|
+
await target.close();
|
|
4322
|
+
process.exit(1);
|
|
4323
|
+
}
|
|
4324
|
+
if (!expectError && isErrorRes) {
|
|
4325
|
+
console.error(colors.red(`\u2717 Command failed unexpectedly.`));
|
|
4326
|
+
await target.close();
|
|
4327
|
+
process.exit(1);
|
|
4328
|
+
}
|
|
4329
|
+
if (expectError && isErrorRes) {
|
|
4330
|
+
console.log(colors.yellow(` \u2713 Expected error caught: tool returned isError: true`));
|
|
4331
|
+
}
|
|
4332
|
+
} catch (err) {
|
|
4333
|
+
if (expectError) {
|
|
4334
|
+
console.log(colors.yellow(` \u2713 Expected error caught: ${err.message}`));
|
|
4335
|
+
} else {
|
|
4336
|
+
if (err?.message?.includes("-32601") || err?.code === -32601) {
|
|
4337
|
+
let msg = "Server does not support this feature (Method not found)";
|
|
4338
|
+
if (trimmed.startsWith("prompts/")) msg = "This server does not have any prompts.";
|
|
4339
|
+
else if (trimmed.startsWith("resources/"))
|
|
4340
|
+
msg = "This server does not have any resources.";
|
|
4341
|
+
else if (trimmed.startsWith("tools/")) msg = "This server does not have any tools.";
|
|
4342
|
+
console.log(colors.yellow(` ${msg}`));
|
|
4343
|
+
} else {
|
|
4344
|
+
console.error(colors.red(`\u2717 Error: ${err.message}`));
|
|
4345
|
+
}
|
|
4346
|
+
console.log(colors.dim("\nShutting down..."));
|
|
4347
|
+
await target.close();
|
|
4348
|
+
process.exit(1);
|
|
4349
|
+
}
|
|
4350
|
+
}
|
|
4351
|
+
expectError = false;
|
|
3380
4352
|
}
|
|
4353
|
+
console.log(colors.dim("\nShutting down..."));
|
|
4354
|
+
await target.close();
|
|
4355
|
+
process.exit(0);
|
|
4356
|
+
} else {
|
|
4357
|
+
mainMenuLoop(target, interceptor);
|
|
3381
4358
|
}
|
|
3382
4359
|
}
|
|
4360
|
+
async function readScriptLines(filepath) {
|
|
4361
|
+
const content = await readFile4(filepath, "utf8");
|
|
4362
|
+
return content.split("\n");
|
|
4363
|
+
}
|
|
3383
4364
|
|
|
3384
4365
|
// src/server.ts
|
|
3385
4366
|
import { createHash } from "crypto";
|
|
@@ -3444,7 +4425,7 @@ async function startServer(opts) {
|
|
|
3444
4425
|
mediaThresholdKb: opts.mediaThresholdKb
|
|
3445
4426
|
});
|
|
3446
4427
|
const mcpServer = new McpServer(
|
|
3447
|
-
{ name: "run-mcp", version: "1.
|
|
4428
|
+
{ name: "run-mcp", version: "1.7.0" },
|
|
3448
4429
|
{
|
|
3449
4430
|
capabilities: {
|
|
3450
4431
|
tools: {},
|
|
@@ -3605,7 +4586,15 @@ async function startServer(opts) {
|
|
|
3605
4586
|
process.env[key] = value;
|
|
3606
4587
|
}
|
|
3607
4588
|
}
|
|
3608
|
-
target = new TargetManager(cmdToUse, argsToUse ?? []
|
|
4589
|
+
target = new TargetManager(cmdToUse, argsToUse ?? [], {
|
|
4590
|
+
sandbox: opts.sandbox,
|
|
4591
|
+
allowRead: opts.allowRead,
|
|
4592
|
+
allowWrite: opts.allowWrite,
|
|
4593
|
+
allowNet: opts.allowNet,
|
|
4594
|
+
denyRead: opts.denyRead,
|
|
4595
|
+
denyWrite: opts.denyWrite,
|
|
4596
|
+
denyNet: opts.denyNet
|
|
4597
|
+
});
|
|
3609
4598
|
setupTargetListeners(target);
|
|
3610
4599
|
try {
|
|
3611
4600
|
await target.connect();
|
|
@@ -3709,10 +4698,11 @@ async function startServer(opts) {
|
|
|
3709
4698
|
),
|
|
3710
4699
|
summary: z.boolean().optional().describe(
|
|
3711
4700
|
"If true, returns only the name and description of each primitive (omitting full schemas) when included to save tokens."
|
|
3712
|
-
)
|
|
4701
|
+
),
|
|
4702
|
+
sandbox: z.enum(["auto", "docker", "native", "audit", "none"]).optional().describe("Sandbox mode to use for this server")
|
|
3713
4703
|
}
|
|
3714
4704
|
},
|
|
3715
|
-
async ({ command, args, env, include, summary }) => {
|
|
4705
|
+
async ({ command, args, env, include, summary, sandbox }) => {
|
|
3716
4706
|
if (target?.connected) {
|
|
3717
4707
|
return {
|
|
3718
4708
|
content: [
|
|
@@ -3734,7 +4724,15 @@ async function startServer(opts) {
|
|
|
3734
4724
|
process.env[key] = value;
|
|
3735
4725
|
}
|
|
3736
4726
|
}
|
|
3737
|
-
target = new TargetManager(command, args ?? []
|
|
4727
|
+
target = new TargetManager(command, args ?? [], {
|
|
4728
|
+
sandbox: sandbox ?? opts.sandbox,
|
|
4729
|
+
allowRead: opts.allowRead,
|
|
4730
|
+
allowWrite: opts.allowWrite,
|
|
4731
|
+
allowNet: opts.allowNet,
|
|
4732
|
+
denyRead: opts.denyRead,
|
|
4733
|
+
denyWrite: opts.denyWrite,
|
|
4734
|
+
denyNet: opts.denyNet
|
|
4735
|
+
});
|
|
3738
4736
|
setupTargetListeners(target);
|
|
3739
4737
|
try {
|
|
3740
4738
|
await target.connect();
|
|
@@ -4069,6 +5067,7 @@ Available: ${available}`
|
|
|
4069
5067
|
type: z.enum(["tool", "resource", "prompt"]).describe("The MCP primitive type to invoke"),
|
|
4070
5068
|
name: z.string().describe("Tool name, resource URI, or prompt name"),
|
|
4071
5069
|
arguments: z.record(z.unknown()).optional().describe("Arguments for the tool or prompt (not used for resources)"),
|
|
5070
|
+
args: z.record(z.unknown()).optional().describe("Arguments for the tool or prompt (alias for 'arguments')"),
|
|
4072
5071
|
// Auto-connect params (only needed if not already connected)
|
|
4073
5072
|
auto_connect: z.object({
|
|
4074
5073
|
command: z.string().describe("Command to spawn the server (e.g. 'node')."),
|
|
@@ -4092,12 +5091,14 @@ Available: ${available}`
|
|
|
4092
5091
|
type: primitiveType,
|
|
4093
5092
|
name,
|
|
4094
5093
|
arguments: callArgs,
|
|
5094
|
+
args: callArgsAlias,
|
|
4095
5095
|
auto_connect,
|
|
4096
5096
|
disconnect_after,
|
|
4097
5097
|
timeout_ms,
|
|
4098
5098
|
include_metadata,
|
|
4099
5099
|
max_text_length
|
|
4100
5100
|
}) => {
|
|
5101
|
+
const finalArgs = callArgs ?? callArgsAlias;
|
|
4101
5102
|
try {
|
|
4102
5103
|
const connectError = await ensureConnected(
|
|
4103
5104
|
auto_connect?.command,
|
|
@@ -4147,14 +5148,14 @@ Available tools: ${toolNames.join(", ")}`
|
|
|
4147
5148
|
}
|
|
4148
5149
|
const schema = matchedTool.inputSchema;
|
|
4149
5150
|
const requiredProps = schema?.required ?? [];
|
|
4150
|
-
const providedKeys = Object.keys(
|
|
5151
|
+
const providedKeys = Object.keys(finalArgs ?? {});
|
|
4151
5152
|
const missingProps = requiredProps.filter((p) => !providedKeys.includes(p));
|
|
4152
5153
|
if (missingProps.length > 0) {
|
|
4153
5154
|
return {
|
|
4154
5155
|
content: [
|
|
4155
5156
|
{
|
|
4156
5157
|
type: "text",
|
|
4157
|
-
text: `Tool "${name}" requires: ${missingProps.join(", ")}. Received: ${JSON.stringify(
|
|
5158
|
+
text: `Tool "${name}" requires: ${missingProps.join(", ")}. Received: ${JSON.stringify(finalArgs ?? {})}`
|
|
4158
5159
|
}
|
|
4159
5160
|
],
|
|
4160
5161
|
isError: true
|
|
@@ -4168,7 +5169,7 @@ Available tools: ${toolNames.join(", ")}`
|
|
|
4168
5169
|
const { result: toolResult, metadata } = await interceptor.callToolWithMetadata(
|
|
4169
5170
|
target,
|
|
4170
5171
|
name,
|
|
4171
|
-
|
|
5172
|
+
finalArgs ?? {},
|
|
4172
5173
|
timeout_ms,
|
|
4173
5174
|
max_text_length
|
|
4174
5175
|
);
|
|
@@ -4178,7 +5179,7 @@ Available tools: ${toolNames.join(", ")}`
|
|
|
4178
5179
|
result = await interceptor.callTool(
|
|
4179
5180
|
target,
|
|
4180
5181
|
name,
|
|
4181
|
-
|
|
5182
|
+
finalArgs ?? {},
|
|
4182
5183
|
timeout_ms,
|
|
4183
5184
|
max_text_length
|
|
4184
5185
|
);
|
|
@@ -4202,6 +5203,7 @@ Available tools: ${toolNames.join(", ")}`
|
|
|
4202
5203
|
text: `--- metadata ---
|
|
4203
5204
|
${JSON.stringify(meta)}`
|
|
4204
5205
|
});
|
|
5206
|
+
result.meta = meta;
|
|
4205
5207
|
}
|
|
4206
5208
|
break;
|
|
4207
5209
|
}
|
|
@@ -4233,6 +5235,7 @@ ${JSON.stringify(meta)}`
|
|
|
4233
5235
|
text: `--- metadata ---
|
|
4234
5236
|
${JSON.stringify(meta)}`
|
|
4235
5237
|
});
|
|
5238
|
+
result.meta = meta;
|
|
4236
5239
|
}
|
|
4237
5240
|
break;
|
|
4238
5241
|
}
|
|
@@ -4262,7 +5265,7 @@ Available prompts: ${promptNames.join(", ")}`
|
|
|
4262
5265
|
target,
|
|
4263
5266
|
{
|
|
4264
5267
|
name,
|
|
4265
|
-
arguments:
|
|
5268
|
+
arguments: finalArgs ?? {}
|
|
4266
5269
|
},
|
|
4267
5270
|
timeout_ms,
|
|
4268
5271
|
max_text_length
|
|
@@ -4301,6 +5304,7 @@ ${item.text}` });
|
|
|
4301
5304
|
text: `--- metadata ---
|
|
4302
5305
|
${JSON.stringify(meta)}`
|
|
4303
5306
|
});
|
|
5307
|
+
result.meta = meta;
|
|
4304
5308
|
}
|
|
4305
5309
|
break;
|
|
4306
5310
|
}
|
|
@@ -4350,6 +5354,230 @@ ${JSON.stringify(meta)}`
|
|
|
4350
5354
|
};
|
|
4351
5355
|
}
|
|
4352
5356
|
);
|
|
5357
|
+
mcpServer.registerTool(
|
|
5358
|
+
"validate_mcp_server",
|
|
5359
|
+
{
|
|
5360
|
+
title: "Validate MCP Server",
|
|
5361
|
+
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.",
|
|
5362
|
+
inputSchema: {
|
|
5363
|
+
command: z.string().describe("Command to run (e.g. 'node', 'python')"),
|
|
5364
|
+
args: z.array(z.string()).optional().describe("Arguments to pass"),
|
|
5365
|
+
env: z.record(z.string()).optional().describe("Extra environment variables")
|
|
5366
|
+
}
|
|
5367
|
+
},
|
|
5368
|
+
async ({ command, args, env }) => {
|
|
5369
|
+
const originalEnv = {};
|
|
5370
|
+
if (env) {
|
|
5371
|
+
for (const [key, value] of Object.entries(env)) {
|
|
5372
|
+
originalEnv[key] = process.env[key];
|
|
5373
|
+
process.env[key] = value;
|
|
5374
|
+
}
|
|
5375
|
+
}
|
|
5376
|
+
const tempTarget = new TargetManager(command, args ?? [], {
|
|
5377
|
+
sandbox: opts.sandbox,
|
|
5378
|
+
allowRead: opts.allowRead,
|
|
5379
|
+
allowWrite: opts.allowWrite,
|
|
5380
|
+
allowNet: opts.allowNet,
|
|
5381
|
+
denyRead: opts.denyRead,
|
|
5382
|
+
denyWrite: opts.denyWrite,
|
|
5383
|
+
denyNet: opts.denyNet
|
|
5384
|
+
});
|
|
5385
|
+
const stderrLines = [];
|
|
5386
|
+
tempTarget.on("stderr", (text) => {
|
|
5387
|
+
stderrLines.push(text);
|
|
5388
|
+
});
|
|
5389
|
+
try {
|
|
5390
|
+
const connectPromise = tempTarget.connect();
|
|
5391
|
+
const timeoutPromise = new Promise(
|
|
5392
|
+
(_, reject) => setTimeout(() => reject(new Error("Connection timed out after 5000ms")), 5e3)
|
|
5393
|
+
);
|
|
5394
|
+
await Promise.race([connectPromise, timeoutPromise]);
|
|
5395
|
+
const toolsResult = await tempTarget.listTools();
|
|
5396
|
+
const caps = tempTarget.getServerCapabilities() ?? {};
|
|
5397
|
+
const ver = tempTarget.getServerVersion();
|
|
5398
|
+
await tempTarget.close();
|
|
5399
|
+
return {
|
|
5400
|
+
content: [
|
|
5401
|
+
{
|
|
5402
|
+
type: "text",
|
|
5403
|
+
text: `Validation Result: SUCCESS
|
|
5404
|
+
Server Name: ${ver?.name ?? "unknown"}
|
|
5405
|
+
Server Version: ${ver?.version ?? "unknown"}
|
|
5406
|
+
Tools Count: ${toolsResult.tools.length}
|
|
5407
|
+
Capabilities: ${Object.keys(caps).join(", ") || "none"}
|
|
5408
|
+
|
|
5409
|
+
Captured Stderr:
|
|
5410
|
+
${stderrLines.join("\n") || "(none)"}`
|
|
5411
|
+
}
|
|
5412
|
+
]
|
|
5413
|
+
};
|
|
5414
|
+
} catch (err) {
|
|
5415
|
+
await tempTarget.close().catch(() => {
|
|
5416
|
+
});
|
|
5417
|
+
return {
|
|
5418
|
+
content: [
|
|
5419
|
+
{
|
|
5420
|
+
type: "text",
|
|
5421
|
+
text: `Validation Result: FAILED
|
|
5422
|
+
Error: ${err.message}
|
|
5423
|
+
|
|
5424
|
+
Captured Stderr:
|
|
5425
|
+
${stderrLines.join("\n") || "(none)"}`
|
|
5426
|
+
}
|
|
5427
|
+
],
|
|
5428
|
+
isError: true
|
|
5429
|
+
};
|
|
5430
|
+
} finally {
|
|
5431
|
+
if (env) {
|
|
5432
|
+
for (const key of Object.keys(env)) {
|
|
5433
|
+
if (originalEnv[key] === void 0) {
|
|
5434
|
+
delete process.env[key];
|
|
5435
|
+
} else {
|
|
5436
|
+
process.env[key] = originalEnv[key];
|
|
5437
|
+
}
|
|
5438
|
+
}
|
|
5439
|
+
}
|
|
5440
|
+
}
|
|
5441
|
+
}
|
|
5442
|
+
);
|
|
5443
|
+
mcpServer.registerTool(
|
|
5444
|
+
"search_all_local_mcp_servers",
|
|
5445
|
+
{
|
|
5446
|
+
title: "Search All Local MCP Servers",
|
|
5447
|
+
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.",
|
|
5448
|
+
inputSchema: {
|
|
5449
|
+
query: z.string().describe("Search query (case-insensitive substring match)"),
|
|
5450
|
+
type: z.array(z.enum(["tools", "resources", "prompts"])).optional().describe("Primitives to search. Defaults to ['tools'].")
|
|
5451
|
+
}
|
|
5452
|
+
},
|
|
5453
|
+
async ({ query, type }) => {
|
|
5454
|
+
const searchTypes = type ?? ["tools"];
|
|
5455
|
+
const lowerQuery = query.toLowerCase();
|
|
5456
|
+
try {
|
|
5457
|
+
const servers = await discoverServers();
|
|
5458
|
+
if (servers.length === 0) {
|
|
5459
|
+
return {
|
|
5460
|
+
content: [{ type: "text", text: "No local MCP servers found to search." }]
|
|
5461
|
+
};
|
|
5462
|
+
}
|
|
5463
|
+
const uniqueServers = /* @__PURE__ */ new Map();
|
|
5464
|
+
for (const s of servers) {
|
|
5465
|
+
const key = `${s.name}::${s.config.command}::${(s.config.args || []).join(" ")}`;
|
|
5466
|
+
if (!uniqueServers.has(key)) {
|
|
5467
|
+
uniqueServers.set(key, s);
|
|
5468
|
+
} else if (s.source.includes("Project")) {
|
|
5469
|
+
uniqueServers.set(key, s);
|
|
5470
|
+
}
|
|
5471
|
+
}
|
|
5472
|
+
const matchResults = [];
|
|
5473
|
+
for (const s of uniqueServers.values()) {
|
|
5474
|
+
const originalEnv = {};
|
|
5475
|
+
if (s.config.env) {
|
|
5476
|
+
for (const [key, value] of Object.entries(s.config.env)) {
|
|
5477
|
+
originalEnv[key] = process.env[key];
|
|
5478
|
+
process.env[key] = value;
|
|
5479
|
+
}
|
|
5480
|
+
}
|
|
5481
|
+
const tempTarget = new TargetManager(s.config.command, s.config.args || [], {
|
|
5482
|
+
sandbox: opts.sandbox,
|
|
5483
|
+
allowRead: opts.allowRead,
|
|
5484
|
+
allowWrite: opts.allowWrite,
|
|
5485
|
+
allowNet: opts.allowNet,
|
|
5486
|
+
denyRead: opts.denyRead,
|
|
5487
|
+
denyWrite: opts.denyWrite,
|
|
5488
|
+
denyNet: opts.denyNet
|
|
5489
|
+
});
|
|
5490
|
+
try {
|
|
5491
|
+
await Promise.race([
|
|
5492
|
+
tempTarget.connect(),
|
|
5493
|
+
new Promise(
|
|
5494
|
+
(_, reject) => setTimeout(() => reject(new Error("Timeout")), 3e3)
|
|
5495
|
+
)
|
|
5496
|
+
]);
|
|
5497
|
+
const caps = tempTarget.getServerCapabilities() ?? {};
|
|
5498
|
+
if (searchTypes.includes("tools") && caps.tools) {
|
|
5499
|
+
const { tools } = await tempTarget.listTools();
|
|
5500
|
+
for (const t of tools) {
|
|
5501
|
+
if (t.name.toLowerCase().includes(lowerQuery) || t.description && t.description.toLowerCase().includes(lowerQuery)) {
|
|
5502
|
+
matchResults.push({
|
|
5503
|
+
server: s.name,
|
|
5504
|
+
primitive: "tool",
|
|
5505
|
+
name: t.name,
|
|
5506
|
+
description: t.description
|
|
5507
|
+
});
|
|
5508
|
+
}
|
|
5509
|
+
}
|
|
5510
|
+
}
|
|
5511
|
+
if (searchTypes.includes("resources") && caps.resources) {
|
|
5512
|
+
const { resources } = await tempTarget.listResources();
|
|
5513
|
+
for (const r of resources) {
|
|
5514
|
+
if (r.name && r.name.toLowerCase().includes(lowerQuery) || r.uri.toLowerCase().includes(lowerQuery) || r.description && r.description.toLowerCase().includes(lowerQuery)) {
|
|
5515
|
+
matchResults.push({
|
|
5516
|
+
server: s.name,
|
|
5517
|
+
primitive: "resource",
|
|
5518
|
+
name: r.name || r.uri,
|
|
5519
|
+
uri: r.uri,
|
|
5520
|
+
description: r.description
|
|
5521
|
+
});
|
|
5522
|
+
}
|
|
5523
|
+
}
|
|
5524
|
+
}
|
|
5525
|
+
if (searchTypes.includes("prompts") && caps.prompts) {
|
|
5526
|
+
const { prompts } = await tempTarget.listPrompts();
|
|
5527
|
+
for (const p of prompts) {
|
|
5528
|
+
if (p.name.toLowerCase().includes(lowerQuery) || p.description && p.description.toLowerCase().includes(lowerQuery)) {
|
|
5529
|
+
matchResults.push({
|
|
5530
|
+
server: s.name,
|
|
5531
|
+
primitive: "prompt",
|
|
5532
|
+
name: p.name,
|
|
5533
|
+
description: p.description
|
|
5534
|
+
});
|
|
5535
|
+
}
|
|
5536
|
+
}
|
|
5537
|
+
}
|
|
5538
|
+
} catch {
|
|
5539
|
+
} finally {
|
|
5540
|
+
await tempTarget.close().catch(() => {
|
|
5541
|
+
});
|
|
5542
|
+
if (s.config.env) {
|
|
5543
|
+
for (const key of Object.keys(s.config.env)) {
|
|
5544
|
+
if (originalEnv[key] === void 0) {
|
|
5545
|
+
delete process.env[key];
|
|
5546
|
+
} else {
|
|
5547
|
+
process.env[key] = originalEnv[key];
|
|
5548
|
+
}
|
|
5549
|
+
}
|
|
5550
|
+
}
|
|
5551
|
+
}
|
|
5552
|
+
}
|
|
5553
|
+
if (matchResults.length === 0) {
|
|
5554
|
+
return {
|
|
5555
|
+
content: [
|
|
5556
|
+
{
|
|
5557
|
+
type: "text",
|
|
5558
|
+
text: `No matches found for query "${query}".`
|
|
5559
|
+
}
|
|
5560
|
+
]
|
|
5561
|
+
};
|
|
5562
|
+
}
|
|
5563
|
+
return {
|
|
5564
|
+
content: [
|
|
5565
|
+
{
|
|
5566
|
+
type: "text",
|
|
5567
|
+
text: `Search results for "${query}":
|
|
5568
|
+
|
|
5569
|
+
${JSON.stringify(matchResults, null, 2)}`
|
|
5570
|
+
}
|
|
5571
|
+
]
|
|
5572
|
+
};
|
|
5573
|
+
} catch (err) {
|
|
5574
|
+
return {
|
|
5575
|
+
content: [{ type: "text", text: `Error searching servers: ${err.message}` }],
|
|
5576
|
+
isError: true
|
|
5577
|
+
};
|
|
5578
|
+
}
|
|
5579
|
+
}
|
|
5580
|
+
);
|
|
4353
5581
|
const transport = new StdioServerTransport();
|
|
4354
5582
|
mcpServer.server.onclose = async () => {
|
|
4355
5583
|
if (target) {
|
|
@@ -4364,7 +5592,8 @@ ${JSON.stringify(meta)}`
|
|
|
4364
5592
|
|
|
4365
5593
|
// src/index.ts
|
|
4366
5594
|
function requireTargetCommand(targetCommand, subcommandUsage) {
|
|
4367
|
-
|
|
5595
|
+
const target = activeTargetCommand ?? targetCommand;
|
|
5596
|
+
if (!target || target.length === 0) {
|
|
4368
5597
|
process.stderr.write(`Error: Target server command must be separated by '--'.
|
|
4369
5598
|
`);
|
|
4370
5599
|
process.stderr.write(`This avoids option parsing conflicts.
|
|
@@ -4372,19 +5601,19 @@ function requireTargetCommand(targetCommand, subcommandUsage) {
|
|
|
4372
5601
|
`);
|
|
4373
5602
|
process.stderr.write(`Usage: ${subcommandUsage}
|
|
4374
5603
|
`);
|
|
4375
|
-
process.exit(
|
|
5604
|
+
process.exit(64);
|
|
4376
5605
|
}
|
|
4377
|
-
return
|
|
5606
|
+
return target;
|
|
4378
5607
|
}
|
|
4379
|
-
var SESSION_DIR =
|
|
5608
|
+
var SESSION_DIR = join6(tmpdir3(), "run-mcp", "sessions");
|
|
4380
5609
|
function getSessionPath(name) {
|
|
4381
|
-
return
|
|
5610
|
+
return join6(SESSION_DIR, `${name}.json`);
|
|
4382
5611
|
}
|
|
4383
5612
|
async function getSession(name) {
|
|
4384
5613
|
const path2 = getSessionPath(name);
|
|
4385
|
-
if (!
|
|
5614
|
+
if (!existsSync6(path2)) return null;
|
|
4386
5615
|
try {
|
|
4387
|
-
const data = await
|
|
5616
|
+
const data = await readFile5(path2, "utf8");
|
|
4388
5617
|
const parsed = JSON.parse(data);
|
|
4389
5618
|
try {
|
|
4390
5619
|
process.kill(parsed.pid, 0);
|
|
@@ -4399,7 +5628,7 @@ async function getSession(name) {
|
|
|
4399
5628
|
}
|
|
4400
5629
|
}
|
|
4401
5630
|
function sendDaemonRequest(port, request) {
|
|
4402
|
-
return new Promise((
|
|
5631
|
+
return new Promise((resolve3, reject) => {
|
|
4403
5632
|
const socket = createConnection({ port });
|
|
4404
5633
|
let buffer = "";
|
|
4405
5634
|
socket.on("connect", () => {
|
|
@@ -4414,7 +5643,7 @@ function sendDaemonRequest(port, request) {
|
|
|
4414
5643
|
if (parsed.error) {
|
|
4415
5644
|
reject(new Error(parsed.error.message));
|
|
4416
5645
|
} else {
|
|
4417
|
-
|
|
5646
|
+
resolve3(parsed.result);
|
|
4418
5647
|
}
|
|
4419
5648
|
} catch (err) {
|
|
4420
5649
|
reject(new Error(`Failed to parse daemon response: ${err}`));
|
|
@@ -4436,11 +5665,16 @@ async function handleHeadlessSession(sessionName, targetCommand, operation, opts
|
|
|
4436
5665
|
`);
|
|
4437
5666
|
process.stderr.write(`Usage: ${subcommandUsage}
|
|
4438
5667
|
`);
|
|
4439
|
-
process.exit(
|
|
5668
|
+
process.exit(64);
|
|
4440
5669
|
}
|
|
4441
5670
|
const target = activeTargetCommand;
|
|
4442
|
-
const binPath =
|
|
4443
|
-
const
|
|
5671
|
+
const binPath = resolve2(import.meta.dirname, "./index.js");
|
|
5672
|
+
const daemonArgs = ["daemon", sessionName];
|
|
5673
|
+
if (opts.sandbox) {
|
|
5674
|
+
daemonArgs.push("--sandbox", opts.sandbox);
|
|
5675
|
+
}
|
|
5676
|
+
daemonArgs.push(...target);
|
|
5677
|
+
const daemonProcess = spawn("node", [binPath, ...daemonArgs], {
|
|
4444
5678
|
detached: true,
|
|
4445
5679
|
stdio: "ignore"
|
|
4446
5680
|
});
|
|
@@ -4449,7 +5683,7 @@ async function handleHeadlessSession(sessionName, targetCommand, operation, opts
|
|
|
4449
5683
|
while (attempts < 50) {
|
|
4450
5684
|
session = await getSession(sessionName);
|
|
4451
5685
|
if (session) break;
|
|
4452
|
-
await new Promise((
|
|
5686
|
+
await new Promise((resolve3) => setTimeout(resolve3, 100));
|
|
4453
5687
|
attempts++;
|
|
4454
5688
|
}
|
|
4455
5689
|
if (!session) {
|
|
@@ -4482,7 +5716,14 @@ function parseHeadlessOpts(opts) {
|
|
|
4482
5716
|
timeoutMs: opts.timeout ? Number.parseInt(opts.timeout, 10) : void 0,
|
|
4483
5717
|
raw: opts.raw,
|
|
4484
5718
|
showStderr: opts.showStderr,
|
|
4485
|
-
mediaThresholdKb: opts.mediaThreshold ? Number.parseInt(opts.mediaThreshold, 10) : void 0
|
|
5719
|
+
mediaThresholdKb: opts.mediaThreshold ? Number.parseInt(opts.mediaThreshold, 10) : void 0,
|
|
5720
|
+
sandbox: opts.sandbox,
|
|
5721
|
+
allowRead: opts.allowRead,
|
|
5722
|
+
allowWrite: opts.allowWrite,
|
|
5723
|
+
allowNet: opts.allowNet,
|
|
5724
|
+
denyRead: opts.denyRead,
|
|
5725
|
+
denyWrite: opts.denyWrite,
|
|
5726
|
+
denyNet: opts.denyNet
|
|
4486
5727
|
};
|
|
4487
5728
|
}
|
|
4488
5729
|
var activeTargetCommand;
|
|
@@ -4496,7 +5737,7 @@ program.enablePositionalOptions();
|
|
|
4496
5737
|
program.command("call").argument("<tool>", "Tool name to call").argument("[json_args]", "JSON arguments for the tool").argument("[target_command...]", "Target server command (after --)").description("Call a tool on a target MCP server and print the result as JSON").option("-o, --out-dir <path>", "Output directory for saved media").option("-t, --timeout <ms>", "Timeout in milliseconds (default: 30000)").option(
|
|
4497
5738
|
"-m, --media-threshold <kb>",
|
|
4498
5739
|
"Media size threshold in KB to save to disk (0 to always save, -1 to keep inline)"
|
|
4499
|
-
).option("--raw", "Print the full result object including metadata").option("--show-stderr", "Stream target server stderr to process stderr").option("--session <name>", "Persistent session name").allowUnknownOption().action(
|
|
5740
|
+
).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(
|
|
4500
5741
|
async (tool, jsonArgs, targetCommand, opts) => {
|
|
4501
5742
|
const operation = { type: "call", tool, args: jsonArgs };
|
|
4502
5743
|
const parsedOpts = parseHeadlessOpts(opts);
|
|
@@ -4517,7 +5758,7 @@ program.command("call").argument("<tool>", "Tool name to call").argument("[json_
|
|
|
4517
5758
|
}
|
|
4518
5759
|
}
|
|
4519
5760
|
);
|
|
4520
|
-
program.command("list-tools").argument("[target_command...]", "Target server command (after --)").description("List all tools on a target MCP server as JSON").option("--show-stderr", "Stream target server stderr to process stderr").option("--session <name>", "Persistent session name").allowUnknownOption().action(async (targetCommand, opts) => {
|
|
5761
|
+
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) => {
|
|
4521
5762
|
const operation = { type: "list-tools" };
|
|
4522
5763
|
const parsedOpts = parseHeadlessOpts(opts);
|
|
4523
5764
|
if (opts.session) {
|
|
@@ -4536,7 +5777,7 @@ program.command("list-tools").argument("[target_command...]", "Target server com
|
|
|
4536
5777
|
await runHeadless(target, operation, parsedOpts);
|
|
4537
5778
|
}
|
|
4538
5779
|
});
|
|
4539
|
-
program.command("list-resources").argument("[target_command...]", "Target server command (after --)").description("List all resources on a target MCP server as JSON").option("--show-stderr", "Stream target server stderr to process stderr").option("--session <name>", "Persistent session name").allowUnknownOption().action(async (targetCommand, opts) => {
|
|
5780
|
+
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) => {
|
|
4540
5781
|
const operation = { type: "list-resources" };
|
|
4541
5782
|
const parsedOpts = parseHeadlessOpts(opts);
|
|
4542
5783
|
if (opts.session) {
|
|
@@ -4555,7 +5796,7 @@ program.command("list-resources").argument("[target_command...]", "Target server
|
|
|
4555
5796
|
await runHeadless(target, operation, parsedOpts);
|
|
4556
5797
|
}
|
|
4557
5798
|
});
|
|
4558
|
-
program.command("list-prompts").argument("[target_command...]", "Target server command (after --)").description("List all prompts on a target MCP server as JSON").option("--show-stderr", "Stream target server stderr to process stderr").option("--session <name>", "Persistent session name").allowUnknownOption().action(async (targetCommand, opts) => {
|
|
5799
|
+
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) => {
|
|
4559
5800
|
const operation = { type: "list-prompts" };
|
|
4560
5801
|
const parsedOpts = parseHeadlessOpts(opts);
|
|
4561
5802
|
if (opts.session) {
|
|
@@ -4577,7 +5818,7 @@ program.command("list-prompts").argument("[target_command...]", "Target server c
|
|
|
4577
5818
|
program.command("read").argument("<uri>", "Resource URI to read").argument("[target_command...]", "Target server command (after --)").description("Read a resource by URI from a target MCP server").option(
|
|
4578
5819
|
"-m, --media-threshold <kb>",
|
|
4579
5820
|
"Media size threshold in KB to save to disk (0 to always save, -1 to keep inline)"
|
|
4580
|
-
).option("--show-stderr", "Stream target server stderr to process stderr").option("--session <name>", "Persistent session name").allowUnknownOption().action(async (uri, targetCommand, opts) => {
|
|
5821
|
+
).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) => {
|
|
4581
5822
|
const operation = { type: "read", uri };
|
|
4582
5823
|
const parsedOpts = parseHeadlessOpts(opts);
|
|
4583
5824
|
if (opts.session) {
|
|
@@ -4596,7 +5837,7 @@ program.command("read").argument("<uri>", "Resource URI to read").argument("[tar
|
|
|
4596
5837
|
await runHeadless(target, operation, parsedOpts);
|
|
4597
5838
|
}
|
|
4598
5839
|
});
|
|
4599
|
-
program.command("describe").argument("<tool>", "Tool name to describe").argument("[target_command...]", "Target server command (after --)").description("Print a tool's full schema as JSON").option("--show-stderr", "Stream target server stderr to process stderr").option("--session <name>", "Persistent session name").allowUnknownOption().action(async (tool, targetCommand, opts) => {
|
|
5840
|
+
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) => {
|
|
4600
5841
|
const operation = { type: "describe", tool };
|
|
4601
5842
|
const parsedOpts = parseHeadlessOpts(opts);
|
|
4602
5843
|
if (opts.session) {
|
|
@@ -4618,7 +5859,7 @@ program.command("describe").argument("<tool>", "Tool name to describe").argument
|
|
|
4618
5859
|
program.command("get-prompt").argument("<name>", "Prompt name").argument("[json_args]", "JSON arguments for the prompt").argument("[target_command...]", "Target server command (after --)").description("Get a prompt with optional arguments from a target MCP server").option(
|
|
4619
5860
|
"-m, --media-threshold <kb>",
|
|
4620
5861
|
"Media size threshold in KB to save to disk (0 to always save, -1 to keep inline)"
|
|
4621
|
-
).option("--show-stderr", "Stream target server stderr to process stderr").option("--session <name>", "Persistent session name").allowUnknownOption().action(
|
|
5862
|
+
).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(
|
|
4622
5863
|
async (name, jsonArgs, targetCommand, opts) => {
|
|
4623
5864
|
const operation = { type: "get-prompt", name, args: jsonArgs };
|
|
4624
5865
|
const parsedOpts = parseHeadlessOpts(opts);
|
|
@@ -4639,17 +5880,19 @@ program.command("get-prompt").argument("<name>", "Prompt name").argument("[json_
|
|
|
4639
5880
|
}
|
|
4640
5881
|
}
|
|
4641
5882
|
);
|
|
4642
|
-
program.command("daemon").argument("<session_name>", "Session name").argument("[target_command...]", "Target server command").description("Start run-mcp in background session daemon mode").allowUnknownOption().action(async (sessionName, targetCommand) => {
|
|
5883
|
+
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) => {
|
|
4643
5884
|
const targetCmd = activeTargetCommand ?? targetCommand;
|
|
4644
5885
|
if (!targetCmd || targetCmd.length === 0) {
|
|
4645
5886
|
process.stderr.write("Error: No target command provided for daemon.\n");
|
|
4646
|
-
process.exit(
|
|
5887
|
+
process.exit(64);
|
|
4647
5888
|
}
|
|
4648
5889
|
const server = createServer();
|
|
4649
5890
|
server.listen(0, "127.0.0.1", async () => {
|
|
4650
5891
|
const addr = server.address();
|
|
4651
5892
|
const port = addr.port;
|
|
4652
|
-
const target = new TargetManager(targetCmd[0], targetCmd.slice(1)
|
|
5893
|
+
const target = new TargetManager(targetCmd[0], targetCmd.slice(1), {
|
|
5894
|
+
sandbox: opts.sandbox
|
|
5895
|
+
});
|
|
4653
5896
|
const interceptor = new ResponseInterceptor();
|
|
4654
5897
|
try {
|
|
4655
5898
|
await target.connect();
|
|
@@ -4658,8 +5901,8 @@ program.command("daemon").argument("<session_name>", "Session name").argument("[
|
|
|
4658
5901
|
`);
|
|
4659
5902
|
process.exit(1);
|
|
4660
5903
|
}
|
|
4661
|
-
await
|
|
4662
|
-
await
|
|
5904
|
+
await mkdir3(SESSION_DIR, { recursive: true });
|
|
5905
|
+
await writeFile3(
|
|
4663
5906
|
getSessionPath(sessionName),
|
|
4664
5907
|
JSON.stringify({ port, pid: process.pid }),
|
|
4665
5908
|
"utf8"
|
|
@@ -4676,12 +5919,12 @@ program.command("daemon").argument("<session_name>", "Session name").argument("[
|
|
|
4676
5919
|
try {
|
|
4677
5920
|
const req = JSON.parse(trimmed);
|
|
4678
5921
|
if (req.method === "execute") {
|
|
4679
|
-
const { operation, opts } = req.params;
|
|
5922
|
+
const { operation, opts: opts2 } = req.params;
|
|
4680
5923
|
const { result, hasError } = await executeOperation(
|
|
4681
5924
|
target,
|
|
4682
5925
|
interceptor,
|
|
4683
5926
|
operation,
|
|
4684
|
-
|
|
5927
|
+
opts2
|
|
4685
5928
|
);
|
|
4686
5929
|
socket.write(
|
|
4687
5930
|
JSON.stringify({ jsonrpc: "2.0", result: { result, hasError }, id: req.id }) + "\n"
|
|
@@ -4732,7 +5975,7 @@ program.command("close-session").argument("<session_name>", "Session name").desc
|
|
|
4732
5975
|
}
|
|
4733
5976
|
}
|
|
4734
5977
|
});
|
|
4735
|
-
program.name("run-mcp").description("A smart interactive REPL and live test harness for MCP servers").version("1.
|
|
5978
|
+
program.name("run-mcp").description("A smart interactive REPL and live test harness for MCP servers").version("1.7.0").passThroughOptions().allowUnknownOption().argument(
|
|
4736
5979
|
"[target_command...]",
|
|
4737
5980
|
"Command to spawn the target MCP server (starts REPL if provided, Agent server otherwise)"
|
|
4738
5981
|
).option("-o, --out-dir <path>", "Directory to save intercepted images and audio").option(
|
|
@@ -4744,7 +5987,10 @@ program.name("run-mcp").description("A smart interactive REPL and live test harn
|
|
|
4744
5987
|
).option(
|
|
4745
5988
|
"-m, --media-threshold <kb>",
|
|
4746
5989
|
"Media size threshold in KB to save to disk (0 to always save, -1 to keep inline)"
|
|
4747
|
-
).option("--mcp", "Force start Agent Server mode even if run interactively without arguments").option("-s, --script <file>", "Read commands from a file instead of stdin (REPL Mode only)").
|
|
5990
|
+
).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(
|
|
5991
|
+
"--open-media",
|
|
5992
|
+
"Automatically open intercepted images and audio files using the host OS viewer"
|
|
5993
|
+
).option("--sandbox <mode>", "Sandbox execution mode: auto, docker, native, audit, none", "none").addHelpText(
|
|
4748
5994
|
"after",
|
|
4749
5995
|
`
|
|
4750
5996
|
Examples:
|
|
@@ -4781,6 +6027,8 @@ Agent Mode Tools:
|
|
|
4781
6027
|
disconnect_from_mcp \u2192 Tear down and reconnect after changes
|
|
4782
6028
|
mcp_server_status \u2192 Check connection status
|
|
4783
6029
|
get_mcp_server_stderr \u2192 View target server stderr output
|
|
6030
|
+
validate_mcp_server \u2192 Validate an MCP server command and collect diagnostics
|
|
6031
|
+
search_all_local_mcp_servers \u2192 Scan and search all local MCP servers for a query
|
|
4784
6032
|
|
|
4785
6033
|
REPL Mode Commands (once connected):
|
|
4786
6034
|
tools/list List all available tools
|
|
@@ -4809,18 +6057,20 @@ REPL Mode Commands (once connected):
|
|
|
4809
6057
|
Shortcuts: tl td tc ts rl rr rt rs ru pl pg (see help for details)`
|
|
4810
6058
|
).action(
|
|
4811
6059
|
async (targetCommand, opts) => {
|
|
4812
|
-
|
|
4813
|
-
process.stderr.write(
|
|
4814
|
-
"Error: Target server command must be separated by '--'.\nThis avoids argument parsing ambiguity.\n\nExample:\n run-mcp -- node my-server.js\n run-mcp -s script.txt -- node my-server.js\n"
|
|
4815
|
-
);
|
|
4816
|
-
process.exit(1);
|
|
4817
|
-
}
|
|
4818
|
-
const target = activeTargetCommand ?? [];
|
|
6060
|
+
const target = activeTargetCommand ?? targetCommand ?? [];
|
|
4819
6061
|
if (target && target.length > 0) {
|
|
4820
6062
|
await startRepl(target, {
|
|
4821
6063
|
script: opts.script,
|
|
4822
6064
|
outDir: opts.outDir,
|
|
4823
|
-
mediaThresholdKb: opts.mediaThreshold ? Number.parseInt(opts.mediaThreshold, 10) : void 0
|
|
6065
|
+
mediaThresholdKb: opts.mediaThreshold ? Number.parseInt(opts.mediaThreshold, 10) : void 0,
|
|
6066
|
+
openMedia: opts.openMedia,
|
|
6067
|
+
sandbox: opts.sandbox,
|
|
6068
|
+
allowRead: opts.allowRead,
|
|
6069
|
+
allowWrite: opts.allowWrite,
|
|
6070
|
+
allowNet: opts.allowNet,
|
|
6071
|
+
denyRead: opts.denyRead,
|
|
6072
|
+
denyWrite: opts.denyWrite,
|
|
6073
|
+
denyNet: opts.denyNet
|
|
4824
6074
|
});
|
|
4825
6075
|
} else {
|
|
4826
6076
|
if (opts.mcp || !process.stdin.isTTY) {
|
|
@@ -4828,7 +6078,14 @@ Shortcuts: tl td tc ts rl rr rt rs ru pl pg (see help for details)`
|
|
|
4828
6078
|
outDir: opts.outDir,
|
|
4829
6079
|
timeoutMs: opts.timeout ? Number.parseInt(opts.timeout, 10) : void 0,
|
|
4830
6080
|
maxTextLength: opts.maxText ? Number.parseInt(opts.maxText, 10) : void 0,
|
|
4831
|
-
mediaThresholdKb: opts.mediaThreshold ? Number.parseInt(opts.mediaThreshold, 10) : void 0
|
|
6081
|
+
mediaThresholdKb: opts.mediaThreshold ? Number.parseInt(opts.mediaThreshold, 10) : void 0,
|
|
6082
|
+
sandbox: opts.sandbox,
|
|
6083
|
+
allowRead: opts.allowRead,
|
|
6084
|
+
allowWrite: opts.allowWrite,
|
|
6085
|
+
allowNet: opts.allowNet,
|
|
6086
|
+
denyRead: opts.denyRead,
|
|
6087
|
+
denyWrite: opts.denyWrite,
|
|
6088
|
+
denyNet: opts.denyNet
|
|
4832
6089
|
});
|
|
4833
6090
|
} else {
|
|
4834
6091
|
const selected = await pickDiscoveredServer();
|
|
@@ -4842,10 +6099,26 @@ Shortcuts: tl td tc ts rl rr rt rs ru pl pg (see help for details)`
|
|
|
4842
6099
|
await startRepl([selected.config.command, ...selected.config.args || []], {
|
|
4843
6100
|
script: opts.script,
|
|
4844
6101
|
outDir: opts.outDir,
|
|
4845
|
-
mediaThresholdKb: opts.mediaThreshold ? Number.parseInt(opts.mediaThreshold, 10) : void 0
|
|
6102
|
+
mediaThresholdKb: opts.mediaThreshold ? Number.parseInt(opts.mediaThreshold, 10) : void 0,
|
|
6103
|
+
openMedia: opts.openMedia,
|
|
6104
|
+
sandbox: opts.sandbox,
|
|
6105
|
+
allowRead: opts.allowRead,
|
|
6106
|
+
allowWrite: opts.allowWrite,
|
|
6107
|
+
allowNet: opts.allowNet,
|
|
6108
|
+
denyRead: opts.denyRead,
|
|
6109
|
+
denyWrite: opts.denyWrite,
|
|
6110
|
+
denyNet: opts.denyNet
|
|
4846
6111
|
});
|
|
4847
6112
|
}
|
|
4848
6113
|
}
|
|
4849
6114
|
}
|
|
4850
6115
|
);
|
|
6116
|
+
for (const cmd of [program, ...program.commands]) {
|
|
6117
|
+
if (cmd.options.some((o) => o.long === "--sandbox")) {
|
|
6118
|
+
cmd.option("--allow-read <paths...>", "Paths to allow reading under the sandbox").option("--allow-write <paths...>", "Paths to allow writing under the sandbox").option(
|
|
6119
|
+
"--allow-net <domains...>",
|
|
6120
|
+
"Network domains to allow connecting to under the sandbox"
|
|
6121
|
+
).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");
|
|
6122
|
+
}
|
|
6123
|
+
}
|
|
4851
6124
|
program.parse(argvToParse);
|