mcpman 0.6.0 → 0.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 +83 -0
- package/dist/chunk-YZNTMR6O.js +88 -0
- package/dist/index.cjs +1828 -419
- package/dist/index.js +1698 -410
- package/dist/lockfile-RBA7HB24.js +25 -0
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,4 +1,13 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
LOCKFILE_NAME,
|
|
4
|
+
addEntry,
|
|
5
|
+
createEmptyLockfile,
|
|
6
|
+
findLockfile,
|
|
7
|
+
readLockfile,
|
|
8
|
+
resolveLockfilePath,
|
|
9
|
+
writeLockfile
|
|
10
|
+
} from "./chunk-YZNTMR6O.js";
|
|
2
11
|
import {
|
|
3
12
|
computeTrustScore
|
|
4
13
|
} from "./chunk-RGKHLY5G.js";
|
|
@@ -20,7 +29,7 @@ import {
|
|
|
20
29
|
} from "./chunk-6X6Q6UZC.js";
|
|
21
30
|
|
|
22
31
|
// src/index.ts
|
|
23
|
-
import { defineCommand as
|
|
32
|
+
import { defineCommand as defineCommand27, runMain } from "citty";
|
|
24
33
|
|
|
25
34
|
// src/commands/audit.ts
|
|
26
35
|
import * as p from "@clack/prompts";
|
|
@@ -28,89 +37,26 @@ import { defineCommand } from "citty";
|
|
|
28
37
|
import { createSpinner } from "nanospinner";
|
|
29
38
|
import pc from "picocolors";
|
|
30
39
|
|
|
31
|
-
// src/core/
|
|
40
|
+
// src/core/security-scanner.ts
|
|
32
41
|
import fs from "fs";
|
|
33
42
|
import os from "os";
|
|
34
43
|
import path from "path";
|
|
35
|
-
var
|
|
36
|
-
function findLockfile() {
|
|
37
|
-
let dir = process.cwd();
|
|
38
|
-
while (true) {
|
|
39
|
-
const candidate = path.join(dir, LOCKFILE_NAME);
|
|
40
|
-
if (fs.existsSync(candidate)) return candidate;
|
|
41
|
-
const parent = path.dirname(dir);
|
|
42
|
-
if (parent === dir) break;
|
|
43
|
-
dir = parent;
|
|
44
|
-
}
|
|
45
|
-
return null;
|
|
46
|
-
}
|
|
47
|
-
function getGlobalLockfilePath() {
|
|
48
|
-
return path.join(os.homedir(), ".mcpman", LOCKFILE_NAME);
|
|
49
|
-
}
|
|
50
|
-
function resolveLockfilePath() {
|
|
51
|
-
return findLockfile() ?? getGlobalLockfilePath();
|
|
52
|
-
}
|
|
53
|
-
function readLockfile(filePath) {
|
|
54
|
-
const target = filePath ?? resolveLockfilePath();
|
|
55
|
-
if (!fs.existsSync(target)) {
|
|
56
|
-
return { lockfileVersion: 1, servers: {} };
|
|
57
|
-
}
|
|
58
|
-
try {
|
|
59
|
-
const raw = fs.readFileSync(target, "utf-8");
|
|
60
|
-
return JSON.parse(raw);
|
|
61
|
-
} catch {
|
|
62
|
-
return { lockfileVersion: 1, servers: {} };
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
function serialize(data) {
|
|
66
|
-
const sorted = {
|
|
67
|
-
lockfileVersion: data.lockfileVersion,
|
|
68
|
-
servers: Object.fromEntries(
|
|
69
|
-
Object.entries(data.servers).sort(([a], [b]) => a.localeCompare(b))
|
|
70
|
-
)
|
|
71
|
-
};
|
|
72
|
-
return `${JSON.stringify(sorted, null, 2)}
|
|
73
|
-
`;
|
|
74
|
-
}
|
|
75
|
-
function writeLockfile(data, filePath) {
|
|
76
|
-
const target = filePath ?? resolveLockfilePath();
|
|
77
|
-
const dir = path.dirname(target);
|
|
78
|
-
if (!fs.existsSync(dir)) {
|
|
79
|
-
fs.mkdirSync(dir, { recursive: true });
|
|
80
|
-
}
|
|
81
|
-
const tmp = `${target}.tmp`;
|
|
82
|
-
fs.writeFileSync(tmp, serialize(data), "utf-8");
|
|
83
|
-
fs.renameSync(tmp, target);
|
|
84
|
-
}
|
|
85
|
-
function addEntry(name, entry, filePath) {
|
|
86
|
-
const data = readLockfile(filePath);
|
|
87
|
-
data.servers[name] = entry;
|
|
88
|
-
writeLockfile(data, filePath);
|
|
89
|
-
}
|
|
90
|
-
function createEmptyLockfile(filePath) {
|
|
91
|
-
writeLockfile({ lockfileVersion: 1, servers: {} }, filePath);
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
// src/core/security-scanner.ts
|
|
95
|
-
import fs2 from "fs";
|
|
96
|
-
import os2 from "os";
|
|
97
|
-
import path2 from "path";
|
|
98
|
-
var CACHE_PATH = path2.join(os2.homedir(), ".mcpman", ".audit-cache.json");
|
|
44
|
+
var CACHE_PATH = path.join(os.homedir(), ".mcpman", ".audit-cache.json");
|
|
99
45
|
var CACHE_TTL_MS = 24 * 60 * 60 * 1e3;
|
|
100
46
|
function readCache() {
|
|
101
47
|
try {
|
|
102
|
-
if (!
|
|
103
|
-
return JSON.parse(
|
|
48
|
+
if (!fs.existsSync(CACHE_PATH)) return {};
|
|
49
|
+
return JSON.parse(fs.readFileSync(CACHE_PATH, "utf-8"));
|
|
104
50
|
} catch {
|
|
105
51
|
return {};
|
|
106
52
|
}
|
|
107
53
|
}
|
|
108
54
|
function writeCache(cache) {
|
|
109
|
-
const dir =
|
|
110
|
-
if (!
|
|
55
|
+
const dir = path.dirname(CACHE_PATH);
|
|
56
|
+
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
|
111
57
|
const tmp = `${CACHE_PATH}.tmp`;
|
|
112
|
-
|
|
113
|
-
|
|
58
|
+
fs.writeFileSync(tmp, JSON.stringify(cache, null, 2), "utf-8");
|
|
59
|
+
fs.renameSync(tmp, CACHE_PATH);
|
|
114
60
|
}
|
|
115
61
|
function getCachedReport(name, version) {
|
|
116
62
|
const cache = readCache();
|
|
@@ -340,13 +286,13 @@ async function resolveFromGitHub(githubUrl) {
|
|
|
340
286
|
|
|
341
287
|
// src/core/plugin-loader.ts
|
|
342
288
|
import { execSync } from "child_process";
|
|
343
|
-
import
|
|
289
|
+
import fs3 from "fs";
|
|
344
290
|
import { createRequire } from "module";
|
|
345
|
-
import
|
|
291
|
+
import path3 from "path";
|
|
346
292
|
|
|
347
293
|
// src/core/config-service.ts
|
|
348
|
-
import
|
|
349
|
-
import
|
|
294
|
+
import fs2 from "fs";
|
|
295
|
+
import path2 from "path";
|
|
350
296
|
var VALID_KEYS = /* @__PURE__ */ new Set([
|
|
351
297
|
"defaultClient",
|
|
352
298
|
"updateCheckInterval",
|
|
@@ -356,7 +302,7 @@ var VALID_KEYS = /* @__PURE__ */ new Set([
|
|
|
356
302
|
]);
|
|
357
303
|
function readConfig(configPath = getConfigPath()) {
|
|
358
304
|
try {
|
|
359
|
-
const raw =
|
|
305
|
+
const raw = fs2.readFileSync(configPath, "utf-8");
|
|
360
306
|
const parsed = JSON.parse(raw);
|
|
361
307
|
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
362
308
|
return {};
|
|
@@ -367,11 +313,11 @@ function readConfig(configPath = getConfigPath()) {
|
|
|
367
313
|
}
|
|
368
314
|
}
|
|
369
315
|
function writeConfig(data, configPath = getConfigPath()) {
|
|
370
|
-
const dir =
|
|
371
|
-
|
|
316
|
+
const dir = path2.dirname(configPath);
|
|
317
|
+
fs2.mkdirSync(dir, { recursive: true });
|
|
372
318
|
const tmp = `${configPath}.tmp`;
|
|
373
|
-
|
|
374
|
-
|
|
319
|
+
fs2.writeFileSync(tmp, JSON.stringify(data, null, 2), { encoding: "utf-8" });
|
|
320
|
+
fs2.renameSync(tmp, configPath);
|
|
375
321
|
}
|
|
376
322
|
function getConfigValue(key, configPath = getConfigPath()) {
|
|
377
323
|
const data = readConfig(configPath);
|
|
@@ -395,8 +341,8 @@ function isValidPlugin(obj) {
|
|
|
395
341
|
}
|
|
396
342
|
function loadPlugin(pkg, pluginDir = getPluginDir()) {
|
|
397
343
|
try {
|
|
398
|
-
const requirePath =
|
|
399
|
-
const pluginRequire = createRequire(
|
|
344
|
+
const requirePath = path3.join(pluginDir, "node_modules", pkg);
|
|
345
|
+
const pluginRequire = createRequire(path3.join(pluginDir, "index.js"));
|
|
400
346
|
const mod = pluginRequire(requirePath);
|
|
401
347
|
const exported = mod?.default ?? mod;
|
|
402
348
|
if (isValidPlugin(exported)) return exported;
|
|
@@ -416,10 +362,10 @@ function loadAllPlugins(pluginDir = getPluginDir()) {
|
|
|
416
362
|
return plugins;
|
|
417
363
|
}
|
|
418
364
|
function installPluginPackage(name, pluginDir = getPluginDir()) {
|
|
419
|
-
|
|
420
|
-
const pkgJsonPath =
|
|
421
|
-
if (!
|
|
422
|
-
|
|
365
|
+
fs3.mkdirSync(pluginDir, { recursive: true });
|
|
366
|
+
const pkgJsonPath = path3.join(pluginDir, "package.json");
|
|
367
|
+
if (!fs3.existsSync(pkgJsonPath)) {
|
|
368
|
+
fs3.writeFileSync(
|
|
423
369
|
pkgJsonPath,
|
|
424
370
|
JSON.stringify({ name: "mcpman-plugins", private: true }, null, 2)
|
|
425
371
|
);
|
|
@@ -907,10 +853,286 @@ async function runAuditFix(reports, servers, skipConfirm) {
|
|
|
907
853
|
`);
|
|
908
854
|
}
|
|
909
855
|
|
|
910
|
-
// src/commands/
|
|
911
|
-
import
|
|
856
|
+
// src/commands/completions.ts
|
|
857
|
+
import fs4 from "fs";
|
|
858
|
+
import os2 from "os";
|
|
859
|
+
import path4 from "path";
|
|
912
860
|
import { defineCommand as defineCommand2 } from "citty";
|
|
913
861
|
import pc2 from "picocolors";
|
|
862
|
+
|
|
863
|
+
// src/core/completion-generator.ts
|
|
864
|
+
function getCommandList() {
|
|
865
|
+
return [
|
|
866
|
+
"install",
|
|
867
|
+
"list",
|
|
868
|
+
"remove",
|
|
869
|
+
"doctor",
|
|
870
|
+
"init",
|
|
871
|
+
"secrets",
|
|
872
|
+
"sync",
|
|
873
|
+
"audit",
|
|
874
|
+
"update",
|
|
875
|
+
"upgrade",
|
|
876
|
+
"config",
|
|
877
|
+
"search",
|
|
878
|
+
"info",
|
|
879
|
+
"run",
|
|
880
|
+
"logs",
|
|
881
|
+
"test",
|
|
882
|
+
"profiles",
|
|
883
|
+
"plugin",
|
|
884
|
+
"export",
|
|
885
|
+
"import",
|
|
886
|
+
"create",
|
|
887
|
+
"link",
|
|
888
|
+
"watch",
|
|
889
|
+
"registry",
|
|
890
|
+
"completions",
|
|
891
|
+
"why"
|
|
892
|
+
];
|
|
893
|
+
}
|
|
894
|
+
var SERVER_ARG_COMMANDS = [
|
|
895
|
+
"run",
|
|
896
|
+
"test",
|
|
897
|
+
"logs",
|
|
898
|
+
"watch",
|
|
899
|
+
"remove",
|
|
900
|
+
"update",
|
|
901
|
+
"info",
|
|
902
|
+
"audit",
|
|
903
|
+
"link",
|
|
904
|
+
"why"
|
|
905
|
+
];
|
|
906
|
+
function getServerNames(lockfilePath) {
|
|
907
|
+
try {
|
|
908
|
+
const data = readLockfile(lockfilePath);
|
|
909
|
+
return Object.keys(data.servers);
|
|
910
|
+
} catch {
|
|
911
|
+
return [];
|
|
912
|
+
}
|
|
913
|
+
}
|
|
914
|
+
function generateBashCompletion() {
|
|
915
|
+
const serverCmds = SERVER_ARG_COMMANDS.join("|");
|
|
916
|
+
return `# mcpman bash completion
|
|
917
|
+
# Add to ~/.bashrc or ~/.bash_profile:
|
|
918
|
+
# source <(mcpman completions bash)
|
|
919
|
+
# Or append permanently:
|
|
920
|
+
# mcpman completions bash >> ~/.bashrc
|
|
921
|
+
|
|
922
|
+
_mcpman_completions() {
|
|
923
|
+
local cur="\${COMP_WORDS[COMP_CWORD]}"
|
|
924
|
+
local prev="\${COMP_WORDS[COMP_CWORD-1]}"
|
|
925
|
+
|
|
926
|
+
if [ "$COMP_CWORD" -eq 1 ]; then
|
|
927
|
+
COMPREPLY=($(compgen -W "$(mcpman completions --list-commands 2>/dev/null)" -- "$cur"))
|
|
928
|
+
return
|
|
929
|
+
fi
|
|
930
|
+
|
|
931
|
+
case "$prev" in
|
|
932
|
+
${serverCmds})
|
|
933
|
+
COMPREPLY=($(compgen -W "$(mcpman completions --list-servers 2>/dev/null)" -- "$cur"))
|
|
934
|
+
;;
|
|
935
|
+
--client|-c)
|
|
936
|
+
COMPREPLY=($(compgen -W "claude-desktop cursor vscode windsurf" -- "$cur"))
|
|
937
|
+
;;
|
|
938
|
+
--runtime|-r)
|
|
939
|
+
COMPREPLY=($(compgen -W "node python" -- "$cur"))
|
|
940
|
+
;;
|
|
941
|
+
esac
|
|
942
|
+
}
|
|
943
|
+
|
|
944
|
+
complete -F _mcpman_completions mcpman
|
|
945
|
+
`;
|
|
946
|
+
}
|
|
947
|
+
function generateZshCompletion() {
|
|
948
|
+
const serverCmds = SERVER_ARG_COMMANDS.map((c) => `'${c}'`).join(" ");
|
|
949
|
+
return `#compdef mcpman
|
|
950
|
+
# mcpman zsh completion
|
|
951
|
+
# Add to ~/.zshrc:
|
|
952
|
+
# source <(mcpman completions zsh)
|
|
953
|
+
# Or use compinit:
|
|
954
|
+
# mcpman completions zsh > "\${fpath[1]}/_mcpman"
|
|
955
|
+
|
|
956
|
+
_mcpman() {
|
|
957
|
+
local state
|
|
958
|
+
|
|
959
|
+
_arguments \\
|
|
960
|
+
'1: :->command' \\
|
|
961
|
+
'*: :->args'
|
|
962
|
+
|
|
963
|
+
case $state in
|
|
964
|
+
command)
|
|
965
|
+
local commands
|
|
966
|
+
commands=($(mcpman completions --list-commands 2>/dev/null))
|
|
967
|
+
_describe 'command' commands
|
|
968
|
+
;;
|
|
969
|
+
args)
|
|
970
|
+
local cmd="\${words[2]}"
|
|
971
|
+
local server_cmds=(${serverCmds})
|
|
972
|
+
if (( server_cmds[(I)$cmd] )); then
|
|
973
|
+
local servers
|
|
974
|
+
servers=($(mcpman completions --list-servers 2>/dev/null))
|
|
975
|
+
_describe 'server' servers
|
|
976
|
+
fi
|
|
977
|
+
;;
|
|
978
|
+
esac
|
|
979
|
+
}
|
|
980
|
+
|
|
981
|
+
compdef _mcpman mcpman
|
|
982
|
+
`;
|
|
983
|
+
}
|
|
984
|
+
function generateFishCompletion() {
|
|
985
|
+
const serverCmds = SERVER_ARG_COMMANDS.join(" ");
|
|
986
|
+
return `# mcpman fish completion
|
|
987
|
+
# Add to ~/.config/fish/completions/mcpman.fish:
|
|
988
|
+
# mcpman completions fish > ~/.config/fish/completions/mcpman.fish
|
|
989
|
+
|
|
990
|
+
# Disable file completion for mcpman
|
|
991
|
+
complete -c mcpman -f
|
|
992
|
+
|
|
993
|
+
# Subcommand completions (dynamic)
|
|
994
|
+
complete -c mcpman -n '__fish_use_subcommand' \\
|
|
995
|
+
-a "(mcpman completions --list-commands 2>/dev/null)"
|
|
996
|
+
|
|
997
|
+
# Server name completions for commands that take a server arg
|
|
998
|
+
for cmd in ${serverCmds}
|
|
999
|
+
complete -c mcpman -n "__fish_seen_subcommand_from $cmd" \\
|
|
1000
|
+
-a "(mcpman completions --list-servers 2>/dev/null)"
|
|
1001
|
+
end
|
|
1002
|
+
|
|
1003
|
+
# --client flag completions
|
|
1004
|
+
complete -c mcpman -l client -s c \\
|
|
1005
|
+
-a "claude-desktop cursor vscode windsurf" \\
|
|
1006
|
+
-d "Target client"
|
|
1007
|
+
|
|
1008
|
+
# --runtime flag completions
|
|
1009
|
+
complete -c mcpman -l runtime -s r \\
|
|
1010
|
+
-a "node python" \\
|
|
1011
|
+
-d "Runtime"
|
|
1012
|
+
`;
|
|
1013
|
+
}
|
|
1014
|
+
|
|
1015
|
+
// src/commands/completions.ts
|
|
1016
|
+
var completions_default = defineCommand2({
|
|
1017
|
+
meta: {
|
|
1018
|
+
name: "completions",
|
|
1019
|
+
description: "Generate shell completion scripts (bash, zsh, fish)"
|
|
1020
|
+
},
|
|
1021
|
+
args: {
|
|
1022
|
+
shell: {
|
|
1023
|
+
type: "positional",
|
|
1024
|
+
description: "Shell: bash, zsh, fish, or install",
|
|
1025
|
+
required: false
|
|
1026
|
+
},
|
|
1027
|
+
"list-commands": {
|
|
1028
|
+
type: "boolean",
|
|
1029
|
+
description: "Output all command names (used by completion scripts)",
|
|
1030
|
+
default: false
|
|
1031
|
+
},
|
|
1032
|
+
"list-servers": {
|
|
1033
|
+
type: "boolean",
|
|
1034
|
+
description: "Output server names from lockfile (used by completion scripts)",
|
|
1035
|
+
default: false
|
|
1036
|
+
}
|
|
1037
|
+
},
|
|
1038
|
+
async run({ args }) {
|
|
1039
|
+
if (args["list-commands"]) {
|
|
1040
|
+
console.log(getCommandList().join("\n"));
|
|
1041
|
+
return;
|
|
1042
|
+
}
|
|
1043
|
+
if (args["list-servers"]) {
|
|
1044
|
+
console.log(getServerNames().join("\n"));
|
|
1045
|
+
return;
|
|
1046
|
+
}
|
|
1047
|
+
const shell = args.shell;
|
|
1048
|
+
if (!shell) {
|
|
1049
|
+
printUsage();
|
|
1050
|
+
return;
|
|
1051
|
+
}
|
|
1052
|
+
switch (shell.toLowerCase()) {
|
|
1053
|
+
case "bash":
|
|
1054
|
+
process.stdout.write(generateBashCompletion());
|
|
1055
|
+
break;
|
|
1056
|
+
case "zsh":
|
|
1057
|
+
process.stdout.write(generateZshCompletion());
|
|
1058
|
+
break;
|
|
1059
|
+
case "fish":
|
|
1060
|
+
process.stdout.write(generateFishCompletion());
|
|
1061
|
+
break;
|
|
1062
|
+
case "install":
|
|
1063
|
+
await installCompletion();
|
|
1064
|
+
break;
|
|
1065
|
+
default:
|
|
1066
|
+
console.error(pc2.red(` Error: Unknown shell '${shell}'. Use: bash, zsh, or fish.`));
|
|
1067
|
+
process.exit(1);
|
|
1068
|
+
}
|
|
1069
|
+
}
|
|
1070
|
+
});
|
|
1071
|
+
function printUsage() {
|
|
1072
|
+
console.log(pc2.bold("\n mcpman completions \u2014 Shell completion setup\n"));
|
|
1073
|
+
console.log(" Usage:");
|
|
1074
|
+
console.log(` ${pc2.cyan("mcpman completions bash")} Output bash completion script`);
|
|
1075
|
+
console.log(` ${pc2.cyan("mcpman completions zsh")} Output zsh completion script`);
|
|
1076
|
+
console.log(` ${pc2.cyan("mcpman completions fish")} Output fish completion script`);
|
|
1077
|
+
console.log(` ${pc2.cyan("mcpman completions install")} Auto-detect shell and install
|
|
1078
|
+
`);
|
|
1079
|
+
console.log(" Quick setup:");
|
|
1080
|
+
console.log(` ${pc2.dim("# bash")}`);
|
|
1081
|
+
console.log(` ${pc2.cyan("source <(mcpman completions bash)")}`);
|
|
1082
|
+
console.log(` ${pc2.dim("# zsh")}`);
|
|
1083
|
+
console.log(` ${pc2.cyan("source <(mcpman completions zsh)")}
|
|
1084
|
+
`);
|
|
1085
|
+
}
|
|
1086
|
+
async function installCompletion() {
|
|
1087
|
+
const shellBin = process.env.SHELL ?? "";
|
|
1088
|
+
let detectedShell = "";
|
|
1089
|
+
if (shellBin.includes("zsh")) detectedShell = "zsh";
|
|
1090
|
+
else if (shellBin.includes("fish")) detectedShell = "fish";
|
|
1091
|
+
else if (shellBin.includes("bash")) detectedShell = "bash";
|
|
1092
|
+
if (!detectedShell) {
|
|
1093
|
+
console.error(pc2.red(" Could not detect shell from $SHELL. Run manually:"));
|
|
1094
|
+
console.error(pc2.dim(" source <(mcpman completions bash|zsh|fish)"));
|
|
1095
|
+
process.exit(1);
|
|
1096
|
+
}
|
|
1097
|
+
const home = os2.homedir();
|
|
1098
|
+
let rcFile;
|
|
1099
|
+
let script;
|
|
1100
|
+
if (detectedShell === "zsh") {
|
|
1101
|
+
rcFile = path4.join(home, ".zshrc");
|
|
1102
|
+
script = generateZshCompletion();
|
|
1103
|
+
} else if (detectedShell === "fish") {
|
|
1104
|
+
const fishDir = path4.join(home, ".config", "fish", "completions");
|
|
1105
|
+
fs4.mkdirSync(fishDir, { recursive: true });
|
|
1106
|
+
rcFile = path4.join(fishDir, "mcpman.fish");
|
|
1107
|
+
fs4.writeFileSync(rcFile, generateFishCompletion(), "utf-8");
|
|
1108
|
+
console.log(pc2.green(` Installed fish completions to ${rcFile}`));
|
|
1109
|
+
return;
|
|
1110
|
+
} else {
|
|
1111
|
+
rcFile = path4.join(home, ".bashrc");
|
|
1112
|
+
script = generateBashCompletion();
|
|
1113
|
+
}
|
|
1114
|
+
const marker = "# mcpman completions";
|
|
1115
|
+
let existing = "";
|
|
1116
|
+
try {
|
|
1117
|
+
existing = fs4.readFileSync(rcFile, "utf-8");
|
|
1118
|
+
} catch {
|
|
1119
|
+
}
|
|
1120
|
+
if (existing.includes(marker)) {
|
|
1121
|
+
console.log(pc2.yellow(` Completions already installed in ${rcFile}. Skipping.`));
|
|
1122
|
+
return;
|
|
1123
|
+
}
|
|
1124
|
+
fs4.appendFileSync(rcFile, `
|
|
1125
|
+
${marker}
|
|
1126
|
+
source <(mcpman completions ${detectedShell})
|
|
1127
|
+
`);
|
|
1128
|
+
console.log(pc2.green(` Installed ${detectedShell} completions in ${rcFile}`));
|
|
1129
|
+
console.log(pc2.dim(` Restart your shell or run: source ${rcFile}`));
|
|
1130
|
+
}
|
|
1131
|
+
|
|
1132
|
+
// src/commands/config.ts
|
|
1133
|
+
import * as p2 from "@clack/prompts";
|
|
1134
|
+
import { defineCommand as defineCommand3 } from "citty";
|
|
1135
|
+
import pc3 from "picocolors";
|
|
914
1136
|
function coerceValue(raw) {
|
|
915
1137
|
if (raw === "true") return true;
|
|
916
1138
|
if (raw === "false") return false;
|
|
@@ -918,7 +1140,7 @@ function coerceValue(raw) {
|
|
|
918
1140
|
if (!Number.isNaN(num) && raw.trim() !== "") return num;
|
|
919
1141
|
return raw;
|
|
920
1142
|
}
|
|
921
|
-
var setCommand =
|
|
1143
|
+
var setCommand = defineCommand3({
|
|
922
1144
|
meta: { name: "set", description: "Set a config value" },
|
|
923
1145
|
args: {
|
|
924
1146
|
key: {
|
|
@@ -936,14 +1158,14 @@ var setCommand = defineCommand2({
|
|
|
936
1158
|
try {
|
|
937
1159
|
const coerced = coerceValue(args.value);
|
|
938
1160
|
setConfigValue(args.key, coerced);
|
|
939
|
-
console.log(`${
|
|
1161
|
+
console.log(`${pc3.green("\u2713")} Set ${pc3.bold(args.key)} = ${pc3.cyan(String(coerced))}`);
|
|
940
1162
|
} catch (err) {
|
|
941
|
-
console.error(`${
|
|
1163
|
+
console.error(`${pc3.red("\u2717")} ${String(err)}`);
|
|
942
1164
|
process.exit(1);
|
|
943
1165
|
}
|
|
944
1166
|
}
|
|
945
1167
|
});
|
|
946
|
-
var getCommand =
|
|
1168
|
+
var getCommand = defineCommand3({
|
|
947
1169
|
meta: { name: "get", description: "Get a config value" },
|
|
948
1170
|
args: {
|
|
949
1171
|
key: {
|
|
@@ -955,32 +1177,32 @@ var getCommand = defineCommand2({
|
|
|
955
1177
|
run({ args }) {
|
|
956
1178
|
const val = getConfigValue(args.key);
|
|
957
1179
|
if (val === void 0) {
|
|
958
|
-
console.log(
|
|
1180
|
+
console.log(pc3.dim(`${args.key}: (not set)`));
|
|
959
1181
|
} else {
|
|
960
|
-
console.log(`${
|
|
1182
|
+
console.log(`${pc3.bold(args.key)}: ${pc3.cyan(String(val))}`);
|
|
961
1183
|
}
|
|
962
1184
|
}
|
|
963
1185
|
});
|
|
964
|
-
var listCommand =
|
|
1186
|
+
var listCommand = defineCommand3({
|
|
965
1187
|
meta: { name: "list", description: "List all config values" },
|
|
966
1188
|
run() {
|
|
967
1189
|
const data = readConfig();
|
|
968
1190
|
const entries = Object.entries(data);
|
|
969
1191
|
if (entries.length === 0) {
|
|
970
|
-
console.log(
|
|
1192
|
+
console.log(pc3.dim("No config values set. Use `mcpman config set <key> <value>`."));
|
|
971
1193
|
return;
|
|
972
1194
|
}
|
|
973
1195
|
console.log("");
|
|
974
|
-
console.log(
|
|
1196
|
+
console.log(pc3.bold("mcpman config:"));
|
|
975
1197
|
console.log("");
|
|
976
1198
|
for (const [key, val] of entries) {
|
|
977
|
-
console.log(` ${
|
|
1199
|
+
console.log(` ${pc3.green("\u25CF")} ${pc3.bold(key)} ${pc3.cyan(String(val))}`);
|
|
978
1200
|
}
|
|
979
1201
|
console.log("");
|
|
980
|
-
console.log(
|
|
1202
|
+
console.log(pc3.dim(` ${entries.length} key${entries.length !== 1 ? "s" : ""} configured`));
|
|
981
1203
|
}
|
|
982
1204
|
});
|
|
983
|
-
var resetCommand =
|
|
1205
|
+
var resetCommand = defineCommand3({
|
|
984
1206
|
meta: { name: "reset", description: "Reset config to defaults (removes config file)" },
|
|
985
1207
|
async run() {
|
|
986
1208
|
const confirmed = await p2.confirm({
|
|
@@ -992,10 +1214,10 @@ var resetCommand = defineCommand2({
|
|
|
992
1214
|
return;
|
|
993
1215
|
}
|
|
994
1216
|
writeConfig({});
|
|
995
|
-
console.log(`${
|
|
1217
|
+
console.log(`${pc3.green("\u2713")} Config reset to defaults.`);
|
|
996
1218
|
}
|
|
997
1219
|
});
|
|
998
|
-
var config_default =
|
|
1220
|
+
var config_default = defineCommand3({
|
|
999
1221
|
meta: {
|
|
1000
1222
|
name: "config",
|
|
1001
1223
|
description: "Manage mcpman CLI configuration"
|
|
@@ -1008,9 +1230,297 @@ var config_default = defineCommand2({
|
|
|
1008
1230
|
}
|
|
1009
1231
|
});
|
|
1010
1232
|
|
|
1233
|
+
// src/commands/create.ts
|
|
1234
|
+
import path6 from "path";
|
|
1235
|
+
import { defineCommand as defineCommand4 } from "citty";
|
|
1236
|
+
import pc4 from "picocolors";
|
|
1237
|
+
|
|
1238
|
+
// src/core/scaffold-service.ts
|
|
1239
|
+
import fs5 from "fs";
|
|
1240
|
+
import path5 from "path";
|
|
1241
|
+
function sanitizeName(name) {
|
|
1242
|
+
return name.toLowerCase().replace(/[^a-z0-9-]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
|
|
1243
|
+
}
|
|
1244
|
+
function generateNodeProject(options) {
|
|
1245
|
+
const { name, description } = options;
|
|
1246
|
+
const packageJson = JSON.stringify(
|
|
1247
|
+
{
|
|
1248
|
+
name,
|
|
1249
|
+
version: "0.1.0",
|
|
1250
|
+
description,
|
|
1251
|
+
type: "module",
|
|
1252
|
+
bin: { [name]: "./dist/index.js" },
|
|
1253
|
+
main: "./dist/index.js",
|
|
1254
|
+
scripts: {
|
|
1255
|
+
build: "tsc",
|
|
1256
|
+
start: "node dist/index.js",
|
|
1257
|
+
dev: "tsx src/index.ts"
|
|
1258
|
+
},
|
|
1259
|
+
mcp: {
|
|
1260
|
+
name,
|
|
1261
|
+
description,
|
|
1262
|
+
transport: "stdio",
|
|
1263
|
+
env: []
|
|
1264
|
+
},
|
|
1265
|
+
dependencies: {
|
|
1266
|
+
"@modelcontextprotocol/sdk": "^1.0.0"
|
|
1267
|
+
},
|
|
1268
|
+
devDependencies: {
|
|
1269
|
+
typescript: "^5.0.0",
|
|
1270
|
+
tsx: "^4.0.0",
|
|
1271
|
+
"@types/node": "^20.0.0"
|
|
1272
|
+
}
|
|
1273
|
+
},
|
|
1274
|
+
null,
|
|
1275
|
+
2
|
|
1276
|
+
);
|
|
1277
|
+
const tsconfig = JSON.stringify(
|
|
1278
|
+
{
|
|
1279
|
+
compilerOptions: {
|
|
1280
|
+
target: "ES2022",
|
|
1281
|
+
module: "NodeNext",
|
|
1282
|
+
moduleResolution: "NodeNext",
|
|
1283
|
+
outDir: "./dist",
|
|
1284
|
+
rootDir: "./src",
|
|
1285
|
+
strict: true,
|
|
1286
|
+
esModuleInterop: true,
|
|
1287
|
+
skipLibCheck: true
|
|
1288
|
+
},
|
|
1289
|
+
include: ["src"]
|
|
1290
|
+
},
|
|
1291
|
+
null,
|
|
1292
|
+
2
|
|
1293
|
+
);
|
|
1294
|
+
const indexTs = `#!/usr/bin/env node
|
|
1295
|
+
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
1296
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
1297
|
+
|
|
1298
|
+
const server = new Server(
|
|
1299
|
+
{ name: "${name}", version: "0.1.0" },
|
|
1300
|
+
{ capabilities: { tools: {} } }
|
|
1301
|
+
);
|
|
1302
|
+
|
|
1303
|
+
server.setRequestHandler("tools/list", async () => ({
|
|
1304
|
+
tools: [
|
|
1305
|
+
{
|
|
1306
|
+
name: "hello",
|
|
1307
|
+
description: "Say hello",
|
|
1308
|
+
inputSchema: { type: "object", properties: { name: { type: "string" } } },
|
|
1309
|
+
},
|
|
1310
|
+
],
|
|
1311
|
+
}));
|
|
1312
|
+
|
|
1313
|
+
server.setRequestHandler("tools/call", async (request) => {
|
|
1314
|
+
if (request.params.name === "hello") {
|
|
1315
|
+
const who = request.params.arguments?.name ?? "world";
|
|
1316
|
+
return { content: [{ type: "text", text: \`Hello, \${who}!\` }] };
|
|
1317
|
+
}
|
|
1318
|
+
throw new Error(\`Unknown tool: \${request.params.name}\`);
|
|
1319
|
+
});
|
|
1320
|
+
|
|
1321
|
+
const transport = new StdioServerTransport();
|
|
1322
|
+
await server.connect(transport);
|
|
1323
|
+
`;
|
|
1324
|
+
return {
|
|
1325
|
+
"package.json": packageJson,
|
|
1326
|
+
"tsconfig.json": tsconfig,
|
|
1327
|
+
"src/index.ts": indexTs
|
|
1328
|
+
};
|
|
1329
|
+
}
|
|
1330
|
+
function generatePythonProject(options) {
|
|
1331
|
+
const { name, description } = options;
|
|
1332
|
+
const pyprojectToml = `[build-system]
|
|
1333
|
+
requires = ["setuptools>=68"]
|
|
1334
|
+
build-backend = "setuptools.backends.legacy:build"
|
|
1335
|
+
|
|
1336
|
+
[project]
|
|
1337
|
+
name = "${name}"
|
|
1338
|
+
version = "0.1.0"
|
|
1339
|
+
description = "${description}"
|
|
1340
|
+
requires-python = ">=3.10"
|
|
1341
|
+
dependencies = ["mcp>=1.0.0"]
|
|
1342
|
+
|
|
1343
|
+
[project.scripts]
|
|
1344
|
+
${name} = "${name.replace(/-/g, "_")}:main"
|
|
1345
|
+
|
|
1346
|
+
[tool.mcp]
|
|
1347
|
+
name = "${name}"
|
|
1348
|
+
description = "${description}"
|
|
1349
|
+
transport = "stdio"
|
|
1350
|
+
`;
|
|
1351
|
+
const mainPy = `#!/usr/bin/env python3
|
|
1352
|
+
"""${description}"""
|
|
1353
|
+
from mcp.server import Server
|
|
1354
|
+
from mcp.server.stdio import stdio_server
|
|
1355
|
+
|
|
1356
|
+
app = Server("${name}")
|
|
1357
|
+
|
|
1358
|
+
@app.list_tools()
|
|
1359
|
+
async def list_tools():
|
|
1360
|
+
return [
|
|
1361
|
+
{
|
|
1362
|
+
"name": "hello",
|
|
1363
|
+
"description": "Say hello",
|
|
1364
|
+
"inputSchema": {
|
|
1365
|
+
"type": "object",
|
|
1366
|
+
"properties": {"name": {"type": "string"}},
|
|
1367
|
+
},
|
|
1368
|
+
}
|
|
1369
|
+
]
|
|
1370
|
+
|
|
1371
|
+
@app.call_tool()
|
|
1372
|
+
async def call_tool(name: str, arguments: dict):
|
|
1373
|
+
if name == "hello":
|
|
1374
|
+
who = arguments.get("name", "world")
|
|
1375
|
+
return [{"type": "text", "text": f"Hello, {who}!"}]
|
|
1376
|
+
raise ValueError(f"Unknown tool: {name}")
|
|
1377
|
+
|
|
1378
|
+
async def main():
|
|
1379
|
+
async with stdio_server() as (read, write):
|
|
1380
|
+
await app.run(read, write)
|
|
1381
|
+
|
|
1382
|
+
if __name__ == "__main__":
|
|
1383
|
+
import asyncio
|
|
1384
|
+
asyncio.run(main())
|
|
1385
|
+
`;
|
|
1386
|
+
return {
|
|
1387
|
+
"pyproject.toml": pyprojectToml,
|
|
1388
|
+
"main.py": mainPy
|
|
1389
|
+
};
|
|
1390
|
+
}
|
|
1391
|
+
function writeScaffold(dir, files) {
|
|
1392
|
+
if (fs5.existsSync(dir)) {
|
|
1393
|
+
const existing = fs5.readdirSync(dir);
|
|
1394
|
+
if (existing.length > 0) {
|
|
1395
|
+
throw new Error(`Directory '${dir}' already exists and is not empty.`);
|
|
1396
|
+
}
|
|
1397
|
+
}
|
|
1398
|
+
for (const [relativePath, content] of Object.entries(files)) {
|
|
1399
|
+
const fullPath = path5.join(dir, relativePath);
|
|
1400
|
+
const parentDir = path5.dirname(fullPath);
|
|
1401
|
+
fs5.mkdirSync(parentDir, { recursive: true });
|
|
1402
|
+
fs5.writeFileSync(fullPath, content, "utf-8");
|
|
1403
|
+
}
|
|
1404
|
+
}
|
|
1405
|
+
|
|
1406
|
+
// src/commands/create.ts
|
|
1407
|
+
var create_default = defineCommand4({
|
|
1408
|
+
meta: {
|
|
1409
|
+
name: "create",
|
|
1410
|
+
description: "Scaffold a new MCP server project"
|
|
1411
|
+
},
|
|
1412
|
+
args: {
|
|
1413
|
+
name: {
|
|
1414
|
+
type: "positional",
|
|
1415
|
+
description: "Project name",
|
|
1416
|
+
required: false
|
|
1417
|
+
},
|
|
1418
|
+
runtime: {
|
|
1419
|
+
type: "string",
|
|
1420
|
+
description: "Runtime: node or python (default: node)",
|
|
1421
|
+
alias: "r"
|
|
1422
|
+
},
|
|
1423
|
+
description: {
|
|
1424
|
+
type: "string",
|
|
1425
|
+
description: "Project description",
|
|
1426
|
+
alias: "d"
|
|
1427
|
+
},
|
|
1428
|
+
yes: {
|
|
1429
|
+
type: "boolean",
|
|
1430
|
+
description: "Accept all defaults, skip prompts",
|
|
1431
|
+
alias: "y",
|
|
1432
|
+
default: false
|
|
1433
|
+
}
|
|
1434
|
+
},
|
|
1435
|
+
async run({ args }) {
|
|
1436
|
+
let projectName = args.name ?? "";
|
|
1437
|
+
let projectDescription = args.description ?? "";
|
|
1438
|
+
let runtime = args.runtime ?? "node";
|
|
1439
|
+
const acceptDefaults = args.yes;
|
|
1440
|
+
if (!acceptDefaults) {
|
|
1441
|
+
if (!projectName) {
|
|
1442
|
+
const readline = await import("readline");
|
|
1443
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
1444
|
+
projectName = await new Promise((resolve) => {
|
|
1445
|
+
rl.question(pc4.cyan(" Project name: "), (answer) => {
|
|
1446
|
+
rl.close();
|
|
1447
|
+
resolve(answer.trim());
|
|
1448
|
+
});
|
|
1449
|
+
});
|
|
1450
|
+
}
|
|
1451
|
+
if (!projectDescription) {
|
|
1452
|
+
const readline = await import("readline");
|
|
1453
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
1454
|
+
projectDescription = await new Promise((resolve) => {
|
|
1455
|
+
rl.question(pc4.cyan(" Description (optional): "), (answer) => {
|
|
1456
|
+
rl.close();
|
|
1457
|
+
resolve(answer.trim());
|
|
1458
|
+
});
|
|
1459
|
+
});
|
|
1460
|
+
}
|
|
1461
|
+
if (!args.runtime) {
|
|
1462
|
+
const readline = await import("readline");
|
|
1463
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
1464
|
+
const answer = await new Promise((resolve) => {
|
|
1465
|
+
rl.question(pc4.cyan(" Runtime [node/python] (default: node): "), (a) => {
|
|
1466
|
+
rl.close();
|
|
1467
|
+
resolve(a.trim() || "node");
|
|
1468
|
+
});
|
|
1469
|
+
});
|
|
1470
|
+
runtime = answer;
|
|
1471
|
+
}
|
|
1472
|
+
}
|
|
1473
|
+
if (!projectName) {
|
|
1474
|
+
console.error(pc4.red(" Error: Project name is required."));
|
|
1475
|
+
process.exit(1);
|
|
1476
|
+
}
|
|
1477
|
+
const sanitized = sanitizeName(projectName);
|
|
1478
|
+
if (!sanitized) {
|
|
1479
|
+
console.error(pc4.red(` Error: Invalid project name '${projectName}'.`));
|
|
1480
|
+
process.exit(1);
|
|
1481
|
+
}
|
|
1482
|
+
if (runtime !== "node" && runtime !== "python") {
|
|
1483
|
+
console.error(pc4.red(` Error: Unknown runtime '${runtime}'. Use node or python.`));
|
|
1484
|
+
process.exit(1);
|
|
1485
|
+
}
|
|
1486
|
+
const options = {
|
|
1487
|
+
name: sanitized,
|
|
1488
|
+
description: projectDescription || `${sanitized} MCP server`,
|
|
1489
|
+
runtime,
|
|
1490
|
+
transport: "stdio"
|
|
1491
|
+
};
|
|
1492
|
+
const files = runtime === "python" ? generatePythonProject(options) : generateNodeProject(options);
|
|
1493
|
+
const targetDir = path6.resolve(sanitized);
|
|
1494
|
+
try {
|
|
1495
|
+
writeScaffold(targetDir, files);
|
|
1496
|
+
} catch (err) {
|
|
1497
|
+
console.error(pc4.red(` Error: ${err instanceof Error ? err.message : String(err)}`));
|
|
1498
|
+
process.exit(1);
|
|
1499
|
+
}
|
|
1500
|
+
console.log(pc4.green(`
|
|
1501
|
+
Created ${pc4.bold(sanitized)}/
|
|
1502
|
+
`));
|
|
1503
|
+
console.log(pc4.dim(" Files generated:"));
|
|
1504
|
+
for (const file of Object.keys(files)) {
|
|
1505
|
+
console.log(` ${pc4.cyan(file)}`);
|
|
1506
|
+
}
|
|
1507
|
+
console.log("\n Next steps:");
|
|
1508
|
+
if (runtime === "node") {
|
|
1509
|
+
console.log(` ${pc4.bold(`cd ${sanitized}`)}`);
|
|
1510
|
+
console.log(` ${pc4.bold("npm install")}`);
|
|
1511
|
+
console.log(` ${pc4.bold("mcpman link .")}`);
|
|
1512
|
+
} else {
|
|
1513
|
+
console.log(` ${pc4.bold(`cd ${sanitized}`)}`);
|
|
1514
|
+
console.log(` ${pc4.bold("pip install -e .")}`);
|
|
1515
|
+
console.log(` ${pc4.bold("mcpman link .")}`);
|
|
1516
|
+
}
|
|
1517
|
+
console.log();
|
|
1518
|
+
}
|
|
1519
|
+
});
|
|
1520
|
+
|
|
1011
1521
|
// src/commands/doctor.ts
|
|
1012
|
-
import { defineCommand as
|
|
1013
|
-
import
|
|
1522
|
+
import { defineCommand as defineCommand5 } from "citty";
|
|
1523
|
+
import pc5 from "picocolors";
|
|
1014
1524
|
|
|
1015
1525
|
// src/core/diagnostics.ts
|
|
1016
1526
|
import { exec } from "child_process";
|
|
@@ -1355,12 +1865,12 @@ async function getInstalledServers(clientFilter) {
|
|
|
1355
1865
|
|
|
1356
1866
|
// src/commands/doctor.ts
|
|
1357
1867
|
var CHECK_ICON = {
|
|
1358
|
-
pass:
|
|
1359
|
-
fail:
|
|
1360
|
-
skip:
|
|
1361
|
-
warn:
|
|
1868
|
+
pass: pc5.green("\u2713"),
|
|
1869
|
+
fail: pc5.red("\u2717"),
|
|
1870
|
+
skip: pc5.dim("-"),
|
|
1871
|
+
warn: pc5.yellow("\u26A0")
|
|
1362
1872
|
};
|
|
1363
|
-
var doctor_default =
|
|
1873
|
+
var doctor_default = defineCommand5({
|
|
1364
1874
|
meta: {
|
|
1365
1875
|
name: "doctor",
|
|
1366
1876
|
description: "Check MCP server health and configuration"
|
|
@@ -1373,11 +1883,11 @@ var doctor_default = defineCommand3({
|
|
|
1373
1883
|
}
|
|
1374
1884
|
},
|
|
1375
1885
|
async run({ args }) {
|
|
1376
|
-
console.log(
|
|
1886
|
+
console.log(pc5.bold("\n mcpman doctor\n"));
|
|
1377
1887
|
const servers = await getInstalledServers();
|
|
1378
1888
|
if (servers.length === 0) {
|
|
1379
1889
|
console.log(
|
|
1380
|
-
|
|
1890
|
+
pc5.dim(" No MCP servers installed. Run mcpman install <server> to get started.")
|
|
1381
1891
|
);
|
|
1382
1892
|
return;
|
|
1383
1893
|
}
|
|
@@ -1394,20 +1904,20 @@ var doctor_default = defineCommand3({
|
|
|
1394
1904
|
if (pluginSummary.total > 0) {
|
|
1395
1905
|
printPluginSection(pluginSummary);
|
|
1396
1906
|
}
|
|
1397
|
-
console.log(
|
|
1907
|
+
console.log(pc5.dim(` ${"\u2500".repeat(50)}`));
|
|
1398
1908
|
const parts = [];
|
|
1399
|
-
if (passed > 0) parts.push(
|
|
1400
|
-
if (failed > 0) parts.push(
|
|
1909
|
+
if (passed > 0) parts.push(pc5.green(`${passed} healthy`));
|
|
1910
|
+
if (failed > 0) parts.push(pc5.red(`${failed} unhealthy`));
|
|
1401
1911
|
console.log(` Summary: ${parts.join(", ")}`);
|
|
1402
1912
|
if (pluginSummary.total > 0) {
|
|
1403
1913
|
const pParts = [];
|
|
1404
|
-
if (pluginSummary.healthy > 0) pParts.push(
|
|
1405
|
-
if (pluginSummary.unhealthy > 0) pParts.push(
|
|
1914
|
+
if (pluginSummary.healthy > 0) pParts.push(pc5.green(`${pluginSummary.healthy} ok`));
|
|
1915
|
+
if (pluginSummary.unhealthy > 0) pParts.push(pc5.red(`${pluginSummary.unhealthy} broken`));
|
|
1406
1916
|
console.log(` Plugins: ${pParts.join(", ")}`);
|
|
1407
1917
|
}
|
|
1408
1918
|
if (failed > 0 || pluginSummary.unhealthy > 0) {
|
|
1409
1919
|
if (!args.fix) {
|
|
1410
|
-
console.log(
|
|
1920
|
+
console.log(pc5.dim(` Run ${pc5.cyan("mcpman doctor --fix")} for fix suggestions.
|
|
1411
1921
|
`));
|
|
1412
1922
|
}
|
|
1413
1923
|
process.exit(1);
|
|
@@ -1416,26 +1926,26 @@ var doctor_default = defineCommand3({
|
|
|
1416
1926
|
}
|
|
1417
1927
|
});
|
|
1418
1928
|
function printPluginSection(summary) {
|
|
1419
|
-
console.log(
|
|
1929
|
+
console.log(pc5.bold(` Plugins (${summary.total})`));
|
|
1420
1930
|
for (const r of summary.results) {
|
|
1421
|
-
const icon = r.loadable && r.prefixUnique && r.resolvable ?
|
|
1931
|
+
const icon = r.loadable && r.prefixUnique && r.resolvable ? pc5.green("\u2713") : pc5.red("\u2717");
|
|
1422
1932
|
const label = r.pluginName ? `${r.packageName} (${r.pluginName})` : r.packageName;
|
|
1423
|
-
const prefix = r.prefix ?
|
|
1933
|
+
const prefix = r.prefix ? pc5.dim(` [${r.prefix}]`) : "";
|
|
1424
1934
|
console.log(` ${icon} ${label}${prefix}`);
|
|
1425
1935
|
if (r.error) {
|
|
1426
|
-
console.log(` ${
|
|
1936
|
+
console.log(` ${pc5.yellow("\u2192")} ${r.error}`);
|
|
1427
1937
|
}
|
|
1428
1938
|
}
|
|
1429
1939
|
console.log();
|
|
1430
1940
|
}
|
|
1431
1941
|
function printServerResult(result, showFix) {
|
|
1432
|
-
const icon = result.status === "healthy" ?
|
|
1433
|
-
console.log(` ${icon} ${
|
|
1942
|
+
const icon = result.status === "healthy" ? pc5.green("\u25CF") : pc5.red("\u25CF");
|
|
1943
|
+
console.log(` ${icon} ${pc5.bold(result.serverName)}`);
|
|
1434
1944
|
for (const check of result.checks) {
|
|
1435
1945
|
const checkIcon = check.skipped ? CHECK_ICON.skip : check.passed ? CHECK_ICON.pass : CHECK_ICON.fail;
|
|
1436
1946
|
console.log(` ${checkIcon} ${check.name}: ${check.message}`);
|
|
1437
1947
|
if (showFix && !check.passed && !check.skipped && check.fix) {
|
|
1438
|
-
console.log(` ${
|
|
1948
|
+
console.log(` ${pc5.yellow("\u2192")} Fix: ${pc5.cyan(check.fix)}`);
|
|
1439
1949
|
}
|
|
1440
1950
|
}
|
|
1441
1951
|
console.log();
|
|
@@ -1458,17 +1968,17 @@ async function runParallel(tasks, concurrency) {
|
|
|
1458
1968
|
}
|
|
1459
1969
|
|
|
1460
1970
|
// src/commands/export-command.ts
|
|
1461
|
-
import
|
|
1462
|
-
import
|
|
1463
|
-
import { defineCommand as
|
|
1464
|
-
import
|
|
1971
|
+
import fs7 from "fs";
|
|
1972
|
+
import path7 from "path";
|
|
1973
|
+
import { defineCommand as defineCommand6 } from "citty";
|
|
1974
|
+
import pc6 from "picocolors";
|
|
1465
1975
|
|
|
1466
1976
|
// src/core/export-import-service.ts
|
|
1467
|
-
import
|
|
1977
|
+
import fs6 from "fs";
|
|
1468
1978
|
|
|
1469
1979
|
// src/utils/constants.ts
|
|
1470
1980
|
var APP_NAME = "mcpman";
|
|
1471
|
-
var APP_VERSION = "0.
|
|
1981
|
+
var APP_VERSION = "0.7.0";
|
|
1472
1982
|
var APP_DESCRIPTION = "The package manager for MCP servers";
|
|
1473
1983
|
|
|
1474
1984
|
// src/core/export-import-service.ts
|
|
@@ -1484,7 +1994,7 @@ function createExportBundle(opts = {}) {
|
|
|
1484
1994
|
};
|
|
1485
1995
|
if (includeVault) {
|
|
1486
1996
|
const vaultPath = getVaultPath();
|
|
1487
|
-
if (
|
|
1997
|
+
if (fs6.existsSync(vaultPath)) {
|
|
1488
1998
|
bundle.vault = readVault();
|
|
1489
1999
|
}
|
|
1490
2000
|
}
|
|
@@ -1550,7 +2060,7 @@ function importBundle(bundle, opts = {}) {
|
|
|
1550
2060
|
|
|
1551
2061
|
// src/commands/export-command.ts
|
|
1552
2062
|
var DEFAULT_OUTPUT = "mcpman-export.json";
|
|
1553
|
-
var export_command_default =
|
|
2063
|
+
var export_command_default = defineCommand6({
|
|
1554
2064
|
meta: {
|
|
1555
2065
|
name: "export",
|
|
1556
2066
|
description: "Export mcpman config, lockfile, vault, and plugins to a portable JSON file"
|
|
@@ -1574,29 +2084,29 @@ var export_command_default = defineCommand4({
|
|
|
1574
2084
|
},
|
|
1575
2085
|
run({ args }) {
|
|
1576
2086
|
const outputFile = args.output || DEFAULT_OUTPUT;
|
|
1577
|
-
const outputPath =
|
|
2087
|
+
const outputPath = path7.resolve(outputFile);
|
|
1578
2088
|
const bundle = createExportBundle({
|
|
1579
2089
|
includeVault: !args["no-vault"],
|
|
1580
2090
|
includePlugins: !args["no-plugins"]
|
|
1581
2091
|
});
|
|
1582
2092
|
const serverCount = Object.keys(bundle.lockfile.servers).length;
|
|
1583
2093
|
const configKeys = Object.keys(bundle.config).length;
|
|
1584
|
-
|
|
1585
|
-
console.log(`${
|
|
1586
|
-
console.log(
|
|
1587
|
-
console.log(
|
|
1588
|
-
console.log(
|
|
1589
|
-
console.log(
|
|
2094
|
+
fs7.writeFileSync(outputPath, JSON.stringify(bundle, null, 2), "utf-8");
|
|
2095
|
+
console.log(`${pc6.green("\u2713")} Exported to ${pc6.bold(outputFile)}`);
|
|
2096
|
+
console.log(pc6.dim(` Config keys: ${configKeys}`));
|
|
2097
|
+
console.log(pc6.dim(` Servers: ${serverCount}`));
|
|
2098
|
+
console.log(pc6.dim(` Vault: ${bundle.vault ? "included" : "excluded"}`));
|
|
2099
|
+
console.log(pc6.dim(` Plugins: ${bundle.plugins?.length ?? 0}`));
|
|
1590
2100
|
}
|
|
1591
2101
|
});
|
|
1592
2102
|
|
|
1593
2103
|
// src/commands/import-command.ts
|
|
1594
|
-
import
|
|
1595
|
-
import
|
|
2104
|
+
import fs8 from "fs";
|
|
2105
|
+
import path8 from "path";
|
|
1596
2106
|
import * as p3 from "@clack/prompts";
|
|
1597
|
-
import { defineCommand as
|
|
1598
|
-
import
|
|
1599
|
-
var import_command_default =
|
|
2107
|
+
import { defineCommand as defineCommand7 } from "citty";
|
|
2108
|
+
import pc7 from "picocolors";
|
|
2109
|
+
var import_command_default = defineCommand7({
|
|
1600
2110
|
meta: {
|
|
1601
2111
|
name: "import",
|
|
1602
2112
|
description: "Import mcpman config, lockfile, vault, and plugins from an export file"
|
|
@@ -1619,21 +2129,21 @@ var import_command_default = defineCommand5({
|
|
|
1619
2129
|
}
|
|
1620
2130
|
},
|
|
1621
2131
|
async run({ args }) {
|
|
1622
|
-
const filePath =
|
|
1623
|
-
if (!
|
|
1624
|
-
console.error(`${
|
|
2132
|
+
const filePath = path8.resolve(args.file);
|
|
2133
|
+
if (!fs8.existsSync(filePath)) {
|
|
2134
|
+
console.error(`${pc7.red("\u2717")} File not found: ${filePath}`);
|
|
1625
2135
|
process.exit(1);
|
|
1626
2136
|
}
|
|
1627
2137
|
let raw;
|
|
1628
2138
|
try {
|
|
1629
|
-
raw = JSON.parse(
|
|
2139
|
+
raw = JSON.parse(fs8.readFileSync(filePath, "utf-8"));
|
|
1630
2140
|
} catch {
|
|
1631
|
-
console.error(`${
|
|
2141
|
+
console.error(`${pc7.red("\u2717")} Invalid JSON in ${filePath}`);
|
|
1632
2142
|
process.exit(1);
|
|
1633
2143
|
}
|
|
1634
2144
|
const error2 = validateBundle(raw);
|
|
1635
2145
|
if (error2) {
|
|
1636
|
-
console.error(`${
|
|
2146
|
+
console.error(`${pc7.red("\u2717")} Invalid export bundle: ${error2}`);
|
|
1637
2147
|
process.exit(1);
|
|
1638
2148
|
}
|
|
1639
2149
|
const bundle = raw;
|
|
@@ -1643,16 +2153,16 @@ var import_command_default = defineCommand5({
|
|
|
1643
2153
|
const hasVault = !!bundle.vault;
|
|
1644
2154
|
const isDryRun = !!args["dry-run"];
|
|
1645
2155
|
console.log("");
|
|
1646
|
-
console.log(
|
|
1647
|
-
console.log(
|
|
1648
|
-
console.log(
|
|
1649
|
-
console.log(` Config keys: ${
|
|
1650
|
-
console.log(` Servers: ${
|
|
1651
|
-
console.log(` Vault: ${hasVault ?
|
|
1652
|
-
console.log(` Plugins: ${
|
|
2156
|
+
console.log(pc7.bold("Import summary:"));
|
|
2157
|
+
console.log(pc7.dim(` Source version: mcpman ${bundle.mcpmanVersion}`));
|
|
2158
|
+
console.log(pc7.dim(` Exported at: ${bundle.exportedAt}`));
|
|
2159
|
+
console.log(` Config keys: ${pc7.cyan(String(configKeys))}`);
|
|
2160
|
+
console.log(` Servers: ${pc7.cyan(String(serverCount))}`);
|
|
2161
|
+
console.log(` Vault: ${hasVault ? pc7.green("included") : pc7.dim("not included")}`);
|
|
2162
|
+
console.log(` Plugins: ${pc7.cyan(String(pluginCount))}`);
|
|
1653
2163
|
console.log("");
|
|
1654
2164
|
if (isDryRun) {
|
|
1655
|
-
console.log(
|
|
2165
|
+
console.log(pc7.yellow(" [dry-run] No changes applied."));
|
|
1656
2166
|
return;
|
|
1657
2167
|
}
|
|
1658
2168
|
if (!args.yes) {
|
|
@@ -1666,18 +2176,18 @@ var import_command_default = defineCommand5({
|
|
|
1666
2176
|
}
|
|
1667
2177
|
}
|
|
1668
2178
|
const summary = importBundle(bundle, { dryRun: false });
|
|
1669
|
-
console.log(`${
|
|
1670
|
-
console.log(
|
|
1671
|
-
console.log(
|
|
1672
|
-
console.log(
|
|
1673
|
-
console.log(
|
|
2179
|
+
console.log(`${pc7.green("\u2713")} Import complete`);
|
|
2180
|
+
console.log(pc7.dim(` Config keys restored: ${summary.configKeys}`));
|
|
2181
|
+
console.log(pc7.dim(` Servers restored: ${summary.servers}`));
|
|
2182
|
+
console.log(pc7.dim(` Vault: ${summary.vaultImported ? "restored" : "skipped"}`));
|
|
2183
|
+
console.log(pc7.dim(` Plugins installed: ${summary.pluginsInstalled}`));
|
|
1674
2184
|
}
|
|
1675
2185
|
});
|
|
1676
2186
|
|
|
1677
2187
|
// src/commands/info.ts
|
|
1678
|
-
import { defineCommand as
|
|
2188
|
+
import { defineCommand as defineCommand8 } from "citty";
|
|
1679
2189
|
import { createSpinner as createSpinner2 } from "nanospinner";
|
|
1680
|
-
import
|
|
2190
|
+
import pc8 from "picocolors";
|
|
1681
2191
|
|
|
1682
2192
|
// src/core/package-info.ts
|
|
1683
2193
|
async function buildInfo(name, entry, source = "npm") {
|
|
@@ -1732,11 +2242,11 @@ async function getPackageInfo(serverName) {
|
|
|
1732
2242
|
// src/commands/info.ts
|
|
1733
2243
|
function colorRisk2(score, riskLevel) {
|
|
1734
2244
|
const label = score !== null ? `${score}/100 (${riskLevel})` : riskLevel;
|
|
1735
|
-
if (riskLevel === "LOW") return
|
|
1736
|
-
if (riskLevel === "MEDIUM") return
|
|
1737
|
-
if (riskLevel === "HIGH") return
|
|
1738
|
-
if (riskLevel === "CRITICAL") return
|
|
1739
|
-
return
|
|
2245
|
+
if (riskLevel === "LOW") return pc8.green(label);
|
|
2246
|
+
if (riskLevel === "MEDIUM") return pc8.yellow(label);
|
|
2247
|
+
if (riskLevel === "HIGH") return pc8.red(label);
|
|
2248
|
+
if (riskLevel === "CRITICAL") return pc8.bold(pc8.red(label));
|
|
2249
|
+
return pc8.dim(label);
|
|
1740
2250
|
}
|
|
1741
2251
|
function formatDaysAgo(isoDate) {
|
|
1742
2252
|
if (!isoDate) return "unknown";
|
|
@@ -1746,54 +2256,54 @@ function formatDaysAgo(isoDate) {
|
|
|
1746
2256
|
return `${days} days ago`;
|
|
1747
2257
|
}
|
|
1748
2258
|
function printInfo(info2) {
|
|
1749
|
-
const installedBadge = info2.isInstalled ?
|
|
2259
|
+
const installedBadge = info2.isInstalled ? pc8.green(" [installed]") : pc8.dim(" [not installed]");
|
|
1750
2260
|
console.log();
|
|
1751
|
-
console.log(
|
|
1752
|
-
console.log(
|
|
1753
|
-
console.log(` ${
|
|
1754
|
-
console.log(` ${
|
|
2261
|
+
console.log(pc8.bold(` ${info2.name}@${info2.version}`) + installedBadge);
|
|
2262
|
+
console.log(pc8.dim(` ${"\u2500".repeat(60)}`));
|
|
2263
|
+
console.log(` ${pc8.dim("Source:")} ${info2.source}`);
|
|
2264
|
+
console.log(` ${pc8.dim("Runtime:")} ${info2.runtime}`);
|
|
1755
2265
|
if (info2.description) {
|
|
1756
|
-
console.log(` ${
|
|
2266
|
+
console.log(` ${pc8.dim("Description:")} ${info2.description}`);
|
|
1757
2267
|
}
|
|
1758
2268
|
if (info2.deprecated) {
|
|
1759
|
-
console.log(` ${
|
|
2269
|
+
console.log(` ${pc8.red("[DEPRECATED]")} This package is deprecated`);
|
|
1760
2270
|
}
|
|
1761
2271
|
console.log();
|
|
1762
|
-
console.log(` ${
|
|
1763
|
-
console.log(` ${
|
|
2272
|
+
console.log(` ${pc8.bold("Trust & Security")}`);
|
|
2273
|
+
console.log(` ${pc8.dim("Trust score:")} ${colorRisk2(info2.trustScore, info2.riskLevel)}`);
|
|
1764
2274
|
if (info2.source === "npm") {
|
|
1765
2275
|
console.log(
|
|
1766
|
-
` ${
|
|
2276
|
+
` ${pc8.dim("Downloads:")} ${info2.weeklyDownloads.toLocaleString()}/week ${pc8.dim("|")} ${pc8.dim("Age:")} ${info2.packageAge}d ${pc8.dim("|")} ${pc8.dim("Maintainers:")} ${info2.maintainerCount}`
|
|
1767
2277
|
);
|
|
1768
2278
|
if (info2.lastPublish) {
|
|
1769
|
-
console.log(` ${
|
|
2279
|
+
console.log(` ${pc8.dim("Last publish:")} ${formatDaysAgo(info2.lastPublish)}`);
|
|
1770
2280
|
}
|
|
1771
2281
|
} else {
|
|
1772
|
-
console.log(
|
|
2282
|
+
console.log(pc8.dim(" (Trust data available for npm packages only)"));
|
|
1773
2283
|
}
|
|
1774
2284
|
console.log();
|
|
1775
|
-
console.log(` ${
|
|
2285
|
+
console.log(` ${pc8.bold("Environment Variables")}`);
|
|
1776
2286
|
if (info2.envVars.length > 0) {
|
|
1777
2287
|
for (const env of info2.envVars) {
|
|
1778
|
-
console.log(` ${
|
|
2288
|
+
console.log(` ${pc8.cyan("\u2022")} ${env}`);
|
|
1779
2289
|
}
|
|
1780
2290
|
} else {
|
|
1781
|
-
console.log(
|
|
2291
|
+
console.log(pc8.dim(" none required"));
|
|
1782
2292
|
}
|
|
1783
2293
|
console.log();
|
|
1784
|
-
console.log(` ${
|
|
2294
|
+
console.log(` ${pc8.bold("Installed Clients")}`);
|
|
1785
2295
|
if (info2.installedClients.length > 0) {
|
|
1786
2296
|
for (const client of info2.installedClients) {
|
|
1787
|
-
console.log(` ${
|
|
2297
|
+
console.log(` ${pc8.green("\u2713")} ${client}`);
|
|
1788
2298
|
}
|
|
1789
2299
|
} else {
|
|
1790
|
-
console.log(
|
|
2300
|
+
console.log(pc8.dim(" Not installed in any client"));
|
|
1791
2301
|
}
|
|
1792
2302
|
console.log();
|
|
1793
|
-
console.log(
|
|
2303
|
+
console.log(pc8.dim(` ${"\u2500".repeat(60)}`));
|
|
1794
2304
|
console.log();
|
|
1795
2305
|
}
|
|
1796
|
-
var info_default =
|
|
2306
|
+
var info_default = defineCommand8({
|
|
1797
2307
|
meta: {
|
|
1798
2308
|
name: "info",
|
|
1799
2309
|
description: "Show detailed metadata for an MCP server (installed or from registry)"
|
|
@@ -1817,13 +2327,13 @@ var info_default = defineCommand6({
|
|
|
1817
2327
|
info2 = await getPackageInfo(args.server);
|
|
1818
2328
|
} catch (err) {
|
|
1819
2329
|
spinner5.error({ text: "Failed to fetch package info" });
|
|
1820
|
-
console.error(
|
|
2330
|
+
console.error(pc8.red(String(err)));
|
|
1821
2331
|
process.exit(1);
|
|
1822
2332
|
}
|
|
1823
2333
|
if (!info2) {
|
|
1824
2334
|
spinner5.error({ text: `Package not found: ${args.server}` });
|
|
1825
2335
|
console.log(
|
|
1826
|
-
|
|
2336
|
+
pc8.dim(`
|
|
1827
2337
|
"${args.server}" was not found in the npm registry or your lockfile.
|
|
1828
2338
|
`)
|
|
1829
2339
|
);
|
|
@@ -1839,10 +2349,10 @@ var info_default = defineCommand6({
|
|
|
1839
2349
|
});
|
|
1840
2350
|
|
|
1841
2351
|
// src/commands/init.ts
|
|
1842
|
-
import
|
|
2352
|
+
import path9 from "path";
|
|
1843
2353
|
import * as p4 from "@clack/prompts";
|
|
1844
|
-
import { defineCommand as
|
|
1845
|
-
var init_default =
|
|
2354
|
+
import { defineCommand as defineCommand9 } from "citty";
|
|
2355
|
+
var init_default = defineCommand9({
|
|
1846
2356
|
meta: {
|
|
1847
2357
|
name: "init",
|
|
1848
2358
|
description: "Initialize mcpman.lock in the current project"
|
|
@@ -1858,7 +2368,7 @@ var init_default = defineCommand7({
|
|
|
1858
2368
|
async run({ args }) {
|
|
1859
2369
|
const nonInteractive = args.yes || !process.stdout.isTTY;
|
|
1860
2370
|
p4.intro("mcpman init");
|
|
1861
|
-
const targetPath =
|
|
2371
|
+
const targetPath = path9.join(process.cwd(), LOCKFILE_NAME);
|
|
1862
2372
|
const existing = findLockfile();
|
|
1863
2373
|
if (existing) {
|
|
1864
2374
|
if (nonInteractive) {
|
|
@@ -1942,7 +2452,7 @@ var init_default = defineCommand7({
|
|
|
1942
2452
|
|
|
1943
2453
|
// src/commands/install.ts
|
|
1944
2454
|
import * as p7 from "@clack/prompts";
|
|
1945
|
-
import { defineCommand as
|
|
2455
|
+
import { defineCommand as defineCommand10 } from "citty";
|
|
1946
2456
|
|
|
1947
2457
|
// src/core/installer.ts
|
|
1948
2458
|
import * as p6 from "@clack/prompts";
|
|
@@ -2094,7 +2604,7 @@ async function installServer(input, options = {}) {
|
|
|
2094
2604
|
}
|
|
2095
2605
|
|
|
2096
2606
|
// src/utils/logger.ts
|
|
2097
|
-
import
|
|
2607
|
+
import pc9 from "picocolors";
|
|
2098
2608
|
var noColor = process.env.NO_COLOR !== void 0 || process.argv.includes("--no-color");
|
|
2099
2609
|
var isVerbose = process.argv.includes("--verbose");
|
|
2100
2610
|
var isJson = process.argv.includes("--json");
|
|
@@ -2103,18 +2613,18 @@ function colorize(fn, text2) {
|
|
|
2103
2613
|
}
|
|
2104
2614
|
function info(message) {
|
|
2105
2615
|
if (isJson) return;
|
|
2106
|
-
console.log(`${colorize(
|
|
2616
|
+
console.log(`${colorize(pc9.cyan, "i")} ${message}`);
|
|
2107
2617
|
}
|
|
2108
2618
|
function error(message) {
|
|
2109
2619
|
if (isJson) return;
|
|
2110
|
-
console.error(`${colorize(
|
|
2620
|
+
console.error(`${colorize(pc9.red, "\u2717")} ${message}`);
|
|
2111
2621
|
}
|
|
2112
2622
|
function json(data) {
|
|
2113
2623
|
console.log(JSON.stringify(data, null, 2));
|
|
2114
2624
|
}
|
|
2115
2625
|
|
|
2116
2626
|
// src/commands/install.ts
|
|
2117
|
-
var install_default =
|
|
2627
|
+
var install_default = defineCommand10({
|
|
2118
2628
|
meta: {
|
|
2119
2629
|
name: "install",
|
|
2120
2630
|
description: "Install an MCP server into one or more AI clients"
|
|
@@ -2175,28 +2685,228 @@ async function restoreFromLockfile() {
|
|
|
2175
2685
|
p7.outro("Restore complete.");
|
|
2176
2686
|
}
|
|
2177
2687
|
|
|
2178
|
-
// src/commands/
|
|
2179
|
-
import
|
|
2180
|
-
import
|
|
2181
|
-
|
|
2182
|
-
|
|
2183
|
-
|
|
2184
|
-
|
|
2185
|
-
|
|
2186
|
-
|
|
2187
|
-
|
|
2188
|
-
|
|
2189
|
-
|
|
2190
|
-
|
|
2191
|
-
|
|
2192
|
-
|
|
2193
|
-
|
|
2194
|
-
|
|
2195
|
-
|
|
2196
|
-
|
|
2197
|
-
|
|
2198
|
-
|
|
2199
|
-
|
|
2688
|
+
// src/commands/link.ts
|
|
2689
|
+
import path11 from "path";
|
|
2690
|
+
import { defineCommand as defineCommand11 } from "citty";
|
|
2691
|
+
import pc10 from "picocolors";
|
|
2692
|
+
|
|
2693
|
+
// src/core/link-service.ts
|
|
2694
|
+
import fs9 from "fs";
|
|
2695
|
+
import path10 from "path";
|
|
2696
|
+
function detectLocalServer(dir) {
|
|
2697
|
+
if (!fs9.existsSync(dir)) {
|
|
2698
|
+
throw new Error(`Directory does not exist: ${dir}`);
|
|
2699
|
+
}
|
|
2700
|
+
const pkgPath = path10.join(dir, "package.json");
|
|
2701
|
+
if (fs9.existsSync(pkgPath)) {
|
|
2702
|
+
return detectNodeServer(dir, pkgPath);
|
|
2703
|
+
}
|
|
2704
|
+
const pyprojectPath = path10.join(dir, "pyproject.toml");
|
|
2705
|
+
if (fs9.existsSync(pyprojectPath)) {
|
|
2706
|
+
return detectPythonServer(dir, pyprojectPath);
|
|
2707
|
+
}
|
|
2708
|
+
throw new Error(
|
|
2709
|
+
`No package.json or pyproject.toml found in '${dir}'. Is this an MCP server project?`
|
|
2710
|
+
);
|
|
2711
|
+
}
|
|
2712
|
+
function detectNodeServer(dir, pkgPath) {
|
|
2713
|
+
const raw = fs9.readFileSync(pkgPath, "utf-8");
|
|
2714
|
+
const pkg = JSON.parse(raw);
|
|
2715
|
+
const name = String(pkg.name ?? path10.basename(dir));
|
|
2716
|
+
const version = String(pkg.version ?? "0.0.0");
|
|
2717
|
+
let entryPoint = null;
|
|
2718
|
+
if (pkg.bin) {
|
|
2719
|
+
if (typeof pkg.bin === "string") {
|
|
2720
|
+
entryPoint = path10.resolve(dir, pkg.bin);
|
|
2721
|
+
} else if (typeof pkg.bin === "object" && pkg.bin !== null) {
|
|
2722
|
+
const binObj = pkg.bin;
|
|
2723
|
+
const firstBin = Object.values(binObj)[0];
|
|
2724
|
+
if (firstBin) entryPoint = path10.resolve(dir, firstBin);
|
|
2725
|
+
}
|
|
2726
|
+
}
|
|
2727
|
+
if (!entryPoint && pkg.main) {
|
|
2728
|
+
entryPoint = path10.resolve(dir, String(pkg.main));
|
|
2729
|
+
}
|
|
2730
|
+
if (!entryPoint) {
|
|
2731
|
+
const candidates = ["src/index.ts", "src/index.js", "index.ts", "index.js"];
|
|
2732
|
+
for (const c of candidates) {
|
|
2733
|
+
const candidate = path10.join(dir, c);
|
|
2734
|
+
if (fs9.existsSync(candidate)) {
|
|
2735
|
+
entryPoint = candidate;
|
|
2736
|
+
break;
|
|
2737
|
+
}
|
|
2738
|
+
}
|
|
2739
|
+
}
|
|
2740
|
+
if (!entryPoint) {
|
|
2741
|
+
throw new Error(`Cannot determine entry point for Node server in '${dir}'.`);
|
|
2742
|
+
}
|
|
2743
|
+
const isTs = entryPoint.endsWith(".ts");
|
|
2744
|
+
const command = isTs ? "npx" : "node";
|
|
2745
|
+
const args = isTs ? ["tsx", entryPoint] : [entryPoint];
|
|
2746
|
+
const mcpField = pkg.mcp;
|
|
2747
|
+
const envVars = [];
|
|
2748
|
+
if (mcpField?.env && Array.isArray(mcpField.env)) {
|
|
2749
|
+
for (const e of mcpField.env) {
|
|
2750
|
+
if (typeof e === "string") envVars.push(e);
|
|
2751
|
+
else if (typeof e === "object" && e !== null && "name" in e) {
|
|
2752
|
+
envVars.push(String(e.name));
|
|
2753
|
+
}
|
|
2754
|
+
}
|
|
2755
|
+
}
|
|
2756
|
+
return { name, version, command, args, envVars, absolutePath: dir, runtime: "node" };
|
|
2757
|
+
}
|
|
2758
|
+
function detectPythonServer(dir, pyprojectPath) {
|
|
2759
|
+
const raw = fs9.readFileSync(pyprojectPath, "utf-8");
|
|
2760
|
+
const name = extractTomlValue(raw, "name") ?? path10.basename(dir);
|
|
2761
|
+
const version = extractTomlValue(raw, "version") ?? "0.0.0";
|
|
2762
|
+
let pythonCmd = "python3";
|
|
2763
|
+
const venvPython = path10.join(dir, ".venv", "bin", "python");
|
|
2764
|
+
if (fs9.existsSync(venvPython)) {
|
|
2765
|
+
pythonCmd = venvPython;
|
|
2766
|
+
}
|
|
2767
|
+
const entryCandidate = [
|
|
2768
|
+
path10.join(dir, "main.py"),
|
|
2769
|
+
path10.join(dir, name.replace(/-/g, "_"), "main.py"),
|
|
2770
|
+
path10.join(dir, "__main__.py")
|
|
2771
|
+
].find((p12) => fs9.existsSync(p12));
|
|
2772
|
+
const entryPoint = entryCandidate ?? path10.join(dir, "main.py");
|
|
2773
|
+
return {
|
|
2774
|
+
name,
|
|
2775
|
+
version,
|
|
2776
|
+
command: pythonCmd,
|
|
2777
|
+
args: [entryPoint],
|
|
2778
|
+
envVars: [],
|
|
2779
|
+
absolutePath: dir,
|
|
2780
|
+
runtime: "python"
|
|
2781
|
+
};
|
|
2782
|
+
}
|
|
2783
|
+
function extractTomlValue(content, key) {
|
|
2784
|
+
const match = content.match(new RegExp(`^${key}\\s*=\\s*"([^"]+)"`, "m"));
|
|
2785
|
+
return match ? match[1] : null;
|
|
2786
|
+
}
|
|
2787
|
+
async function registerLinkedServer(linkResult, clients, lockfilePath, nameOverride) {
|
|
2788
|
+
const serverName = nameOverride ?? linkResult.name;
|
|
2789
|
+
const registered = [];
|
|
2790
|
+
for (const client of clients) {
|
|
2791
|
+
try {
|
|
2792
|
+
await client.addServer(serverName, {
|
|
2793
|
+
command: linkResult.command,
|
|
2794
|
+
args: linkResult.args
|
|
2795
|
+
});
|
|
2796
|
+
registered.push(client.type);
|
|
2797
|
+
} catch {
|
|
2798
|
+
}
|
|
2799
|
+
}
|
|
2800
|
+
const lockEntry = {
|
|
2801
|
+
version: linkResult.version,
|
|
2802
|
+
source: "local",
|
|
2803
|
+
resolved: linkResult.absolutePath,
|
|
2804
|
+
integrity: "local",
|
|
2805
|
+
runtime: linkResult.runtime,
|
|
2806
|
+
command: linkResult.command,
|
|
2807
|
+
args: linkResult.args,
|
|
2808
|
+
envVars: linkResult.envVars,
|
|
2809
|
+
installedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2810
|
+
clients: registered
|
|
2811
|
+
};
|
|
2812
|
+
const existing = readLockfile(lockfilePath);
|
|
2813
|
+
existing.servers[serverName] = lockEntry;
|
|
2814
|
+
const { writeLockfile: writeLockfile2 } = await import("./lockfile-RBA7HB24.js");
|
|
2815
|
+
writeLockfile2(existing, lockfilePath);
|
|
2816
|
+
return registered;
|
|
2817
|
+
}
|
|
2818
|
+
|
|
2819
|
+
// src/commands/link.ts
|
|
2820
|
+
var link_default = defineCommand11({
|
|
2821
|
+
meta: {
|
|
2822
|
+
name: "link",
|
|
2823
|
+
description: "Register a local MCP server directory with AI clients"
|
|
2824
|
+
},
|
|
2825
|
+
args: {
|
|
2826
|
+
dir: {
|
|
2827
|
+
type: "positional",
|
|
2828
|
+
description: "Path to local MCP server directory (default: .)",
|
|
2829
|
+
required: false
|
|
2830
|
+
},
|
|
2831
|
+
client: {
|
|
2832
|
+
type: "string",
|
|
2833
|
+
description: "Register with specific client only (claude-desktop, cursor, vscode, windsurf)",
|
|
2834
|
+
alias: "c"
|
|
2835
|
+
},
|
|
2836
|
+
name: {
|
|
2837
|
+
type: "string",
|
|
2838
|
+
description: "Override the detected server name",
|
|
2839
|
+
alias: "n"
|
|
2840
|
+
}
|
|
2841
|
+
},
|
|
2842
|
+
async run({ args }) {
|
|
2843
|
+
const dirArg = args.dir ?? ".";
|
|
2844
|
+
const clientFilter = args.client;
|
|
2845
|
+
const nameOverride = args.name;
|
|
2846
|
+
const absoluteDir = path11.resolve(dirArg);
|
|
2847
|
+
let linkResult;
|
|
2848
|
+
try {
|
|
2849
|
+
linkResult = detectLocalServer(absoluteDir);
|
|
2850
|
+
} catch (err) {
|
|
2851
|
+
console.error(pc10.red(` Error: ${err instanceof Error ? err.message : String(err)}`));
|
|
2852
|
+
process.exit(1);
|
|
2853
|
+
}
|
|
2854
|
+
const serverName = nameOverride ?? linkResult.name;
|
|
2855
|
+
console.log(pc10.dim(`
|
|
2856
|
+
Detected: ${pc10.cyan(serverName)} (${linkResult.runtime})`));
|
|
2857
|
+
console.log(pc10.dim(` Path: ${absoluteDir}`));
|
|
2858
|
+
console.log(pc10.dim(` Command: ${linkResult.command} ${linkResult.args.join(" ")}`));
|
|
2859
|
+
const allClients = await getInstalledClients();
|
|
2860
|
+
const clients = clientFilter ? allClients.filter((c) => c.type === clientFilter) : allClients;
|
|
2861
|
+
if (clientFilter && clients.length === 0) {
|
|
2862
|
+
console.error(pc10.red(` Error: Unknown client '${clientFilter}'.`));
|
|
2863
|
+
process.exit(1);
|
|
2864
|
+
}
|
|
2865
|
+
let registered;
|
|
2866
|
+
try {
|
|
2867
|
+
registered = await registerLinkedServer(linkResult, clients, void 0, nameOverride);
|
|
2868
|
+
} catch (err) {
|
|
2869
|
+
console.error(pc10.red(` Error: ${err instanceof Error ? err.message : String(err)}`));
|
|
2870
|
+
process.exit(1);
|
|
2871
|
+
}
|
|
2872
|
+
if (registered.length === 0) {
|
|
2873
|
+
console.log(pc10.yellow(" Warning: No clients registered. Are any AI clients installed?"));
|
|
2874
|
+
console.log(pc10.dim(` Server saved to lockfile with source "local".`));
|
|
2875
|
+
} else {
|
|
2876
|
+
console.log(pc10.green(`
|
|
2877
|
+
Linked ${pc10.bold(serverName)} to: ${registered.join(", ")}
|
|
2878
|
+
`));
|
|
2879
|
+
console.log(pc10.dim(` Run ${pc10.cyan("mcpman list")} to verify.`));
|
|
2880
|
+
console.log(
|
|
2881
|
+
pc10.dim(` Run ${pc10.cyan(`mcpman watch ${serverName}`)} to start with auto-restart.`)
|
|
2882
|
+
);
|
|
2883
|
+
}
|
|
2884
|
+
console.log();
|
|
2885
|
+
}
|
|
2886
|
+
});
|
|
2887
|
+
|
|
2888
|
+
// src/commands/list.ts
|
|
2889
|
+
import { defineCommand as defineCommand12 } from "citty";
|
|
2890
|
+
import pc11 from "picocolors";
|
|
2891
|
+
var STATUS_ICON = {
|
|
2892
|
+
healthy: pc11.green("\u25CF"),
|
|
2893
|
+
unhealthy: pc11.red("\u25CF"),
|
|
2894
|
+
unknown: pc11.dim("\u25CB")
|
|
2895
|
+
};
|
|
2896
|
+
var list_default = defineCommand12({
|
|
2897
|
+
meta: {
|
|
2898
|
+
name: "list",
|
|
2899
|
+
description: "List installed MCP servers"
|
|
2900
|
+
},
|
|
2901
|
+
args: {
|
|
2902
|
+
client: {
|
|
2903
|
+
type: "string",
|
|
2904
|
+
description: "Filter by client (claude, cursor, vscode, windsurf)"
|
|
2905
|
+
},
|
|
2906
|
+
json: {
|
|
2907
|
+
type: "boolean",
|
|
2908
|
+
description: "Output as JSON",
|
|
2909
|
+
default: false
|
|
2200
2910
|
}
|
|
2201
2911
|
},
|
|
2202
2912
|
async run({ args }) {
|
|
@@ -2204,8 +2914,8 @@ var list_default = defineCommand9({
|
|
|
2204
2914
|
if (servers.length === 0) {
|
|
2205
2915
|
const filter = args.client ? ` for client "${args.client}"` : "";
|
|
2206
2916
|
console.log(
|
|
2207
|
-
|
|
2208
|
-
`No MCP servers installed${filter}. Run ${
|
|
2917
|
+
pc11.dim(
|
|
2918
|
+
`No MCP servers installed${filter}. Run ${pc11.cyan("mcpman install <server>")} to get started.`
|
|
2209
2919
|
)
|
|
2210
2920
|
);
|
|
2211
2921
|
return;
|
|
@@ -2231,9 +2941,9 @@ var list_default = defineCommand9({
|
|
|
2231
2941
|
const nameWidth = Math.max(4, ...withStatus.map((s) => s.name.length));
|
|
2232
2942
|
const clientsWidth = Math.max(7, ...withStatus.map((s) => formatClients(s.clients).length));
|
|
2233
2943
|
const header = ` ${pad("NAME", nameWidth)} ${pad("CLIENT(S)", clientsWidth)} ${pad("COMMAND", 20)} STATUS`;
|
|
2234
|
-
console.log(
|
|
2944
|
+
console.log(pc11.dim(header));
|
|
2235
2945
|
console.log(
|
|
2236
|
-
|
|
2946
|
+
pc11.dim(` ${"-".repeat(nameWidth)} ${"-".repeat(clientsWidth)} ${"-".repeat(20)} ------`)
|
|
2237
2947
|
);
|
|
2238
2948
|
for (const s of withStatus) {
|
|
2239
2949
|
const icon = STATUS_ICON[s.status];
|
|
@@ -2248,7 +2958,7 @@ var list_default = defineCommand9({
|
|
|
2248
2958
|
}
|
|
2249
2959
|
const clientSet = new Set(withStatus.flatMap((s) => s.clients));
|
|
2250
2960
|
console.log(
|
|
2251
|
-
|
|
2961
|
+
pc11.dim(
|
|
2252
2962
|
`
|
|
2253
2963
|
${withStatus.length} server${withStatus.length !== 1 ? "s" : ""} \xB7 ${clientSet.size} client${clientSet.size !== 1 ? "s" : ""}`
|
|
2254
2964
|
)
|
|
@@ -2273,9 +2983,9 @@ function formatClients(clients) {
|
|
|
2273
2983
|
|
|
2274
2984
|
// src/commands/logs.ts
|
|
2275
2985
|
import { spawn as spawn2 } from "child_process";
|
|
2276
|
-
import { defineCommand as
|
|
2277
|
-
import
|
|
2278
|
-
var logs_default =
|
|
2986
|
+
import { defineCommand as defineCommand13 } from "citty";
|
|
2987
|
+
import pc12 from "picocolors";
|
|
2988
|
+
var logs_default = defineCommand13({
|
|
2279
2989
|
meta: {
|
|
2280
2990
|
name: "logs",
|
|
2281
2991
|
description: "Stream stdout/stderr from an MCP server"
|
|
@@ -2298,7 +3008,7 @@ var logs_default = defineCommand10({
|
|
|
2298
3008
|
const lockfile = readLockfile();
|
|
2299
3009
|
const entry = lockfile.servers[serverName];
|
|
2300
3010
|
if (!entry) {
|
|
2301
|
-
console.error(
|
|
3011
|
+
console.error(pc12.red(` Error: Server '${serverName}' is not installed.`));
|
|
2302
3012
|
process.exit(1);
|
|
2303
3013
|
}
|
|
2304
3014
|
const lockfileEnv = parseEnvFlags(entry.envVars);
|
|
@@ -2308,24 +3018,24 @@ var logs_default = defineCommand10({
|
|
|
2308
3018
|
...lockfileEnv,
|
|
2309
3019
|
...vaultEnv
|
|
2310
3020
|
};
|
|
2311
|
-
console.log(
|
|
3021
|
+
console.log(pc12.dim(` Streaming logs for ${pc12.cyan(serverName)}... (Ctrl+C to stop)
|
|
2312
3022
|
`));
|
|
2313
3023
|
const child = spawn2(entry.command, entry.args, {
|
|
2314
3024
|
env: finalEnv,
|
|
2315
3025
|
stdio: ["pipe", "pipe", "pipe"]
|
|
2316
3026
|
});
|
|
2317
3027
|
child.stdout?.on("data", (chunk) => {
|
|
2318
|
-
process.stdout.write(
|
|
3028
|
+
process.stdout.write(pc12.dim("[stdout] ") + chunk.toString());
|
|
2319
3029
|
});
|
|
2320
3030
|
child.stderr?.on("data", (chunk) => {
|
|
2321
|
-
process.stderr.write(
|
|
3031
|
+
process.stderr.write(pc12.yellow("[stderr] ") + chunk.toString());
|
|
2322
3032
|
});
|
|
2323
3033
|
child.on("error", (err) => {
|
|
2324
|
-
console.error(
|
|
3034
|
+
console.error(pc12.red(` Failed to start '${serverName}': ${err.message}`));
|
|
2325
3035
|
process.exit(1);
|
|
2326
3036
|
});
|
|
2327
3037
|
child.on("close", (code) => {
|
|
2328
|
-
console.log(
|
|
3038
|
+
console.log(pc12.dim(`
|
|
2329
3039
|
Process exited with code ${code ?? 0}`));
|
|
2330
3040
|
process.exit(code ?? 0);
|
|
2331
3041
|
});
|
|
@@ -2350,10 +3060,10 @@ async function loadVaultSecrets(serverName) {
|
|
|
2350
3060
|
}
|
|
2351
3061
|
|
|
2352
3062
|
// src/commands/plugin.ts
|
|
2353
|
-
import { defineCommand as
|
|
3063
|
+
import { defineCommand as defineCommand14 } from "citty";
|
|
2354
3064
|
import { createSpinner as createSpinner3 } from "nanospinner";
|
|
2355
|
-
import
|
|
2356
|
-
var addCommand =
|
|
3065
|
+
import pc13 from "picocolors";
|
|
3066
|
+
var addCommand = defineCommand14({
|
|
2357
3067
|
meta: { name: "add", description: "Install a plugin package" },
|
|
2358
3068
|
args: {
|
|
2359
3069
|
package: {
|
|
@@ -2371,23 +3081,23 @@ var addCommand = defineCommand11({
|
|
|
2371
3081
|
spinner5.stop();
|
|
2372
3082
|
if (loaded) {
|
|
2373
3083
|
console.log(
|
|
2374
|
-
`${
|
|
3084
|
+
`${pc13.green("\u2713")} Plugin ${pc13.bold(loaded.name)} installed (prefix: ${pc13.cyan(loaded.prefix)})`
|
|
2375
3085
|
);
|
|
2376
3086
|
} else {
|
|
2377
3087
|
console.log(
|
|
2378
|
-
`${
|
|
3088
|
+
`${pc13.yellow("\u26A0")} Package ${pc13.bold(pkg)} installed but does not export a valid mcpman plugin.`
|
|
2379
3089
|
);
|
|
2380
3090
|
}
|
|
2381
3091
|
} catch (err) {
|
|
2382
3092
|
spinner5.stop();
|
|
2383
3093
|
console.error(
|
|
2384
|
-
`${
|
|
3094
|
+
`${pc13.red("\u2717")} Failed to install plugin: ${err instanceof Error ? err.message : String(err)}`
|
|
2385
3095
|
);
|
|
2386
3096
|
process.exit(1);
|
|
2387
3097
|
}
|
|
2388
3098
|
}
|
|
2389
3099
|
});
|
|
2390
|
-
var removeCommand =
|
|
3100
|
+
var removeCommand = defineCommand14({
|
|
2391
3101
|
meta: { name: "remove", description: "Uninstall a plugin package" },
|
|
2392
3102
|
args: {
|
|
2393
3103
|
package: {
|
|
@@ -2400,46 +3110,46 @@ var removeCommand = defineCommand11({
|
|
|
2400
3110
|
const pkg = args.package;
|
|
2401
3111
|
const installed = listPluginPackages();
|
|
2402
3112
|
if (!installed.includes(pkg)) {
|
|
2403
|
-
console.log(
|
|
3113
|
+
console.log(pc13.dim(`Plugin "${pkg}" is not installed.`));
|
|
2404
3114
|
return;
|
|
2405
3115
|
}
|
|
2406
3116
|
try {
|
|
2407
3117
|
removePluginPackage(pkg);
|
|
2408
|
-
console.log(`${
|
|
3118
|
+
console.log(`${pc13.green("\u2713")} Plugin ${pc13.bold(pkg)} removed.`);
|
|
2409
3119
|
} catch (err) {
|
|
2410
3120
|
console.error(
|
|
2411
|
-
`${
|
|
3121
|
+
`${pc13.red("\u2717")} Failed to remove plugin: ${err instanceof Error ? err.message : String(err)}`
|
|
2412
3122
|
);
|
|
2413
3123
|
process.exit(1);
|
|
2414
3124
|
}
|
|
2415
3125
|
}
|
|
2416
3126
|
});
|
|
2417
|
-
var listCommand2 =
|
|
3127
|
+
var listCommand2 = defineCommand14({
|
|
2418
3128
|
meta: { name: "list", description: "List installed plugins" },
|
|
2419
3129
|
run() {
|
|
2420
3130
|
const packages = listPluginPackages();
|
|
2421
3131
|
if (packages.length === 0) {
|
|
2422
|
-
console.log(
|
|
3132
|
+
console.log(pc13.dim("No plugins installed. Use `mcpman plugin add <package>`."));
|
|
2423
3133
|
return;
|
|
2424
3134
|
}
|
|
2425
3135
|
console.log("");
|
|
2426
|
-
console.log(
|
|
3136
|
+
console.log(pc13.bold("Installed plugins:"));
|
|
2427
3137
|
console.log("");
|
|
2428
3138
|
for (const pkg of packages) {
|
|
2429
3139
|
const loaded = loadPlugin(pkg);
|
|
2430
3140
|
if (loaded) {
|
|
2431
3141
|
console.log(
|
|
2432
|
-
` ${
|
|
3142
|
+
` ${pc13.green("\u25CF")} ${pc13.bold(loaded.name)} prefix: ${pc13.cyan(loaded.prefix)} pkg: ${pc13.dim(pkg)}`
|
|
2433
3143
|
);
|
|
2434
3144
|
} else {
|
|
2435
|
-
console.log(` ${
|
|
3145
|
+
console.log(` ${pc13.yellow("\u25CF")} ${pc13.dim(pkg)} ${pc13.yellow("(failed to load)")}`);
|
|
2436
3146
|
}
|
|
2437
3147
|
}
|
|
2438
3148
|
console.log("");
|
|
2439
|
-
console.log(
|
|
3149
|
+
console.log(pc13.dim(` ${packages.length} plugin${packages.length !== 1 ? "s" : ""} installed`));
|
|
2440
3150
|
}
|
|
2441
3151
|
});
|
|
2442
|
-
var plugin_default =
|
|
3152
|
+
var plugin_default = defineCommand14({
|
|
2443
3153
|
meta: {
|
|
2444
3154
|
name: "plugin",
|
|
2445
3155
|
description: "Manage mcpman plugins for custom registries"
|
|
@@ -2452,22 +3162,22 @@ var plugin_default = defineCommand11({
|
|
|
2452
3162
|
});
|
|
2453
3163
|
|
|
2454
3164
|
// src/commands/profiles.ts
|
|
2455
|
-
import { defineCommand as
|
|
2456
|
-
import
|
|
3165
|
+
import { defineCommand as defineCommand15 } from "citty";
|
|
3166
|
+
import pc14 from "picocolors";
|
|
2457
3167
|
|
|
2458
3168
|
// src/core/profile-service.ts
|
|
2459
|
-
import
|
|
2460
|
-
import
|
|
3169
|
+
import fs10 from "fs";
|
|
3170
|
+
import path12 from "path";
|
|
2461
3171
|
function ensureDir(dir = getProfilesDir()) {
|
|
2462
|
-
|
|
3172
|
+
fs10.mkdirSync(dir, { recursive: true });
|
|
2463
3173
|
}
|
|
2464
3174
|
function profilePath(name, dir = getProfilesDir()) {
|
|
2465
|
-
return
|
|
3175
|
+
return path12.join(dir, `${name}.json`);
|
|
2466
3176
|
}
|
|
2467
3177
|
function createProfile(name, description = "", dir = getProfilesDir()) {
|
|
2468
3178
|
ensureDir(dir);
|
|
2469
3179
|
const filePath = profilePath(name, dir);
|
|
2470
|
-
if (
|
|
3180
|
+
if (fs10.existsSync(filePath)) {
|
|
2471
3181
|
throw new Error(`Profile '${name}' already exists. Delete it first or use a different name.`);
|
|
2472
3182
|
}
|
|
2473
3183
|
const lockfile = readLockfile();
|
|
@@ -2477,16 +3187,16 @@ function createProfile(name, description = "", dir = getProfilesDir()) {
|
|
|
2477
3187
|
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2478
3188
|
servers: lockfile.servers
|
|
2479
3189
|
};
|
|
2480
|
-
|
|
3190
|
+
fs10.writeFileSync(filePath, JSON.stringify(profile, null, 2), "utf-8");
|
|
2481
3191
|
return profile;
|
|
2482
3192
|
}
|
|
2483
3193
|
function listProfiles(dir = getProfilesDir()) {
|
|
2484
3194
|
ensureDir(dir);
|
|
2485
|
-
const files =
|
|
3195
|
+
const files = fs10.readdirSync(dir).filter((f) => f.endsWith(".json"));
|
|
2486
3196
|
const profiles = [];
|
|
2487
3197
|
for (const file of files) {
|
|
2488
3198
|
try {
|
|
2489
|
-
const raw =
|
|
3199
|
+
const raw = fs10.readFileSync(path12.join(dir, file), "utf-8");
|
|
2490
3200
|
const data = JSON.parse(raw);
|
|
2491
3201
|
profiles.push(data);
|
|
2492
3202
|
} catch {
|
|
@@ -2496,9 +3206,9 @@ function listProfiles(dir = getProfilesDir()) {
|
|
|
2496
3206
|
}
|
|
2497
3207
|
function loadProfile(name, dir = getProfilesDir()) {
|
|
2498
3208
|
const filePath = profilePath(name, dir);
|
|
2499
|
-
if (!
|
|
3209
|
+
if (!fs10.existsSync(filePath)) return null;
|
|
2500
3210
|
try {
|
|
2501
|
-
const raw =
|
|
3211
|
+
const raw = fs10.readFileSync(filePath, "utf-8");
|
|
2502
3212
|
return JSON.parse(raw);
|
|
2503
3213
|
} catch {
|
|
2504
3214
|
return null;
|
|
@@ -2506,13 +3216,13 @@ function loadProfile(name, dir = getProfilesDir()) {
|
|
|
2506
3216
|
}
|
|
2507
3217
|
function deleteProfile(name, dir = getProfilesDir()) {
|
|
2508
3218
|
const filePath = profilePath(name, dir);
|
|
2509
|
-
if (!
|
|
2510
|
-
|
|
3219
|
+
if (!fs10.existsSync(filePath)) return false;
|
|
3220
|
+
fs10.unlinkSync(filePath);
|
|
2511
3221
|
return true;
|
|
2512
3222
|
}
|
|
2513
3223
|
|
|
2514
3224
|
// src/commands/profiles.ts
|
|
2515
|
-
var profiles_default =
|
|
3225
|
+
var profiles_default = defineCommand15({
|
|
2516
3226
|
meta: {
|
|
2517
3227
|
name: "profiles",
|
|
2518
3228
|
description: "Manage named server configuration profiles"
|
|
@@ -2541,16 +3251,16 @@ var profiles_default = defineCommand12({
|
|
|
2541
3251
|
case "create": {
|
|
2542
3252
|
if (!name) {
|
|
2543
3253
|
console.error(
|
|
2544
|
-
|
|
3254
|
+
pc14.red(" Error: Profile name required. Usage: mcpman profiles create <name>")
|
|
2545
3255
|
);
|
|
2546
3256
|
process.exit(1);
|
|
2547
3257
|
}
|
|
2548
3258
|
try {
|
|
2549
3259
|
const profile = createProfile(name, args.description ?? "");
|
|
2550
3260
|
const count = Object.keys(profile.servers).length;
|
|
2551
|
-
console.log(
|
|
3261
|
+
console.log(pc14.green(` \u2713 Profile '${name}' created with ${count} server(s).`));
|
|
2552
3262
|
} catch (err) {
|
|
2553
|
-
console.error(
|
|
3263
|
+
console.error(pc14.red(` Error: ${err instanceof Error ? err.message : String(err)}`));
|
|
2554
3264
|
process.exit(1);
|
|
2555
3265
|
}
|
|
2556
3266
|
break;
|
|
@@ -2558,38 +3268,38 @@ var profiles_default = defineCommand12({
|
|
|
2558
3268
|
case "switch": {
|
|
2559
3269
|
if (!name) {
|
|
2560
3270
|
console.error(
|
|
2561
|
-
|
|
3271
|
+
pc14.red(" Error: Profile name required. Usage: mcpman profiles switch <name>")
|
|
2562
3272
|
);
|
|
2563
3273
|
process.exit(1);
|
|
2564
3274
|
}
|
|
2565
3275
|
const profile = loadProfile(name);
|
|
2566
3276
|
if (!profile) {
|
|
2567
|
-
console.error(
|
|
3277
|
+
console.error(pc14.red(` Error: Profile '${name}' not found.`));
|
|
2568
3278
|
process.exit(1);
|
|
2569
3279
|
}
|
|
2570
3280
|
const lockData = { lockfileVersion: 1, servers: profile.servers };
|
|
2571
3281
|
writeLockfile(lockData);
|
|
2572
3282
|
const count = Object.keys(profile.servers).length;
|
|
2573
|
-
console.log(
|
|
2574
|
-
console.log(
|
|
3283
|
+
console.log(pc14.green(` \u2713 Switched to profile '${name}' (${count} servers).`));
|
|
3284
|
+
console.log(pc14.dim(" Run mcpman sync to apply to all clients."));
|
|
2575
3285
|
break;
|
|
2576
3286
|
}
|
|
2577
3287
|
case "list": {
|
|
2578
3288
|
const profiles = listProfiles();
|
|
2579
3289
|
if (profiles.length === 0) {
|
|
2580
3290
|
console.log(
|
|
2581
|
-
|
|
3291
|
+
pc14.dim(" No profiles saved. Create one with: mcpman profiles create <name>")
|
|
2582
3292
|
);
|
|
2583
3293
|
return;
|
|
2584
3294
|
}
|
|
2585
|
-
console.log(
|
|
3295
|
+
console.log(pc14.bold(`
|
|
2586
3296
|
Profiles (${profiles.length})
|
|
2587
3297
|
`));
|
|
2588
3298
|
for (const p12 of profiles) {
|
|
2589
3299
|
const count = Object.keys(p12.servers).length;
|
|
2590
|
-
const desc = p12.description ?
|
|
3300
|
+
const desc = p12.description ? pc14.dim(` \u2014 ${p12.description}`) : "";
|
|
2591
3301
|
console.log(
|
|
2592
|
-
` ${
|
|
3302
|
+
` ${pc14.cyan("\u25CF")} ${pc14.bold(p12.name)} ${pc14.dim(`${count} server(s)`)}${desc}`
|
|
2593
3303
|
);
|
|
2594
3304
|
}
|
|
2595
3305
|
console.log();
|
|
@@ -2598,32 +3308,200 @@ var profiles_default = defineCommand12({
|
|
|
2598
3308
|
case "delete": {
|
|
2599
3309
|
if (!name) {
|
|
2600
3310
|
console.error(
|
|
2601
|
-
|
|
3311
|
+
pc14.red(" Error: Profile name required. Usage: mcpman profiles delete <name>")
|
|
2602
3312
|
);
|
|
2603
3313
|
process.exit(1);
|
|
2604
3314
|
}
|
|
2605
3315
|
const deleted = deleteProfile(name);
|
|
2606
3316
|
if (deleted) {
|
|
2607
|
-
console.log(
|
|
3317
|
+
console.log(pc14.green(` \u2713 Profile '${name}' deleted.`));
|
|
2608
3318
|
} else {
|
|
2609
|
-
console.error(
|
|
3319
|
+
console.error(pc14.red(` Error: Profile '${name}' not found.`));
|
|
2610
3320
|
process.exit(1);
|
|
2611
3321
|
}
|
|
2612
3322
|
break;
|
|
2613
3323
|
}
|
|
2614
3324
|
default:
|
|
2615
3325
|
console.error(
|
|
2616
|
-
|
|
3326
|
+
pc14.red(` Unknown action '${action}'. Use: create, switch, list, or delete.`)
|
|
2617
3327
|
);
|
|
2618
3328
|
process.exit(1);
|
|
2619
3329
|
}
|
|
2620
3330
|
}
|
|
2621
3331
|
});
|
|
2622
3332
|
|
|
3333
|
+
// src/commands/registry.ts
|
|
3334
|
+
import { defineCommand as defineCommand16 } from "citty";
|
|
3335
|
+
import pc15 from "picocolors";
|
|
3336
|
+
|
|
3337
|
+
// src/core/registry-manager.ts
|
|
3338
|
+
var BUILTIN_REGISTRIES = [
|
|
3339
|
+
{ name: "npm", url: "https://registry.npmjs.org", builtin: true },
|
|
3340
|
+
{ name: "smithery", url: "https://registry.smithery.ai", builtin: true }
|
|
3341
|
+
];
|
|
3342
|
+
function validateUrl(url) {
|
|
3343
|
+
try {
|
|
3344
|
+
new URL(url);
|
|
3345
|
+
} catch {
|
|
3346
|
+
throw new Error(
|
|
3347
|
+
`Invalid URL: "${url}". Must be a valid URL (e.g. https://registry.example.com)`
|
|
3348
|
+
);
|
|
3349
|
+
}
|
|
3350
|
+
}
|
|
3351
|
+
function readCustomRegistries(configPath) {
|
|
3352
|
+
const config = readConfig(configPath);
|
|
3353
|
+
const raw = config.registries;
|
|
3354
|
+
if (!Array.isArray(raw)) return [];
|
|
3355
|
+
return raw.filter((r) => !r.builtin);
|
|
3356
|
+
}
|
|
3357
|
+
function writeCustomRegistries(entries, configPath) {
|
|
3358
|
+
const config = readConfig(configPath);
|
|
3359
|
+
config.registries = entries;
|
|
3360
|
+
writeConfig(config, configPath);
|
|
3361
|
+
}
|
|
3362
|
+
function getRegistries(configPath) {
|
|
3363
|
+
const custom = readCustomRegistries(configPath);
|
|
3364
|
+
return [...BUILTIN_REGISTRIES, ...custom];
|
|
3365
|
+
}
|
|
3366
|
+
function addRegistry(name, url, configPath) {
|
|
3367
|
+
validateUrl(url);
|
|
3368
|
+
const all = getRegistries(configPath);
|
|
3369
|
+
if (all.some((r) => r.name === name)) {
|
|
3370
|
+
throw new Error(`Registry '${name}' already exists. Use a different name or remove it first.`);
|
|
3371
|
+
}
|
|
3372
|
+
const custom = readCustomRegistries(configPath);
|
|
3373
|
+
custom.push({ name, url, builtin: false });
|
|
3374
|
+
writeCustomRegistries(custom, configPath);
|
|
3375
|
+
}
|
|
3376
|
+
function removeRegistry(name, configPath) {
|
|
3377
|
+
if (BUILTIN_REGISTRIES.some((r) => r.name === name)) {
|
|
3378
|
+
throw new Error(`Cannot remove built-in registry '${name}'.`);
|
|
3379
|
+
}
|
|
3380
|
+
const custom = readCustomRegistries(configPath);
|
|
3381
|
+
const idx = custom.findIndex((r) => r.name === name);
|
|
3382
|
+
if (idx === -1) {
|
|
3383
|
+
throw new Error(`Registry '${name}' not found.`);
|
|
3384
|
+
}
|
|
3385
|
+
custom.splice(idx, 1);
|
|
3386
|
+
writeCustomRegistries(custom, configPath);
|
|
3387
|
+
}
|
|
3388
|
+
function setDefaultRegistry(name, configPath) {
|
|
3389
|
+
const all = getRegistries(configPath);
|
|
3390
|
+
if (!all.some((r) => r.name === name)) {
|
|
3391
|
+
throw new Error(
|
|
3392
|
+
`Registry '${name}' not found. Add it first with: mcpman registry add ${name} <url>`
|
|
3393
|
+
);
|
|
3394
|
+
}
|
|
3395
|
+
const config = readConfig(configPath);
|
|
3396
|
+
config.defaultRegistry = name;
|
|
3397
|
+
writeConfig(config, configPath);
|
|
3398
|
+
}
|
|
3399
|
+
function getDefaultRegistry(configPath) {
|
|
3400
|
+
const config = readConfig(configPath);
|
|
3401
|
+
return String(config.defaultRegistry ?? "npm");
|
|
3402
|
+
}
|
|
3403
|
+
|
|
3404
|
+
// src/commands/registry.ts
|
|
3405
|
+
var registry_default = defineCommand16({
|
|
3406
|
+
meta: {
|
|
3407
|
+
name: "registry",
|
|
3408
|
+
description: "Manage custom registry URLs"
|
|
3409
|
+
},
|
|
3410
|
+
args: {
|
|
3411
|
+
action: {
|
|
3412
|
+
type: "positional",
|
|
3413
|
+
description: "Action: list, add, remove, set-default",
|
|
3414
|
+
required: true
|
|
3415
|
+
},
|
|
3416
|
+
name: {
|
|
3417
|
+
type: "positional",
|
|
3418
|
+
description: "Registry name (for add/remove/set-default)",
|
|
3419
|
+
required: false
|
|
3420
|
+
},
|
|
3421
|
+
url: {
|
|
3422
|
+
type: "positional",
|
|
3423
|
+
description: "Registry URL (for add)",
|
|
3424
|
+
required: false
|
|
3425
|
+
}
|
|
3426
|
+
},
|
|
3427
|
+
async run({ args }) {
|
|
3428
|
+
const action = args.action.toLowerCase();
|
|
3429
|
+
const name = args.name;
|
|
3430
|
+
const url = args.url;
|
|
3431
|
+
switch (action) {
|
|
3432
|
+
case "list": {
|
|
3433
|
+
const registries = getRegistries();
|
|
3434
|
+
const defaultName = getDefaultRegistry();
|
|
3435
|
+
console.log(pc15.bold("\n Registries\n"));
|
|
3436
|
+
for (const r of registries) {
|
|
3437
|
+
const isDefault = r.name === defaultName;
|
|
3438
|
+
const defaultTag = isDefault ? pc15.green(" (default)") : "";
|
|
3439
|
+
const builtinTag = r.builtin ? pc15.dim(" [builtin]") : "";
|
|
3440
|
+
console.log(
|
|
3441
|
+
` ${isDefault ? pc15.green("\u25CF") : pc15.dim("\u25CB")} ${pc15.bold(r.name)}${defaultTag}${builtinTag}`
|
|
3442
|
+
);
|
|
3443
|
+
console.log(` ${pc15.dim(r.url)}`);
|
|
3444
|
+
}
|
|
3445
|
+
console.log();
|
|
3446
|
+
break;
|
|
3447
|
+
}
|
|
3448
|
+
case "add": {
|
|
3449
|
+
if (!name) {
|
|
3450
|
+
console.error(pc15.red(" Error: Usage: mcpman registry add <name> <url>"));
|
|
3451
|
+
process.exit(1);
|
|
3452
|
+
}
|
|
3453
|
+
if (!url) {
|
|
3454
|
+
console.error(pc15.red(" Error: Usage: mcpman registry add <name> <url>"));
|
|
3455
|
+
process.exit(1);
|
|
3456
|
+
}
|
|
3457
|
+
try {
|
|
3458
|
+
addRegistry(name, url);
|
|
3459
|
+
console.log(pc15.green(` Added registry '${name}' \u2192 ${url}`));
|
|
3460
|
+
} catch (err) {
|
|
3461
|
+
console.error(pc15.red(` Error: ${err instanceof Error ? err.message : String(err)}`));
|
|
3462
|
+
process.exit(1);
|
|
3463
|
+
}
|
|
3464
|
+
break;
|
|
3465
|
+
}
|
|
3466
|
+
case "remove": {
|
|
3467
|
+
if (!name) {
|
|
3468
|
+
console.error(pc15.red(" Error: Usage: mcpman registry remove <name>"));
|
|
3469
|
+
process.exit(1);
|
|
3470
|
+
}
|
|
3471
|
+
try {
|
|
3472
|
+
removeRegistry(name);
|
|
3473
|
+
console.log(pc15.green(` Removed registry '${name}'.`));
|
|
3474
|
+
} catch (err) {
|
|
3475
|
+
console.error(pc15.red(` Error: ${err instanceof Error ? err.message : String(err)}`));
|
|
3476
|
+
process.exit(1);
|
|
3477
|
+
}
|
|
3478
|
+
break;
|
|
3479
|
+
}
|
|
3480
|
+
case "set-default": {
|
|
3481
|
+
if (!name) {
|
|
3482
|
+
console.error(pc15.red(" Error: Usage: mcpman registry set-default <name>"));
|
|
3483
|
+
process.exit(1);
|
|
3484
|
+
}
|
|
3485
|
+
try {
|
|
3486
|
+
setDefaultRegistry(name);
|
|
3487
|
+
console.log(pc15.green(` Default registry set to '${name}'.`));
|
|
3488
|
+
} catch (err) {
|
|
3489
|
+
console.error(pc15.red(` Error: ${err instanceof Error ? err.message : String(err)}`));
|
|
3490
|
+
process.exit(1);
|
|
3491
|
+
}
|
|
3492
|
+
break;
|
|
3493
|
+
}
|
|
3494
|
+
default:
|
|
3495
|
+
console.error(pc15.red(` Unknown action '${action}'. Use: list, add, remove, set-default.`));
|
|
3496
|
+
process.exit(1);
|
|
3497
|
+
}
|
|
3498
|
+
}
|
|
3499
|
+
});
|
|
3500
|
+
|
|
2623
3501
|
// src/commands/remove.ts
|
|
2624
3502
|
import * as p8 from "@clack/prompts";
|
|
2625
|
-
import { defineCommand as
|
|
2626
|
-
import
|
|
3503
|
+
import { defineCommand as defineCommand17 } from "citty";
|
|
3504
|
+
import pc16 from "picocolors";
|
|
2627
3505
|
var CLIENT_DISPLAY2 = {
|
|
2628
3506
|
"claude-desktop": "Claude",
|
|
2629
3507
|
cursor: "Cursor",
|
|
@@ -2633,7 +3511,7 @@ var CLIENT_DISPLAY2 = {
|
|
|
2633
3511
|
function clientDisplayName(type) {
|
|
2634
3512
|
return CLIENT_DISPLAY2[type] ?? type;
|
|
2635
3513
|
}
|
|
2636
|
-
var remove_default =
|
|
3514
|
+
var remove_default = defineCommand17({
|
|
2637
3515
|
meta: {
|
|
2638
3516
|
name: "remove",
|
|
2639
3517
|
description: "Remove an MCP server from one or more AI clients"
|
|
@@ -2660,7 +3538,7 @@ var remove_default = defineCommand13({
|
|
|
2660
3538
|
}
|
|
2661
3539
|
},
|
|
2662
3540
|
async run({ args }) {
|
|
2663
|
-
p8.intro(
|
|
3541
|
+
p8.intro(pc16.bold("mcpman remove"));
|
|
2664
3542
|
const serverName = args.server;
|
|
2665
3543
|
const servers = await getInstalledServers();
|
|
2666
3544
|
const match = servers.find((s) => s.name === serverName);
|
|
@@ -2670,7 +3548,7 @@ var remove_default = defineCommand13({
|
|
|
2670
3548
|
(s) => s.name.includes(serverName) || serverName.includes(s.name)
|
|
2671
3549
|
);
|
|
2672
3550
|
if (similar.length > 0) {
|
|
2673
|
-
p8.log.info(`Did you mean: ${similar.map((s) =>
|
|
3551
|
+
p8.log.info(`Did you mean: ${similar.map((s) => pc16.cyan(s.name)).join(", ")}?`);
|
|
2674
3552
|
}
|
|
2675
3553
|
p8.outro("Nothing to remove.");
|
|
2676
3554
|
return;
|
|
@@ -2705,7 +3583,7 @@ var remove_default = defineCommand13({
|
|
|
2705
3583
|
if (!args.yes) {
|
|
2706
3584
|
const clientNames = targetClients.map(clientDisplayName).join(", ");
|
|
2707
3585
|
const confirmed = await p8.confirm({
|
|
2708
|
-
message: `Remove ${
|
|
3586
|
+
message: `Remove ${pc16.cyan(serverName)} from ${pc16.yellow(clientNames)}?`
|
|
2709
3587
|
});
|
|
2710
3588
|
if (p8.isCancel(confirmed) || !confirmed) {
|
|
2711
3589
|
p8.outro("Cancelled.");
|
|
@@ -2730,18 +3608,18 @@ var remove_default = defineCommand13({
|
|
|
2730
3608
|
}
|
|
2731
3609
|
if (errors.length > 0) {
|
|
2732
3610
|
for (const e of errors) p8.log.error(e);
|
|
2733
|
-
p8.outro(
|
|
3611
|
+
p8.outro(pc16.red("Completed with errors."));
|
|
2734
3612
|
process.exit(1);
|
|
2735
3613
|
}
|
|
2736
|
-
p8.outro(
|
|
3614
|
+
p8.outro(pc16.green(`Removed "${serverName}" successfully.`));
|
|
2737
3615
|
}
|
|
2738
3616
|
});
|
|
2739
3617
|
|
|
2740
3618
|
// src/commands/run.ts
|
|
2741
3619
|
import { spawn as spawn3 } from "child_process";
|
|
2742
|
-
import { defineCommand as
|
|
2743
|
-
import
|
|
2744
|
-
var run_default =
|
|
3620
|
+
import { defineCommand as defineCommand18 } from "citty";
|
|
3621
|
+
import pc17 from "picocolors";
|
|
3622
|
+
var run_default = defineCommand18({
|
|
2745
3623
|
meta: {
|
|
2746
3624
|
name: "run",
|
|
2747
3625
|
description: "Run an installed MCP server with vault secrets injected"
|
|
@@ -2763,8 +3641,8 @@ var run_default = defineCommand14({
|
|
|
2763
3641
|
const lockfile = readLockfile();
|
|
2764
3642
|
const entry = lockfile.servers[serverName];
|
|
2765
3643
|
if (!entry) {
|
|
2766
|
-
console.error(
|
|
2767
|
-
console.error(
|
|
3644
|
+
console.error(pc17.red(` Error: Server '${serverName}' is not installed.`));
|
|
3645
|
+
console.error(pc17.dim(` Run ${pc17.cyan("mcpman install <server>")} to install it first.`));
|
|
2768
3646
|
process.exit(1);
|
|
2769
3647
|
}
|
|
2770
3648
|
const lockfileEnv = parseEnvFlags(entry.envVars);
|
|
@@ -2776,7 +3654,7 @@ var run_default = defineCommand14({
|
|
|
2776
3654
|
...vaultEnv,
|
|
2777
3655
|
...cliEnv
|
|
2778
3656
|
};
|
|
2779
|
-
console.log(
|
|
3657
|
+
console.log(pc17.dim(` Running ${pc17.cyan(serverName)}...`));
|
|
2780
3658
|
const child = spawn3(entry.command, entry.args, {
|
|
2781
3659
|
env: finalEnv,
|
|
2782
3660
|
stdio: "inherit"
|
|
@@ -2794,7 +3672,7 @@ var run_default = defineCommand14({
|
|
|
2794
3672
|
resolve();
|
|
2795
3673
|
});
|
|
2796
3674
|
child.on("error", (err) => {
|
|
2797
|
-
console.error(
|
|
3675
|
+
console.error(pc17.red(` Failed to start '${serverName}': ${err.message}`));
|
|
2798
3676
|
process.exit(1);
|
|
2799
3677
|
resolve();
|
|
2800
3678
|
});
|
|
@@ -2810,15 +3688,15 @@ async function loadVaultSecrets2(serverName) {
|
|
|
2810
3688
|
const password = await getMasterPassword();
|
|
2811
3689
|
return getSecretsForServer(serverName, password);
|
|
2812
3690
|
} catch {
|
|
2813
|
-
console.warn(
|
|
3691
|
+
console.warn(pc17.yellow(" Warning: Could not load vault secrets, continuing without them."));
|
|
2814
3692
|
return {};
|
|
2815
3693
|
}
|
|
2816
3694
|
}
|
|
2817
3695
|
|
|
2818
3696
|
// src/commands/search.ts
|
|
2819
|
-
import { defineCommand as
|
|
3697
|
+
import { defineCommand as defineCommand19 } from "citty";
|
|
2820
3698
|
import { createSpinner as createSpinner4 } from "nanospinner";
|
|
2821
|
-
import
|
|
3699
|
+
import pc18 from "picocolors";
|
|
2822
3700
|
|
|
2823
3701
|
// src/core/registry-search.ts
|
|
2824
3702
|
var SEARCH_TIMEOUT_MS = 1e4;
|
|
@@ -2891,10 +3769,10 @@ function pad2(s, width) {
|
|
|
2891
3769
|
function highlightMatch(name, query) {
|
|
2892
3770
|
const idx = name.toLowerCase().indexOf(query.toLowerCase());
|
|
2893
3771
|
if (idx === -1) return name;
|
|
2894
|
-
return name.slice(0, idx) +
|
|
3772
|
+
return name.slice(0, idx) + pc18.yellow(name.slice(idx, idx + query.length)) + name.slice(idx + query.length);
|
|
2895
3773
|
}
|
|
2896
3774
|
function formatDownloads(n) {
|
|
2897
|
-
if (!n) return
|
|
3775
|
+
if (!n) return pc18.dim("\u2014");
|
|
2898
3776
|
if (n >= 1e6) return `${(n / 1e6).toFixed(1)}M`;
|
|
2899
3777
|
if (n >= 1e3) return `${(n / 1e3).toFixed(1)}k`;
|
|
2900
3778
|
return String(n);
|
|
@@ -2905,9 +3783,9 @@ function printNpmResults(results, query) {
|
|
|
2905
3783
|
const dlWidth = 9;
|
|
2906
3784
|
const descMax = 50;
|
|
2907
3785
|
const header = ` ${pad2("NAME", nameWidth)} ${pad2("VERSION", verWidth)} ${pad2("DOWNLOADS", dlWidth)} DESCRIPTION`;
|
|
2908
|
-
console.log(
|
|
3786
|
+
console.log(pc18.dim(header));
|
|
2909
3787
|
console.log(
|
|
2910
|
-
|
|
3788
|
+
pc18.dim(
|
|
2911
3789
|
` ${"-".repeat(nameWidth)} ${"-".repeat(verWidth)} ${"-".repeat(dlWidth)} ${"-".repeat(descMax)}`
|
|
2912
3790
|
)
|
|
2913
3791
|
);
|
|
@@ -2915,8 +3793,8 @@ function printNpmResults(results, query) {
|
|
|
2915
3793
|
const name = highlightMatch(pad2(r.name, nameWidth), query);
|
|
2916
3794
|
const ver = pad2(r.version, verWidth);
|
|
2917
3795
|
const dl = pad2(formatDownloads(r.downloads), dlWidth);
|
|
2918
|
-
const desc = truncate2(r.description ||
|
|
2919
|
-
console.log(` ${name} ${
|
|
3796
|
+
const desc = truncate2(r.description || pc18.dim("(no description)"), descMax);
|
|
3797
|
+
console.log(` ${name} ${pc18.dim(ver)} ${dl} ${desc}`);
|
|
2920
3798
|
}
|
|
2921
3799
|
}
|
|
2922
3800
|
function printSmitheryResults(results, query) {
|
|
@@ -2924,19 +3802,19 @@ function printSmitheryResults(results, query) {
|
|
|
2924
3802
|
const usesWidth = 8;
|
|
2925
3803
|
const descMax = 50;
|
|
2926
3804
|
const header = ` ${pad2("NAME", nameWidth)} ${pad2("USES", usesWidth)} DESCRIPTION`;
|
|
2927
|
-
console.log(
|
|
3805
|
+
console.log(pc18.dim(header));
|
|
2928
3806
|
console.log(
|
|
2929
|
-
|
|
3807
|
+
pc18.dim(` ${"-".repeat(nameWidth)} ${"-".repeat(usesWidth)} ${"-".repeat(descMax)}`)
|
|
2930
3808
|
);
|
|
2931
3809
|
for (const r of results) {
|
|
2932
3810
|
const name = highlightMatch(pad2(r.name, nameWidth), query);
|
|
2933
3811
|
const uses = pad2(formatDownloads(r.useCount), usesWidth);
|
|
2934
|
-
const badge = r.verified ?
|
|
2935
|
-
const desc = truncate2(r.description ||
|
|
3812
|
+
const badge = r.verified ? pc18.green(" \u2713") : "";
|
|
3813
|
+
const desc = truncate2(r.description || pc18.dim("(no description)"), descMax);
|
|
2936
3814
|
console.log(` ${name}${badge} ${uses} ${desc}`);
|
|
2937
3815
|
}
|
|
2938
3816
|
}
|
|
2939
|
-
var search_default =
|
|
3817
|
+
var search_default = defineCommand19({
|
|
2940
3818
|
meta: {
|
|
2941
3819
|
name: "search",
|
|
2942
3820
|
description: "Search for MCP servers on npm or Smithery registry"
|
|
@@ -2968,7 +3846,7 @@ var search_default = defineCommand15({
|
|
|
2968
3846
|
const registry = args.registry.toLowerCase();
|
|
2969
3847
|
const limit = Math.min(Math.max(1, Number.parseInt(args.limit, 10) || 20), 100);
|
|
2970
3848
|
if (registry !== "npm" && registry !== "smithery") {
|
|
2971
|
-
console.error(
|
|
3849
|
+
console.error(pc18.red(` Unknown registry "${registry}". Use "npm" or "smithery".`));
|
|
2972
3850
|
process.exit(1);
|
|
2973
3851
|
}
|
|
2974
3852
|
const spinner5 = createSpinner4(`Searching ${registry} for "${query}"...`).start();
|
|
@@ -2976,20 +3854,20 @@ var search_default = defineCommand15({
|
|
|
2976
3854
|
const results2 = await searchNpm(query, limit);
|
|
2977
3855
|
spinner5.stop();
|
|
2978
3856
|
if (results2.length === 0) {
|
|
2979
|
-
console.log(
|
|
3857
|
+
console.log(pc18.dim(`
|
|
2980
3858
|
No results found for "${query}" on npm.
|
|
2981
3859
|
`));
|
|
2982
3860
|
return;
|
|
2983
3861
|
}
|
|
2984
3862
|
console.log(
|
|
2985
|
-
|
|
3863
|
+
pc18.bold(
|
|
2986
3864
|
`
|
|
2987
3865
|
mcpman search \u2014 npm (${results2.length} result${results2.length !== 1 ? "s" : ""})
|
|
2988
3866
|
`
|
|
2989
3867
|
)
|
|
2990
3868
|
);
|
|
2991
3869
|
printNpmResults(results2, query);
|
|
2992
|
-
console.log(
|
|
3870
|
+
console.log(pc18.dim("\n Install with: mcpman install <name>\n"));
|
|
2993
3871
|
if (args.all) {
|
|
2994
3872
|
await printPluginResults(query, limit);
|
|
2995
3873
|
}
|
|
@@ -2998,20 +3876,20 @@ var search_default = defineCommand15({
|
|
|
2998
3876
|
const results = await searchSmithery(query, limit);
|
|
2999
3877
|
spinner5.stop();
|
|
3000
3878
|
if (results.length === 0) {
|
|
3001
|
-
console.log(
|
|
3879
|
+
console.log(pc18.dim(`
|
|
3002
3880
|
No results found for "${query}" on Smithery.
|
|
3003
3881
|
`));
|
|
3004
3882
|
return;
|
|
3005
3883
|
}
|
|
3006
3884
|
console.log(
|
|
3007
|
-
|
|
3885
|
+
pc18.bold(
|
|
3008
3886
|
`
|
|
3009
3887
|
mcpman search \u2014 Smithery (${results.length} result${results.length !== 1 ? "s" : ""})
|
|
3010
3888
|
`
|
|
3011
3889
|
)
|
|
3012
3890
|
);
|
|
3013
3891
|
printSmitheryResults(results, query);
|
|
3014
|
-
console.log(
|
|
3892
|
+
console.log(pc18.dim("\n Install with: mcpman install <name>\n"));
|
|
3015
3893
|
if (args.all) {
|
|
3016
3894
|
await printPluginResults(query, limit);
|
|
3017
3895
|
}
|
|
@@ -3021,7 +3899,7 @@ async function printPluginResults(query, limit) {
|
|
|
3021
3899
|
const pluginResults = await searchPlugins(query, limit);
|
|
3022
3900
|
if (pluginResults.length === 0) return;
|
|
3023
3901
|
console.log(
|
|
3024
|
-
|
|
3902
|
+
pc18.bold(
|
|
3025
3903
|
`
|
|
3026
3904
|
Plugins (${pluginResults.length} result${pluginResults.length !== 1 ? "s" : ""})
|
|
3027
3905
|
`
|
|
@@ -3031,22 +3909,22 @@ async function printPluginResults(query, limit) {
|
|
|
3031
3909
|
const srcWidth = Math.max(6, ...pluginResults.map((r) => r.source.length));
|
|
3032
3910
|
const descMax = 50;
|
|
3033
3911
|
const header = ` ${pad2("NAME", nameWidth)} ${pad2("SOURCE", srcWidth)} DESCRIPTION`;
|
|
3034
|
-
console.log(
|
|
3912
|
+
console.log(pc18.dim(header));
|
|
3035
3913
|
console.log(
|
|
3036
|
-
|
|
3914
|
+
pc18.dim(` ${"-".repeat(nameWidth)} ${"-".repeat(srcWidth)} ${"-".repeat(descMax)}`)
|
|
3037
3915
|
);
|
|
3038
3916
|
for (const r of pluginResults) {
|
|
3039
3917
|
const name = highlightMatch(pad2(r.name, nameWidth), query);
|
|
3040
3918
|
const src = pad2(r.source, srcWidth);
|
|
3041
|
-
const desc = truncate2(r.description ||
|
|
3042
|
-
console.log(` ${name} ${
|
|
3919
|
+
const desc = truncate2(r.description || pc18.dim("(no description)"), descMax);
|
|
3920
|
+
console.log(` ${name} ${pc18.dim(src)} ${desc}`);
|
|
3043
3921
|
}
|
|
3044
3922
|
}
|
|
3045
3923
|
|
|
3046
3924
|
// src/commands/secrets.ts
|
|
3047
3925
|
import * as p9 from "@clack/prompts";
|
|
3048
|
-
import { defineCommand as
|
|
3049
|
-
import
|
|
3926
|
+
import { defineCommand as defineCommand20 } from "citty";
|
|
3927
|
+
import pc19 from "picocolors";
|
|
3050
3928
|
function maskValue(value) {
|
|
3051
3929
|
if (value.length <= 8) return "***";
|
|
3052
3930
|
return `${value.slice(0, 4)}***${value.slice(-3)}`;
|
|
@@ -3056,7 +3934,7 @@ function parseKeyValue(input) {
|
|
|
3056
3934
|
if (idx <= 0) return null;
|
|
3057
3935
|
return { key: input.slice(0, idx), value: input.slice(idx + 1) };
|
|
3058
3936
|
}
|
|
3059
|
-
var setCommand2 =
|
|
3937
|
+
var setCommand2 = defineCommand20({
|
|
3060
3938
|
meta: { name: "set", description: "Store an encrypted secret for a server" },
|
|
3061
3939
|
args: {
|
|
3062
3940
|
server: {
|
|
@@ -3073,10 +3951,10 @@ var setCommand2 = defineCommand16({
|
|
|
3073
3951
|
async run({ args }) {
|
|
3074
3952
|
const parsed = parseKeyValue(args.keyvalue);
|
|
3075
3953
|
if (!parsed) {
|
|
3076
|
-
console.error(`${
|
|
3954
|
+
console.error(`${pc19.red("\u2717")} Invalid format. Expected KEY=VALUE`);
|
|
3077
3955
|
process.exit(1);
|
|
3078
3956
|
}
|
|
3079
|
-
p9.intro(
|
|
3957
|
+
p9.intro(pc19.cyan("mcpman secrets set"));
|
|
3080
3958
|
const isNew = listSecrets(args.server).length === 0 || !listSecrets(args.server)[0]?.keys.includes(parsed.key);
|
|
3081
3959
|
const vaultPath = (await import("./vault-service-UTZAV6N6.js")).getVaultPath();
|
|
3082
3960
|
const vaultExists = (await import("fs")).existsSync(vaultPath);
|
|
@@ -3085,16 +3963,16 @@ var setCommand2 = defineCommand16({
|
|
|
3085
3963
|
spin.start("Encrypting secret...");
|
|
3086
3964
|
try {
|
|
3087
3965
|
setSecret(args.server, parsed.key, parsed.value, password);
|
|
3088
|
-
spin.stop(`${
|
|
3966
|
+
spin.stop(`${pc19.green("\u2713")} Stored ${pc19.bold(parsed.key)} for ${pc19.cyan(args.server)}`);
|
|
3089
3967
|
} catch (err) {
|
|
3090
|
-
spin.stop(`${
|
|
3091
|
-
console.error(
|
|
3968
|
+
spin.stop(`${pc19.red("\u2717")} Failed to store secret`);
|
|
3969
|
+
console.error(pc19.dim(String(err)));
|
|
3092
3970
|
process.exit(1);
|
|
3093
3971
|
}
|
|
3094
|
-
p9.outro(
|
|
3972
|
+
p9.outro(pc19.dim("Secret encrypted and saved to vault."));
|
|
3095
3973
|
}
|
|
3096
3974
|
});
|
|
3097
|
-
var listCommand3 =
|
|
3975
|
+
var listCommand3 = defineCommand20({
|
|
3098
3976
|
meta: { name: "list", description: "List secret keys stored in the vault" },
|
|
3099
3977
|
args: {
|
|
3100
3978
|
server: {
|
|
@@ -3106,27 +3984,27 @@ var listCommand3 = defineCommand16({
|
|
|
3106
3984
|
async run({ args }) {
|
|
3107
3985
|
const results = listSecrets(args.server || void 0);
|
|
3108
3986
|
if (results.length === 0) {
|
|
3109
|
-
const filter = args.server ? ` for ${
|
|
3110
|
-
console.log(
|
|
3987
|
+
const filter = args.server ? ` for ${pc19.cyan(args.server)}` : "";
|
|
3988
|
+
console.log(pc19.dim(`No secrets stored${filter}.`));
|
|
3111
3989
|
return;
|
|
3112
3990
|
}
|
|
3113
3991
|
console.log("");
|
|
3114
3992
|
for (const { server, keys } of results) {
|
|
3115
|
-
console.log(
|
|
3993
|
+
console.log(pc19.bold(pc19.cyan(server)));
|
|
3116
3994
|
for (const key of keys) {
|
|
3117
|
-
console.log(` ${
|
|
3995
|
+
console.log(` ${pc19.green("\u25CF")} ${pc19.bold(key)} ${pc19.dim(maskValue("\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022"))}`);
|
|
3118
3996
|
}
|
|
3119
3997
|
console.log("");
|
|
3120
3998
|
}
|
|
3121
3999
|
const total = results.reduce((n, r) => n + r.keys.length, 0);
|
|
3122
4000
|
console.log(
|
|
3123
|
-
|
|
4001
|
+
pc19.dim(
|
|
3124
4002
|
` ${total} secret${total !== 1 ? "s" : ""} in ${results.length} server${results.length !== 1 ? "s" : ""}`
|
|
3125
4003
|
)
|
|
3126
4004
|
);
|
|
3127
4005
|
}
|
|
3128
4006
|
});
|
|
3129
|
-
var removeCommand2 =
|
|
4007
|
+
var removeCommand2 = defineCommand20({
|
|
3130
4008
|
meta: { name: "remove", description: "Delete a secret from the vault" },
|
|
3131
4009
|
args: {
|
|
3132
4010
|
server: {
|
|
@@ -3142,7 +4020,7 @@ var removeCommand2 = defineCommand16({
|
|
|
3142
4020
|
},
|
|
3143
4021
|
async run({ args }) {
|
|
3144
4022
|
const confirmed = await p9.confirm({
|
|
3145
|
-
message: `Remove ${
|
|
4023
|
+
message: `Remove ${pc19.bold(args.key)} from ${pc19.cyan(args.server)}?`,
|
|
3146
4024
|
initialValue: false
|
|
3147
4025
|
});
|
|
3148
4026
|
if (p9.isCancel(confirmed) || !confirmed) {
|
|
@@ -3151,15 +4029,15 @@ var removeCommand2 = defineCommand16({
|
|
|
3151
4029
|
}
|
|
3152
4030
|
try {
|
|
3153
4031
|
removeSecret(args.server, args.key);
|
|
3154
|
-
console.log(`${
|
|
4032
|
+
console.log(`${pc19.green("\u2713")} Removed ${pc19.bold(args.key)} from ${pc19.cyan(args.server)}`);
|
|
3155
4033
|
} catch (err) {
|
|
3156
|
-
console.error(`${
|
|
3157
|
-
console.error(
|
|
4034
|
+
console.error(`${pc19.red("\u2717")} Failed to remove secret`);
|
|
4035
|
+
console.error(pc19.dim(String(err)));
|
|
3158
4036
|
process.exit(1);
|
|
3159
4037
|
}
|
|
3160
4038
|
}
|
|
3161
4039
|
});
|
|
3162
|
-
var secrets_default =
|
|
4040
|
+
var secrets_default = defineCommand20({
|
|
3163
4041
|
meta: {
|
|
3164
4042
|
name: "secrets",
|
|
3165
4043
|
description: "Manage encrypted secrets for MCP servers"
|
|
@@ -3173,8 +4051,8 @@ var secrets_default = defineCommand16({
|
|
|
3173
4051
|
|
|
3174
4052
|
// src/commands/sync.ts
|
|
3175
4053
|
import * as p10 from "@clack/prompts";
|
|
3176
|
-
import { defineCommand as
|
|
3177
|
-
import
|
|
4054
|
+
import { defineCommand as defineCommand21 } from "citty";
|
|
4055
|
+
import pc20 from "picocolors";
|
|
3178
4056
|
|
|
3179
4057
|
// src/core/config-diff.ts
|
|
3180
4058
|
function reconstructServerEntry(lockEntry) {
|
|
@@ -3326,7 +4204,7 @@ var CLIENT_DISPLAY3 = {
|
|
|
3326
4204
|
vscode: "VS Code",
|
|
3327
4205
|
windsurf: "Windsurf"
|
|
3328
4206
|
};
|
|
3329
|
-
var sync_default =
|
|
4207
|
+
var sync_default = defineCommand21({
|
|
3330
4208
|
meta: {
|
|
3331
4209
|
name: "sync",
|
|
3332
4210
|
description: "Sync MCP server configs across all detected AI clients"
|
|
@@ -3353,7 +4231,7 @@ var sync_default = defineCommand17({
|
|
|
3353
4231
|
}
|
|
3354
4232
|
},
|
|
3355
4233
|
async run({ args }) {
|
|
3356
|
-
p10.intro(`${
|
|
4234
|
+
p10.intro(`${pc20.cyan("mcpman sync")}`);
|
|
3357
4235
|
const sourceClient = args.source;
|
|
3358
4236
|
if (sourceClient && !VALID_CLIENTS.includes(sourceClient)) {
|
|
3359
4237
|
p10.log.error(
|
|
@@ -3389,20 +4267,20 @@ var sync_default = defineCommand17({
|
|
|
3389
4267
|
const extraCount = actions.filter((a) => a.action === "extra").length;
|
|
3390
4268
|
const removeCount = actions.filter((a) => a.action === "remove").length;
|
|
3391
4269
|
if (addCount === 0 && removeCount === 0 && extraCount === 0) {
|
|
3392
|
-
p10.outro(
|
|
4270
|
+
p10.outro(pc20.green("All clients are in sync."));
|
|
3393
4271
|
process.exit(0);
|
|
3394
4272
|
}
|
|
3395
4273
|
const parts = [];
|
|
3396
|
-
if (addCount > 0) parts.push(
|
|
3397
|
-
if (removeCount > 0) parts.push(
|
|
3398
|
-
if (extraCount > 0) parts.push(
|
|
4274
|
+
if (addCount > 0) parts.push(pc20.green(`${addCount} to add`));
|
|
4275
|
+
if (removeCount > 0) parts.push(pc20.red(`${removeCount} to remove`));
|
|
4276
|
+
if (extraCount > 0) parts.push(pc20.yellow(`${extraCount} extra (informational)`));
|
|
3399
4277
|
p10.log.info(parts.join(" \xB7 "));
|
|
3400
4278
|
if (args["dry-run"]) {
|
|
3401
|
-
p10.outro(
|
|
4279
|
+
p10.outro(pc20.dim("Dry run \u2014 no changes applied."));
|
|
3402
4280
|
process.exit(1);
|
|
3403
4281
|
}
|
|
3404
4282
|
if (addCount === 0 && removeCount === 0) {
|
|
3405
|
-
p10.outro(
|
|
4283
|
+
p10.outro(pc20.dim("No additions needed. Extra servers left untouched."));
|
|
3406
4284
|
process.exit(1);
|
|
3407
4285
|
}
|
|
3408
4286
|
if (!args.yes) {
|
|
@@ -3414,7 +4292,7 @@ var sync_default = defineCommand17({
|
|
|
3414
4292
|
initialValue: true
|
|
3415
4293
|
});
|
|
3416
4294
|
if (p10.isCancel(confirmed) || !confirmed) {
|
|
3417
|
-
p10.outro(
|
|
4295
|
+
p10.outro(pc20.dim("Cancelled \u2014 no changes applied."));
|
|
3418
4296
|
process.exit(0);
|
|
3419
4297
|
}
|
|
3420
4298
|
}
|
|
@@ -3433,7 +4311,7 @@ var sync_default = defineCommand17({
|
|
|
3433
4311
|
}
|
|
3434
4312
|
}
|
|
3435
4313
|
p10.outro(
|
|
3436
|
-
result.failed === 0 ?
|
|
4314
|
+
result.failed === 0 ? pc20.green("Sync complete.") : pc20.yellow("Sync complete with errors.")
|
|
3437
4315
|
);
|
|
3438
4316
|
process.exit(result.failed > 0 ? 1 : 0);
|
|
3439
4317
|
}
|
|
@@ -3449,8 +4327,8 @@ function printDiffTable(actions) {
|
|
|
3449
4327
|
...actions.map((a) => CLIENT_DISPLAY3[a.client]?.length ?? a.client.length)
|
|
3450
4328
|
);
|
|
3451
4329
|
const header = ` ${pad3("SERVER", nameWidth)} ${pad3("CLIENT", clientWidth)} STATUS`;
|
|
3452
|
-
console.log(
|
|
3453
|
-
console.log(
|
|
4330
|
+
console.log(pc20.dim(header));
|
|
4331
|
+
console.log(pc20.dim(` ${"-".repeat(nameWidth)} ${"-".repeat(clientWidth)} ------`));
|
|
3454
4332
|
for (const action of actions) {
|
|
3455
4333
|
const clientDisplay = CLIENT_DISPLAY3[action.client] ?? action.client;
|
|
3456
4334
|
const [icon, statusText] = formatAction(action.action);
|
|
@@ -3463,13 +4341,13 @@ function printDiffTable(actions) {
|
|
|
3463
4341
|
function formatAction(action) {
|
|
3464
4342
|
switch (action) {
|
|
3465
4343
|
case "add":
|
|
3466
|
-
return [
|
|
4344
|
+
return [pc20.green("+"), pc20.green("missing \u2014 will add")];
|
|
3467
4345
|
case "extra":
|
|
3468
|
-
return [
|
|
4346
|
+
return [pc20.yellow("?"), pc20.yellow("extra (not in lockfile)")];
|
|
3469
4347
|
case "remove":
|
|
3470
|
-
return [
|
|
4348
|
+
return [pc20.red("\u2013"), pc20.red("extra \u2014 will remove")];
|
|
3471
4349
|
case "ok":
|
|
3472
|
-
return [
|
|
4350
|
+
return [pc20.dim("\xB7"), pc20.dim("in sync")];
|
|
3473
4351
|
}
|
|
3474
4352
|
}
|
|
3475
4353
|
function pad3(s, width) {
|
|
@@ -3477,8 +4355,8 @@ function pad3(s, width) {
|
|
|
3477
4355
|
}
|
|
3478
4356
|
|
|
3479
4357
|
// src/commands/test-command.ts
|
|
3480
|
-
import { defineCommand as
|
|
3481
|
-
import
|
|
4358
|
+
import { defineCommand as defineCommand22 } from "citty";
|
|
4359
|
+
import pc21 from "picocolors";
|
|
3482
4360
|
|
|
3483
4361
|
// src/core/mcp-tester.ts
|
|
3484
4362
|
import { spawn as spawn4 } from "child_process";
|
|
@@ -3576,7 +4454,7 @@ async function testMcpServer(serverName, command, args, env) {
|
|
|
3576
4454
|
}
|
|
3577
4455
|
|
|
3578
4456
|
// src/commands/test-command.ts
|
|
3579
|
-
var test_command_default =
|
|
4457
|
+
var test_command_default = defineCommand22({
|
|
3580
4458
|
meta: {
|
|
3581
4459
|
name: "test",
|
|
3582
4460
|
description: "Test MCP server connectivity and capabilities"
|
|
@@ -3597,10 +4475,10 @@ var test_command_default = defineCommand18({
|
|
|
3597
4475
|
const lockfile = readLockfile();
|
|
3598
4476
|
const serverNames = args.all ? Object.keys(lockfile.servers) : args.server ? [args.server] : [];
|
|
3599
4477
|
if (serverNames.length === 0) {
|
|
3600
|
-
console.error(
|
|
4478
|
+
console.error(pc21.red(" Error: Specify a server name or use --all."));
|
|
3601
4479
|
process.exit(1);
|
|
3602
4480
|
}
|
|
3603
|
-
console.log(
|
|
4481
|
+
console.log(pc21.bold(`
|
|
3604
4482
|
mcpman test \u2014 ${serverNames.length} server(s)
|
|
3605
4483
|
`));
|
|
3606
4484
|
let passed = 0;
|
|
@@ -3608,7 +4486,7 @@ var test_command_default = defineCommand18({
|
|
|
3608
4486
|
for (const name of serverNames) {
|
|
3609
4487
|
const entry = lockfile.servers[name];
|
|
3610
4488
|
if (!entry) {
|
|
3611
|
-
console.log(` ${
|
|
4489
|
+
console.log(` ${pc21.red("\u2717")} ${pc21.bold(name)} \u2014 not installed`);
|
|
3612
4490
|
failed++;
|
|
3613
4491
|
continue;
|
|
3614
4492
|
}
|
|
@@ -3619,27 +4497,27 @@ var test_command_default = defineCommand18({
|
|
|
3619
4497
|
if (result.passed) {
|
|
3620
4498
|
passed++;
|
|
3621
4499
|
console.log(
|
|
3622
|
-
` ${
|
|
4500
|
+
` ${pc21.green("\u2713")} ${pc21.bold(name)} ${pc21.dim(`(${result.responseTimeMs}ms)`)}`
|
|
3623
4501
|
);
|
|
3624
4502
|
if (result.tools.length > 0) {
|
|
3625
|
-
console.log(
|
|
4503
|
+
console.log(pc21.dim(` Tools: ${result.tools.join(", ")}`));
|
|
3626
4504
|
}
|
|
3627
4505
|
} else {
|
|
3628
4506
|
failed++;
|
|
3629
|
-
console.log(` ${
|
|
4507
|
+
console.log(` ${pc21.red("\u2717")} ${pc21.bold(name)} ${pc21.dim(`(${result.responseTimeMs}ms)`)}`);
|
|
3630
4508
|
if (result.error) {
|
|
3631
|
-
console.log(` ${
|
|
4509
|
+
console.log(` ${pc21.red(result.error)}`);
|
|
3632
4510
|
}
|
|
3633
4511
|
console.log(
|
|
3634
|
-
` ${
|
|
4512
|
+
` ${pc21.dim("initialize:")} ${result.initializeOk ? pc21.green("ok") : pc21.red("fail")} ${pc21.dim("tools/list:")} ${result.toolsListOk ? pc21.green("ok") : pc21.red("fail")}`
|
|
3635
4513
|
);
|
|
3636
4514
|
}
|
|
3637
4515
|
}
|
|
3638
|
-
console.log(
|
|
4516
|
+
console.log(pc21.dim(`
|
|
3639
4517
|
${"\u2500".repeat(40)}`));
|
|
3640
4518
|
const parts = [];
|
|
3641
|
-
if (passed > 0) parts.push(
|
|
3642
|
-
if (failed > 0) parts.push(
|
|
4519
|
+
if (passed > 0) parts.push(pc21.green(`${passed} passed`));
|
|
4520
|
+
if (failed > 0) parts.push(pc21.red(`${failed} failed`));
|
|
3643
4521
|
console.log(` ${parts.join(", ")}
|
|
3644
4522
|
`);
|
|
3645
4523
|
if (failed > 0) process.exit(1);
|
|
@@ -3658,23 +4536,23 @@ async function loadVaultSecrets3(serverName) {
|
|
|
3658
4536
|
|
|
3659
4537
|
// src/commands/update.ts
|
|
3660
4538
|
import * as p11 from "@clack/prompts";
|
|
3661
|
-
import { defineCommand as
|
|
3662
|
-
import
|
|
4539
|
+
import { defineCommand as defineCommand23 } from "citty";
|
|
4540
|
+
import pc23 from "picocolors";
|
|
3663
4541
|
|
|
3664
4542
|
// src/core/update-notifier.ts
|
|
3665
|
-
import
|
|
4543
|
+
import fs11 from "fs";
|
|
3666
4544
|
import os3 from "os";
|
|
3667
|
-
import
|
|
3668
|
-
import
|
|
3669
|
-
var CACHE_FILE =
|
|
4545
|
+
import path13 from "path";
|
|
4546
|
+
import pc22 from "picocolors";
|
|
4547
|
+
var CACHE_FILE = path13.join(os3.homedir(), ".mcpman", ".update-check");
|
|
3670
4548
|
var TTL_MS = 24 * 60 * 60 * 1e3;
|
|
3671
4549
|
function writeUpdateCache(data) {
|
|
3672
4550
|
try {
|
|
3673
|
-
const dir =
|
|
3674
|
-
if (!
|
|
4551
|
+
const dir = path13.dirname(CACHE_FILE);
|
|
4552
|
+
if (!fs11.existsSync(dir)) fs11.mkdirSync(dir, { recursive: true });
|
|
3675
4553
|
const tmp = `${CACHE_FILE}.tmp`;
|
|
3676
|
-
|
|
3677
|
-
|
|
4554
|
+
fs11.writeFileSync(tmp, JSON.stringify(data, null, 2), "utf-8");
|
|
4555
|
+
fs11.renameSync(tmp, CACHE_FILE);
|
|
3678
4556
|
} catch {
|
|
3679
4557
|
}
|
|
3680
4558
|
}
|
|
@@ -3697,19 +4575,19 @@ function printTable(updates) {
|
|
|
3697
4575
|
"LATEST".padEnd(VER_W),
|
|
3698
4576
|
"STATUS"
|
|
3699
4577
|
].join(" ");
|
|
3700
|
-
console.log(
|
|
4578
|
+
console.log(pc23.bold(`
|
|
3701
4579
|
${header}`));
|
|
3702
|
-
console.log(
|
|
4580
|
+
console.log(pc23.dim(` ${"\u2500".repeat(NAME_W + VER_W * 2 + 20)}`));
|
|
3703
4581
|
for (const u of updates) {
|
|
3704
4582
|
const nameCol = u.server.slice(0, NAME_W).padEnd(NAME_W);
|
|
3705
4583
|
const curCol = u.currentVersion.padEnd(VER_W);
|
|
3706
4584
|
const latCol = u.latestVersion.padEnd(VER_W);
|
|
3707
|
-
const statusCol = u.hasUpdate ?
|
|
4585
|
+
const statusCol = u.hasUpdate ? pc23.yellow(`Update available${u.updateType ? ` [${u.updateType}]` : ""}`) : pc23.green("Up to date");
|
|
3708
4586
|
console.log(` ${nameCol} ${curCol} ${latCol} ${statusCol}`);
|
|
3709
4587
|
}
|
|
3710
4588
|
console.log();
|
|
3711
4589
|
}
|
|
3712
|
-
var update_default =
|
|
4590
|
+
var update_default = defineCommand23({
|
|
3713
4591
|
meta: {
|
|
3714
4592
|
name: "update",
|
|
3715
4593
|
description: "Check for and apply updates to installed MCP servers"
|
|
@@ -3770,12 +4648,12 @@ var update_default = defineCommand19({
|
|
|
3770
4648
|
printTable(updates);
|
|
3771
4649
|
const outdated = updates.filter((u) => u.hasUpdate);
|
|
3772
4650
|
if (outdated.length === 0) {
|
|
3773
|
-
console.log(
|
|
4651
|
+
console.log(pc23.green(" All servers are up to date."));
|
|
3774
4652
|
return;
|
|
3775
4653
|
}
|
|
3776
4654
|
if (args.check) {
|
|
3777
4655
|
console.log(
|
|
3778
|
-
|
|
4656
|
+
pc23.yellow(` ${outdated.length} update(s) available. Run mcpman update to apply.`)
|
|
3779
4657
|
);
|
|
3780
4658
|
return;
|
|
3781
4659
|
}
|
|
@@ -3796,10 +4674,10 @@ var update_default = defineCommand19({
|
|
|
3796
4674
|
s.start(`Updating ${update.server}...`);
|
|
3797
4675
|
const result = await applyServerUpdate(update.server, servers[update.server], clients);
|
|
3798
4676
|
if (result.success) {
|
|
3799
|
-
s.stop(`${
|
|
4677
|
+
s.stop(`${pc23.green("\u2713")} ${update.server}: ${result.fromVersion} \u2192 ${result.toVersion}`);
|
|
3800
4678
|
successCount++;
|
|
3801
4679
|
} else {
|
|
3802
|
-
s.stop(`${
|
|
4680
|
+
s.stop(`${pc23.red("\u2717")} ${update.server}: ${result.error}`);
|
|
3803
4681
|
}
|
|
3804
4682
|
}
|
|
3805
4683
|
const freshLockfile = readLockfile(resolveLockfilePath());
|
|
@@ -3811,9 +4689,9 @@ var update_default = defineCommand19({
|
|
|
3811
4689
|
|
|
3812
4690
|
// src/commands/upgrade.ts
|
|
3813
4691
|
import { execSync as execSync2 } from "child_process";
|
|
3814
|
-
import { defineCommand as
|
|
3815
|
-
import
|
|
3816
|
-
var upgrade_default =
|
|
4692
|
+
import { defineCommand as defineCommand24 } from "citty";
|
|
4693
|
+
import pc24 from "picocolors";
|
|
4694
|
+
var upgrade_default = defineCommand24({
|
|
3817
4695
|
meta: {
|
|
3818
4696
|
name: "upgrade",
|
|
3819
4697
|
description: "Upgrade mcpman to the latest version"
|
|
@@ -3826,41 +4704,445 @@ var upgrade_default = defineCommand20({
|
|
|
3826
4704
|
}
|
|
3827
4705
|
},
|
|
3828
4706
|
async run({ args }) {
|
|
3829
|
-
console.log(
|
|
4707
|
+
console.log(pc24.dim(` Current version: ${APP_VERSION}`));
|
|
3830
4708
|
let latest;
|
|
3831
4709
|
try {
|
|
3832
4710
|
latest = execSync2("npm view mcpman version", { encoding: "utf-8", timeout: 15e3 }).trim();
|
|
3833
4711
|
} catch {
|
|
3834
|
-
console.error(
|
|
4712
|
+
console.error(pc24.red(" Error: Could not check latest version from npm."));
|
|
3835
4713
|
process.exit(1);
|
|
3836
4714
|
}
|
|
3837
4715
|
if (latest === APP_VERSION) {
|
|
3838
|
-
console.log(
|
|
4716
|
+
console.log(pc24.green(" \u2713 Already on the latest version."));
|
|
3839
4717
|
return;
|
|
3840
4718
|
}
|
|
3841
|
-
console.log(
|
|
4719
|
+
console.log(pc24.yellow(` Update available: ${APP_VERSION} \u2192 ${latest}`));
|
|
3842
4720
|
if (args.check) {
|
|
3843
|
-
console.log(
|
|
4721
|
+
console.log(pc24.dim(" Run mcpman upgrade to install."));
|
|
3844
4722
|
return;
|
|
3845
4723
|
}
|
|
3846
|
-
console.log(
|
|
4724
|
+
console.log(pc24.dim(" Installing..."));
|
|
3847
4725
|
try {
|
|
3848
4726
|
execSync2(`npm install -g mcpman@${latest}`, { stdio: "inherit", timeout: 6e4 });
|
|
3849
|
-
console.log(
|
|
4727
|
+
console.log(pc24.green(`
|
|
3850
4728
|
\u2713 Upgraded to mcpman@${latest}`));
|
|
3851
4729
|
} catch {
|
|
3852
|
-
console.error(
|
|
4730
|
+
console.error(pc24.red(" Error: Upgrade failed. Try manually: npm install -g mcpman@latest"));
|
|
3853
4731
|
process.exit(1);
|
|
3854
4732
|
}
|
|
3855
4733
|
}
|
|
3856
4734
|
});
|
|
3857
4735
|
|
|
4736
|
+
// src/commands/watch.ts
|
|
4737
|
+
import { defineCommand as defineCommand25 } from "citty";
|
|
4738
|
+
import pc25 from "picocolors";
|
|
4739
|
+
|
|
4740
|
+
// src/core/file-watcher-service.ts
|
|
4741
|
+
import { spawn as spawn5 } from "child_process";
|
|
4742
|
+
import fs12 from "fs";
|
|
4743
|
+
var IGNORE_PATTERNS = [
|
|
4744
|
+
"node_modules",
|
|
4745
|
+
".git",
|
|
4746
|
+
"dist",
|
|
4747
|
+
"build",
|
|
4748
|
+
"__pycache__",
|
|
4749
|
+
".pyc",
|
|
4750
|
+
".egg-info",
|
|
4751
|
+
".tox"
|
|
4752
|
+
];
|
|
4753
|
+
function shouldIgnore(filename) {
|
|
4754
|
+
return IGNORE_PATTERNS.some((p12) => filename.includes(p12));
|
|
4755
|
+
}
|
|
4756
|
+
function hasWatchedExtension(filename, extensions) {
|
|
4757
|
+
return extensions.some((ext) => filename.endsWith(`.${ext}`));
|
|
4758
|
+
}
|
|
4759
|
+
function timestamp() {
|
|
4760
|
+
return (/* @__PURE__ */ new Date()).toLocaleTimeString("en-US", { hour12: false });
|
|
4761
|
+
}
|
|
4762
|
+
var ServerWatcher = class {
|
|
4763
|
+
child = null;
|
|
4764
|
+
watcher = null;
|
|
4765
|
+
restartCount = 0;
|
|
4766
|
+
debounceTimer = null;
|
|
4767
|
+
options = null;
|
|
4768
|
+
stopping = false;
|
|
4769
|
+
/** Start watching and spawn the initial server process */
|
|
4770
|
+
start(options) {
|
|
4771
|
+
this.options = options;
|
|
4772
|
+
this.stopping = false;
|
|
4773
|
+
console.log(`
|
|
4774
|
+
Watching ${options.serverName} (${options.watchDir})...`);
|
|
4775
|
+
console.log(` Extensions: ${options.extensions.join(", ")}`);
|
|
4776
|
+
console.log(` Debounce: ${options.debounceMs}ms
|
|
4777
|
+
`);
|
|
4778
|
+
this.spawnChild();
|
|
4779
|
+
try {
|
|
4780
|
+
this.watcher = fs12.watch(options.watchDir, { recursive: true }, (_event, filename) => {
|
|
4781
|
+
if (!filename) return;
|
|
4782
|
+
this.onFileChange(filename);
|
|
4783
|
+
});
|
|
4784
|
+
} catch (err) {
|
|
4785
|
+
console.error(
|
|
4786
|
+
` Warning: Could not watch directory: ${err instanceof Error ? err.message : String(err)}`
|
|
4787
|
+
);
|
|
4788
|
+
}
|
|
4789
|
+
}
|
|
4790
|
+
/** Gracefully stop watcher and child process */
|
|
4791
|
+
stop() {
|
|
4792
|
+
this.stopping = true;
|
|
4793
|
+
if (this.debounceTimer) {
|
|
4794
|
+
clearTimeout(this.debounceTimer);
|
|
4795
|
+
this.debounceTimer = null;
|
|
4796
|
+
}
|
|
4797
|
+
if (this.watcher) {
|
|
4798
|
+
this.watcher.close();
|
|
4799
|
+
this.watcher = null;
|
|
4800
|
+
}
|
|
4801
|
+
if (this.child && !this.child.killed) {
|
|
4802
|
+
this.child.kill("SIGTERM");
|
|
4803
|
+
}
|
|
4804
|
+
console.log(`
|
|
4805
|
+
Stopped. (${this.restartCount} restart${this.restartCount !== 1 ? "s" : ""})`);
|
|
4806
|
+
}
|
|
4807
|
+
/** Spawn the server child process, piping its stdio to parent */
|
|
4808
|
+
spawnChild() {
|
|
4809
|
+
if (!this.options) return;
|
|
4810
|
+
const { command, args, env, serverName, clearOnRestart } = this.options;
|
|
4811
|
+
if (clearOnRestart && this.restartCount > 0) {
|
|
4812
|
+
process.stdout.write("\x1Bc");
|
|
4813
|
+
}
|
|
4814
|
+
console.log(` [${timestamp()}] Starting ${serverName}...`);
|
|
4815
|
+
this.child = spawn5(command, args, { env, stdio: ["pipe", "pipe", "pipe"] });
|
|
4816
|
+
this.child.stdout?.on("data", (data) => {
|
|
4817
|
+
process.stdout.write(` [stdout] ${data.toString().trimEnd()}
|
|
4818
|
+
`);
|
|
4819
|
+
});
|
|
4820
|
+
this.child.stderr?.on("data", (data) => {
|
|
4821
|
+
process.stderr.write(` [stderr] ${data.toString().trimEnd()}
|
|
4822
|
+
`);
|
|
4823
|
+
});
|
|
4824
|
+
this.child.on("error", (err) => {
|
|
4825
|
+
console.error(` [${timestamp()}] Error: ${err.message}`);
|
|
4826
|
+
});
|
|
4827
|
+
this.child.on("close", (code) => {
|
|
4828
|
+
if (!this.stopping) {
|
|
4829
|
+
console.log(` [${timestamp()}] Process exited (code ${code ?? "?"})`);
|
|
4830
|
+
}
|
|
4831
|
+
});
|
|
4832
|
+
}
|
|
4833
|
+
/** Kill child process: SIGTERM first, then SIGKILL after 2s */
|
|
4834
|
+
async killChild() {
|
|
4835
|
+
if (!this.child || this.child.killed) return;
|
|
4836
|
+
const childRef = this.child;
|
|
4837
|
+
return new Promise((resolve) => {
|
|
4838
|
+
const child = childRef;
|
|
4839
|
+
const killTimer = setTimeout(() => {
|
|
4840
|
+
if (!child.killed) {
|
|
4841
|
+
child.kill("SIGKILL");
|
|
4842
|
+
}
|
|
4843
|
+
resolve();
|
|
4844
|
+
}, 2e3);
|
|
4845
|
+
child.on("close", () => {
|
|
4846
|
+
clearTimeout(killTimer);
|
|
4847
|
+
resolve();
|
|
4848
|
+
});
|
|
4849
|
+
child.kill("SIGTERM");
|
|
4850
|
+
});
|
|
4851
|
+
}
|
|
4852
|
+
/** Debounced file change handler */
|
|
4853
|
+
onFileChange(filename) {
|
|
4854
|
+
if (!this.options) return;
|
|
4855
|
+
if (shouldIgnore(filename)) return;
|
|
4856
|
+
if (!hasWatchedExtension(filename, this.options.extensions)) return;
|
|
4857
|
+
if (this.debounceTimer) {
|
|
4858
|
+
clearTimeout(this.debounceTimer);
|
|
4859
|
+
}
|
|
4860
|
+
this.debounceTimer = setTimeout(async () => {
|
|
4861
|
+
this.debounceTimer = null;
|
|
4862
|
+
this.restartCount++;
|
|
4863
|
+
console.log(` [${timestamp()}] File changed: ${filename}`);
|
|
4864
|
+
console.log(
|
|
4865
|
+
` [${timestamp()}] Restarting ${this.options?.serverName}... (restart #${this.restartCount})`
|
|
4866
|
+
);
|
|
4867
|
+
await this.killChild();
|
|
4868
|
+
this.spawnChild();
|
|
4869
|
+
}, this.options.debounceMs);
|
|
4870
|
+
}
|
|
4871
|
+
getRestartCount() {
|
|
4872
|
+
return this.restartCount;
|
|
4873
|
+
}
|
|
4874
|
+
};
|
|
4875
|
+
|
|
4876
|
+
// src/commands/watch.ts
|
|
4877
|
+
var DEFAULT_EXTENSIONS = ["ts", "js", "json", "py", "mjs", "cjs"];
|
|
4878
|
+
var DEFAULT_DEBOUNCE_MS = 300;
|
|
4879
|
+
var watch_default = defineCommand25({
|
|
4880
|
+
meta: {
|
|
4881
|
+
name: "watch",
|
|
4882
|
+
description: "Watch a local MCP server for file changes and auto-restart"
|
|
4883
|
+
},
|
|
4884
|
+
args: {
|
|
4885
|
+
server: {
|
|
4886
|
+
type: "positional",
|
|
4887
|
+
description: "Server name (must be in lockfile)",
|
|
4888
|
+
required: true
|
|
4889
|
+
},
|
|
4890
|
+
dir: {
|
|
4891
|
+
type: "string",
|
|
4892
|
+
description: "Directory to watch (default: resolved path from lockfile)"
|
|
4893
|
+
},
|
|
4894
|
+
ext: {
|
|
4895
|
+
type: "string",
|
|
4896
|
+
description: `File extensions to watch, comma-separated (default: ${DEFAULT_EXTENSIONS.join(",")})`
|
|
4897
|
+
},
|
|
4898
|
+
delay: {
|
|
4899
|
+
type: "string",
|
|
4900
|
+
description: `Debounce delay in ms (default: ${DEFAULT_DEBOUNCE_MS})`
|
|
4901
|
+
},
|
|
4902
|
+
clear: {
|
|
4903
|
+
type: "boolean",
|
|
4904
|
+
description: "Clear terminal on each restart",
|
|
4905
|
+
default: false
|
|
4906
|
+
},
|
|
4907
|
+
env: {
|
|
4908
|
+
type: "string",
|
|
4909
|
+
description: "Override env var KEY=VAL (repeatable)",
|
|
4910
|
+
alias: "e"
|
|
4911
|
+
}
|
|
4912
|
+
},
|
|
4913
|
+
async run({ args }) {
|
|
4914
|
+
const serverName = args.server;
|
|
4915
|
+
const lockfile = readLockfile();
|
|
4916
|
+
const entry = lockfile.servers[serverName];
|
|
4917
|
+
if (!entry) {
|
|
4918
|
+
console.error(pc25.red(` Error: Server '${serverName}' not found in lockfile.`));
|
|
4919
|
+
console.error(pc25.dim(` Run ${pc25.cyan("mcpman link .")} to register a local server.`));
|
|
4920
|
+
process.exit(1);
|
|
4921
|
+
}
|
|
4922
|
+
let watchDir = args.dir;
|
|
4923
|
+
if (!watchDir) {
|
|
4924
|
+
if (entry.source === "local" && entry.resolved) {
|
|
4925
|
+
watchDir = entry.resolved;
|
|
4926
|
+
} else {
|
|
4927
|
+
console.error(pc25.red(` Error: Cannot determine watch directory for '${serverName}'.`));
|
|
4928
|
+
console.error(pc25.dim(" Use --dir to specify the directory to watch."));
|
|
4929
|
+
process.exit(1);
|
|
4930
|
+
}
|
|
4931
|
+
}
|
|
4932
|
+
const extensions = args.ext ? args.ext.split(",").map((e) => e.trim().replace(/^\./, "")) : DEFAULT_EXTENSIONS;
|
|
4933
|
+
const debounceMs = args.delay ? Number.parseInt(args.delay, 10) || DEFAULT_DEBOUNCE_MS : DEFAULT_DEBOUNCE_MS;
|
|
4934
|
+
const lockfileEnv = parseEnvFlags(entry.envVars);
|
|
4935
|
+
const vaultEnv = await loadVaultSecrets4(serverName);
|
|
4936
|
+
const cliEnv = parseEnvFlags(args.env);
|
|
4937
|
+
const finalEnv = {
|
|
4938
|
+
...process.env,
|
|
4939
|
+
...lockfileEnv,
|
|
4940
|
+
...vaultEnv,
|
|
4941
|
+
...cliEnv
|
|
4942
|
+
};
|
|
4943
|
+
const watcher = new ServerWatcher();
|
|
4944
|
+
const handleStop = () => {
|
|
4945
|
+
watcher.stop();
|
|
4946
|
+
process.exit(0);
|
|
4947
|
+
};
|
|
4948
|
+
process.on("SIGINT", handleStop);
|
|
4949
|
+
process.on("SIGTERM", handleStop);
|
|
4950
|
+
watcher.start({
|
|
4951
|
+
command: entry.command,
|
|
4952
|
+
args: entry.args,
|
|
4953
|
+
env: finalEnv,
|
|
4954
|
+
watchDir,
|
|
4955
|
+
extensions,
|
|
4956
|
+
debounceMs,
|
|
4957
|
+
clearOnRestart: args.clear,
|
|
4958
|
+
serverName
|
|
4959
|
+
});
|
|
4960
|
+
}
|
|
4961
|
+
});
|
|
4962
|
+
async function loadVaultSecrets4(serverName) {
|
|
4963
|
+
try {
|
|
4964
|
+
const entries = listSecrets(serverName);
|
|
4965
|
+
if (entries.length === 0 || entries[0].keys.length === 0) return {};
|
|
4966
|
+
const password = await getMasterPassword();
|
|
4967
|
+
return getSecretsForServer(serverName, password);
|
|
4968
|
+
} catch {
|
|
4969
|
+
return {};
|
|
4970
|
+
}
|
|
4971
|
+
}
|
|
4972
|
+
|
|
4973
|
+
// src/commands/why.ts
|
|
4974
|
+
import { defineCommand as defineCommand26 } from "citty";
|
|
4975
|
+
import pc26 from "picocolors";
|
|
4976
|
+
|
|
4977
|
+
// src/core/why-service.ts
|
|
4978
|
+
import fs13 from "fs";
|
|
4979
|
+
import path14 from "path";
|
|
4980
|
+
var ALL_CLIENT_TYPES = ["claude-desktop", "cursor", "vscode", "windsurf"];
|
|
4981
|
+
async function getServerProvenance(serverName, lockfilePath, profilesDir) {
|
|
4982
|
+
const lockfile = readLockfile(lockfilePath);
|
|
4983
|
+
const entry = lockfile.servers[serverName];
|
|
4984
|
+
if (!entry) {
|
|
4985
|
+
const orphanedClients = await findOrphanedClients(serverName);
|
|
4986
|
+
const anyRegistered = orphanedClients.some((c) => c.registered);
|
|
4987
|
+
if (!anyRegistered) return null;
|
|
4988
|
+
return {
|
|
4989
|
+
name: serverName,
|
|
4990
|
+
version: "unknown",
|
|
4991
|
+
source: "unknown",
|
|
4992
|
+
resolved: "",
|
|
4993
|
+
integrity: "",
|
|
4994
|
+
installedAt: "",
|
|
4995
|
+
clients: orphanedClients,
|
|
4996
|
+
profiles: [],
|
|
4997
|
+
envVars: [],
|
|
4998
|
+
orphaned: true
|
|
4999
|
+
};
|
|
5000
|
+
}
|
|
5001
|
+
const clientStatuses = await buildClientStatuses(serverName, entry.clients);
|
|
5002
|
+
const profiles = scanProfiles(serverName, profilesDir ?? getProfilesDir());
|
|
5003
|
+
return {
|
|
5004
|
+
name: serverName,
|
|
5005
|
+
version: entry.version,
|
|
5006
|
+
source: entry.source,
|
|
5007
|
+
resolved: entry.resolved,
|
|
5008
|
+
integrity: entry.integrity,
|
|
5009
|
+
installedAt: entry.installedAt,
|
|
5010
|
+
clients: clientStatuses,
|
|
5011
|
+
profiles,
|
|
5012
|
+
envVars: entry.envVars ?? [],
|
|
5013
|
+
orphaned: false
|
|
5014
|
+
};
|
|
5015
|
+
}
|
|
5016
|
+
async function buildClientStatuses(serverName, lockfileClients) {
|
|
5017
|
+
return ALL_CLIENT_TYPES.map((type) => ({
|
|
5018
|
+
type,
|
|
5019
|
+
registered: lockfileClients.includes(type)
|
|
5020
|
+
}));
|
|
5021
|
+
}
|
|
5022
|
+
async function findOrphanedClients(serverName) {
|
|
5023
|
+
const { getInstalledClients: getInstalledClients2 } = await import("./client-detector-CY7WPF3K.js");
|
|
5024
|
+
const handlers = await getInstalledClients2();
|
|
5025
|
+
const results = [];
|
|
5026
|
+
for (const handler of handlers) {
|
|
5027
|
+
try {
|
|
5028
|
+
const config = await handler.readConfig();
|
|
5029
|
+
const registered = serverName in (config.servers ?? {});
|
|
5030
|
+
results.push({ type: handler.type, registered });
|
|
5031
|
+
} catch {
|
|
5032
|
+
results.push({ type: handler.type, registered: false });
|
|
5033
|
+
}
|
|
5034
|
+
}
|
|
5035
|
+
return results;
|
|
5036
|
+
}
|
|
5037
|
+
function scanProfiles(serverName, profilesDir) {
|
|
5038
|
+
const found = [];
|
|
5039
|
+
if (!fs13.existsSync(profilesDir)) return found;
|
|
5040
|
+
let files;
|
|
5041
|
+
try {
|
|
5042
|
+
files = fs13.readdirSync(profilesDir).filter((f) => f.endsWith(".json"));
|
|
5043
|
+
} catch {
|
|
5044
|
+
return found;
|
|
5045
|
+
}
|
|
5046
|
+
for (const file of files) {
|
|
5047
|
+
try {
|
|
5048
|
+
const raw = fs13.readFileSync(path14.join(profilesDir, file), "utf-8");
|
|
5049
|
+
const profile = JSON.parse(raw);
|
|
5050
|
+
if (serverName in (profile.servers ?? {})) {
|
|
5051
|
+
found.push(profile.name ?? file.replace(".json", ""));
|
|
5052
|
+
}
|
|
5053
|
+
} catch {
|
|
5054
|
+
}
|
|
5055
|
+
}
|
|
5056
|
+
return found.sort();
|
|
5057
|
+
}
|
|
5058
|
+
function formatWhyOutput(result) {
|
|
5059
|
+
const lines = [];
|
|
5060
|
+
lines.push(` Server: ${result.name}`);
|
|
5061
|
+
lines.push(` Version: ${result.version}`);
|
|
5062
|
+
lines.push(` Source: ${result.source}`);
|
|
5063
|
+
if (result.resolved) lines.push(` Resolved: ${result.resolved}`);
|
|
5064
|
+
if (result.integrity && result.integrity !== "local") {
|
|
5065
|
+
lines.push(` Integrity: ${result.integrity}`);
|
|
5066
|
+
}
|
|
5067
|
+
if (result.installedAt) lines.push(` Installed: ${result.installedAt}`);
|
|
5068
|
+
lines.push("");
|
|
5069
|
+
lines.push(" Clients:");
|
|
5070
|
+
for (const c of result.clients) {
|
|
5071
|
+
const status = c.registered ? "registered" : "not registered";
|
|
5072
|
+
lines.push(` ${c.type.padEnd(20)} ${status}`);
|
|
5073
|
+
}
|
|
5074
|
+
if (result.profiles.length > 0) {
|
|
5075
|
+
lines.push("");
|
|
5076
|
+
lines.push(" Profiles:");
|
|
5077
|
+
for (const p12 of result.profiles) {
|
|
5078
|
+
lines.push(` ${p12}`);
|
|
5079
|
+
}
|
|
5080
|
+
}
|
|
5081
|
+
if (result.envVars.length > 0) {
|
|
5082
|
+
lines.push("");
|
|
5083
|
+
lines.push(" Env Vars:");
|
|
5084
|
+
for (const v of result.envVars) {
|
|
5085
|
+
lines.push(` ${v}`);
|
|
5086
|
+
}
|
|
5087
|
+
}
|
|
5088
|
+
return lines.join("\n");
|
|
5089
|
+
}
|
|
5090
|
+
|
|
5091
|
+
// src/commands/why.ts
|
|
5092
|
+
var why_default = defineCommand26({
|
|
5093
|
+
meta: {
|
|
5094
|
+
name: "why",
|
|
5095
|
+
description: "Show why a server is installed (provenance, clients, profiles)"
|
|
5096
|
+
},
|
|
5097
|
+
args: {
|
|
5098
|
+
server: {
|
|
5099
|
+
type: "positional",
|
|
5100
|
+
description: "Server name to inspect",
|
|
5101
|
+
required: true
|
|
5102
|
+
},
|
|
5103
|
+
json: {
|
|
5104
|
+
type: "boolean",
|
|
5105
|
+
description: "Output as JSON for scripting",
|
|
5106
|
+
default: false
|
|
5107
|
+
}
|
|
5108
|
+
},
|
|
5109
|
+
async run({ args }) {
|
|
5110
|
+
const serverName = args.server;
|
|
5111
|
+
const asJson = args.json;
|
|
5112
|
+
const result = await getServerProvenance(serverName);
|
|
5113
|
+
if (!result) {
|
|
5114
|
+
console.error(pc26.red(` Server '${serverName}' not found in lockfile or any client config.`));
|
|
5115
|
+
console.error(pc26.dim(` Run ${pc26.cyan("mcpman list")} to see installed servers.`));
|
|
5116
|
+
process.exit(1);
|
|
5117
|
+
}
|
|
5118
|
+
if (result.orphaned) {
|
|
5119
|
+
console.log(pc26.yellow(`
|
|
5120
|
+
Server '${serverName}' is orphaned:`));
|
|
5121
|
+
console.log(pc26.dim(" Found in client config(s) but not in lockfile."));
|
|
5122
|
+
console.log(pc26.dim(` Run ${pc26.cyan("mcpman sync --remove")} to clean up.
|
|
5123
|
+
`));
|
|
5124
|
+
const registeredClients = result.clients.filter((c) => c.registered).map((c) => c.type);
|
|
5125
|
+
if (registeredClients.length > 0) {
|
|
5126
|
+
console.log(` Registered in: ${registeredClients.join(", ")}`);
|
|
5127
|
+
}
|
|
5128
|
+
return;
|
|
5129
|
+
}
|
|
5130
|
+
if (asJson) {
|
|
5131
|
+
console.log(JSON.stringify(result, null, 2));
|
|
5132
|
+
return;
|
|
5133
|
+
}
|
|
5134
|
+
console.log();
|
|
5135
|
+
console.log(formatWhyOutput(result));
|
|
5136
|
+
console.log();
|
|
5137
|
+
}
|
|
5138
|
+
});
|
|
5139
|
+
|
|
3858
5140
|
// src/index.ts
|
|
3859
5141
|
process.on("SIGINT", () => {
|
|
3860
5142
|
console.log("\nAborted.");
|
|
3861
5143
|
process.exit(130);
|
|
3862
5144
|
});
|
|
3863
|
-
var main =
|
|
5145
|
+
var main = defineCommand27({
|
|
3864
5146
|
meta: {
|
|
3865
5147
|
name: APP_NAME,
|
|
3866
5148
|
version: APP_VERSION,
|
|
@@ -3886,7 +5168,13 @@ var main = defineCommand21({
|
|
|
3886
5168
|
profiles: profiles_default,
|
|
3887
5169
|
plugin: plugin_default,
|
|
3888
5170
|
export: export_command_default,
|
|
3889
|
-
import: import_command_default
|
|
5171
|
+
import: import_command_default,
|
|
5172
|
+
create: create_default,
|
|
5173
|
+
link: link_default,
|
|
5174
|
+
watch: watch_default,
|
|
5175
|
+
registry: registry_default,
|
|
5176
|
+
completions: completions_default,
|
|
5177
|
+
why: why_default
|
|
3890
5178
|
}
|
|
3891
5179
|
});
|
|
3892
5180
|
runMain(main);
|