run-mcp 1.6.3 → 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 +2081 -854
- 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,15 +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 = [
|
|
1623
2414
|
"menu",
|
|
1624
2415
|
"explore",
|
|
1625
2416
|
"interactive",
|
|
2417
|
+
"view",
|
|
1626
2418
|
"tools/list",
|
|
1627
2419
|
"tools/describe",
|
|
1628
2420
|
"tools/call",
|
|
@@ -1664,48 +2456,259 @@ var KNOWN_COMMANDS = [
|
|
|
1664
2456
|
"pl",
|
|
1665
2457
|
"pg"
|
|
1666
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
|
+
}
|
|
1667
2479
|
var cachedToolNames = [];
|
|
2480
|
+
function setCachedToolNames(names) {
|
|
2481
|
+
cachedToolNames = names;
|
|
2482
|
+
}
|
|
1668
2483
|
var cachedResourceUris = [];
|
|
2484
|
+
function setCachedResourceUris(uris) {
|
|
2485
|
+
cachedResourceUris = uris;
|
|
2486
|
+
}
|
|
1669
2487
|
var cachedPromptNames = [];
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
let commands = [...KNOWN_COMMANDS];
|
|
1673
|
-
if (!activeCapabilities?.resources) {
|
|
1674
|
-
commands = commands.filter(
|
|
1675
|
-
(c) => !c.startsWith("resources/") && !["rl", "rr", "rt", "rs", "ru"].includes(c)
|
|
1676
|
-
);
|
|
1677
|
-
}
|
|
1678
|
-
if (!activeCapabilities?.prompts) {
|
|
1679
|
-
commands = commands.filter((c) => !c.startsWith("prompts/") && !["pl", "pg"].includes(c));
|
|
1680
|
-
}
|
|
1681
|
-
return commands;
|
|
2488
|
+
function setCachedPromptNames(names) {
|
|
2489
|
+
cachedPromptNames = names;
|
|
1682
2490
|
}
|
|
1683
|
-
|
|
1684
|
-
|
|
2491
|
+
var activeCapabilities = null;
|
|
2492
|
+
function setActiveCapabilities(caps) {
|
|
1685
2493
|
activeCapabilities = caps;
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
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)) {
|
|
1692
2519
|
try {
|
|
1693
|
-
const
|
|
1694
|
-
|
|
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);
|
|
1695
2524
|
} catch {
|
|
1696
2525
|
}
|
|
1697
2526
|
}
|
|
1698
|
-
|
|
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)) {
|
|
1699
2544
|
try {
|
|
1700
|
-
const
|
|
1701
|
-
|
|
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
|
+
}
|
|
1702
2551
|
} catch {
|
|
1703
2552
|
}
|
|
1704
2553
|
}
|
|
1705
2554
|
}
|
|
1706
|
-
|
|
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
|
|
1707
2698
|
function resetTabCycle() {
|
|
1708
|
-
|
|
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;
|
|
1709
2712
|
}
|
|
1710
2713
|
function computeMatches(line) {
|
|
1711
2714
|
const expanded = resolveAlias(line);
|
|
@@ -1734,8 +2737,12 @@ var completer = (line) => {
|
|
|
1734
2737
|
if (tabCycleState) {
|
|
1735
2738
|
const inCycle = line === tabCycleState.original || tabCycleState.matches.includes(line);
|
|
1736
2739
|
if (inCycle) {
|
|
1737
|
-
|
|
1738
|
-
|
|
2740
|
+
const nextIndex = (tabCycleState.index + 1) % tabCycleState.matches.length;
|
|
2741
|
+
setTabCycleState({
|
|
2742
|
+
...tabCycleState,
|
|
2743
|
+
index: nextIndex
|
|
2744
|
+
});
|
|
2745
|
+
const next = tabCycleState.matches[nextIndex];
|
|
1739
2746
|
setImmediate(() => {
|
|
1740
2747
|
if (activeRl) {
|
|
1741
2748
|
activeRl.line = next;
|
|
@@ -1745,421 +2752,70 @@ var completer = (line) => {
|
|
|
1745
2752
|
});
|
|
1746
2753
|
return [[], ""];
|
|
1747
2754
|
}
|
|
1748
|
-
|
|
2755
|
+
setTabCycleState(null);
|
|
1749
2756
|
}
|
|
1750
2757
|
const [matches, matchLine] = computeMatches(line);
|
|
1751
2758
|
if (matches.length > 1) {
|
|
1752
|
-
|
|
2759
|
+
setTabCycleState({ matches, index: -1, original: line });
|
|
1753
2760
|
}
|
|
1754
|
-
return [matches, matchLine];
|
|
1755
|
-
};
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
}
|
|
1770
|
-
var activeRl = null;
|
|
1771
|
-
var isScriptMode = false;
|
|
1772
|
-
var globalPauseReadlineClose = false;
|
|
1773
|
-
var deferNextPrompt = false;
|
|
1774
|
-
async function withSuspendedReadline(target, interceptor, fn) {
|
|
1775
|
-
const wasActive = !!activeRl;
|
|
1776
|
-
if (wasActive) {
|
|
1777
|
-
globalPauseReadlineClose = true;
|
|
1778
|
-
activeRl.close();
|
|
1779
|
-
activeRl = null;
|
|
1780
|
-
}
|
|
1781
|
-
try {
|
|
1782
|
-
return await fn();
|
|
1783
|
-
} finally {
|
|
1784
|
-
if (wasActive) {
|
|
1785
|
-
globalPauseReadlineClose = false;
|
|
1786
|
-
if (!isScriptMode) {
|
|
1787
|
-
deferNextPrompt = true;
|
|
1788
|
-
startReadlineLoop(target, interceptor);
|
|
1789
|
-
}
|
|
1790
|
-
}
|
|
1791
|
-
}
|
|
1792
|
-
}
|
|
1793
|
-
function getPrompt(target) {
|
|
1794
|
-
if (target.connected) return `${pc2.green("\u2713")}${pc2.cyan("> ")}`;
|
|
1795
|
-
return `${pc2.red("\u2717")}${pc2.cyan("> ")}`;
|
|
1796
|
-
}
|
|
1797
|
-
function printBanner(serverName, serverVersion, toolCount, resourceCount, promptCount) {
|
|
1798
|
-
const parts = [];
|
|
1799
|
-
parts.push(`${pc2.bold(toolCount.toString())} tools`);
|
|
1800
|
-
if (resourceCount > 0) parts.push(`${pc2.bold(resourceCount.toString())} resources`);
|
|
1801
|
-
if (promptCount > 0) parts.push(`${pc2.bold(promptCount.toString())} prompts`);
|
|
1802
|
-
const baseTitle = serverVersion ? `${serverName} ${pc2.dim(`v${serverVersion}`)}` : serverName;
|
|
1803
|
-
const isAgentHarness = serverName === "run-mcp";
|
|
1804
|
-
const title = isAgentHarness ? `${baseTitle} ${pc2.bgBlue(pc2.white(" AGENT HARNESS "))}` : baseTitle;
|
|
1805
|
-
const BOX_WIDTH = 53;
|
|
1806
|
-
const padLine = (content) => {
|
|
1807
|
-
const visible = stripAnsi(content).length;
|
|
1808
|
-
const padding = Math.max(0, BOX_WIDTH - visible);
|
|
1809
|
-
return `${pc2.cyan(" \u2502")}${content}${"".padEnd(padding)}${pc2.cyan("\u2502")}`;
|
|
1810
|
-
};
|
|
1811
|
-
const partsStr = ` ${parts.join(" \u2022 ")}`;
|
|
1812
|
-
console.log();
|
|
1813
|
-
console.log(pc2.cyan(` \u250C${"\u2500".repeat(BOX_WIDTH)}\u2510`));
|
|
1814
|
-
console.log(padLine(` ${title}`));
|
|
1815
|
-
console.log(padLine(partsStr));
|
|
1816
|
-
console.log(pc2.cyan(` \u251C${"\u2500".repeat(BOX_WIDTH)}\u2524`));
|
|
1817
|
-
console.log(padLine(" Quick start:"));
|
|
1818
|
-
console.log(padLine(` ${pc2.green("tools/list")} See all tools`));
|
|
1819
|
-
console.log(padLine(` ${pc2.green("tools/call")} ${pc2.dim("<name>")} Call a tool`));
|
|
1820
|
-
console.log(padLine(` ${pc2.green("help")} All commands`));
|
|
1821
|
-
console.log(padLine(""));
|
|
1822
|
-
console.log(padLine(pc2.dim(" Tab completion is active. Start typing to explore.")));
|
|
1823
|
-
console.log(pc2.cyan(` \u2514${"\u2500".repeat(BOX_WIDTH)}\u2518`));
|
|
1824
|
-
console.log();
|
|
1825
|
-
}
|
|
1826
|
-
function stripAnsi(str) {
|
|
1827
|
-
return str.replace(/\x1b\[[0-9;]*m/g, "");
|
|
1828
|
-
}
|
|
1829
|
-
async function startRepl(targetCommand, opts) {
|
|
1830
|
-
const [command, ...args] = targetCommand;
|
|
1831
|
-
const target = new TargetManager(command, args);
|
|
1832
|
-
const interceptor = new ResponseInterceptor({
|
|
1833
|
-
outDir: opts.outDir,
|
|
1834
|
-
mediaThresholdKb: opts.mediaThresholdKb
|
|
1835
|
-
});
|
|
1836
|
-
isScriptMode = !!opts.script;
|
|
1837
|
-
target.on("stderr", (text) => {
|
|
1838
|
-
for (const line of text.split("\n")) {
|
|
1839
|
-
console.error(pc2.dim(`[server] ${line}`));
|
|
1840
|
-
}
|
|
1841
|
-
});
|
|
1842
|
-
console.log(pc2.cyan("\u27F3 Connecting to target MCP server..."));
|
|
1843
|
-
console.log(pc2.dim(` Command: ${targetCommand.join(" ")}`));
|
|
1844
|
-
try {
|
|
1845
|
-
await target.connect();
|
|
1846
|
-
} catch (err) {
|
|
1847
|
-
const msg = err.message ?? String(err);
|
|
1848
|
-
if (msg.includes("ENOENT") || msg.includes("spawn")) {
|
|
1849
|
-
console.error(pc2.red(`\u2717 Failed to start server: command "${command}" not found.`));
|
|
1850
|
-
console.error(pc2.dim(` Check that "${command}" is installed and in your PATH.`));
|
|
1851
|
-
} else {
|
|
1852
|
-
console.error(pc2.red(`\u2717 Failed to connect: ${msg}`));
|
|
1853
|
-
console.error(pc2.dim(` Check that the target command starts a valid MCP server on stdio.`));
|
|
1854
|
-
}
|
|
1855
|
-
process.exit(1);
|
|
1856
|
-
}
|
|
1857
|
-
const status = target.getStatus();
|
|
1858
|
-
console.log(pc2.green(`\u2713 Connected (PID: ${status.pid})`));
|
|
1859
|
-
if (!isScriptMode) {
|
|
1860
|
-
target.enableAutoReconnect();
|
|
1861
|
-
target.on(
|
|
1862
|
-
"reconnecting",
|
|
1863
|
-
({ attempt, maxAttempts }) => {
|
|
1864
|
-
console.log(
|
|
1865
|
-
pc2.yellow(`
|
|
1866
|
-
\u27F3 Server disconnected. Reconnecting (${attempt}/${maxAttempts})...`)
|
|
1867
|
-
);
|
|
1868
|
-
}
|
|
1869
|
-
);
|
|
1870
|
-
target.on("reconnected", async ({ attempt }) => {
|
|
1871
|
-
const s = target.getStatus();
|
|
1872
|
-
console.log(pc2.green(`\u2713 Reconnected (PID: ${s.pid}, attempt ${attempt})`));
|
|
1873
|
-
await refreshCaches(target);
|
|
1874
|
-
});
|
|
1875
|
-
target.on("reconnect_failed", ({ reason, message }) => {
|
|
1876
|
-
console.error(pc2.red(`\u2717 ${message}`));
|
|
1877
|
-
if (reason === "max_retries") {
|
|
1878
|
-
console.log(
|
|
1879
|
-
pc2.dim(" Use 'exit' to quit or wait for the server to be fixed and restart manually.")
|
|
1880
|
-
);
|
|
1881
|
-
}
|
|
1882
|
-
});
|
|
1883
|
-
target.on("notification", (notification) => {
|
|
1884
|
-
const method = notification.method;
|
|
1885
|
-
if (method === "notifications/message") {
|
|
1886
|
-
const lvl = notification.params?.level ?? "info";
|
|
1887
|
-
const data = notification.params?.data ?? "";
|
|
1888
|
-
const text = typeof data === "string" ? data : JSON.stringify(data);
|
|
1889
|
-
console.log(pc2.dim(`
|
|
1890
|
-
[${lvl}] ${text}`));
|
|
1891
|
-
} else if (method === "notifications/tools/list_changed") {
|
|
1892
|
-
console.log(pc2.yellow("\n \u27F3 Server tools changed. Run tools/list to see updates."));
|
|
1893
|
-
refreshCaches(target).catch(() => {
|
|
1894
|
-
});
|
|
1895
|
-
} else if (method === "notifications/resources/list_changed") {
|
|
1896
|
-
console.log(pc2.yellow("\n \u27F3 Server resources changed. Run resources/list to see."));
|
|
1897
|
-
refreshCaches(target).catch(() => {
|
|
1898
|
-
});
|
|
1899
|
-
} else if (method === "notifications/resources/updated") {
|
|
1900
|
-
const uri = notification.params?.uri ?? "unknown";
|
|
1901
|
-
console.log(pc2.yellow(`
|
|
1902
|
-
\u27F3 Resource updated: ${uri}`));
|
|
1903
|
-
} else if (method === "notifications/prompts/list_changed") {
|
|
1904
|
-
console.log(pc2.yellow("\n \u27F3 Server prompts changed. Run prompts/list to see."));
|
|
1905
|
-
refreshCaches(target).catch(() => {
|
|
1906
|
-
});
|
|
1907
|
-
}
|
|
1908
|
-
});
|
|
1909
|
-
target.on("sampling_request", async ({ request, respond, reject: rejectFn }) => {
|
|
1910
|
-
console.log(pc2.magenta("\n \u2554\u2550\u2550 Sampling Request \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550"));
|
|
1911
|
-
const messages = request?.messages ?? [];
|
|
1912
|
-
for (const msg of messages) {
|
|
1913
|
-
const role = msg.role === "user" ? pc2.blue("user") : pc2.magenta("assistant");
|
|
1914
|
-
const text = msg.content?.text ?? JSON.stringify(msg.content);
|
|
1915
|
-
console.log(pc2.magenta(` \u2551 ${role}: ${text}`));
|
|
1916
|
-
}
|
|
1917
|
-
console.log(pc2.magenta(" \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550"));
|
|
1918
|
-
if (activeRl) {
|
|
1919
|
-
try {
|
|
1920
|
-
const answer = await question(activeRl, ` ${pc2.bold("Approve? [y/N/text]:")} `);
|
|
1921
|
-
const trimmed = answer.trim().toLowerCase();
|
|
1922
|
-
if (trimmed === "y" || trimmed === "yes") {
|
|
1923
|
-
respond({
|
|
1924
|
-
model: "user-approved",
|
|
1925
|
-
role: "assistant",
|
|
1926
|
-
content: { type: "text", text: "Approved by user." }
|
|
1927
|
-
});
|
|
1928
|
-
} else if (trimmed === "n" || trimmed === "no" || trimmed === "") {
|
|
1929
|
-
rejectFn(new Error("Sampling request rejected by user"));
|
|
1930
|
-
} else {
|
|
1931
|
-
respond({
|
|
1932
|
-
model: "user-provided",
|
|
1933
|
-
role: "assistant",
|
|
1934
|
-
content: { type: "text", text: answer.trim() }
|
|
1935
|
-
});
|
|
1936
|
-
}
|
|
1937
|
-
} catch (err) {
|
|
1938
|
-
if (err instanceof AbortFlowError) {
|
|
1939
|
-
rejectFn(new Error("Sampling request rejected by user"));
|
|
1940
|
-
} else {
|
|
1941
|
-
throw err;
|
|
1942
|
-
}
|
|
1943
|
-
}
|
|
1944
|
-
} else {
|
|
1945
|
-
rejectFn(new Error("No interactive terminal available for sampling approval"));
|
|
1946
|
-
}
|
|
1947
|
-
});
|
|
1948
|
-
target.on("elicitation_request", async ({ request, respond, reject: rejectFn }) => {
|
|
1949
|
-
console.log(pc2.cyan("\n \u2554\u2550\u2550 Elicitation Request \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550"));
|
|
1950
|
-
console.log(pc2.cyan(` \u2551 ${request?.message ?? "Server requests input"}`));
|
|
1951
|
-
console.log(pc2.cyan(" \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550"));
|
|
1952
|
-
if (activeRl) {
|
|
1953
|
-
try {
|
|
1954
|
-
const answer = await question(
|
|
1955
|
-
activeRl,
|
|
1956
|
-
` ${pc2.bold("Your response (empty to decline):")} `
|
|
1957
|
-
);
|
|
1958
|
-
if (answer.trim() === "") {
|
|
1959
|
-
respond({ action: "decline" });
|
|
1960
|
-
} else {
|
|
1961
|
-
try {
|
|
1962
|
-
const parsed = JSON.parse(answer.trim());
|
|
1963
|
-
respond({ action: "accept", content: parsed });
|
|
1964
|
-
} catch {
|
|
1965
|
-
respond({ action: "accept", content: { value: answer.trim() } });
|
|
1966
|
-
}
|
|
1967
|
-
}
|
|
1968
|
-
} catch (err) {
|
|
1969
|
-
if (err instanceof AbortFlowError) {
|
|
1970
|
-
respond({ action: "decline" });
|
|
1971
|
-
} else {
|
|
1972
|
-
throw err;
|
|
1973
|
-
}
|
|
1974
|
-
}
|
|
1975
|
-
} else {
|
|
1976
|
-
rejectFn(new Error("No interactive terminal available for elicitation"));
|
|
1977
|
-
}
|
|
1978
|
-
});
|
|
1979
|
-
}
|
|
1980
|
-
let toolCount;
|
|
1981
|
-
let resourceCount = 0;
|
|
1982
|
-
let promptCount = 0;
|
|
1983
|
-
try {
|
|
1984
|
-
const { tools } = await target.listTools();
|
|
1985
|
-
toolCount = tools.length;
|
|
1986
|
-
const caps = target.getServerCapabilities() ?? {};
|
|
1987
|
-
if (caps.resources) {
|
|
1988
|
-
try {
|
|
1989
|
-
const { resources } = await target.listResources();
|
|
1990
|
-
resourceCount = resources.length;
|
|
1991
|
-
} catch {
|
|
1992
|
-
}
|
|
1993
|
-
}
|
|
1994
|
-
if (caps.prompts) {
|
|
1995
|
-
try {
|
|
1996
|
-
const { prompts } = await target.listPrompts();
|
|
1997
|
-
promptCount = prompts.length;
|
|
1998
|
-
} catch {
|
|
1999
|
-
}
|
|
2000
|
-
}
|
|
2001
|
-
const serverInfo = target.getServerVersion();
|
|
2002
|
-
const serverName = serverInfo?.name ?? "MCP Server";
|
|
2003
|
-
const serverVersion = serverInfo?.version ?? "";
|
|
2004
|
-
printBanner(serverName, serverVersion, toolCount, resourceCount, promptCount);
|
|
2005
|
-
if (toolCount >= 10) {
|
|
2006
|
-
const groups = groupToolsByPrefix(
|
|
2007
|
-
cachedToolNames.length > 0 ? cachedToolNames : tools.map((t) => t.name)
|
|
2008
|
-
);
|
|
2009
|
-
if (!groups.has("All")) {
|
|
2010
|
-
for (const [label, members] of groups) {
|
|
2011
|
-
console.log(` ${pc2.bold(label.padEnd(16))} ${pc2.dim(members.join(", "))}`);
|
|
2012
|
-
}
|
|
2013
|
-
console.log();
|
|
2014
|
-
}
|
|
2761
|
+
return [matches, matchLine];
|
|
2762
|
+
};
|
|
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 {
|
|
2015
2776
|
}
|
|
2016
|
-
} catch (err) {
|
|
2017
|
-
console.log(pc2.yellow(` Warning: Could not list tools: ${err.message}
|
|
2018
|
-
`));
|
|
2019
2777
|
}
|
|
2020
|
-
|
|
2021
|
-
|
|
2022
|
-
|
|
2023
|
-
|
|
2024
|
-
|
|
2025
|
-
for (const line of lines) {
|
|
2026
|
-
let trimmed = line.trim();
|
|
2027
|
-
if (!trimmed || trimmed.startsWith("#")) {
|
|
2028
|
-
if (trimmed === "# @expect-error") {
|
|
2029
|
-
expectError = true;
|
|
2030
|
-
}
|
|
2031
|
-
continue;
|
|
2032
|
-
}
|
|
2033
|
-
if (trimmed.endsWith("# @expect-error")) {
|
|
2034
|
-
expectError = true;
|
|
2035
|
-
trimmed = trimmed.replace(/\s*#\s*@expect-error$/, "");
|
|
2036
|
-
}
|
|
2037
|
-
const interpolated = interpolateString(trimmed, scriptContext);
|
|
2038
|
-
try {
|
|
2039
|
-
const res = await handleCommand(interpolated, target, interceptor);
|
|
2040
|
-
if (res !== void 0) {
|
|
2041
|
-
scriptContext.LAST = res;
|
|
2042
|
-
}
|
|
2043
|
-
const isErrorRes = res && typeof res === "object" && res.isError === true;
|
|
2044
|
-
if (expectError && !isErrorRes) {
|
|
2045
|
-
console.error(pc2.red(`\u2717 Expected an error but the command succeeded.`));
|
|
2046
|
-
await target.close();
|
|
2047
|
-
process.exit(1);
|
|
2048
|
-
}
|
|
2049
|
-
if (!expectError && isErrorRes) {
|
|
2050
|
-
console.error(pc2.red(`\u2717 Command failed unexpectedly.`));
|
|
2051
|
-
await target.close();
|
|
2052
|
-
process.exit(1);
|
|
2053
|
-
}
|
|
2054
|
-
if (expectError && isErrorRes) {
|
|
2055
|
-
console.log(pc2.yellow(` \u2713 Expected error caught: tool returned isError: true`));
|
|
2056
|
-
}
|
|
2057
|
-
} catch (err) {
|
|
2058
|
-
if (expectError) {
|
|
2059
|
-
console.log(pc2.yellow(` \u2713 Expected error caught: ${err.message}`));
|
|
2060
|
-
} else {
|
|
2061
|
-
if (err?.message?.includes("-32601") || err?.code === -32601) {
|
|
2062
|
-
let msg = "Server does not support this feature (Method not found)";
|
|
2063
|
-
if (trimmed.startsWith("prompts/")) msg = "This server does not have any prompts.";
|
|
2064
|
-
else if (trimmed.startsWith("resources/"))
|
|
2065
|
-
msg = "This server does not have any resources.";
|
|
2066
|
-
else if (trimmed.startsWith("tools/")) msg = "This server does not have any tools.";
|
|
2067
|
-
console.log(pc2.yellow(` ${msg}`));
|
|
2068
|
-
} else {
|
|
2069
|
-
console.error(pc2.red(`\u2717 Error: ${err.message}`));
|
|
2070
|
-
}
|
|
2071
|
-
console.log(pc2.dim("\nShutting down..."));
|
|
2072
|
-
await target.close();
|
|
2073
|
-
process.exit(1);
|
|
2074
|
-
}
|
|
2075
|
-
}
|
|
2076
|
-
expectError = false;
|
|
2778
|
+
if (caps.prompts) {
|
|
2779
|
+
try {
|
|
2780
|
+
const { prompts } = await target.listPrompts();
|
|
2781
|
+
setCachedPromptNames(prompts.map((p) => p.name));
|
|
2782
|
+
} catch {
|
|
2077
2783
|
}
|
|
2078
|
-
console.log(pc2.dim("\nShutting down..."));
|
|
2079
|
-
await target.close();
|
|
2080
|
-
process.exit(0);
|
|
2081
|
-
} else {
|
|
2082
|
-
mainMenuLoop(target, interceptor);
|
|
2083
2784
|
}
|
|
2084
2785
|
}
|
|
2085
|
-
|
|
2086
|
-
|
|
2087
|
-
|
|
2088
|
-
|
|
2089
|
-
|
|
2090
|
-
|
|
2091
|
-
|
|
2092
|
-
|
|
2093
|
-
|
|
2094
|
-
// Node's readline history expects newest first
|
|
2095
|
-
});
|
|
2096
|
-
activeRl = rl;
|
|
2097
|
-
if (process.stdin.isTTY) {
|
|
2098
|
-
process.stdin.on("keypress", (_str, key) => {
|
|
2099
|
-
if (!key || key.name !== "tab") {
|
|
2100
|
-
resetTabCycle();
|
|
2101
|
-
}
|
|
2102
|
-
});
|
|
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";
|
|
2790
|
+
var lastCommand = null;
|
|
2791
|
+
var AbortFlowError = class extends Error {
|
|
2792
|
+
constructor() {
|
|
2793
|
+
super("Aborted by user.");
|
|
2794
|
+
this.name = "AbortFlowError";
|
|
2103
2795
|
}
|
|
2104
|
-
|
|
2105
|
-
|
|
2796
|
+
};
|
|
2797
|
+
function isAbortError(err) {
|
|
2798
|
+
if (!err) return false;
|
|
2799
|
+
return err.name === "ExitPromptError" || err.name === "AbortError" || err.message === "Prompt was aborted" || typeof err.message === "string" && err.message.includes("User force closed");
|
|
2800
|
+
}
|
|
2801
|
+
async function withSuspendedReadline(target, interceptor, fn) {
|
|
2802
|
+
const wasActive = !!activeRl;
|
|
2803
|
+
if (wasActive) {
|
|
2804
|
+
setGlobalPauseReadlineClose(true);
|
|
2805
|
+
activeRl.close();
|
|
2806
|
+
setActiveRl(null);
|
|
2106
2807
|
}
|
|
2107
|
-
|
|
2108
|
-
|
|
2109
|
-
|
|
2110
|
-
|
|
2111
|
-
|
|
2112
|
-
|
|
2113
|
-
|
|
2114
|
-
|
|
2115
|
-
const trimmed = queue.shift();
|
|
2116
|
-
try {
|
|
2117
|
-
await handleCommand(trimmed, target, interceptor);
|
|
2118
|
-
} catch (err) {
|
|
2119
|
-
if (err instanceof AbortFlowError) {
|
|
2120
|
-
console.log(pc2.yellow(" Aborted."));
|
|
2121
|
-
} else if (err?.message?.includes("-32601") || err?.code === -32601) {
|
|
2122
|
-
let msg = "Server does not support this feature (Method not found)";
|
|
2123
|
-
if (trimmed.startsWith("prompts/")) msg = "This server does not have any prompts.";
|
|
2124
|
-
else if (trimmed.startsWith("resources/"))
|
|
2125
|
-
msg = "This server does not have any resources.";
|
|
2126
|
-
else if (trimmed.startsWith("tools/")) msg = "This server does not have any tools.";
|
|
2127
|
-
console.log(pc2.yellow(` ${msg}`));
|
|
2128
|
-
} else {
|
|
2129
|
-
console.error(pc2.red(`\u2717 Error: ${err.message}`));
|
|
2130
|
-
}
|
|
2131
|
-
}
|
|
2132
|
-
if (activeRl) {
|
|
2133
|
-
setImmediate(() => {
|
|
2134
|
-
if (activeRl) {
|
|
2135
|
-
console.log();
|
|
2136
|
-
activeRl.setPrompt(getPrompt(target));
|
|
2137
|
-
activeRl.prompt();
|
|
2138
|
-
}
|
|
2139
|
-
});
|
|
2808
|
+
try {
|
|
2809
|
+
return await fn();
|
|
2810
|
+
} finally {
|
|
2811
|
+
if (wasActive) {
|
|
2812
|
+
setGlobalPauseReadlineClose(false);
|
|
2813
|
+
if (!isScriptMode) {
|
|
2814
|
+
setDeferNextPrompt(true);
|
|
2815
|
+
startReadlineLoop(target, interceptor);
|
|
2140
2816
|
}
|
|
2141
2817
|
}
|
|
2142
|
-
|
|
2143
|
-
};
|
|
2144
|
-
rl.on("line", (line) => {
|
|
2145
|
-
const trimmed = line.trim();
|
|
2146
|
-
if (!trimmed || trimmed.startsWith("#")) {
|
|
2147
|
-
if (!closed && activeRl) activeRl.prompt();
|
|
2148
|
-
return;
|
|
2149
|
-
}
|
|
2150
|
-
replHistory.push(trimmed);
|
|
2151
|
-
queue.push(trimmed);
|
|
2152
|
-
processQueue();
|
|
2153
|
-
});
|
|
2154
|
-
rl.on("close", async () => {
|
|
2155
|
-
closed = true;
|
|
2156
|
-
activeRl = null;
|
|
2157
|
-
if (!globalPauseReadlineClose) {
|
|
2158
|
-
console.log(pc2.dim("\nShutting down..."));
|
|
2159
|
-
await target.close();
|
|
2160
|
-
process.exit(0);
|
|
2161
|
-
}
|
|
2162
|
-
});
|
|
2818
|
+
}
|
|
2163
2819
|
}
|
|
2164
2820
|
async function handleCommand(input3, target, interceptor) {
|
|
2165
2821
|
const expanded = resolveAlias(input3);
|
|
@@ -2175,14 +2831,32 @@ async function handleCommand(input3, target, interceptor) {
|
|
|
2175
2831
|
case "?":
|
|
2176
2832
|
printShortHelp();
|
|
2177
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;
|
|
2839
|
+
}
|
|
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}`));
|
|
2848
|
+
}
|
|
2849
|
+
});
|
|
2850
|
+
return;
|
|
2851
|
+
}
|
|
2178
2852
|
case "menu":
|
|
2179
2853
|
case "explore":
|
|
2180
2854
|
case "interactive":
|
|
2181
2855
|
if (activeRl) {
|
|
2182
|
-
|
|
2856
|
+
setGlobalPauseReadlineClose(true);
|
|
2183
2857
|
activeRl.close();
|
|
2184
|
-
|
|
2185
|
-
|
|
2858
|
+
setActiveRl(null);
|
|
2859
|
+
setGlobalPauseReadlineClose(false);
|
|
2186
2860
|
}
|
|
2187
2861
|
mainMenuLoop(target, interceptor);
|
|
2188
2862
|
return;
|
|
@@ -2249,10 +2923,10 @@ async function handleCommand(input3, target, interceptor) {
|
|
|
2249
2923
|
case "!!":
|
|
2250
2924
|
case "last":
|
|
2251
2925
|
if (lastCommand) {
|
|
2252
|
-
console.log(
|
|
2926
|
+
console.log(colors.dim(` Re-running: ${lastCommand}`));
|
|
2253
2927
|
return await handleCommand(lastCommand, target, interceptor);
|
|
2254
2928
|
} else {
|
|
2255
|
-
console.log(
|
|
2929
|
+
console.log(colors.yellow("No previous command to re-run."));
|
|
2256
2930
|
}
|
|
2257
2931
|
return;
|
|
2258
2932
|
case "status":
|
|
@@ -2260,7 +2934,7 @@ async function handleCommand(input3, target, interceptor) {
|
|
|
2260
2934
|
return;
|
|
2261
2935
|
case "exit":
|
|
2262
2936
|
case "quit": {
|
|
2263
|
-
console.log(
|
|
2937
|
+
console.log(colors.dim("Shutting down..."));
|
|
2264
2938
|
await target.close();
|
|
2265
2939
|
process.exit(0);
|
|
2266
2940
|
return;
|
|
@@ -2271,11 +2945,11 @@ async function handleCommand(input3, target, interceptor) {
|
|
|
2271
2945
|
}
|
|
2272
2946
|
const suggestion = suggestCommand(cmd, getActiveCommands());
|
|
2273
2947
|
if (suggestion) {
|
|
2274
|
-
console.log(
|
|
2948
|
+
console.log(colors.yellow(`Unknown command: ${cmd}.`));
|
|
2275
2949
|
try {
|
|
2276
2950
|
await withSuspendedReadline(target, interceptor, async () => {
|
|
2277
2951
|
const runIt = await confirm({
|
|
2278
|
-
message: `Did you mean ${
|
|
2952
|
+
message: `Did you mean ${colors.bold(suggestion)}?`,
|
|
2279
2953
|
default: true
|
|
2280
2954
|
});
|
|
2281
2955
|
if (runIt) {
|
|
@@ -2288,7 +2962,7 @@ async function handleCommand(input3, target, interceptor) {
|
|
|
2288
2962
|
throw new AbortFlowError();
|
|
2289
2963
|
}
|
|
2290
2964
|
} else {
|
|
2291
|
-
console.log(
|
|
2965
|
+
console.log(colors.yellow(`Unknown command: ${cmd}. Type ${colors.bold("help")} for usage.`));
|
|
2292
2966
|
}
|
|
2293
2967
|
}
|
|
2294
2968
|
}
|
|
@@ -2296,25 +2970,25 @@ async function handleCommand(input3, target, interceptor) {
|
|
|
2296
2970
|
async function cmdToolsList(target) {
|
|
2297
2971
|
const { tools } = await target.listTools();
|
|
2298
2972
|
if (tools.length === 0) {
|
|
2299
|
-
console.log(
|
|
2973
|
+
console.log(colors.dim(" No tools available."));
|
|
2300
2974
|
return;
|
|
2301
2975
|
}
|
|
2302
2976
|
const nameWidth = Math.max(8, ...tools.map((t) => t.name.length));
|
|
2303
|
-
console.log(
|
|
2304
|
-
console.log(
|
|
2977
|
+
console.log(colors.bold(` ${"Name".padEnd(nameWidth)} Description`));
|
|
2978
|
+
console.log(colors.dim(` ${"\u2500".repeat(nameWidth)} ${"\u2500".repeat(50)}`));
|
|
2305
2979
|
for (const tool of tools) {
|
|
2306
|
-
const desc = tool.description ? tool.description.length > 60 ? `${tool.description.slice(0, 57)}...` : tool.description :
|
|
2307
|
-
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}`);
|
|
2308
2982
|
}
|
|
2309
|
-
console.log(
|
|
2983
|
+
console.log(colors.dim(`
|
|
2310
2984
|
${tools.length} tool(s) total.`));
|
|
2311
2985
|
if (tools.length >= 10) {
|
|
2312
2986
|
const groups = groupToolsByPrefix(tools.map((t) => t.name));
|
|
2313
2987
|
if (!groups.has("All")) {
|
|
2314
2988
|
console.log();
|
|
2315
|
-
console.log(
|
|
2989
|
+
console.log(colors.bold(" Groups:"));
|
|
2316
2990
|
for (const [label, members] of groups) {
|
|
2317
|
-
console.log(` ${
|
|
2991
|
+
console.log(` ${colors.cyan(label.padEnd(16))} ${colors.dim(members.join(", "))}`);
|
|
2318
2992
|
}
|
|
2319
2993
|
}
|
|
2320
2994
|
}
|
|
@@ -2322,28 +2996,28 @@ async function cmdToolsList(target) {
|
|
|
2322
2996
|
async function cmdToolsDescribe(target, rest) {
|
|
2323
2997
|
const name = rest.trim();
|
|
2324
2998
|
if (!name) {
|
|
2325
|
-
console.log(
|
|
2999
|
+
console.log(colors.yellow(" Usage: tools/describe <name>"));
|
|
2326
3000
|
if (cachedToolNames.length > 0) {
|
|
2327
3001
|
const preview = cachedToolNames.slice(0, 6);
|
|
2328
3002
|
const more = cachedToolNames.length > 6 ? `, ... (${cachedToolNames.length} total)` : "";
|
|
2329
|
-
console.log(
|
|
3003
|
+
console.log(colors.dim(`
|
|
2330
3004
|
Available tools: ${preview.join(", ")}${more}`));
|
|
2331
|
-
console.log(
|
|
3005
|
+
console.log(colors.dim(` Type ${colors.bold("tools/list")} for all.`));
|
|
2332
3006
|
}
|
|
2333
3007
|
return;
|
|
2334
3008
|
}
|
|
2335
3009
|
const { tools } = await target.listTools();
|
|
2336
3010
|
const tool = tools.find((t) => t.name === name);
|
|
2337
3011
|
if (!tool) {
|
|
2338
|
-
console.log(
|
|
3012
|
+
console.log(colors.red(`Tool "${name}" not found.`));
|
|
2339
3013
|
const suggestion = suggestCommand(
|
|
2340
3014
|
name,
|
|
2341
3015
|
tools.map((t) => t.name)
|
|
2342
3016
|
);
|
|
2343
3017
|
if (suggestion) {
|
|
2344
|
-
console.log(
|
|
3018
|
+
console.log(colors.yellow(`Did you mean ${colors.bold(suggestion)}?`));
|
|
2345
3019
|
} else {
|
|
2346
|
-
console.log(
|
|
3020
|
+
console.log(colors.dim(`Available: ${tools.map((t) => t.name).join(", ")}`));
|
|
2347
3021
|
}
|
|
2348
3022
|
return;
|
|
2349
3023
|
}
|
|
@@ -2374,14 +3048,14 @@ async function cmdToolsCall(target, interceptor, rest) {
|
|
|
2374
3048
|
if (!picked) return;
|
|
2375
3049
|
return cmdToolsCall(target, interceptor, picked);
|
|
2376
3050
|
}
|
|
2377
|
-
console.log(
|
|
3051
|
+
console.log(colors.yellow(" Usage: tools/call <name> [json_args] [--timeout <ms>]"));
|
|
2378
3052
|
if (cachedToolNames.length > 0) {
|
|
2379
3053
|
const preview = cachedToolNames.slice(0, 6);
|
|
2380
3054
|
const more = cachedToolNames.length > 6 ? `, ... (${cachedToolNames.length} total)` : "";
|
|
2381
|
-
console.log(
|
|
3055
|
+
console.log(colors.dim(`
|
|
2382
3056
|
Available tools: ${preview.join(", ")}${more}`));
|
|
2383
3057
|
console.log(
|
|
2384
|
-
|
|
3058
|
+
colors.dim(` Run without args for ${colors.bold("interactive mode")}: tools/call <name>`)
|
|
2385
3059
|
);
|
|
2386
3060
|
}
|
|
2387
3061
|
return;
|
|
@@ -2393,31 +3067,31 @@ async function cmdToolsCall(target, interceptor, rest) {
|
|
|
2393
3067
|
try {
|
|
2394
3068
|
args = JSON.parse(trimmed);
|
|
2395
3069
|
} catch (err) {
|
|
2396
|
-
console.error(
|
|
2397
|
-
console.log(
|
|
3070
|
+
console.error(colors.red(`Invalid JSON: ${err.message}`));
|
|
3071
|
+
console.log(colors.dim(` Received: ${jsonArgs}`));
|
|
2398
3072
|
return;
|
|
2399
3073
|
}
|
|
2400
3074
|
} else {
|
|
2401
3075
|
try {
|
|
2402
3076
|
args = parseHttpieArgs(trimmed);
|
|
2403
3077
|
} catch (err) {
|
|
2404
|
-
console.error(
|
|
3078
|
+
console.error(colors.red(`Invalid shorthand arguments: ${err.message}`));
|
|
2405
3079
|
return;
|
|
2406
3080
|
}
|
|
2407
3081
|
}
|
|
2408
3082
|
const { tools } = await target.listTools();
|
|
2409
3083
|
const tool = tools.find((t) => t.name === toolName);
|
|
2410
3084
|
if (!tool) {
|
|
2411
|
-
console.log(
|
|
3085
|
+
console.log(colors.red(`
|
|
2412
3086
|
\u2717 Tool "${toolName}" not found.`));
|
|
2413
3087
|
const toolNames = tools.map((t) => t.name);
|
|
2414
3088
|
const suggestion = suggestCommand(toolName, toolNames);
|
|
2415
3089
|
if (suggestion) {
|
|
2416
|
-
console.log(
|
|
3090
|
+
console.log(colors.yellow(` \u{1F4A1} Did you mean "${suggestion}"?`));
|
|
2417
3091
|
} else {
|
|
2418
3092
|
const preview = toolNames.slice(0, 6);
|
|
2419
3093
|
const more = toolNames.length > 6 ? `, ... (${toolNames.length} total)` : "";
|
|
2420
|
-
console.log(
|
|
3094
|
+
console.log(colors.dim(` Available tools: ${preview.join(", ")}${more}`));
|
|
2421
3095
|
}
|
|
2422
3096
|
return { isError: true, content: [{ type: "text", text: `Tool not found: ${toolName}` }] };
|
|
2423
3097
|
}
|
|
@@ -2425,14 +3099,14 @@ async function cmdToolsCall(target, interceptor, rest) {
|
|
|
2425
3099
|
const required = schema.required ?? [];
|
|
2426
3100
|
const missing = required.filter((r) => !(r in args));
|
|
2427
3101
|
if (missing.length > 0) {
|
|
2428
|
-
console.log(
|
|
3102
|
+
console.log(colors.yellow(`
|
|
2429
3103
|
Missing required arguments: ${missing.join(", ")}`));
|
|
2430
3104
|
console.log();
|
|
2431
3105
|
const scaffolded = scaffoldArgs(schema);
|
|
2432
|
-
console.log(
|
|
3106
|
+
console.log(colors.dim(" Try:"));
|
|
2433
3107
|
console.log(` tools/call ${toolName} ${scaffolded}`);
|
|
2434
3108
|
console.log();
|
|
2435
|
-
console.log(
|
|
3109
|
+
console.log(colors.dim(" Or run without args for interactive mode:"));
|
|
2436
3110
|
console.log(` tools/call ${toolName}`);
|
|
2437
3111
|
console.log();
|
|
2438
3112
|
return;
|
|
@@ -2444,17 +3118,21 @@ async function cmdToolsCall(target, interceptor, rest) {
|
|
|
2444
3118
|
if (!isScriptMode) {
|
|
2445
3119
|
const fullCmd = `tools/call ${toolName} ${JSON.stringify(args)}`;
|
|
2446
3120
|
replHistory.push(fullCmd);
|
|
3121
|
+
appendToHistoryFile(fullCmd).catch(() => {
|
|
3122
|
+
});
|
|
2447
3123
|
if (activeRl) {
|
|
2448
3124
|
activeRl.history.unshift(fullCmd);
|
|
2449
3125
|
}
|
|
2450
3126
|
}
|
|
2451
3127
|
}
|
|
2452
|
-
console.log(
|
|
3128
|
+
console.log(colors.dim(` Calling ${toolName}...`));
|
|
2453
3129
|
const startTime = Date.now();
|
|
2454
3130
|
const result = await interceptor.callTool(target, toolName, args, timeoutMs);
|
|
2455
3131
|
const elapsed = Date.now() - startTime;
|
|
2456
3132
|
callHistory.push({ toolName, durationMs: elapsed, timestamp: startTime });
|
|
2457
3133
|
lastToolArgsMap.set(toolName, { ...args });
|
|
3134
|
+
saveWizardDefaults().catch(() => {
|
|
3135
|
+
});
|
|
2458
3136
|
const isError = result.isError === true;
|
|
2459
3137
|
console.log();
|
|
2460
3138
|
printResultBlock({
|
|
@@ -2468,17 +3146,17 @@ async function cmdToolsCall(target, interceptor, rest) {
|
|
|
2468
3146
|
for (const item of content) {
|
|
2469
3147
|
if (item.type === "text") {
|
|
2470
3148
|
if (isError) {
|
|
2471
|
-
console.log(
|
|
3149
|
+
console.log(colors.red(` \u2717 ${item.text}`));
|
|
2472
3150
|
} else {
|
|
2473
3151
|
try {
|
|
2474
3152
|
const parsed = JSON.parse(item.text);
|
|
2475
3153
|
if (typeof parsed === "object" && parsed !== null) {
|
|
2476
3154
|
console.log(formatJson(parsed, 2, true));
|
|
2477
3155
|
} else {
|
|
2478
|
-
console.log(
|
|
3156
|
+
console.log(colors.yellow(` ${item.text}`));
|
|
2479
3157
|
}
|
|
2480
3158
|
} catch {
|
|
2481
|
-
console.log(
|
|
3159
|
+
console.log(colors.yellow(` ${item.text}`));
|
|
2482
3160
|
}
|
|
2483
3161
|
}
|
|
2484
3162
|
} else {
|
|
@@ -2492,7 +3170,7 @@ async function cmdToolsCall(target, interceptor, rest) {
|
|
|
2492
3170
|
const errText = Array.isArray(content) ? content.map((c) => c.text || "").join(" ").toLowerCase() : typeof content === "object" ? (content.text || "").toLowerCase() : "";
|
|
2493
3171
|
if (errText.includes("argument") || errText.includes("validation") || errText.includes("schema") || errText.includes("missing") || errText.includes("invalid")) {
|
|
2494
3172
|
console.log(
|
|
2495
|
-
|
|
3173
|
+
colors.yellow(
|
|
2496
3174
|
` \u{1F4A1} Tip: Check the tool arguments via 'tools/describe ${toolName}'
|
|
2497
3175
|
or view the raw server stderr above.`
|
|
2498
3176
|
)
|
|
@@ -2506,15 +3184,15 @@ async function interactiveArgPrompt(target, interceptor, toolName, clearPrevious
|
|
|
2506
3184
|
const { tools } = await target.listTools();
|
|
2507
3185
|
const tool = tools.find((t) => t.name === toolName);
|
|
2508
3186
|
if (!tool) {
|
|
2509
|
-
console.log(
|
|
3187
|
+
console.log(colors.red(`Tool "${toolName}" not found.`));
|
|
2510
3188
|
const suggestion = suggestCommand(
|
|
2511
3189
|
toolName,
|
|
2512
3190
|
tools.map((t) => t.name)
|
|
2513
3191
|
);
|
|
2514
3192
|
if (suggestion) {
|
|
2515
|
-
console.log(
|
|
3193
|
+
console.log(colors.yellow(`Did you mean ${colors.bold(suggestion)}?`));
|
|
2516
3194
|
} else {
|
|
2517
|
-
console.log(
|
|
3195
|
+
console.log(colors.dim(`Available: ${tools.map((t) => t.name).join(", ")}`));
|
|
2518
3196
|
}
|
|
2519
3197
|
return null;
|
|
2520
3198
|
}
|
|
@@ -2524,9 +3202,9 @@ async function interactiveArgPrompt(target, interceptor, toolName, clearPrevious
|
|
|
2524
3202
|
return {};
|
|
2525
3203
|
}
|
|
2526
3204
|
if (isScriptMode) {
|
|
2527
|
-
console.log(
|
|
3205
|
+
console.log(colors.yellow(` Tool "${toolName}" requires arguments.`));
|
|
2528
3206
|
const scaffolded = scaffoldArgs(schema);
|
|
2529
|
-
console.log(
|
|
3207
|
+
console.log(colors.dim(` Usage: tools/call ${toolName} ${scaffolded}`));
|
|
2530
3208
|
return null;
|
|
2531
3209
|
}
|
|
2532
3210
|
const required = schema.required ?? [];
|
|
@@ -2535,10 +3213,10 @@ async function interactiveArgPrompt(target, interceptor, toolName, clearPrevious
|
|
|
2535
3213
|
const optionalProps = allProps.filter(([name]) => !required.includes(name));
|
|
2536
3214
|
const previousArgs = clearPrevious ? void 0 : lastToolArgsMap.get(toolName);
|
|
2537
3215
|
console.log();
|
|
2538
|
-
console.log(` ${
|
|
3216
|
+
console.log(` ${colors.bold(tool.name)}${tool.description ? colors.dim(` \u2014 ${tool.description}`) : ""}`);
|
|
2539
3217
|
if (previousArgs) {
|
|
2540
|
-
console.log(
|
|
2541
|
-
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."));
|
|
2542
3220
|
}
|
|
2543
3221
|
console.log();
|
|
2544
3222
|
const collectedArgs = {};
|
|
@@ -2556,7 +3234,7 @@ async function interactiveArgPrompt(target, interceptor, toolName, clearPrevious
|
|
|
2556
3234
|
const typeStr = prop.type ?? "any";
|
|
2557
3235
|
const desc = prop.description ?? "";
|
|
2558
3236
|
const prevVal = previousArgs?.[name];
|
|
2559
|
-
const label = desc ? `${name} ${
|
|
3237
|
+
const label = desc ? `${name} ${colors.dim(`(${typeStr})`)} ${colors.dim(desc)}` : `${name} ${colors.dim(`(${typeStr})`)}`;
|
|
2560
3238
|
const answerStr = await input2(
|
|
2561
3239
|
{
|
|
2562
3240
|
message: label,
|
|
@@ -2598,7 +3276,7 @@ async function interactiveArgPrompt(target, interceptor, toolName, clearPrevious
|
|
|
2598
3276
|
const typeStr = prop.type ?? "any";
|
|
2599
3277
|
const desc = prop.description ?? "";
|
|
2600
3278
|
const prevVal = previousArgs?.[name];
|
|
2601
|
-
const label = desc ? `${name} ${
|
|
3279
|
+
const label = desc ? `${name} ${colors.dim(`(${typeStr})`)} ${colors.dim(desc)}` : `${name} ${colors.dim(`(${typeStr})`)}`;
|
|
2602
3280
|
const answerStr = await input2(
|
|
2603
3281
|
{
|
|
2604
3282
|
message: label,
|
|
@@ -2637,14 +3315,14 @@ function printJsonTemplate(filled, allProps, currentProp) {
|
|
|
2637
3315
|
const parts = [];
|
|
2638
3316
|
for (const [name] of allProps) {
|
|
2639
3317
|
if (name in filled) {
|
|
2640
|
-
parts.push(`"${name}": ${
|
|
3318
|
+
parts.push(`"${name}": ${colors.green(JSON.stringify(filled[name]))}`);
|
|
2641
3319
|
} else if (name === currentProp) {
|
|
2642
|
-
parts.push(`"${name}": ${
|
|
3320
|
+
parts.push(`"${name}": ${colors.yellow("\u2592")}`);
|
|
2643
3321
|
} else {
|
|
2644
|
-
parts.push(`"${name}": ${
|
|
3322
|
+
parts.push(`"${name}": ${colors.dim("\u2592")}`);
|
|
2645
3323
|
}
|
|
2646
3324
|
}
|
|
2647
|
-
console.log(
|
|
3325
|
+
console.log(colors.dim(" { ") + parts.join(colors.dim(", ")) + colors.dim(" }"));
|
|
2648
3326
|
console.log();
|
|
2649
3327
|
}
|
|
2650
3328
|
function coerceValue(input3, type) {
|
|
@@ -2661,7 +3339,7 @@ function coerceValue(input3, type) {
|
|
|
2661
3339
|
}
|
|
2662
3340
|
}
|
|
2663
3341
|
function question(rl, prompt) {
|
|
2664
|
-
return new Promise((
|
|
3342
|
+
return new Promise((resolve3, reject) => {
|
|
2665
3343
|
let aborted = false;
|
|
2666
3344
|
const onKeypress = (_str, key) => {
|
|
2667
3345
|
if (key && key.name === "escape") {
|
|
@@ -2685,7 +3363,7 @@ function question(rl, prompt) {
|
|
|
2685
3363
|
if (aborted) {
|
|
2686
3364
|
reject(new AbortFlowError());
|
|
2687
3365
|
} else {
|
|
2688
|
-
|
|
3366
|
+
resolve3(answer);
|
|
2689
3367
|
}
|
|
2690
3368
|
});
|
|
2691
3369
|
});
|
|
@@ -2693,11 +3371,11 @@ function question(rl, prompt) {
|
|
|
2693
3371
|
async function cmdToolsScaffold(target, rest) {
|
|
2694
3372
|
const name = rest.trim();
|
|
2695
3373
|
if (!name) {
|
|
2696
|
-
console.log(
|
|
3374
|
+
console.log(colors.yellow(" Usage: tools/scaffold <name>"));
|
|
2697
3375
|
if (cachedToolNames.length > 0) {
|
|
2698
3376
|
const preview = cachedToolNames.slice(0, 6);
|
|
2699
3377
|
const more = cachedToolNames.length > 6 ? `, ... (${cachedToolNames.length} total)` : "";
|
|
2700
|
-
console.log(
|
|
3378
|
+
console.log(colors.dim(`
|
|
2701
3379
|
Available tools: ${preview.join(", ")}${more}`));
|
|
2702
3380
|
}
|
|
2703
3381
|
return;
|
|
@@ -2705,38 +3383,38 @@ async function cmdToolsScaffold(target, rest) {
|
|
|
2705
3383
|
const { tools } = await target.listTools();
|
|
2706
3384
|
const tool = tools.find((t) => t.name === name);
|
|
2707
3385
|
if (!tool) {
|
|
2708
|
-
console.log(
|
|
3386
|
+
console.log(colors.red(`Tool "${name}" not found.`));
|
|
2709
3387
|
const suggestion = suggestCommand(
|
|
2710
3388
|
name,
|
|
2711
3389
|
tools.map((t) => t.name)
|
|
2712
3390
|
);
|
|
2713
3391
|
if (suggestion) {
|
|
2714
|
-
console.log(
|
|
3392
|
+
console.log(colors.yellow(`Did you mean ${colors.bold(suggestion)}?`));
|
|
2715
3393
|
} else {
|
|
2716
|
-
console.log(
|
|
3394
|
+
console.log(colors.dim(`Available: ${tools.map((t) => t.name).join(", ")}`));
|
|
2717
3395
|
}
|
|
2718
3396
|
return;
|
|
2719
3397
|
}
|
|
2720
3398
|
const scaffolded = scaffoldArgs(tool.inputSchema);
|
|
2721
|
-
console.log(
|
|
3399
|
+
console.log(colors.cyan("\n Ready-to-paste command:"));
|
|
2722
3400
|
console.log(` tools/call ${name} ${scaffolded}
|
|
2723
3401
|
`);
|
|
2724
3402
|
}
|
|
2725
3403
|
async function cmdResourcesList(target) {
|
|
2726
3404
|
const { resources } = await target.listResources();
|
|
2727
3405
|
if (resources.length === 0) {
|
|
2728
|
-
console.log(
|
|
3406
|
+
console.log(colors.dim(" No resources available."));
|
|
2729
3407
|
return;
|
|
2730
3408
|
}
|
|
2731
3409
|
const uriWidth = Math.max(6, ...resources.map((r) => r.uri.length));
|
|
2732
|
-
console.log(
|
|
2733
|
-
console.log(
|
|
3410
|
+
console.log(colors.bold(` ${"URI".padEnd(uriWidth)} Name`));
|
|
3411
|
+
console.log(colors.dim(` ${"\u2500".repeat(uriWidth)} ${"\u2500".repeat(40)}`));
|
|
2734
3412
|
for (const r of resources) {
|
|
2735
3413
|
const uri = r.uri;
|
|
2736
|
-
const name = r.name ??
|
|
2737
|
-
console.log(` ${
|
|
3414
|
+
const name = r.name ?? colors.dim("(unnamed)");
|
|
3415
|
+
console.log(` ${colors.green(uri.padEnd(uriWidth))} ${name}`);
|
|
2738
3416
|
}
|
|
2739
|
-
console.log(
|
|
3417
|
+
console.log(colors.dim(`
|
|
2740
3418
|
${resources.length} resource(s) total.`));
|
|
2741
3419
|
}
|
|
2742
3420
|
async function cmdResourcesRead(target, rest, interceptor) {
|
|
@@ -2756,11 +3434,11 @@ async function cmdResourcesRead(target, rest, interceptor) {
|
|
|
2756
3434
|
if (!picked) return;
|
|
2757
3435
|
return cmdResourcesRead(target, picked, interceptor);
|
|
2758
3436
|
}
|
|
2759
|
-
console.log(
|
|
3437
|
+
console.log(colors.yellow(" Usage: resources/read <uri>"));
|
|
2760
3438
|
if (cachedResourceUris.length > 0) {
|
|
2761
3439
|
const preview = cachedResourceUris.slice(0, 5);
|
|
2762
3440
|
const more = cachedResourceUris.length > 5 ? `, ... (${cachedResourceUris.length} total)` : "";
|
|
2763
|
-
console.log(
|
|
3441
|
+
console.log(colors.dim(`
|
|
2764
3442
|
Available resources: ${preview.join(", ")}${more}`));
|
|
2765
3443
|
}
|
|
2766
3444
|
return;
|
|
@@ -2778,15 +3456,15 @@ async function cmdResourcesRead(target, rest, interceptor) {
|
|
|
2778
3456
|
if (typeof parsed === "object" && parsed !== null) {
|
|
2779
3457
|
console.log(formatJson(parsed, 2, true));
|
|
2780
3458
|
} else {
|
|
2781
|
-
console.log(
|
|
3459
|
+
console.log(colors.yellow(` ${text}`));
|
|
2782
3460
|
}
|
|
2783
3461
|
} catch {
|
|
2784
|
-
console.log(
|
|
3462
|
+
console.log(colors.yellow(` ${text}`));
|
|
2785
3463
|
}
|
|
2786
3464
|
} else if (item.blob !== void 0) {
|
|
2787
3465
|
const mimeType = item.mimeType ?? "application/octet-stream";
|
|
2788
3466
|
const sizeBytes = Buffer.from(item.blob, "base64").length;
|
|
2789
|
-
console.log(
|
|
3467
|
+
console.log(colors.dim(` [Binary: ${mimeType}, ${sizeBytes} bytes]`));
|
|
2790
3468
|
} else {
|
|
2791
3469
|
console.log(formatJson(item, 2, true));
|
|
2792
3470
|
}
|
|
@@ -2797,37 +3475,37 @@ async function cmdResourcesRead(target, rest, interceptor) {
|
|
|
2797
3475
|
async function cmdResourcesTemplates(target) {
|
|
2798
3476
|
const { resourceTemplates } = await target.listResourceTemplates();
|
|
2799
3477
|
if (resourceTemplates.length === 0) {
|
|
2800
|
-
console.log(
|
|
3478
|
+
console.log(colors.dim(" No resource templates available."));
|
|
2801
3479
|
return;
|
|
2802
3480
|
}
|
|
2803
3481
|
const uriWidth = Math.max(
|
|
2804
3482
|
12,
|
|
2805
3483
|
...resourceTemplates.map((t) => t.uriTemplate.length)
|
|
2806
3484
|
);
|
|
2807
|
-
console.log(
|
|
2808
|
-
console.log(
|
|
3485
|
+
console.log(colors.bold(` ${"URI Template".padEnd(uriWidth)} Name`));
|
|
3486
|
+
console.log(colors.dim(` ${"\u2500".repeat(uriWidth)} ${"\u2500".repeat(40)}`));
|
|
2809
3487
|
for (const t of resourceTemplates) {
|
|
2810
3488
|
const uriTemplate = t.uriTemplate;
|
|
2811
|
-
const name = t.name ??
|
|
2812
|
-
console.log(` ${
|
|
3489
|
+
const name = t.name ?? colors.dim("(unnamed)");
|
|
3490
|
+
console.log(` ${colors.green(uriTemplate.padEnd(uriWidth))} ${name}`);
|
|
2813
3491
|
}
|
|
2814
|
-
console.log(
|
|
3492
|
+
console.log(colors.dim(`
|
|
2815
3493
|
${resourceTemplates.length} template(s) total.`));
|
|
2816
3494
|
}
|
|
2817
3495
|
async function cmdPromptsList(target) {
|
|
2818
3496
|
const { prompts } = await target.listPrompts();
|
|
2819
3497
|
if (prompts.length === 0) {
|
|
2820
|
-
console.log(
|
|
3498
|
+
console.log(colors.dim(" No prompts available."));
|
|
2821
3499
|
return;
|
|
2822
3500
|
}
|
|
2823
3501
|
const nameWidth = Math.max(8, ...prompts.map((p) => p.name.length));
|
|
2824
|
-
console.log(
|
|
2825
|
-
console.log(
|
|
3502
|
+
console.log(colors.bold(` ${"Name".padEnd(nameWidth)} Description`));
|
|
3503
|
+
console.log(colors.dim(` ${"\u2500".repeat(nameWidth)} ${"\u2500".repeat(50)}`));
|
|
2826
3504
|
for (const p of prompts) {
|
|
2827
|
-
const desc = p.description ? p.description.length > 60 ? `${p.description.slice(0, 57)}...` : p.description :
|
|
2828
|
-
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}`);
|
|
2829
3507
|
}
|
|
2830
|
-
console.log(
|
|
3508
|
+
console.log(colors.dim(`
|
|
2831
3509
|
${prompts.length} prompt(s) total.`));
|
|
2832
3510
|
}
|
|
2833
3511
|
async function cmdPromptsGet(target, rest, interceptor) {
|
|
@@ -2844,9 +3522,9 @@ async function cmdPromptsGet(target, rest, interceptor) {
|
|
|
2844
3522
|
if (!picked) return;
|
|
2845
3523
|
return cmdPromptsGet(target, picked);
|
|
2846
3524
|
}
|
|
2847
|
-
console.log(
|
|
3525
|
+
console.log(colors.yellow(" Usage: prompts/get <name> [json_args]"));
|
|
2848
3526
|
if (cachedPromptNames.length > 0) {
|
|
2849
|
-
console.log(
|
|
3527
|
+
console.log(colors.dim(`
|
|
2850
3528
|
Available prompts: ${cachedPromptNames.join(", ")}`));
|
|
2851
3529
|
}
|
|
2852
3530
|
return;
|
|
@@ -2858,15 +3536,15 @@ async function cmdPromptsGet(target, rest, interceptor) {
|
|
|
2858
3536
|
try {
|
|
2859
3537
|
promptArgs = JSON.parse(trimmed);
|
|
2860
3538
|
} catch (err) {
|
|
2861
|
-
console.error(
|
|
2862
|
-
console.log(
|
|
3539
|
+
console.error(colors.red(`Invalid JSON: ${err.message}`));
|
|
3540
|
+
console.log(colors.dim(` Received: ${jsonArgs}`));
|
|
2863
3541
|
return;
|
|
2864
3542
|
}
|
|
2865
3543
|
} else {
|
|
2866
3544
|
try {
|
|
2867
3545
|
promptArgs = parseHttpieArgs(trimmed);
|
|
2868
3546
|
} catch (err) {
|
|
2869
|
-
console.error(
|
|
3547
|
+
console.error(colors.red(`Invalid shorthand arguments: ${err.message}`));
|
|
2870
3548
|
return;
|
|
2871
3549
|
}
|
|
2872
3550
|
}
|
|
@@ -2874,14 +3552,14 @@ async function cmdPromptsGet(target, rest, interceptor) {
|
|
|
2874
3552
|
const { prompts } = await target.listPrompts();
|
|
2875
3553
|
const prompt = prompts.find((p) => p.name === promptName);
|
|
2876
3554
|
if (!prompt) {
|
|
2877
|
-
console.log(
|
|
3555
|
+
console.log(colors.red(`
|
|
2878
3556
|
\u2717 Prompt "${promptName}" not found.`));
|
|
2879
3557
|
const promptNames = prompts.map((p) => p.name);
|
|
2880
3558
|
const suggestion = suggestCommand(promptName, promptNames);
|
|
2881
3559
|
if (suggestion) {
|
|
2882
|
-
console.log(
|
|
3560
|
+
console.log(colors.yellow(` \u{1F4A1} Did you mean "${suggestion}"?`));
|
|
2883
3561
|
} else {
|
|
2884
|
-
console.log(
|
|
3562
|
+
console.log(colors.dim(` Available prompts: ${promptNames.join(", ")}`));
|
|
2885
3563
|
}
|
|
2886
3564
|
return { isError: true, content: [{ type: "text", text: `Prompt not found: ${promptName}` }] };
|
|
2887
3565
|
}
|
|
@@ -2889,24 +3567,24 @@ async function cmdPromptsGet(target, rest, interceptor) {
|
|
|
2889
3567
|
const result = await target.getPrompt({ name: promptName, arguments: promptArgs });
|
|
2890
3568
|
const elapsed = Date.now() - startTime;
|
|
2891
3569
|
if (result.messages.length === 0) {
|
|
2892
|
-
console.log(
|
|
3570
|
+
console.log(colors.dim(" No messages returned."));
|
|
2893
3571
|
return;
|
|
2894
3572
|
}
|
|
2895
3573
|
console.log();
|
|
2896
3574
|
printResultBlock({ label: "Prompt", labelColor: "blue", elapsed, detail: promptName });
|
|
2897
3575
|
for (const msg of result.messages) {
|
|
2898
|
-
const role = msg.role === "user" ?
|
|
3576
|
+
const role = msg.role === "user" ? colors.blue("user") : colors.magenta("assistant");
|
|
2899
3577
|
const text = msg.content.text ?? JSON.stringify(msg.content);
|
|
2900
3578
|
try {
|
|
2901
3579
|
const parsed = JSON.parse(text);
|
|
2902
3580
|
if (typeof parsed === "object" && parsed !== null) {
|
|
2903
|
-
console.log(` ${
|
|
3581
|
+
console.log(` ${colors.bold(role)}:`);
|
|
2904
3582
|
console.log(formatJson(parsed, 4, true));
|
|
2905
3583
|
} else {
|
|
2906
|
-
console.log(` ${
|
|
3584
|
+
console.log(` ${colors.bold(role)}: ${colors.yellow(text)}`);
|
|
2907
3585
|
}
|
|
2908
3586
|
} catch {
|
|
2909
|
-
console.log(` ${
|
|
3587
|
+
console.log(` ${colors.bold(role)}: ${colors.yellow(text)}`);
|
|
2910
3588
|
}
|
|
2911
3589
|
}
|
|
2912
3590
|
console.log();
|
|
@@ -2916,36 +3594,36 @@ async function cmdPing(target) {
|
|
|
2916
3594
|
try {
|
|
2917
3595
|
const elapsed = await target.ping();
|
|
2918
3596
|
console.log();
|
|
2919
|
-
console.log(
|
|
3597
|
+
console.log(colors.green(` \u2713 Pong! Round-trip: ${elapsed}ms`));
|
|
2920
3598
|
console.log();
|
|
2921
3599
|
} catch (err) {
|
|
2922
3600
|
console.log();
|
|
2923
|
-
console.error(
|
|
3601
|
+
console.error(colors.red(` \u2717 Ping failed: ${err.message}`));
|
|
2924
3602
|
console.log();
|
|
2925
3603
|
}
|
|
2926
3604
|
}
|
|
2927
3605
|
async function cmdLogLevel(target, rest) {
|
|
2928
3606
|
const level = rest.trim().toLowerCase();
|
|
2929
3607
|
if (!level) {
|
|
2930
|
-
console.log(
|
|
2931
|
-
console.log(
|
|
3608
|
+
console.log(colors.yellow(" Usage: log-level <level>"));
|
|
3609
|
+
console.log(colors.dim(` Valid levels: ${LOG_LEVELS.join(", ")}`));
|
|
2932
3610
|
return;
|
|
2933
3611
|
}
|
|
2934
3612
|
if (!LOG_LEVELS.includes(level)) {
|
|
2935
3613
|
const suggestion = suggestCommand(level, [...LOG_LEVELS]);
|
|
2936
3614
|
const hint = suggestion ? ` Did you mean "${suggestion}"?` : "";
|
|
2937
|
-
console.log(
|
|
2938
|
-
console.log(
|
|
3615
|
+
console.log(colors.red(` Unknown log level: "${level}".${hint}`));
|
|
3616
|
+
console.log(colors.dim(` Valid levels: ${LOG_LEVELS.join(", ")}`));
|
|
2939
3617
|
return;
|
|
2940
3618
|
}
|
|
2941
3619
|
try {
|
|
2942
3620
|
await target.setLoggingLevel(level);
|
|
2943
|
-
console.log(
|
|
3621
|
+
console.log(colors.green(` \u2713 Logging level set to: ${level}`));
|
|
2944
3622
|
} catch (err) {
|
|
2945
|
-
console.error(
|
|
3623
|
+
console.error(colors.red(` \u2717 Failed to set log level: ${err.message}`));
|
|
2946
3624
|
const caps = target.getServerCapabilities();
|
|
2947
3625
|
if (!caps?.logging) {
|
|
2948
|
-
console.log(
|
|
3626
|
+
console.log(colors.dim(" The server does not advertise logging support."));
|
|
2949
3627
|
}
|
|
2950
3628
|
}
|
|
2951
3629
|
}
|
|
@@ -2953,28 +3631,28 @@ function cmdHistory(target, rest) {
|
|
|
2953
3631
|
const arg = rest.trim();
|
|
2954
3632
|
if (arg === "clear") {
|
|
2955
3633
|
target.clearHistory();
|
|
2956
|
-
console.log(
|
|
3634
|
+
console.log(colors.dim(" History cleared."));
|
|
2957
3635
|
return;
|
|
2958
3636
|
}
|
|
2959
3637
|
const count = arg ? Number.parseInt(arg, 10) : 20;
|
|
2960
3638
|
const records = target.getHistory(Number.isNaN(count) ? 20 : count);
|
|
2961
3639
|
if (records.length === 0) {
|
|
2962
|
-
console.log(
|
|
3640
|
+
console.log(colors.dim(" No request history yet."));
|
|
2963
3641
|
return;
|
|
2964
3642
|
}
|
|
2965
|
-
console.log(
|
|
2966
|
-
console.log(
|
|
3643
|
+
console.log(colors.bold("\n Request History"));
|
|
3644
|
+
console.log(colors.dim(` ${"\u2500".repeat(70)}`));
|
|
2967
3645
|
for (const rec of records) {
|
|
2968
3646
|
const time = new Date(rec.timestamp).toLocaleTimeString();
|
|
2969
3647
|
const dur = `${rec.durationMs}ms`;
|
|
2970
|
-
const hasError = rec.error ?
|
|
3648
|
+
const hasError = rec.error ? colors.red(" \u2717") : "";
|
|
2971
3649
|
console.log(
|
|
2972
|
-
` ${
|
|
3650
|
+
` ${colors.dim(`#${rec.id}`)} ${colors.dim(time)} ${colors.green(rec.method.padEnd(30))} ${colors.cyan(dur.padStart(8))}${hasError}`
|
|
2973
3651
|
);
|
|
2974
3652
|
}
|
|
2975
3653
|
const total = target.getHistory().length;
|
|
2976
3654
|
console.log(
|
|
2977
|
-
|
|
3655
|
+
colors.dim(`
|
|
2978
3656
|
Showing ${records.length} of ${total} total. Use "history clear" to reset.`)
|
|
2979
3657
|
);
|
|
2980
3658
|
console.log();
|
|
@@ -2983,26 +3661,26 @@ function cmdNotifications(target, rest) {
|
|
|
2983
3661
|
const arg = rest.trim();
|
|
2984
3662
|
if (arg === "clear") {
|
|
2985
3663
|
target.clearNotifications();
|
|
2986
|
-
console.log(
|
|
3664
|
+
console.log(colors.dim(" Notifications cleared."));
|
|
2987
3665
|
return;
|
|
2988
3666
|
}
|
|
2989
3667
|
const count = arg ? Number.parseInt(arg, 10) : 20;
|
|
2990
3668
|
const records = target.getNotifications(Number.isNaN(count) ? 20 : count);
|
|
2991
3669
|
if (records.length === 0) {
|
|
2992
|
-
console.log(
|
|
2993
|
-
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."));
|
|
2994
3672
|
return;
|
|
2995
3673
|
}
|
|
2996
|
-
console.log(
|
|
2997
|
-
console.log(
|
|
3674
|
+
console.log(colors.bold("\n Server Notifications"));
|
|
3675
|
+
console.log(colors.dim(` ${"\u2500".repeat(70)}`));
|
|
2998
3676
|
for (const n of records) {
|
|
2999
3677
|
const time = new Date(n.timestamp).toLocaleTimeString();
|
|
3000
|
-
const params = n.params ? ` ${
|
|
3001
|
-
console.log(` ${
|
|
3678
|
+
const params = n.params ? ` ${colors.dim(JSON.stringify(n.params))}` : "";
|
|
3679
|
+
console.log(` ${colors.dim(time)} ${colors.yellow(n.method)}${params}`);
|
|
3002
3680
|
}
|
|
3003
3681
|
const total = target.getNotifications().length;
|
|
3004
3682
|
console.log(
|
|
3005
|
-
|
|
3683
|
+
colors.dim(`
|
|
3006
3684
|
Showing ${records.length} of ${total} total. Use "notifications clear" to reset.`)
|
|
3007
3685
|
);
|
|
3008
3686
|
console.log();
|
|
@@ -3010,44 +3688,44 @@ function cmdNotifications(target, rest) {
|
|
|
3010
3688
|
async function cmdResourcesSubscribe(target, rest) {
|
|
3011
3689
|
const uri = rest.trim();
|
|
3012
3690
|
if (!uri) {
|
|
3013
|
-
console.log(
|
|
3691
|
+
console.log(colors.yellow(" Usage: resources/subscribe <uri>"));
|
|
3014
3692
|
if (cachedResourceUris.length > 0) {
|
|
3015
|
-
console.log(
|
|
3693
|
+
console.log(colors.dim(` Available: ${cachedResourceUris.join(", ")}`));
|
|
3016
3694
|
}
|
|
3017
3695
|
return;
|
|
3018
3696
|
}
|
|
3019
3697
|
try {
|
|
3020
3698
|
await target.subscribeResource({ uri });
|
|
3021
|
-
console.log(
|
|
3022
|
-
console.log(
|
|
3699
|
+
console.log(colors.green(` \u2713 Subscribed to: ${uri}`));
|
|
3700
|
+
console.log(colors.dim(" You'll see notifications when this resource changes."));
|
|
3023
3701
|
} catch (err) {
|
|
3024
|
-
console.error(
|
|
3702
|
+
console.error(colors.red(` \u2717 Subscribe failed: ${err.message}`));
|
|
3025
3703
|
}
|
|
3026
3704
|
}
|
|
3027
3705
|
async function cmdResourcesUnsubscribe(target, rest) {
|
|
3028
3706
|
const uri = rest.trim();
|
|
3029
3707
|
if (!uri) {
|
|
3030
|
-
console.log(
|
|
3708
|
+
console.log(colors.yellow(" Usage: resources/unsubscribe <uri>"));
|
|
3031
3709
|
return;
|
|
3032
3710
|
}
|
|
3033
3711
|
try {
|
|
3034
3712
|
await target.unsubscribeResource({ uri });
|
|
3035
|
-
console.log(
|
|
3713
|
+
console.log(colors.green(` \u2713 Unsubscribed from: ${uri}`));
|
|
3036
3714
|
} catch (err) {
|
|
3037
|
-
console.error(
|
|
3715
|
+
console.error(colors.red(` \u2717 Unsubscribe failed: ${err.message}`));
|
|
3038
3716
|
}
|
|
3039
3717
|
}
|
|
3040
3718
|
function cmdRootsList(target) {
|
|
3041
3719
|
const roots = target.getRoots();
|
|
3042
3720
|
if (roots.length === 0) {
|
|
3043
|
-
console.log(
|
|
3044
|
-
console.log(
|
|
3721
|
+
console.log(colors.dim(" No roots configured."));
|
|
3722
|
+
console.log(colors.dim(" Use roots/add <uri> [name] to add one."));
|
|
3045
3723
|
return;
|
|
3046
3724
|
}
|
|
3047
|
-
console.log(
|
|
3725
|
+
console.log(colors.bold("\n Client Roots"));
|
|
3048
3726
|
for (const r of roots) {
|
|
3049
3727
|
const name = r.name ? ` (${r.name})` : "";
|
|
3050
|
-
console.log(` ${
|
|
3728
|
+
console.log(` ${colors.green(r.uri)}${colors.dim(name)}`);
|
|
3051
3729
|
}
|
|
3052
3730
|
console.log();
|
|
3053
3731
|
}
|
|
@@ -3056,53 +3734,53 @@ async function cmdRootsAdd(target, rest) {
|
|
|
3056
3734
|
const uri = parts[0];
|
|
3057
3735
|
const name = parts.slice(1).join(" ") || void 0;
|
|
3058
3736
|
if (!uri) {
|
|
3059
|
-
console.log(
|
|
3060
|
-
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"'));
|
|
3061
3739
|
return;
|
|
3062
3740
|
}
|
|
3063
3741
|
await target.addRoot({ uri, name });
|
|
3064
|
-
console.log(
|
|
3065
|
-
console.log(
|
|
3742
|
+
console.log(colors.green(` \u2713 Root added: ${uri}`));
|
|
3743
|
+
console.log(colors.dim(" Server has been notified of the change."));
|
|
3066
3744
|
}
|
|
3067
3745
|
async function cmdRootsRemove(target, rest) {
|
|
3068
3746
|
const uri = rest.trim();
|
|
3069
3747
|
if (!uri) {
|
|
3070
|
-
console.log(
|
|
3748
|
+
console.log(colors.yellow(" Usage: roots/remove <uri>"));
|
|
3071
3749
|
const roots = target.getRoots();
|
|
3072
3750
|
if (roots.length > 0) {
|
|
3073
|
-
console.log(
|
|
3751
|
+
console.log(colors.dim(` Current roots: ${roots.map((r) => r.uri).join(", ")}`));
|
|
3074
3752
|
}
|
|
3075
3753
|
return;
|
|
3076
3754
|
}
|
|
3077
3755
|
const removed = await target.removeRoot(uri);
|
|
3078
3756
|
if (removed) {
|
|
3079
|
-
console.log(
|
|
3757
|
+
console.log(colors.green(` \u2713 Root removed: ${uri}`));
|
|
3080
3758
|
} else {
|
|
3081
|
-
console.log(
|
|
3759
|
+
console.log(colors.yellow(` Root not found: ${uri}`));
|
|
3082
3760
|
}
|
|
3083
3761
|
}
|
|
3084
3762
|
async function cmdReconnect(target) {
|
|
3085
|
-
console.log(
|
|
3763
|
+
console.log(colors.cyan("\u27F3 Disconnecting..."));
|
|
3086
3764
|
await target.close();
|
|
3087
|
-
await new Promise((
|
|
3088
|
-
console.log(
|
|
3765
|
+
await new Promise((resolve3) => setTimeout(resolve3, 200));
|
|
3766
|
+
console.log(colors.cyan("\u27F3 Reconnecting..."));
|
|
3089
3767
|
const { command, args } = target.getStatus();
|
|
3090
|
-
console.log(
|
|
3768
|
+
console.log(colors.dim(` Command: ${command} ${args.join(" ")}`));
|
|
3091
3769
|
try {
|
|
3092
3770
|
await target.connect();
|
|
3093
3771
|
const s = target.getStatus();
|
|
3094
|
-
console.log(
|
|
3772
|
+
console.log(colors.green(`\u2713 Reconnected (PID: ${s.pid})`));
|
|
3095
3773
|
const { tools } = await target.listTools();
|
|
3096
|
-
console.log(
|
|
3774
|
+
console.log(colors.cyan(` ${tools.length} tool(s) available.
|
|
3097
3775
|
`));
|
|
3098
3776
|
await refreshCaches(target);
|
|
3099
3777
|
} catch (err) {
|
|
3100
|
-
console.error(
|
|
3778
|
+
console.error(colors.red(`\u2717 Failed to reconnect: ${err.message}`));
|
|
3101
3779
|
}
|
|
3102
3780
|
}
|
|
3103
3781
|
function cmdTiming() {
|
|
3104
3782
|
if (callHistory.length === 0) {
|
|
3105
|
-
console.log(
|
|
3783
|
+
console.log(colors.dim(" No tool calls recorded yet."));
|
|
3106
3784
|
return;
|
|
3107
3785
|
}
|
|
3108
3786
|
const groups = /* @__PURE__ */ new Map();
|
|
@@ -3112,8 +3790,8 @@ function cmdTiming() {
|
|
|
3112
3790
|
groups.set(record.toolName, list);
|
|
3113
3791
|
}
|
|
3114
3792
|
const nameWidth = Math.max(8, ...[...groups.keys()].map((n) => n.length));
|
|
3115
|
-
console.log(
|
|
3116
|
-
console.log(
|
|
3793
|
+
console.log(colors.bold("\n Tool Call Performance"));
|
|
3794
|
+
console.log(colors.dim(` ${"\u2500".repeat(nameWidth + 50)}`));
|
|
3117
3795
|
let totalCalls = 0;
|
|
3118
3796
|
let totalMs = 0;
|
|
3119
3797
|
let slowestName = "";
|
|
@@ -3121,163 +3799,59 @@ function cmdTiming() {
|
|
|
3121
3799
|
for (const [name, durations] of groups) {
|
|
3122
3800
|
const count = durations.length;
|
|
3123
3801
|
totalCalls += count;
|
|
3124
|
-
const sorted = [...durations].sort((a, b) => a - b);
|
|
3125
|
-
const avg = Math.round(sorted.reduce((a, b) => a + b, 0) / count);
|
|
3126
|
-
const p95 = sorted[Math.floor(count * 0.95)];
|
|
3127
|
-
const max = sorted[sorted.length - 1];
|
|
3128
|
-
totalMs += sorted.reduce((a, b) => a + b, 0);
|
|
3129
|
-
if (max > slowestMs) {
|
|
3130
|
-
slowestMs = max;
|
|
3131
|
-
slowestName = name;
|
|
3132
|
-
}
|
|
3133
|
-
console.log(
|
|
3134
|
-
` ${
|
|
3135
|
-
);
|
|
3136
|
-
}
|
|
3137
|
-
const overallAvg = Math.round(totalMs / totalCalls);
|
|
3138
|
-
console.log(
|
|
3139
|
-
|
|
3140
|
-
`
|
|
3141
|
-
Total: ${totalCalls} call(s), avg ${overallAvg}ms, slowest: ${slowestName} (${slowestMs}ms)`
|
|
3142
|
-
)
|
|
3143
|
-
);
|
|
3144
|
-
console.log();
|
|
3145
|
-
}
|
|
3146
|
-
function cmdStatus(target) {
|
|
3147
|
-
const s = target.getStatus();
|
|
3148
|
-
const uptimeStr = s.uptime >= 60 ? `${Math.floor(s.uptime / 60)}m ${(s.uptime % 60).toFixed(0)}s` : `${s.uptime.toFixed(1)}s`;
|
|
3149
|
-
const lastRespStr = s.lastResponseTime ? `${((Date.now() - s.lastResponseTime) / 1e3).toFixed(1)}s ago` : "never";
|
|
3150
|
-
console.log(pc2.bold("\n Target Server Status"));
|
|
3151
|
-
console.log(` ${pc2.dim("Connected:")} ${s.connected ? pc2.green("yes") : pc2.red("no")}`);
|
|
3152
|
-
console.log(` ${pc2.dim("PID:")} ${s.pid ?? "N/A"}`);
|
|
3153
|
-
console.log(` ${pc2.dim("Uptime:")} ${uptimeStr}`);
|
|
3154
|
-
console.log(` ${pc2.dim("Last response:")} ${lastRespStr}`);
|
|
3155
|
-
console.log(` ${pc2.dim("Stderr lines:")} ${s.stderrLineCount.toLocaleString()}`);
|
|
3156
|
-
console.log(` ${pc2.dim("Reconnects:")} ${s.reconnectAttempts}/${s.maxReconnectAttempts}`);
|
|
3157
|
-
console.log(` ${pc2.dim("Command:")} ${s.command} ${s.args.join(" ")}`);
|
|
3158
|
-
console.log();
|
|
3159
|
-
}
|
|
3160
|
-
function printResultBlock(opts) {
|
|
3161
|
-
const colorFn = pc2[opts.labelColor];
|
|
3162
|
-
const elapsedStr = opts.elapsed < 1e3 ? `${opts.elapsed}ms` : `${(opts.elapsed / 1e3).toFixed(1)}s`;
|
|
3163
|
-
const detail = opts.detail ?? opts.toolName ?? "";
|
|
3164
|
-
console.log(` ${colorFn(opts.label)} ${pc2.dim(detail)} ${pc2.dim(`(${elapsedStr})`)}`);
|
|
3165
|
-
console.log(pc2.dim(` ${"\u2500".repeat(60)}`));
|
|
3166
|
-
}
|
|
3167
|
-
function printShortHelp() {
|
|
3168
|
-
const hasTools = !!activeCapabilities?.tools;
|
|
3169
|
-
const hasResources = !!activeCapabilities?.resources;
|
|
3170
|
-
const hasPrompts = !!activeCapabilities?.prompts;
|
|
3171
|
-
const tC = hasTools ? pc2.green : pc2.dim;
|
|
3172
|
-
const rC = hasResources ? pc2.green : pc2.dim;
|
|
3173
|
-
const pC = hasPrompts ? pc2.green : pc2.dim;
|
|
3174
|
-
console.log(`
|
|
3175
|
-
${pc2.bold("Quick Reference:")}
|
|
3176
|
-
|
|
3177
|
-
${tC("tl")} ${tC("tools/list")} ${rC("rl")} ${rC("resources/list")} ${pC("pl")} ${pC("prompts/list")}
|
|
3178
|
-
${tC("td")} ${tC("tools/describe")} ${rC("rr")} ${rC("resources/read")} ${pC("pg")} ${pC("prompts/get")}
|
|
3179
|
-
${tC("tc")} ${tC("tools/call")} ${rC("rt")} ${rC("resources/templates")}
|
|
3180
|
-
${tC("ts")} ${tC("tools/scaffold")} ${rC("rs")} ${rC("resources/subscribe")}
|
|
3181
|
-
${rC("ru")} ${rC("resources/unsubscribe")}
|
|
3182
|
-
|
|
3183
|
-
${pc2.green("ping")} ${pc2.green("status")} ${pc2.green("timing")} ${pc2.green("history")} ${pc2.green("!!")} ${pc2.green("explore")} ${pc2.green("reconnect")}
|
|
3184
|
-
|
|
3185
|
-
${pc2.dim("Type 'help' for full command reference.")}
|
|
3186
|
-
`);
|
|
3187
|
-
}
|
|
3188
|
-
function printHelp() {
|
|
3189
|
-
const hasTools = !!activeCapabilities?.tools;
|
|
3190
|
-
const hasResources = !!activeCapabilities?.resources;
|
|
3191
|
-
const hasPrompts = !!activeCapabilities?.prompts;
|
|
3192
|
-
const hasLogging = !!activeCapabilities?.logging;
|
|
3193
|
-
const tC = hasTools ? pc2.green : pc2.dim;
|
|
3194
|
-
const rC = hasResources ? pc2.green : pc2.dim;
|
|
3195
|
-
const pC = hasPrompts ? pc2.green : pc2.dim;
|
|
3196
|
-
const lC = hasLogging ? pc2.green : pc2.dim;
|
|
3197
|
-
const tD = hasTools ? (s) => s : pc2.dim;
|
|
3198
|
-
const rD = hasResources ? (s) => s : pc2.dim;
|
|
3199
|
-
const pD = hasPrompts ? (s) => s : pc2.dim;
|
|
3200
|
-
const lD = hasLogging ? (s) => s : pc2.dim;
|
|
3201
|
-
const tH = hasTools ? pc2.bold("Tool Commands:") : pc2.dim(pc2.bold("Tool Commands:")) + pc2.dim(" (Unsupported)");
|
|
3202
|
-
const rH = hasResources ? pc2.bold("Resource Commands:") : pc2.dim(pc2.bold("Resource Commands:")) + pc2.dim(" (Unsupported)");
|
|
3203
|
-
const pH = hasPrompts ? pc2.bold("Prompt Commands:") : pc2.dim(pc2.bold("Prompt Commands:")) + pc2.dim(" (Unsupported)");
|
|
3204
|
-
console.log(`
|
|
3205
|
-
${tH}
|
|
3206
|
-
|
|
3207
|
-
${tC("tools/list")} ${tD("List all available tools")}
|
|
3208
|
-
${tC("tools/describe")} <name> ${tD("Show a tool's input schema")}
|
|
3209
|
-
${tC("tools/call")} <name> [json] [opts] ${tD("Call a tool (interactive if no json)")}
|
|
3210
|
-
${tD("Options:")} ${pc2.dim("--timeout <ms>")} ${tD("Override default timeout (60s)")}
|
|
3211
|
-
${pc2.dim("--clear")} ${tD("Ignore remembered argument defaults")}
|
|
3212
|
-
${tC("tools/scaffold")} <name> ${tD("Generate a template for a tool's arguments")}
|
|
3213
|
-
${tC("tools/forget")} [name] ${tD("Clear remembered interactive defaults")}
|
|
3214
|
-
|
|
3215
|
-
${rH}
|
|
3216
|
-
|
|
3217
|
-
${rC("resources/list")} ${rD("List all available resources")}
|
|
3218
|
-
${rC("resources/read")} <uri> ${rD("Read a resource by URI")}
|
|
3219
|
-
${rC("resources/templates")} ${rD("List resource templates")}
|
|
3220
|
-
${rC("resources/subscribe")} <uri> ${rD("Subscribe to resource changes")}
|
|
3221
|
-
${rC("resources/unsubscribe")} <uri> ${rD("Unsubscribe from resource changes")}
|
|
3222
|
-
|
|
3223
|
-
${pH}
|
|
3224
|
-
|
|
3225
|
-
${pC("prompts/list")} ${pD("List all available prompts")}
|
|
3226
|
-
${pC("prompts/get")} <name> [json_args] ${pD("Get a prompt with arguments")}
|
|
3227
|
-
|
|
3228
|
-
${pc2.bold("Protocol Commands:")}
|
|
3229
|
-
|
|
3230
|
-
${pc2.green("ping")} Verify connection, show round-trip time
|
|
3231
|
-
${lC("log-level")} <level> ${lD("Set server logging verbosity")}${hasLogging ? "" : pc2.dim(" (Unsupported)")}
|
|
3232
|
-
${pc2.green("history")} [count|clear] Show request/response history
|
|
3233
|
-
${pc2.green("notifications")} [count|clear] Show server notifications
|
|
3234
|
-
|
|
3235
|
-
${pc2.bold("Roots Management:")}
|
|
3236
|
-
|
|
3237
|
-
${pc2.green("roots/list")} Show configured client roots
|
|
3238
|
-
${pc2.green("roots/add")} <uri> [name] Add a root directory
|
|
3239
|
-
${pc2.green("roots/remove")} <uri> Remove a root directory
|
|
3240
|
-
|
|
3241
|
-
${pc2.bold("Session Commands:")}
|
|
3242
|
-
|
|
3243
|
-
${pc2.green("!!")} / ${pc2.green("last")} Re-run the last command
|
|
3244
|
-
${pc2.green("reconnect")} Disconnect and reconnect to the server
|
|
3245
|
-
${pc2.green("timing")} Show tool call performance stats
|
|
3246
|
-
${pc2.green("status")} Show target server status
|
|
3247
|
-
${pc2.green("help")} Show this help
|
|
3248
|
-
${pc2.green("exit")} / ${pc2.green("quit")} Disconnect and exit
|
|
3249
|
-
|
|
3250
|
-
${pc2.bold("Shortcuts:")}
|
|
3251
|
-
|
|
3252
|
-
${tC("tl")} ${tC("tools/list")} ${rC("rl")} ${rC("resources/list")} ${pC("pl")} ${pC("prompts/list")}
|
|
3253
|
-
${tC("td")} ${tC("tools/describe")} ${rC("rr")} ${rC("resources/read")} ${pC("pg")} ${pC("prompts/get")}
|
|
3254
|
-
${tC("tc")} ${tC("tools/call")} ${rC("rt")} ${rC("resources/templates")}
|
|
3255
|
-
${tC("ts")} ${tC("tools/scaffold")} ${rC("rs")} ${rC("resources/subscribe")}
|
|
3256
|
-
${rC("ru")} ${rC("resources/unsubscribe")}
|
|
3257
|
-
|
|
3258
|
-
${pc2.dim("Lines starting with # are treated as comments.")}
|
|
3259
|
-
${pc2.dim('JSON arguments can contain spaces: tools/call say {"message": "hello world"}')}
|
|
3260
|
-
${pc2.dim("Run tools/call <name> without JSON for interactive argument prompting.")}
|
|
3261
|
-
${pc2.dim("Use tools/call <name> --clear to ignore remembered defaults.")}
|
|
3262
|
-
`);
|
|
3802
|
+
const sorted = [...durations].sort((a, b) => a - b);
|
|
3803
|
+
const avg = Math.round(sorted.reduce((a, b) => a + b, 0) / count);
|
|
3804
|
+
const p95 = sorted[Math.floor(count * 0.95)];
|
|
3805
|
+
const max = sorted[sorted.length - 1];
|
|
3806
|
+
totalMs += sorted.reduce((a, b) => a + b, 0);
|
|
3807
|
+
if (max > slowestMs) {
|
|
3808
|
+
slowestMs = max;
|
|
3809
|
+
slowestName = name;
|
|
3810
|
+
}
|
|
3811
|
+
console.log(
|
|
3812
|
+
` ${colors.green(name.padEnd(nameWidth))} \xD7 ${count} avg: ${avg}ms p95: ${p95}ms max: ${max}ms`
|
|
3813
|
+
);
|
|
3814
|
+
}
|
|
3815
|
+
const overallAvg = Math.round(totalMs / totalCalls);
|
|
3816
|
+
console.log(
|
|
3817
|
+
colors.dim(
|
|
3818
|
+
`
|
|
3819
|
+
Total: ${totalCalls} call(s), avg ${overallAvg}ms, slowest: ${slowestName} (${slowestMs}ms)`
|
|
3820
|
+
)
|
|
3821
|
+
);
|
|
3822
|
+
console.log();
|
|
3263
3823
|
}
|
|
3264
|
-
|
|
3265
|
-
const
|
|
3266
|
-
|
|
3824
|
+
function cmdStatus(target) {
|
|
3825
|
+
const s = target.getStatus();
|
|
3826
|
+
const uptimeStr = s.uptime >= 60 ? `${Math.floor(s.uptime / 60)}m ${(s.uptime % 60).toFixed(0)}s` : `${s.uptime.toFixed(1)}s`;
|
|
3827
|
+
const lastRespStr = s.lastResponseTime ? `${((Date.now() - s.lastResponseTime) / 1e3).toFixed(1)}s ago` : "never";
|
|
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(" ")}`);
|
|
3836
|
+
console.log();
|
|
3267
3837
|
}
|
|
3268
3838
|
function cmdToolsForget(rest) {
|
|
3269
3839
|
const toolName = rest.trim();
|
|
3270
3840
|
if (toolName) {
|
|
3271
3841
|
if (lastToolArgsMap.has(toolName)) {
|
|
3272
3842
|
lastToolArgsMap.delete(toolName);
|
|
3273
|
-
|
|
3843
|
+
saveWizardDefaults().catch(() => {
|
|
3844
|
+
});
|
|
3845
|
+
console.log(colors.green(` Cleared remembered args for ${colors.bold(toolName)}.`));
|
|
3274
3846
|
} else {
|
|
3275
|
-
console.log(
|
|
3847
|
+
console.log(colors.yellow(` No remembered args for "${toolName}".`));
|
|
3276
3848
|
}
|
|
3277
3849
|
} else {
|
|
3278
3850
|
const count = lastToolArgsMap.size;
|
|
3279
3851
|
lastToolArgsMap.clear();
|
|
3280
|
-
|
|
3852
|
+
saveWizardDefaults().catch(() => {
|
|
3853
|
+
});
|
|
3854
|
+
console.log(colors.green(` Cleared remembered args for ${count} tool${count === 1 ? "" : "s"}.`));
|
|
3281
3855
|
}
|
|
3282
3856
|
}
|
|
3283
3857
|
async function pickInteractive(items, message) {
|
|
@@ -3377,55 +3951,416 @@ async function showMainMenu(target, interceptor) {
|
|
|
3377
3951
|
);
|
|
3378
3952
|
}
|
|
3379
3953
|
});
|
|
3380
|
-
if (answer.type === "tool") {
|
|
3381
|
-
await cmdToolsCall(target, interceptor, answer.name);
|
|
3382
|
-
} else if (answer.type === "resource") {
|
|
3383
|
-
await cmdResourcesRead(target, answer.uri);
|
|
3384
|
-
} else if (answer.type === "prompt") {
|
|
3385
|
-
await cmdPromptsGet(target, answer.name);
|
|
3386
|
-
} else if (answer.type === "command") {
|
|
3387
|
-
if (answer.name === "status") {
|
|
3388
|
-
cmdStatus(target);
|
|
3389
|
-
} else if (answer.name === "reconnect") {
|
|
3390
|
-
await cmdReconnect(target);
|
|
3391
|
-
} else if (answer.name === "exit") {
|
|
3392
|
-
console.log(
|
|
3393
|
-
await target.close();
|
|
3394
|
-
process.exit(0);
|
|
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;
|
|
4257
|
+
try {
|
|
4258
|
+
const { tools } = await target.listTools();
|
|
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 {
|
|
3395
4266
|
}
|
|
3396
|
-
} else if (answer.type === "raw") {
|
|
3397
|
-
return true;
|
|
3398
4267
|
}
|
|
3399
|
-
|
|
3400
|
-
|
|
3401
|
-
|
|
3402
|
-
|
|
3403
|
-
|
|
3404
|
-
|
|
4268
|
+
if (caps.prompts) {
|
|
4269
|
+
try {
|
|
4270
|
+
const { prompts } = await target.listPrompts();
|
|
4271
|
+
promptCount = prompts.length;
|
|
4272
|
+
} catch {
|
|
4273
|
+
}
|
|
3405
4274
|
}
|
|
3406
|
-
|
|
3407
|
-
|
|
3408
|
-
|
|
3409
|
-
|
|
3410
|
-
|
|
3411
|
-
|
|
3412
|
-
|
|
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)
|
|
4283
|
+
);
|
|
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();
|
|
4289
|
+
}
|
|
4290
|
+
}
|
|
4291
|
+
} catch (err) {
|
|
4292
|
+
console.log(colors.yellow(` Warning: Could not list tools: ${err.message}
|
|
4293
|
+
`));
|
|
3413
4294
|
}
|
|
3414
|
-
|
|
3415
|
-
|
|
3416
|
-
|
|
3417
|
-
|
|
3418
|
-
|
|
3419
|
-
|
|
3420
|
-
|
|
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;
|
|
3421
4307
|
}
|
|
3422
|
-
|
|
3423
|
-
|
|
3424
|
-
|
|
3425
|
-
|
|
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;
|
|
3426
4352
|
}
|
|
4353
|
+
console.log(colors.dim("\nShutting down..."));
|
|
4354
|
+
await target.close();
|
|
4355
|
+
process.exit(0);
|
|
4356
|
+
} else {
|
|
4357
|
+
mainMenuLoop(target, interceptor);
|
|
3427
4358
|
}
|
|
3428
4359
|
}
|
|
4360
|
+
async function readScriptLines(filepath) {
|
|
4361
|
+
const content = await readFile4(filepath, "utf8");
|
|
4362
|
+
return content.split("\n");
|
|
4363
|
+
}
|
|
3429
4364
|
|
|
3430
4365
|
// src/server.ts
|
|
3431
4366
|
import { createHash } from "crypto";
|
|
@@ -3490,7 +4425,7 @@ async function startServer(opts) {
|
|
|
3490
4425
|
mediaThresholdKb: opts.mediaThresholdKb
|
|
3491
4426
|
});
|
|
3492
4427
|
const mcpServer = new McpServer(
|
|
3493
|
-
{ name: "run-mcp", version: "1.
|
|
4428
|
+
{ name: "run-mcp", version: "1.7.0" },
|
|
3494
4429
|
{
|
|
3495
4430
|
capabilities: {
|
|
3496
4431
|
tools: {},
|
|
@@ -3651,7 +4586,15 @@ async function startServer(opts) {
|
|
|
3651
4586
|
process.env[key] = value;
|
|
3652
4587
|
}
|
|
3653
4588
|
}
|
|
3654
|
-
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
|
+
});
|
|
3655
4598
|
setupTargetListeners(target);
|
|
3656
4599
|
try {
|
|
3657
4600
|
await target.connect();
|
|
@@ -3755,10 +4698,11 @@ async function startServer(opts) {
|
|
|
3755
4698
|
),
|
|
3756
4699
|
summary: z.boolean().optional().describe(
|
|
3757
4700
|
"If true, returns only the name and description of each primitive (omitting full schemas) when included to save tokens."
|
|
3758
|
-
)
|
|
4701
|
+
),
|
|
4702
|
+
sandbox: z.enum(["auto", "docker", "native", "audit", "none"]).optional().describe("Sandbox mode to use for this server")
|
|
3759
4703
|
}
|
|
3760
4704
|
},
|
|
3761
|
-
async ({ command, args, env, include, summary }) => {
|
|
4705
|
+
async ({ command, args, env, include, summary, sandbox }) => {
|
|
3762
4706
|
if (target?.connected) {
|
|
3763
4707
|
return {
|
|
3764
4708
|
content: [
|
|
@@ -3780,7 +4724,15 @@ async function startServer(opts) {
|
|
|
3780
4724
|
process.env[key] = value;
|
|
3781
4725
|
}
|
|
3782
4726
|
}
|
|
3783
|
-
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
|
+
});
|
|
3784
4736
|
setupTargetListeners(target);
|
|
3785
4737
|
try {
|
|
3786
4738
|
await target.connect();
|
|
@@ -4115,6 +5067,7 @@ Available: ${available}`
|
|
|
4115
5067
|
type: z.enum(["tool", "resource", "prompt"]).describe("The MCP primitive type to invoke"),
|
|
4116
5068
|
name: z.string().describe("Tool name, resource URI, or prompt name"),
|
|
4117
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')"),
|
|
4118
5071
|
// Auto-connect params (only needed if not already connected)
|
|
4119
5072
|
auto_connect: z.object({
|
|
4120
5073
|
command: z.string().describe("Command to spawn the server (e.g. 'node')."),
|
|
@@ -4138,12 +5091,14 @@ Available: ${available}`
|
|
|
4138
5091
|
type: primitiveType,
|
|
4139
5092
|
name,
|
|
4140
5093
|
arguments: callArgs,
|
|
5094
|
+
args: callArgsAlias,
|
|
4141
5095
|
auto_connect,
|
|
4142
5096
|
disconnect_after,
|
|
4143
5097
|
timeout_ms,
|
|
4144
5098
|
include_metadata,
|
|
4145
5099
|
max_text_length
|
|
4146
5100
|
}) => {
|
|
5101
|
+
const finalArgs = callArgs ?? callArgsAlias;
|
|
4147
5102
|
try {
|
|
4148
5103
|
const connectError = await ensureConnected(
|
|
4149
5104
|
auto_connect?.command,
|
|
@@ -4193,14 +5148,14 @@ Available tools: ${toolNames.join(", ")}`
|
|
|
4193
5148
|
}
|
|
4194
5149
|
const schema = matchedTool.inputSchema;
|
|
4195
5150
|
const requiredProps = schema?.required ?? [];
|
|
4196
|
-
const providedKeys = Object.keys(
|
|
5151
|
+
const providedKeys = Object.keys(finalArgs ?? {});
|
|
4197
5152
|
const missingProps = requiredProps.filter((p) => !providedKeys.includes(p));
|
|
4198
5153
|
if (missingProps.length > 0) {
|
|
4199
5154
|
return {
|
|
4200
5155
|
content: [
|
|
4201
5156
|
{
|
|
4202
5157
|
type: "text",
|
|
4203
|
-
text: `Tool "${name}" requires: ${missingProps.join(", ")}. Received: ${JSON.stringify(
|
|
5158
|
+
text: `Tool "${name}" requires: ${missingProps.join(", ")}. Received: ${JSON.stringify(finalArgs ?? {})}`
|
|
4204
5159
|
}
|
|
4205
5160
|
],
|
|
4206
5161
|
isError: true
|
|
@@ -4214,7 +5169,7 @@ Available tools: ${toolNames.join(", ")}`
|
|
|
4214
5169
|
const { result: toolResult, metadata } = await interceptor.callToolWithMetadata(
|
|
4215
5170
|
target,
|
|
4216
5171
|
name,
|
|
4217
|
-
|
|
5172
|
+
finalArgs ?? {},
|
|
4218
5173
|
timeout_ms,
|
|
4219
5174
|
max_text_length
|
|
4220
5175
|
);
|
|
@@ -4224,7 +5179,7 @@ Available tools: ${toolNames.join(", ")}`
|
|
|
4224
5179
|
result = await interceptor.callTool(
|
|
4225
5180
|
target,
|
|
4226
5181
|
name,
|
|
4227
|
-
|
|
5182
|
+
finalArgs ?? {},
|
|
4228
5183
|
timeout_ms,
|
|
4229
5184
|
max_text_length
|
|
4230
5185
|
);
|
|
@@ -4248,6 +5203,7 @@ Available tools: ${toolNames.join(", ")}`
|
|
|
4248
5203
|
text: `--- metadata ---
|
|
4249
5204
|
${JSON.stringify(meta)}`
|
|
4250
5205
|
});
|
|
5206
|
+
result.meta = meta;
|
|
4251
5207
|
}
|
|
4252
5208
|
break;
|
|
4253
5209
|
}
|
|
@@ -4279,6 +5235,7 @@ ${JSON.stringify(meta)}`
|
|
|
4279
5235
|
text: `--- metadata ---
|
|
4280
5236
|
${JSON.stringify(meta)}`
|
|
4281
5237
|
});
|
|
5238
|
+
result.meta = meta;
|
|
4282
5239
|
}
|
|
4283
5240
|
break;
|
|
4284
5241
|
}
|
|
@@ -4308,7 +5265,7 @@ Available prompts: ${promptNames.join(", ")}`
|
|
|
4308
5265
|
target,
|
|
4309
5266
|
{
|
|
4310
5267
|
name,
|
|
4311
|
-
arguments:
|
|
5268
|
+
arguments: finalArgs ?? {}
|
|
4312
5269
|
},
|
|
4313
5270
|
timeout_ms,
|
|
4314
5271
|
max_text_length
|
|
@@ -4347,6 +5304,7 @@ ${item.text}` });
|
|
|
4347
5304
|
text: `--- metadata ---
|
|
4348
5305
|
${JSON.stringify(meta)}`
|
|
4349
5306
|
});
|
|
5307
|
+
result.meta = meta;
|
|
4350
5308
|
}
|
|
4351
5309
|
break;
|
|
4352
5310
|
}
|
|
@@ -4396,6 +5354,230 @@ ${JSON.stringify(meta)}`
|
|
|
4396
5354
|
};
|
|
4397
5355
|
}
|
|
4398
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
|
+
);
|
|
4399
5581
|
const transport = new StdioServerTransport();
|
|
4400
5582
|
mcpServer.server.onclose = async () => {
|
|
4401
5583
|
if (target) {
|
|
@@ -4410,7 +5592,8 @@ ${JSON.stringify(meta)}`
|
|
|
4410
5592
|
|
|
4411
5593
|
// src/index.ts
|
|
4412
5594
|
function requireTargetCommand(targetCommand, subcommandUsage) {
|
|
4413
|
-
|
|
5595
|
+
const target = activeTargetCommand ?? targetCommand;
|
|
5596
|
+
if (!target || target.length === 0) {
|
|
4414
5597
|
process.stderr.write(`Error: Target server command must be separated by '--'.
|
|
4415
5598
|
`);
|
|
4416
5599
|
process.stderr.write(`This avoids option parsing conflicts.
|
|
@@ -4418,19 +5601,19 @@ function requireTargetCommand(targetCommand, subcommandUsage) {
|
|
|
4418
5601
|
`);
|
|
4419
5602
|
process.stderr.write(`Usage: ${subcommandUsage}
|
|
4420
5603
|
`);
|
|
4421
|
-
process.exit(
|
|
5604
|
+
process.exit(64);
|
|
4422
5605
|
}
|
|
4423
|
-
return
|
|
5606
|
+
return target;
|
|
4424
5607
|
}
|
|
4425
|
-
var SESSION_DIR =
|
|
5608
|
+
var SESSION_DIR = join6(tmpdir3(), "run-mcp", "sessions");
|
|
4426
5609
|
function getSessionPath(name) {
|
|
4427
|
-
return
|
|
5610
|
+
return join6(SESSION_DIR, `${name}.json`);
|
|
4428
5611
|
}
|
|
4429
5612
|
async function getSession(name) {
|
|
4430
5613
|
const path2 = getSessionPath(name);
|
|
4431
|
-
if (!
|
|
5614
|
+
if (!existsSync6(path2)) return null;
|
|
4432
5615
|
try {
|
|
4433
|
-
const data = await
|
|
5616
|
+
const data = await readFile5(path2, "utf8");
|
|
4434
5617
|
const parsed = JSON.parse(data);
|
|
4435
5618
|
try {
|
|
4436
5619
|
process.kill(parsed.pid, 0);
|
|
@@ -4445,7 +5628,7 @@ async function getSession(name) {
|
|
|
4445
5628
|
}
|
|
4446
5629
|
}
|
|
4447
5630
|
function sendDaemonRequest(port, request) {
|
|
4448
|
-
return new Promise((
|
|
5631
|
+
return new Promise((resolve3, reject) => {
|
|
4449
5632
|
const socket = createConnection({ port });
|
|
4450
5633
|
let buffer = "";
|
|
4451
5634
|
socket.on("connect", () => {
|
|
@@ -4460,7 +5643,7 @@ function sendDaemonRequest(port, request) {
|
|
|
4460
5643
|
if (parsed.error) {
|
|
4461
5644
|
reject(new Error(parsed.error.message));
|
|
4462
5645
|
} else {
|
|
4463
|
-
|
|
5646
|
+
resolve3(parsed.result);
|
|
4464
5647
|
}
|
|
4465
5648
|
} catch (err) {
|
|
4466
5649
|
reject(new Error(`Failed to parse daemon response: ${err}`));
|
|
@@ -4482,11 +5665,16 @@ async function handleHeadlessSession(sessionName, targetCommand, operation, opts
|
|
|
4482
5665
|
`);
|
|
4483
5666
|
process.stderr.write(`Usage: ${subcommandUsage}
|
|
4484
5667
|
`);
|
|
4485
|
-
process.exit(
|
|
5668
|
+
process.exit(64);
|
|
4486
5669
|
}
|
|
4487
5670
|
const target = activeTargetCommand;
|
|
4488
|
-
const binPath =
|
|
4489
|
-
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], {
|
|
4490
5678
|
detached: true,
|
|
4491
5679
|
stdio: "ignore"
|
|
4492
5680
|
});
|
|
@@ -4495,7 +5683,7 @@ async function handleHeadlessSession(sessionName, targetCommand, operation, opts
|
|
|
4495
5683
|
while (attempts < 50) {
|
|
4496
5684
|
session = await getSession(sessionName);
|
|
4497
5685
|
if (session) break;
|
|
4498
|
-
await new Promise((
|
|
5686
|
+
await new Promise((resolve3) => setTimeout(resolve3, 100));
|
|
4499
5687
|
attempts++;
|
|
4500
5688
|
}
|
|
4501
5689
|
if (!session) {
|
|
@@ -4528,7 +5716,14 @@ function parseHeadlessOpts(opts) {
|
|
|
4528
5716
|
timeoutMs: opts.timeout ? Number.parseInt(opts.timeout, 10) : void 0,
|
|
4529
5717
|
raw: opts.raw,
|
|
4530
5718
|
showStderr: opts.showStderr,
|
|
4531
|
-
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
|
|
4532
5727
|
};
|
|
4533
5728
|
}
|
|
4534
5729
|
var activeTargetCommand;
|
|
@@ -4542,7 +5737,7 @@ program.enablePositionalOptions();
|
|
|
4542
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(
|
|
4543
5738
|
"-m, --media-threshold <kb>",
|
|
4544
5739
|
"Media size threshold in KB to save to disk (0 to always save, -1 to keep inline)"
|
|
4545
|
-
).option("--raw", "Print the full result object including metadata").option("--show-stderr", "Stream target server stderr to process stderr").option("--session <name>", "Persistent session name").allowUnknownOption().action(
|
|
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(
|
|
4546
5741
|
async (tool, jsonArgs, targetCommand, opts) => {
|
|
4547
5742
|
const operation = { type: "call", tool, args: jsonArgs };
|
|
4548
5743
|
const parsedOpts = parseHeadlessOpts(opts);
|
|
@@ -4563,7 +5758,7 @@ program.command("call").argument("<tool>", "Tool name to call").argument("[json_
|
|
|
4563
5758
|
}
|
|
4564
5759
|
}
|
|
4565
5760
|
);
|
|
4566
|
-
program.command("list-tools").argument("[target_command...]", "Target server command (after --)").description("List all tools on a target MCP server as JSON").option("--show-stderr", "Stream target server stderr to process stderr").option("--session <name>", "Persistent session name").allowUnknownOption().action(async (targetCommand, opts) => {
|
|
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) => {
|
|
4567
5762
|
const operation = { type: "list-tools" };
|
|
4568
5763
|
const parsedOpts = parseHeadlessOpts(opts);
|
|
4569
5764
|
if (opts.session) {
|
|
@@ -4582,7 +5777,7 @@ program.command("list-tools").argument("[target_command...]", "Target server com
|
|
|
4582
5777
|
await runHeadless(target, operation, parsedOpts);
|
|
4583
5778
|
}
|
|
4584
5779
|
});
|
|
4585
|
-
program.command("list-resources").argument("[target_command...]", "Target server command (after --)").description("List all resources on a target MCP server as JSON").option("--show-stderr", "Stream target server stderr to process stderr").option("--session <name>", "Persistent session name").allowUnknownOption().action(async (targetCommand, opts) => {
|
|
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) => {
|
|
4586
5781
|
const operation = { type: "list-resources" };
|
|
4587
5782
|
const parsedOpts = parseHeadlessOpts(opts);
|
|
4588
5783
|
if (opts.session) {
|
|
@@ -4601,7 +5796,7 @@ program.command("list-resources").argument("[target_command...]", "Target server
|
|
|
4601
5796
|
await runHeadless(target, operation, parsedOpts);
|
|
4602
5797
|
}
|
|
4603
5798
|
});
|
|
4604
|
-
program.command("list-prompts").argument("[target_command...]", "Target server command (after --)").description("List all prompts on a target MCP server as JSON").option("--show-stderr", "Stream target server stderr to process stderr").option("--session <name>", "Persistent session name").allowUnknownOption().action(async (targetCommand, opts) => {
|
|
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) => {
|
|
4605
5800
|
const operation = { type: "list-prompts" };
|
|
4606
5801
|
const parsedOpts = parseHeadlessOpts(opts);
|
|
4607
5802
|
if (opts.session) {
|
|
@@ -4623,7 +5818,7 @@ program.command("list-prompts").argument("[target_command...]", "Target server c
|
|
|
4623
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(
|
|
4624
5819
|
"-m, --media-threshold <kb>",
|
|
4625
5820
|
"Media size threshold in KB to save to disk (0 to always save, -1 to keep inline)"
|
|
4626
|
-
).option("--show-stderr", "Stream target server stderr to process stderr").option("--session <name>", "Persistent session name").allowUnknownOption().action(async (uri, targetCommand, opts) => {
|
|
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) => {
|
|
4627
5822
|
const operation = { type: "read", uri };
|
|
4628
5823
|
const parsedOpts = parseHeadlessOpts(opts);
|
|
4629
5824
|
if (opts.session) {
|
|
@@ -4642,7 +5837,7 @@ program.command("read").argument("<uri>", "Resource URI to read").argument("[tar
|
|
|
4642
5837
|
await runHeadless(target, operation, parsedOpts);
|
|
4643
5838
|
}
|
|
4644
5839
|
});
|
|
4645
|
-
program.command("describe").argument("<tool>", "Tool name to describe").argument("[target_command...]", "Target server command (after --)").description("Print a tool's full schema as JSON").option("--show-stderr", "Stream target server stderr to process stderr").option("--session <name>", "Persistent session name").allowUnknownOption().action(async (tool, targetCommand, opts) => {
|
|
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) => {
|
|
4646
5841
|
const operation = { type: "describe", tool };
|
|
4647
5842
|
const parsedOpts = parseHeadlessOpts(opts);
|
|
4648
5843
|
if (opts.session) {
|
|
@@ -4664,7 +5859,7 @@ program.command("describe").argument("<tool>", "Tool name to describe").argument
|
|
|
4664
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(
|
|
4665
5860
|
"-m, --media-threshold <kb>",
|
|
4666
5861
|
"Media size threshold in KB to save to disk (0 to always save, -1 to keep inline)"
|
|
4667
|
-
).option("--show-stderr", "Stream target server stderr to process stderr").option("--session <name>", "Persistent session name").allowUnknownOption().action(
|
|
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(
|
|
4668
5863
|
async (name, jsonArgs, targetCommand, opts) => {
|
|
4669
5864
|
const operation = { type: "get-prompt", name, args: jsonArgs };
|
|
4670
5865
|
const parsedOpts = parseHeadlessOpts(opts);
|
|
@@ -4685,17 +5880,19 @@ program.command("get-prompt").argument("<name>", "Prompt name").argument("[json_
|
|
|
4685
5880
|
}
|
|
4686
5881
|
}
|
|
4687
5882
|
);
|
|
4688
|
-
program.command("daemon").argument("<session_name>", "Session name").argument("[target_command...]", "Target server command").description("Start run-mcp in background session daemon mode").allowUnknownOption().action(async (sessionName, targetCommand) => {
|
|
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) => {
|
|
4689
5884
|
const targetCmd = activeTargetCommand ?? targetCommand;
|
|
4690
5885
|
if (!targetCmd || targetCmd.length === 0) {
|
|
4691
5886
|
process.stderr.write("Error: No target command provided for daemon.\n");
|
|
4692
|
-
process.exit(
|
|
5887
|
+
process.exit(64);
|
|
4693
5888
|
}
|
|
4694
5889
|
const server = createServer();
|
|
4695
5890
|
server.listen(0, "127.0.0.1", async () => {
|
|
4696
5891
|
const addr = server.address();
|
|
4697
5892
|
const port = addr.port;
|
|
4698
|
-
const target = new TargetManager(targetCmd[0], targetCmd.slice(1)
|
|
5893
|
+
const target = new TargetManager(targetCmd[0], targetCmd.slice(1), {
|
|
5894
|
+
sandbox: opts.sandbox
|
|
5895
|
+
});
|
|
4699
5896
|
const interceptor = new ResponseInterceptor();
|
|
4700
5897
|
try {
|
|
4701
5898
|
await target.connect();
|
|
@@ -4704,8 +5901,8 @@ program.command("daemon").argument("<session_name>", "Session name").argument("[
|
|
|
4704
5901
|
`);
|
|
4705
5902
|
process.exit(1);
|
|
4706
5903
|
}
|
|
4707
|
-
await
|
|
4708
|
-
await
|
|
5904
|
+
await mkdir3(SESSION_DIR, { recursive: true });
|
|
5905
|
+
await writeFile3(
|
|
4709
5906
|
getSessionPath(sessionName),
|
|
4710
5907
|
JSON.stringify({ port, pid: process.pid }),
|
|
4711
5908
|
"utf8"
|
|
@@ -4722,12 +5919,12 @@ program.command("daemon").argument("<session_name>", "Session name").argument("[
|
|
|
4722
5919
|
try {
|
|
4723
5920
|
const req = JSON.parse(trimmed);
|
|
4724
5921
|
if (req.method === "execute") {
|
|
4725
|
-
const { operation, opts } = req.params;
|
|
5922
|
+
const { operation, opts: opts2 } = req.params;
|
|
4726
5923
|
const { result, hasError } = await executeOperation(
|
|
4727
5924
|
target,
|
|
4728
5925
|
interceptor,
|
|
4729
5926
|
operation,
|
|
4730
|
-
|
|
5927
|
+
opts2
|
|
4731
5928
|
);
|
|
4732
5929
|
socket.write(
|
|
4733
5930
|
JSON.stringify({ jsonrpc: "2.0", result: { result, hasError }, id: req.id }) + "\n"
|
|
@@ -4778,7 +5975,7 @@ program.command("close-session").argument("<session_name>", "Session name").desc
|
|
|
4778
5975
|
}
|
|
4779
5976
|
}
|
|
4780
5977
|
});
|
|
4781
|
-
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(
|
|
4782
5979
|
"[target_command...]",
|
|
4783
5980
|
"Command to spawn the target MCP server (starts REPL if provided, Agent server otherwise)"
|
|
4784
5981
|
).option("-o, --out-dir <path>", "Directory to save intercepted images and audio").option(
|
|
@@ -4790,7 +5987,10 @@ program.name("run-mcp").description("A smart interactive REPL and live test harn
|
|
|
4790
5987
|
).option(
|
|
4791
5988
|
"-m, --media-threshold <kb>",
|
|
4792
5989
|
"Media size threshold in KB to save to disk (0 to always save, -1 to keep inline)"
|
|
4793
|
-
).option("--mcp", "Force start Agent Server mode even if run interactively without arguments").option("-s, --script <file>", "Read commands from a file instead of stdin (REPL Mode only)").
|
|
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(
|
|
4794
5994
|
"after",
|
|
4795
5995
|
`
|
|
4796
5996
|
Examples:
|
|
@@ -4827,6 +6027,8 @@ Agent Mode Tools:
|
|
|
4827
6027
|
disconnect_from_mcp \u2192 Tear down and reconnect after changes
|
|
4828
6028
|
mcp_server_status \u2192 Check connection status
|
|
4829
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
|
|
4830
6032
|
|
|
4831
6033
|
REPL Mode Commands (once connected):
|
|
4832
6034
|
tools/list List all available tools
|
|
@@ -4855,18 +6057,20 @@ REPL Mode Commands (once connected):
|
|
|
4855
6057
|
Shortcuts: tl td tc ts rl rr rt rs ru pl pg (see help for details)`
|
|
4856
6058
|
).action(
|
|
4857
6059
|
async (targetCommand, opts) => {
|
|
4858
|
-
|
|
4859
|
-
process.stderr.write(
|
|
4860
|
-
"Error: Target server command must be separated by '--'.\nThis avoids argument parsing ambiguity.\n\nExample:\n run-mcp -- node my-server.js\n run-mcp -s script.txt -- node my-server.js\n"
|
|
4861
|
-
);
|
|
4862
|
-
process.exit(1);
|
|
4863
|
-
}
|
|
4864
|
-
const target = activeTargetCommand ?? [];
|
|
6060
|
+
const target = activeTargetCommand ?? targetCommand ?? [];
|
|
4865
6061
|
if (target && target.length > 0) {
|
|
4866
6062
|
await startRepl(target, {
|
|
4867
6063
|
script: opts.script,
|
|
4868
6064
|
outDir: opts.outDir,
|
|
4869
|
-
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
|
|
4870
6074
|
});
|
|
4871
6075
|
} else {
|
|
4872
6076
|
if (opts.mcp || !process.stdin.isTTY) {
|
|
@@ -4874,7 +6078,14 @@ Shortcuts: tl td tc ts rl rr rt rs ru pl pg (see help for details)`
|
|
|
4874
6078
|
outDir: opts.outDir,
|
|
4875
6079
|
timeoutMs: opts.timeout ? Number.parseInt(opts.timeout, 10) : void 0,
|
|
4876
6080
|
maxTextLength: opts.maxText ? Number.parseInt(opts.maxText, 10) : void 0,
|
|
4877
|
-
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
|
|
4878
6089
|
});
|
|
4879
6090
|
} else {
|
|
4880
6091
|
const selected = await pickDiscoveredServer();
|
|
@@ -4888,10 +6099,26 @@ Shortcuts: tl td tc ts rl rr rt rs ru pl pg (see help for details)`
|
|
|
4888
6099
|
await startRepl([selected.config.command, ...selected.config.args || []], {
|
|
4889
6100
|
script: opts.script,
|
|
4890
6101
|
outDir: opts.outDir,
|
|
4891
|
-
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
|
|
4892
6111
|
});
|
|
4893
6112
|
}
|
|
4894
6113
|
}
|
|
4895
6114
|
}
|
|
4896
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
|
+
}
|
|
4897
6124
|
program.parse(argvToParse);
|