mcpman 0.1.0 → 0.2.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 +8 -2
- package/dist/chunk-6X6Q6UZC.js +141 -0
- package/dist/index.cjs +1391 -167
- package/dist/index.js +1127 -139
- package/dist/trust-scorer-LYC6KZCD.js +77 -0
- package/dist/vault-service-UTZAV6N6.js +29 -0
- package/package.json +11 -4
package/dist/index.cjs
CHANGED
|
@@ -37,6 +37,90 @@ var init_cjs_shims = __esm({
|
|
|
37
37
|
}
|
|
38
38
|
});
|
|
39
39
|
|
|
40
|
+
// src/core/trust-scorer.ts
|
|
41
|
+
var trust_scorer_exports = {};
|
|
42
|
+
__export(trust_scorer_exports, {
|
|
43
|
+
computeTrustScore: () => computeTrustScore
|
|
44
|
+
});
|
|
45
|
+
function vulnScore(vulns) {
|
|
46
|
+
let score = 100;
|
|
47
|
+
for (const v of vulns) {
|
|
48
|
+
if (v.severity === "critical") score -= 25;
|
|
49
|
+
else if (v.severity === "high") score -= 15;
|
|
50
|
+
else if (v.severity === "moderate") score -= 10;
|
|
51
|
+
else score -= 5;
|
|
52
|
+
}
|
|
53
|
+
return Math.max(0, score);
|
|
54
|
+
}
|
|
55
|
+
function downloadScore(weeklyDownloads) {
|
|
56
|
+
if (weeklyDownloads <= 0) return 0;
|
|
57
|
+
if (weeklyDownloads >= 1e6) return 100;
|
|
58
|
+
if (weeklyDownloads >= 1e5) return 80;
|
|
59
|
+
if (weeklyDownloads >= 1e4) return 60;
|
|
60
|
+
if (weeklyDownloads >= 1e3) return 40;
|
|
61
|
+
if (weeklyDownloads >= 100) return 20;
|
|
62
|
+
return 10;
|
|
63
|
+
}
|
|
64
|
+
function ageScore(packageAge) {
|
|
65
|
+
if (packageAge <= 0) return 0;
|
|
66
|
+
if (packageAge >= 730) return 100;
|
|
67
|
+
if (packageAge >= 365) return 80;
|
|
68
|
+
if (packageAge >= 180) return 60;
|
|
69
|
+
if (packageAge >= 30) return 30;
|
|
70
|
+
return 10;
|
|
71
|
+
}
|
|
72
|
+
function publishScore(lastPublish) {
|
|
73
|
+
const daysSince = Math.floor((Date.now() - new Date(lastPublish).getTime()) / 864e5);
|
|
74
|
+
if (daysSince <= 30) return 100;
|
|
75
|
+
if (daysSince <= 90) return 80;
|
|
76
|
+
if (daysSince <= 180) return 60;
|
|
77
|
+
if (daysSince <= 365) return 40;
|
|
78
|
+
return 20;
|
|
79
|
+
}
|
|
80
|
+
function maintainerScore(count, deprecated) {
|
|
81
|
+
let score = 0;
|
|
82
|
+
if (count >= 3) score = 90;
|
|
83
|
+
else if (count === 2) score = 70;
|
|
84
|
+
else score = 50;
|
|
85
|
+
if (!deprecated) score += 10;
|
|
86
|
+
return Math.min(100, score);
|
|
87
|
+
}
|
|
88
|
+
function toRiskLevel(score) {
|
|
89
|
+
if (score >= 80) return "LOW";
|
|
90
|
+
if (score >= 50) return "MEDIUM";
|
|
91
|
+
if (score >= 20) return "HIGH";
|
|
92
|
+
return "CRITICAL";
|
|
93
|
+
}
|
|
94
|
+
function computeTrustScore(metadata, vulns) {
|
|
95
|
+
if (!metadata) {
|
|
96
|
+
const vScore = vulnScore(vulns);
|
|
97
|
+
const score2 = Math.round(vScore * WEIGHT_VULNS * 100) / 100;
|
|
98
|
+
return { score: Math.round(score2), riskLevel: toRiskLevel(score2) };
|
|
99
|
+
}
|
|
100
|
+
const scores = {
|
|
101
|
+
vulns: vulnScore(vulns),
|
|
102
|
+
downloads: downloadScore(metadata.weeklyDownloads),
|
|
103
|
+
age: ageScore(metadata.packageAge),
|
|
104
|
+
publish: publishScore(metadata.lastPublish),
|
|
105
|
+
maintainers: maintainerScore(metadata.maintainerCount, metadata.deprecated)
|
|
106
|
+
};
|
|
107
|
+
const weighted = scores.vulns * WEIGHT_VULNS + scores.downloads * WEIGHT_DOWNLOADS + scores.age * WEIGHT_AGE + scores.publish * WEIGHT_PUBLISH_FREQ + scores.maintainers * WEIGHT_MAINTAINERS;
|
|
108
|
+
const score = Math.min(100, Math.max(0, Math.round(weighted)));
|
|
109
|
+
return { score, riskLevel: toRiskLevel(score) };
|
|
110
|
+
}
|
|
111
|
+
var WEIGHT_VULNS, WEIGHT_DOWNLOADS, WEIGHT_AGE, WEIGHT_PUBLISH_FREQ, WEIGHT_MAINTAINERS;
|
|
112
|
+
var init_trust_scorer = __esm({
|
|
113
|
+
"src/core/trust-scorer.ts"() {
|
|
114
|
+
"use strict";
|
|
115
|
+
init_cjs_shims();
|
|
116
|
+
WEIGHT_VULNS = 0.3;
|
|
117
|
+
WEIGHT_DOWNLOADS = 0.2;
|
|
118
|
+
WEIGHT_AGE = 0.15;
|
|
119
|
+
WEIGHT_PUBLISH_FREQ = 0.15;
|
|
120
|
+
WEIGHT_MAINTAINERS = 0.2;
|
|
121
|
+
}
|
|
122
|
+
});
|
|
123
|
+
|
|
40
124
|
// src/clients/types.ts
|
|
41
125
|
var ConfigParseError, ConfigWriteError;
|
|
42
126
|
var init_types = __esm({
|
|
@@ -64,43 +148,43 @@ var init_types = __esm({
|
|
|
64
148
|
async function atomicWrite(filePath, content) {
|
|
65
149
|
const tmpPath = `${filePath}.tmp`;
|
|
66
150
|
try {
|
|
67
|
-
await
|
|
68
|
-
await
|
|
69
|
-
await
|
|
151
|
+
await import_node_fs3.default.promises.mkdir(import_node_path3.default.dirname(filePath), { recursive: true });
|
|
152
|
+
await import_node_fs3.default.promises.writeFile(tmpPath, content, { encoding: "utf-8", mode: 384 });
|
|
153
|
+
await import_node_fs3.default.promises.rename(tmpPath, filePath);
|
|
70
154
|
} catch (err) {
|
|
71
155
|
try {
|
|
72
|
-
await
|
|
156
|
+
await import_node_fs3.default.promises.unlink(tmpPath);
|
|
73
157
|
} catch {
|
|
74
158
|
}
|
|
75
159
|
throw err;
|
|
76
160
|
}
|
|
77
161
|
}
|
|
78
|
-
async function pathExists(
|
|
162
|
+
async function pathExists(p9) {
|
|
79
163
|
try {
|
|
80
|
-
await
|
|
164
|
+
await import_node_fs3.default.promises.access(p9);
|
|
81
165
|
return true;
|
|
82
166
|
} catch {
|
|
83
167
|
return false;
|
|
84
168
|
}
|
|
85
169
|
}
|
|
86
|
-
var
|
|
170
|
+
var import_node_fs3, import_node_path3, BaseClientHandler;
|
|
87
171
|
var init_base_client_handler = __esm({
|
|
88
172
|
"src/clients/base-client-handler.ts"() {
|
|
89
173
|
"use strict";
|
|
90
174
|
init_cjs_shims();
|
|
91
|
-
|
|
92
|
-
|
|
175
|
+
import_node_fs3 = __toESM(require("fs"), 1);
|
|
176
|
+
import_node_path3 = __toESM(require("path"), 1);
|
|
93
177
|
init_types();
|
|
94
178
|
BaseClientHandler = class {
|
|
95
179
|
async isInstalled() {
|
|
96
|
-
const dir =
|
|
180
|
+
const dir = import_node_path3.default.dirname(this.getConfigPath());
|
|
97
181
|
return pathExists(dir);
|
|
98
182
|
}
|
|
99
183
|
/** Read raw JSON from disk, return empty object if file missing */
|
|
100
184
|
async readRaw() {
|
|
101
185
|
const configPath = this.getConfigPath();
|
|
102
186
|
try {
|
|
103
|
-
const raw = await
|
|
187
|
+
const raw = await import_node_fs3.default.promises.readFile(configPath, "utf-8");
|
|
104
188
|
return JSON.parse(raw);
|
|
105
189
|
} catch (err) {
|
|
106
190
|
if (err.code === "ENOENT") {
|
|
@@ -151,26 +235,26 @@ var init_base_client_handler = __esm({
|
|
|
151
235
|
|
|
152
236
|
// src/utils/paths.ts
|
|
153
237
|
function getHomedir() {
|
|
154
|
-
return
|
|
238
|
+
return import_node_os3.default.homedir();
|
|
155
239
|
}
|
|
156
240
|
function getAppDataDir() {
|
|
157
241
|
const home = getHomedir();
|
|
158
242
|
if (process.platform === "darwin") {
|
|
159
|
-
return
|
|
243
|
+
return import_node_path4.default.join(home, "Library", "Application Support");
|
|
160
244
|
}
|
|
161
245
|
if (process.platform === "win32") {
|
|
162
|
-
return process.env.APPDATA ??
|
|
246
|
+
return process.env.APPDATA ?? import_node_path4.default.join(home, "AppData", "Roaming");
|
|
163
247
|
}
|
|
164
|
-
return process.env.XDG_CONFIG_HOME ??
|
|
248
|
+
return process.env.XDG_CONFIG_HOME ?? import_node_path4.default.join(home, ".config");
|
|
165
249
|
}
|
|
166
250
|
function resolveConfigPath(client) {
|
|
167
251
|
const appData = getAppDataDir();
|
|
168
252
|
const home = getHomedir();
|
|
169
253
|
switch (client) {
|
|
170
254
|
case "claude-desktop":
|
|
171
|
-
return
|
|
255
|
+
return import_node_path4.default.join(appData, "Claude", "claude_desktop_config.json");
|
|
172
256
|
case "cursor":
|
|
173
|
-
return
|
|
257
|
+
return import_node_path4.default.join(
|
|
174
258
|
appData,
|
|
175
259
|
"Cursor",
|
|
176
260
|
"User",
|
|
@@ -179,7 +263,7 @@ function resolveConfigPath(client) {
|
|
|
179
263
|
"mcp.json"
|
|
180
264
|
);
|
|
181
265
|
case "windsurf":
|
|
182
|
-
return
|
|
266
|
+
return import_node_path4.default.join(
|
|
183
267
|
appData,
|
|
184
268
|
"Windsurf",
|
|
185
269
|
"User",
|
|
@@ -189,21 +273,21 @@ function resolveConfigPath(client) {
|
|
|
189
273
|
);
|
|
190
274
|
case "vscode":
|
|
191
275
|
if (process.platform === "darwin") {
|
|
192
|
-
return
|
|
276
|
+
return import_node_path4.default.join(appData, "Code", "User", "settings.json");
|
|
193
277
|
}
|
|
194
278
|
if (process.platform === "win32") {
|
|
195
|
-
return
|
|
279
|
+
return import_node_path4.default.join(appData, "Code", "User", "settings.json");
|
|
196
280
|
}
|
|
197
|
-
return
|
|
281
|
+
return import_node_path4.default.join(home, ".config", "Code", "User", "settings.json");
|
|
198
282
|
}
|
|
199
283
|
}
|
|
200
|
-
var
|
|
284
|
+
var import_node_os3, import_node_path4;
|
|
201
285
|
var init_paths = __esm({
|
|
202
286
|
"src/utils/paths.ts"() {
|
|
203
287
|
"use strict";
|
|
204
288
|
init_cjs_shims();
|
|
205
|
-
|
|
206
|
-
|
|
289
|
+
import_node_os3 = __toESM(require("os"), 1);
|
|
290
|
+
import_node_path4 = __toESM(require("path"), 1);
|
|
207
291
|
}
|
|
208
292
|
});
|
|
209
293
|
|
|
@@ -331,14 +415,515 @@ var init_client_detector = __esm({
|
|
|
331
415
|
}
|
|
332
416
|
});
|
|
333
417
|
|
|
418
|
+
// src/core/vault-service.ts
|
|
419
|
+
var vault_service_exports = {};
|
|
420
|
+
__export(vault_service_exports, {
|
|
421
|
+
clearPasswordCache: () => clearPasswordCache,
|
|
422
|
+
decrypt: () => decrypt,
|
|
423
|
+
encrypt: () => encrypt,
|
|
424
|
+
getMasterPassword: () => getMasterPassword,
|
|
425
|
+
getSecret: () => getSecret,
|
|
426
|
+
getSecretsForServer: () => getSecretsForServer,
|
|
427
|
+
getVaultPath: () => getVaultPath,
|
|
428
|
+
listSecrets: () => listSecrets,
|
|
429
|
+
readVault: () => readVault,
|
|
430
|
+
removeSecret: () => removeSecret,
|
|
431
|
+
setSecret: () => setSecret,
|
|
432
|
+
writeVault: () => writeVault
|
|
433
|
+
});
|
|
434
|
+
function getVaultPath() {
|
|
435
|
+
return import_node_path6.default.join(import_node_os4.default.homedir(), ".mcpman", "vault.enc");
|
|
436
|
+
}
|
|
437
|
+
function readVault(vaultPath = getVaultPath()) {
|
|
438
|
+
const empty = { version: 1, servers: {} };
|
|
439
|
+
try {
|
|
440
|
+
const raw = import_node_fs4.default.readFileSync(vaultPath, "utf-8");
|
|
441
|
+
const parsed = JSON.parse(raw);
|
|
442
|
+
if (parsed.version !== 1 || typeof parsed.servers !== "object") return empty;
|
|
443
|
+
return parsed;
|
|
444
|
+
} catch {
|
|
445
|
+
return empty;
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
function writeVault(data, vaultPath = getVaultPath()) {
|
|
449
|
+
const dir = import_node_path6.default.dirname(vaultPath);
|
|
450
|
+
import_node_fs4.default.mkdirSync(dir, { recursive: true });
|
|
451
|
+
const tmp = `${vaultPath}.tmp`;
|
|
452
|
+
import_node_fs4.default.writeFileSync(tmp, JSON.stringify(data, null, 2), { mode: 384 });
|
|
453
|
+
if (process.platform !== "win32") {
|
|
454
|
+
import_node_fs4.default.chmodSync(tmp, 384);
|
|
455
|
+
}
|
|
456
|
+
import_node_fs4.default.renameSync(tmp, vaultPath);
|
|
457
|
+
if (process.platform !== "win32") {
|
|
458
|
+
import_node_fs4.default.chmodSync(vaultPath, 384);
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
function encrypt(value, password2) {
|
|
462
|
+
const salt = import_node_crypto2.default.randomBytes(16);
|
|
463
|
+
const key = import_node_crypto2.default.pbkdf2Sync(password2, salt, 1e5, 32, "sha256");
|
|
464
|
+
const iv = import_node_crypto2.default.randomBytes(16);
|
|
465
|
+
const cipher = import_node_crypto2.default.createCipheriv("aes-256-cbc", key, iv);
|
|
466
|
+
const encrypted = Buffer.concat([cipher.update(value, "utf-8"), cipher.final()]);
|
|
467
|
+
return {
|
|
468
|
+
salt: salt.toString("hex"),
|
|
469
|
+
iv: iv.toString("hex"),
|
|
470
|
+
data: encrypted.toString("hex")
|
|
471
|
+
};
|
|
472
|
+
}
|
|
473
|
+
function decrypt(entry, password2) {
|
|
474
|
+
const salt = Buffer.from(entry.salt, "hex");
|
|
475
|
+
const key = import_node_crypto2.default.pbkdf2Sync(password2, salt, 1e5, 32, "sha256");
|
|
476
|
+
const iv = Buffer.from(entry.iv, "hex");
|
|
477
|
+
const decipher = import_node_crypto2.default.createDecipheriv("aes-256-cbc", key, iv);
|
|
478
|
+
const decrypted = Buffer.concat([
|
|
479
|
+
decipher.update(Buffer.from(entry.data, "hex")),
|
|
480
|
+
decipher.final()
|
|
481
|
+
// throws ERR_OSSL_BAD_DECRYPT on wrong password
|
|
482
|
+
]);
|
|
483
|
+
return decrypted.toString("utf-8");
|
|
484
|
+
}
|
|
485
|
+
async function getMasterPassword(confirm6 = false) {
|
|
486
|
+
if (_cachedPassword) return _cachedPassword;
|
|
487
|
+
const password2 = await p5.password({
|
|
488
|
+
message: "Enter vault master password:",
|
|
489
|
+
validate: (v) => v.length < 8 ? "Password must be at least 8 characters" : void 0
|
|
490
|
+
});
|
|
491
|
+
if (p5.isCancel(password2)) {
|
|
492
|
+
p5.cancel("Vault access cancelled.");
|
|
493
|
+
process.exit(0);
|
|
494
|
+
}
|
|
495
|
+
if (confirm6) {
|
|
496
|
+
const confirm22 = await p5.password({ message: "Confirm master password:" });
|
|
497
|
+
if (p5.isCancel(confirm22) || confirm22 !== password2) {
|
|
498
|
+
p5.cancel("Passwords do not match.");
|
|
499
|
+
process.exit(1);
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
_cachedPassword = password2;
|
|
503
|
+
return _cachedPassword;
|
|
504
|
+
}
|
|
505
|
+
function clearPasswordCache() {
|
|
506
|
+
_cachedPassword = null;
|
|
507
|
+
}
|
|
508
|
+
function setSecret(server, key, value, password2, vaultPath = getVaultPath()) {
|
|
509
|
+
const vault = readVault(vaultPath);
|
|
510
|
+
if (!vault.servers[server]) vault.servers[server] = {};
|
|
511
|
+
vault.servers[server][key] = encrypt(value, password2);
|
|
512
|
+
writeVault(vault, vaultPath);
|
|
513
|
+
}
|
|
514
|
+
function getSecret(server, key, password2, vaultPath = getVaultPath()) {
|
|
515
|
+
const vault = readVault(vaultPath);
|
|
516
|
+
const entry = vault.servers[server]?.[key];
|
|
517
|
+
if (!entry) return null;
|
|
518
|
+
return decrypt(entry, password2);
|
|
519
|
+
}
|
|
520
|
+
function getSecretsForServer(server, password2, vaultPath = getVaultPath()) {
|
|
521
|
+
const vault = readVault(vaultPath);
|
|
522
|
+
const entries = vault.servers[server];
|
|
523
|
+
if (!entries) return {};
|
|
524
|
+
const result = {};
|
|
525
|
+
for (const [k, entry] of Object.entries(entries)) {
|
|
526
|
+
result[k] = decrypt(entry, password2);
|
|
527
|
+
}
|
|
528
|
+
return result;
|
|
529
|
+
}
|
|
530
|
+
function removeSecret(server, key, vaultPath = getVaultPath()) {
|
|
531
|
+
const vault = readVault(vaultPath);
|
|
532
|
+
if (vault.servers[server]) {
|
|
533
|
+
delete vault.servers[server][key];
|
|
534
|
+
if (Object.keys(vault.servers[server]).length === 0) {
|
|
535
|
+
delete vault.servers[server];
|
|
536
|
+
}
|
|
537
|
+
writeVault(vault, vaultPath);
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
function listSecrets(server, vaultPath = getVaultPath()) {
|
|
541
|
+
const vault = readVault(vaultPath);
|
|
542
|
+
const entries = server ? vault.servers[server] ? { [server]: vault.servers[server] } : {} : vault.servers;
|
|
543
|
+
return Object.entries(entries).map(([srv, keys]) => ({
|
|
544
|
+
server: srv,
|
|
545
|
+
keys: Object.keys(keys)
|
|
546
|
+
}));
|
|
547
|
+
}
|
|
548
|
+
var import_node_crypto2, import_node_fs4, import_node_os4, import_node_path6, p5, _cachedPassword;
|
|
549
|
+
var init_vault_service = __esm({
|
|
550
|
+
"src/core/vault-service.ts"() {
|
|
551
|
+
"use strict";
|
|
552
|
+
init_cjs_shims();
|
|
553
|
+
import_node_crypto2 = __toESM(require("crypto"), 1);
|
|
554
|
+
import_node_fs4 = __toESM(require("fs"), 1);
|
|
555
|
+
import_node_os4 = __toESM(require("os"), 1);
|
|
556
|
+
import_node_path6 = __toESM(require("path"), 1);
|
|
557
|
+
p5 = __toESM(require("@clack/prompts"), 1);
|
|
558
|
+
_cachedPassword = null;
|
|
559
|
+
process.on("exit", () => {
|
|
560
|
+
_cachedPassword = null;
|
|
561
|
+
});
|
|
562
|
+
}
|
|
563
|
+
});
|
|
564
|
+
|
|
334
565
|
// src/index.ts
|
|
335
566
|
init_cjs_shims();
|
|
336
|
-
var
|
|
567
|
+
var import_citty10 = require("citty");
|
|
337
568
|
|
|
338
|
-
// src/commands/
|
|
569
|
+
// src/commands/audit.ts
|
|
339
570
|
init_cjs_shims();
|
|
340
571
|
var import_citty = require("citty");
|
|
341
572
|
var import_picocolors = __toESM(require("picocolors"), 1);
|
|
573
|
+
var import_nanospinner = require("nanospinner");
|
|
574
|
+
|
|
575
|
+
// src/core/lockfile.ts
|
|
576
|
+
init_cjs_shims();
|
|
577
|
+
var import_node_fs = __toESM(require("fs"), 1);
|
|
578
|
+
var import_node_path = __toESM(require("path"), 1);
|
|
579
|
+
var import_node_os = __toESM(require("os"), 1);
|
|
580
|
+
var LOCKFILE_NAME = "mcpman.lock";
|
|
581
|
+
function findLockfile() {
|
|
582
|
+
let dir = process.cwd();
|
|
583
|
+
while (true) {
|
|
584
|
+
const candidate = import_node_path.default.join(dir, LOCKFILE_NAME);
|
|
585
|
+
if (import_node_fs.default.existsSync(candidate)) return candidate;
|
|
586
|
+
const parent = import_node_path.default.dirname(dir);
|
|
587
|
+
if (parent === dir) break;
|
|
588
|
+
dir = parent;
|
|
589
|
+
}
|
|
590
|
+
return null;
|
|
591
|
+
}
|
|
592
|
+
function getGlobalLockfilePath() {
|
|
593
|
+
return import_node_path.default.join(import_node_os.default.homedir(), ".mcpman", LOCKFILE_NAME);
|
|
594
|
+
}
|
|
595
|
+
function resolveLockfilePath() {
|
|
596
|
+
return findLockfile() ?? getGlobalLockfilePath();
|
|
597
|
+
}
|
|
598
|
+
function readLockfile(filePath) {
|
|
599
|
+
const target = filePath ?? resolveLockfilePath();
|
|
600
|
+
if (!import_node_fs.default.existsSync(target)) {
|
|
601
|
+
return { lockfileVersion: 1, servers: {} };
|
|
602
|
+
}
|
|
603
|
+
try {
|
|
604
|
+
const raw = import_node_fs.default.readFileSync(target, "utf-8");
|
|
605
|
+
return JSON.parse(raw);
|
|
606
|
+
} catch {
|
|
607
|
+
return { lockfileVersion: 1, servers: {} };
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
function serialize(data) {
|
|
611
|
+
const sorted = {
|
|
612
|
+
lockfileVersion: data.lockfileVersion,
|
|
613
|
+
servers: Object.fromEntries(
|
|
614
|
+
Object.entries(data.servers).sort(([a], [b]) => a.localeCompare(b))
|
|
615
|
+
)
|
|
616
|
+
};
|
|
617
|
+
return JSON.stringify(sorted, null, 2) + "\n";
|
|
618
|
+
}
|
|
619
|
+
function writeLockfile(data, filePath) {
|
|
620
|
+
const target = filePath ?? resolveLockfilePath();
|
|
621
|
+
const dir = import_node_path.default.dirname(target);
|
|
622
|
+
if (!import_node_fs.default.existsSync(dir)) {
|
|
623
|
+
import_node_fs.default.mkdirSync(dir, { recursive: true });
|
|
624
|
+
}
|
|
625
|
+
const tmp = `${target}.tmp`;
|
|
626
|
+
import_node_fs.default.writeFileSync(tmp, serialize(data), "utf-8");
|
|
627
|
+
import_node_fs.default.renameSync(tmp, target);
|
|
628
|
+
}
|
|
629
|
+
function addEntry(name, entry, filePath) {
|
|
630
|
+
const data = readLockfile(filePath);
|
|
631
|
+
data.servers[name] = entry;
|
|
632
|
+
writeLockfile(data, filePath);
|
|
633
|
+
}
|
|
634
|
+
function createEmptyLockfile(filePath) {
|
|
635
|
+
writeLockfile({ lockfileVersion: 1, servers: {} }, filePath);
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
// src/core/security-scanner.ts
|
|
639
|
+
init_cjs_shims();
|
|
640
|
+
var import_node_fs2 = __toESM(require("fs"), 1);
|
|
641
|
+
var import_node_os2 = __toESM(require("os"), 1);
|
|
642
|
+
var import_node_path2 = __toESM(require("path"), 1);
|
|
643
|
+
var CACHE_PATH = import_node_path2.default.join(import_node_os2.default.homedir(), ".mcpman", ".audit-cache.json");
|
|
644
|
+
var CACHE_TTL_MS = 24 * 60 * 60 * 1e3;
|
|
645
|
+
function readCache() {
|
|
646
|
+
try {
|
|
647
|
+
if (!import_node_fs2.default.existsSync(CACHE_PATH)) return {};
|
|
648
|
+
return JSON.parse(import_node_fs2.default.readFileSync(CACHE_PATH, "utf-8"));
|
|
649
|
+
} catch {
|
|
650
|
+
return {};
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
function writeCache(cache) {
|
|
654
|
+
const dir = import_node_path2.default.dirname(CACHE_PATH);
|
|
655
|
+
if (!import_node_fs2.default.existsSync(dir)) import_node_fs2.default.mkdirSync(dir, { recursive: true });
|
|
656
|
+
const tmp = `${CACHE_PATH}.tmp`;
|
|
657
|
+
import_node_fs2.default.writeFileSync(tmp, JSON.stringify(cache, null, 2), "utf-8");
|
|
658
|
+
import_node_fs2.default.renameSync(tmp, CACHE_PATH);
|
|
659
|
+
}
|
|
660
|
+
function getCachedReport(name, version) {
|
|
661
|
+
const cache = readCache();
|
|
662
|
+
const key = `${name}@${version}`;
|
|
663
|
+
const entry = cache[key];
|
|
664
|
+
if (!entry) return null;
|
|
665
|
+
if (Date.now() - entry.timestamp > CACHE_TTL_MS) return null;
|
|
666
|
+
return entry.report;
|
|
667
|
+
}
|
|
668
|
+
function cacheReport(name, version, report) {
|
|
669
|
+
const cache = readCache();
|
|
670
|
+
cache[`${name}@${version}`] = { report, timestamp: Date.now() };
|
|
671
|
+
writeCache(cache);
|
|
672
|
+
}
|
|
673
|
+
async function fetchNpmMetadata(packageName) {
|
|
674
|
+
const timeout = 1e4;
|
|
675
|
+
const signal = AbortSignal.timeout(timeout);
|
|
676
|
+
try {
|
|
677
|
+
const [regRes, dlRes] = await Promise.all([
|
|
678
|
+
fetch(`https://registry.npmjs.org/${encodeURIComponent(packageName)}`, { signal }),
|
|
679
|
+
fetch(`https://api.npmjs.org/downloads/point/last-week/${encodeURIComponent(packageName)}`, {
|
|
680
|
+
signal: AbortSignal.timeout(timeout)
|
|
681
|
+
})
|
|
682
|
+
]);
|
|
683
|
+
if (!regRes.ok) return null;
|
|
684
|
+
const reg = await regRes.json();
|
|
685
|
+
const time = reg["time"] ?? {};
|
|
686
|
+
const created = time["created"] ? new Date(time["created"]) : null;
|
|
687
|
+
const modified = time["modified"] ? new Date(time["modified"]) : null;
|
|
688
|
+
const packageAge = created ? Math.floor((Date.now() - created.getTime()) / 864e5) : 0;
|
|
689
|
+
const maintainers = Array.isArray(reg["maintainers"]) ? reg["maintainers"] : [];
|
|
690
|
+
const latestVersion = reg["dist-tags"]?.["latest"] ?? "";
|
|
691
|
+
const versionData = reg["versions"]?.[latestVersion];
|
|
692
|
+
const deprecated = typeof versionData?.["deprecated"] === "string";
|
|
693
|
+
let weeklyDownloads = 0;
|
|
694
|
+
if (dlRes.ok) {
|
|
695
|
+
const dl = await dlRes.json();
|
|
696
|
+
weeklyDownloads = typeof dl["downloads"] === "number" ? dl["downloads"] : 0;
|
|
697
|
+
}
|
|
698
|
+
return {
|
|
699
|
+
weeklyDownloads,
|
|
700
|
+
lastPublish: modified?.toISOString() ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
701
|
+
packageAge,
|
|
702
|
+
maintainerCount: maintainers.length,
|
|
703
|
+
deprecated
|
|
704
|
+
};
|
|
705
|
+
} catch {
|
|
706
|
+
return null;
|
|
707
|
+
}
|
|
708
|
+
}
|
|
709
|
+
async function fetchVulnerabilities(packageName, version) {
|
|
710
|
+
try {
|
|
711
|
+
const res = await fetch("https://api.osv.dev/v1/query", {
|
|
712
|
+
method: "POST",
|
|
713
|
+
headers: { "Content-Type": "application/json" },
|
|
714
|
+
body: JSON.stringify({ package: { name: packageName, ecosystem: "npm" }, version }),
|
|
715
|
+
signal: AbortSignal.timeout(1e4)
|
|
716
|
+
});
|
|
717
|
+
if (!res.ok) return [];
|
|
718
|
+
const data = await res.json();
|
|
719
|
+
const vulns = Array.isArray(data["vulns"]) ? data["vulns"] : [];
|
|
720
|
+
return vulns.map((v) => {
|
|
721
|
+
const severity = v["database_specific"]?.["severity"];
|
|
722
|
+
const sev = typeof severity === "string" ? severity.toLowerCase() : "moderate";
|
|
723
|
+
const refs = Array.isArray(v["references"]) ? v["references"] : [];
|
|
724
|
+
return {
|
|
725
|
+
severity: ["low", "moderate", "high", "critical"].includes(sev) ? sev : "moderate",
|
|
726
|
+
title: typeof v["summary"] === "string" ? v["summary"] : typeof v["id"] === "string" ? v["id"] : "Unknown vulnerability",
|
|
727
|
+
url: typeof refs[0]?.["url"] === "string" ? refs[0]["url"] : void 0
|
|
728
|
+
};
|
|
729
|
+
});
|
|
730
|
+
} catch {
|
|
731
|
+
return [];
|
|
732
|
+
}
|
|
733
|
+
}
|
|
734
|
+
async function scanServer(name, entry) {
|
|
735
|
+
if (entry.source !== "npm") {
|
|
736
|
+
return {
|
|
737
|
+
server: name,
|
|
738
|
+
source: entry.source,
|
|
739
|
+
score: null,
|
|
740
|
+
riskLevel: "UNKNOWN",
|
|
741
|
+
vulnerabilities: [],
|
|
742
|
+
metadata: null
|
|
743
|
+
};
|
|
744
|
+
}
|
|
745
|
+
const cached = getCachedReport(name, entry.version);
|
|
746
|
+
if (cached) return cached;
|
|
747
|
+
const [metadata, vulnerabilities] = await Promise.all([
|
|
748
|
+
fetchNpmMetadata(name),
|
|
749
|
+
fetchVulnerabilities(name, entry.version)
|
|
750
|
+
]);
|
|
751
|
+
const { computeTrustScore: computeTrustScore2 } = await Promise.resolve().then(() => (init_trust_scorer(), trust_scorer_exports));
|
|
752
|
+
const { score, riskLevel } = computeTrustScore2(metadata, vulnerabilities);
|
|
753
|
+
const report = {
|
|
754
|
+
server: name,
|
|
755
|
+
source: "npm",
|
|
756
|
+
score,
|
|
757
|
+
riskLevel,
|
|
758
|
+
vulnerabilities,
|
|
759
|
+
metadata
|
|
760
|
+
};
|
|
761
|
+
cacheReport(name, entry.version, report);
|
|
762
|
+
return report;
|
|
763
|
+
}
|
|
764
|
+
async function scanAllServers(servers, concurrency = 3) {
|
|
765
|
+
const entries = Object.entries(servers);
|
|
766
|
+
const results = [];
|
|
767
|
+
const executing = /* @__PURE__ */ new Set();
|
|
768
|
+
for (const [name, entry] of entries) {
|
|
769
|
+
const p9 = scanServer(name, entry).then((r) => {
|
|
770
|
+
results.push(r);
|
|
771
|
+
executing.delete(p9);
|
|
772
|
+
});
|
|
773
|
+
executing.add(p9);
|
|
774
|
+
if (executing.size >= concurrency) await Promise.race(executing);
|
|
775
|
+
}
|
|
776
|
+
await Promise.all(executing);
|
|
777
|
+
return results;
|
|
778
|
+
}
|
|
779
|
+
|
|
780
|
+
// src/commands/audit.ts
|
|
781
|
+
function colorRisk(level, score) {
|
|
782
|
+
const label = score !== null ? `${score}/100 (${level})` : level;
|
|
783
|
+
if (level === "LOW") return import_picocolors.default.green(label);
|
|
784
|
+
if (level === "MEDIUM") return import_picocolors.default.yellow(label);
|
|
785
|
+
if (level === "HIGH") return import_picocolors.default.red(label);
|
|
786
|
+
if (level === "CRITICAL") return import_picocolors.default.bold(import_picocolors.default.red(label));
|
|
787
|
+
return import_picocolors.default.dim(label);
|
|
788
|
+
}
|
|
789
|
+
function daysAgo(isoDate) {
|
|
790
|
+
const days = Math.floor((Date.now() - new Date(isoDate).getTime()) / 864e5);
|
|
791
|
+
if (days === 0) return "today";
|
|
792
|
+
if (days === 1) return "1 day ago";
|
|
793
|
+
return `${days} days ago`;
|
|
794
|
+
}
|
|
795
|
+
function countVulns(vulns) {
|
|
796
|
+
const c = { critical: 0, high: 0, moderate: 0, low: 0 };
|
|
797
|
+
for (const v of vulns) c[v.severity]++;
|
|
798
|
+
if (vulns.length === 0) return import_picocolors.default.green("none");
|
|
799
|
+
const parts = [];
|
|
800
|
+
if (c.critical) parts.push(import_picocolors.default.bold(import_picocolors.default.red(`${c.critical} critical`)));
|
|
801
|
+
if (c.high) parts.push(import_picocolors.default.red(`${c.high} high`));
|
|
802
|
+
if (c.moderate) parts.push(import_picocolors.default.yellow(`${c.moderate} moderate`));
|
|
803
|
+
if (c.low) parts.push(import_picocolors.default.dim(`${c.low} low`));
|
|
804
|
+
return parts.join(", ");
|
|
805
|
+
}
|
|
806
|
+
function printReport(report) {
|
|
807
|
+
const riskColored = colorRisk(report.riskLevel, report.score);
|
|
808
|
+
const icon = report.riskLevel === "LOW" ? import_picocolors.default.green("\u25CF") : report.riskLevel === "MEDIUM" ? import_picocolors.default.yellow("\u25CF") : report.riskLevel === "UNKNOWN" ? import_picocolors.default.dim("\u25CB") : import_picocolors.default.red("\u25CF");
|
|
809
|
+
console.log(` ${icon} ${import_picocolors.default.bold(report.server)} Score: ${riskColored}`);
|
|
810
|
+
if (report.source !== "npm") {
|
|
811
|
+
console.log(` ${import_picocolors.default.dim("Non-npm source \u2014 security data unavailable")}`);
|
|
812
|
+
console.log();
|
|
813
|
+
return;
|
|
814
|
+
}
|
|
815
|
+
if (report.metadata) {
|
|
816
|
+
const { weeklyDownloads, packageAge, lastPublish, maintainerCount, deprecated } = report.metadata;
|
|
817
|
+
const dlStr = weeklyDownloads.toLocaleString();
|
|
818
|
+
console.log(
|
|
819
|
+
` ${import_picocolors.default.dim("Downloads:")} ${dlStr}/week ${import_picocolors.default.dim("|")} ${import_picocolors.default.dim("Age:")} ${packageAge}d ${import_picocolors.default.dim("|")} ${import_picocolors.default.dim("Last publish:")} ${daysAgo(lastPublish)} ${import_picocolors.default.dim("|")} ${import_picocolors.default.dim("Maintainers:")} ${maintainerCount}` + (deprecated ? import_picocolors.default.red(" [DEPRECATED]") : "")
|
|
820
|
+
);
|
|
821
|
+
}
|
|
822
|
+
console.log(` ${import_picocolors.default.dim("Vulnerabilities:")} ${countVulns(report.vulnerabilities)}`);
|
|
823
|
+
if (report.vulnerabilities.length > 0) {
|
|
824
|
+
for (const v of report.vulnerabilities) {
|
|
825
|
+
const sevColor = v.severity === "critical" || v.severity === "high" ? import_picocolors.default.red : import_picocolors.default.yellow;
|
|
826
|
+
const url = v.url ? import_picocolors.default.dim(` ${v.url}`) : "";
|
|
827
|
+
console.log(` ${sevColor("\u25B8")} [${v.severity}] ${v.title}${url}`);
|
|
828
|
+
}
|
|
829
|
+
}
|
|
830
|
+
console.log();
|
|
831
|
+
}
|
|
832
|
+
var audit_default = (0, import_citty.defineCommand)({
|
|
833
|
+
meta: {
|
|
834
|
+
name: "audit",
|
|
835
|
+
description: "Scan installed MCP servers for security vulnerabilities and trust scores"
|
|
836
|
+
},
|
|
837
|
+
args: {
|
|
838
|
+
server: {
|
|
839
|
+
type: "positional",
|
|
840
|
+
description: "Specific server to audit (omit to audit all)",
|
|
841
|
+
required: false
|
|
842
|
+
},
|
|
843
|
+
json: {
|
|
844
|
+
type: "boolean",
|
|
845
|
+
description: "Output results as JSON",
|
|
846
|
+
default: false
|
|
847
|
+
},
|
|
848
|
+
fix: {
|
|
849
|
+
type: "boolean",
|
|
850
|
+
description: "Show available fix versions for vulnerable packages",
|
|
851
|
+
default: false
|
|
852
|
+
}
|
|
853
|
+
},
|
|
854
|
+
async run({ args }) {
|
|
855
|
+
const lockfile = readLockfile();
|
|
856
|
+
const { servers } = lockfile;
|
|
857
|
+
if (Object.keys(servers).length === 0) {
|
|
858
|
+
console.log(import_picocolors.default.dim("\n No MCP servers installed. Run mcpman install <server> to get started.\n"));
|
|
859
|
+
return;
|
|
860
|
+
}
|
|
861
|
+
const targets = {};
|
|
862
|
+
if (args.server) {
|
|
863
|
+
if (!servers[args.server]) {
|
|
864
|
+
console.error(import_picocolors.default.red(`
|
|
865
|
+
Server "${args.server}" not found in lockfile.
|
|
866
|
+
`));
|
|
867
|
+
process.exit(1);
|
|
868
|
+
}
|
|
869
|
+
targets[args.server] = servers[args.server];
|
|
870
|
+
} else {
|
|
871
|
+
Object.assign(targets, servers);
|
|
872
|
+
}
|
|
873
|
+
const spinner5 = (0, import_nanospinner.createSpinner)(`Scanning ${Object.keys(targets).length} server(s)...`).start();
|
|
874
|
+
let reports;
|
|
875
|
+
try {
|
|
876
|
+
reports = args.server ? [await scanServer(args.server, targets[args.server])] : await scanAllServers(targets);
|
|
877
|
+
} catch (err) {
|
|
878
|
+
spinner5.error({ text: "Scan failed" });
|
|
879
|
+
console.error(import_picocolors.default.red(String(err)));
|
|
880
|
+
process.exit(1);
|
|
881
|
+
}
|
|
882
|
+
spinner5.success({ text: `Scanned ${reports.length} server(s)` });
|
|
883
|
+
if (args.json) {
|
|
884
|
+
console.log(JSON.stringify(reports, null, 2));
|
|
885
|
+
return;
|
|
886
|
+
}
|
|
887
|
+
console.log(import_picocolors.default.bold("\n mcpman audit\n"));
|
|
888
|
+
console.log(import_picocolors.default.dim(" " + "\u2500".repeat(60)));
|
|
889
|
+
for (const report of reports) {
|
|
890
|
+
printReport(report);
|
|
891
|
+
}
|
|
892
|
+
console.log(import_picocolors.default.dim(" " + "\u2500".repeat(60)));
|
|
893
|
+
const withIssues = reports.filter(
|
|
894
|
+
(r) => r.riskLevel !== "LOW" && r.riskLevel !== "UNKNOWN"
|
|
895
|
+
);
|
|
896
|
+
const npmReports = reports.filter((r) => r.source === "npm");
|
|
897
|
+
const parts = [];
|
|
898
|
+
parts.push(`${reports.length} server(s) scanned`);
|
|
899
|
+
if (npmReports.length < reports.length) {
|
|
900
|
+
parts.push(import_picocolors.default.dim(`${reports.length - npmReports.length} non-npm (unverified)`));
|
|
901
|
+
}
|
|
902
|
+
if (withIssues.length > 0) {
|
|
903
|
+
parts.push(import_picocolors.default.yellow(`${withIssues.length} with issues`));
|
|
904
|
+
} else {
|
|
905
|
+
parts.push(import_picocolors.default.green("all clear"));
|
|
906
|
+
}
|
|
907
|
+
console.log(`
|
|
908
|
+
Summary: ${parts.join(" | ")}
|
|
909
|
+
`);
|
|
910
|
+
if (args.fix) {
|
|
911
|
+
const withVulns = reports.filter((r) => r.vulnerabilities.length > 0);
|
|
912
|
+
if (withVulns.length > 0) {
|
|
913
|
+
console.log(import_picocolors.default.bold(" Fix suggestions:"));
|
|
914
|
+
for (const r of withVulns) {
|
|
915
|
+
console.log(` ${import_picocolors.default.cyan("\u2192")} Run ${import_picocolors.default.cyan(`mcpman install ${r.server}@latest`)} to update`);
|
|
916
|
+
}
|
|
917
|
+
console.log();
|
|
918
|
+
}
|
|
919
|
+
}
|
|
920
|
+
}
|
|
921
|
+
});
|
|
922
|
+
|
|
923
|
+
// src/commands/doctor.ts
|
|
924
|
+
init_cjs_shims();
|
|
925
|
+
var import_citty2 = require("citty");
|
|
926
|
+
var import_picocolors2 = __toESM(require("picocolors"), 1);
|
|
342
927
|
|
|
343
928
|
// src/core/server-inventory.ts
|
|
344
929
|
init_cjs_shims();
|
|
@@ -609,12 +1194,12 @@ async function quickHealthProbe(config, timeoutMs = 3e3) {
|
|
|
609
1194
|
|
|
610
1195
|
// src/commands/doctor.ts
|
|
611
1196
|
var CHECK_ICON = {
|
|
612
|
-
pass:
|
|
613
|
-
fail:
|
|
614
|
-
skip:
|
|
615
|
-
warn:
|
|
1197
|
+
pass: import_picocolors2.default.green("\u2713"),
|
|
1198
|
+
fail: import_picocolors2.default.red("\u2717"),
|
|
1199
|
+
skip: import_picocolors2.default.dim("-"),
|
|
1200
|
+
warn: import_picocolors2.default.yellow("\u26A0")
|
|
616
1201
|
};
|
|
617
|
-
var doctor_default = (0,
|
|
1202
|
+
var doctor_default = (0, import_citty2.defineCommand)({
|
|
618
1203
|
meta: {
|
|
619
1204
|
name: "doctor",
|
|
620
1205
|
description: "Check MCP server health and configuration"
|
|
@@ -627,10 +1212,10 @@ var doctor_default = (0, import_citty.defineCommand)({
|
|
|
627
1212
|
}
|
|
628
1213
|
},
|
|
629
1214
|
async run({ args }) {
|
|
630
|
-
console.log(
|
|
1215
|
+
console.log(import_picocolors2.default.bold("\n mcpman doctor\n"));
|
|
631
1216
|
const servers = await getInstalledServers();
|
|
632
1217
|
if (servers.length === 0) {
|
|
633
|
-
console.log(
|
|
1218
|
+
console.log(import_picocolors2.default.dim(" No MCP servers installed. Run mcpman install <server> to get started."));
|
|
634
1219
|
return;
|
|
635
1220
|
}
|
|
636
1221
|
const tasks = servers.map((s) => () => checkServerHealth(s.name, s.config));
|
|
@@ -642,14 +1227,14 @@ var doctor_default = (0, import_citty.defineCommand)({
|
|
|
642
1227
|
if (result.status === "healthy") passed++;
|
|
643
1228
|
else failed++;
|
|
644
1229
|
}
|
|
645
|
-
console.log(
|
|
1230
|
+
console.log(import_picocolors2.default.dim(" " + "\u2500".repeat(50)));
|
|
646
1231
|
const parts = [];
|
|
647
|
-
if (passed > 0) parts.push(
|
|
648
|
-
if (failed > 0) parts.push(
|
|
1232
|
+
if (passed > 0) parts.push(import_picocolors2.default.green(`${passed} healthy`));
|
|
1233
|
+
if (failed > 0) parts.push(import_picocolors2.default.red(`${failed} unhealthy`));
|
|
649
1234
|
console.log(` Summary: ${parts.join(", ")}`);
|
|
650
1235
|
if (failed > 0) {
|
|
651
1236
|
if (!args.fix) {
|
|
652
|
-
console.log(
|
|
1237
|
+
console.log(import_picocolors2.default.dim(` Run ${import_picocolors2.default.cyan("mcpman doctor --fix")} for fix suggestions.
|
|
653
1238
|
`));
|
|
654
1239
|
}
|
|
655
1240
|
process.exit(1);
|
|
@@ -658,13 +1243,13 @@ var doctor_default = (0, import_citty.defineCommand)({
|
|
|
658
1243
|
}
|
|
659
1244
|
});
|
|
660
1245
|
function printServerResult(result, showFix) {
|
|
661
|
-
const icon = result.status === "healthy" ?
|
|
662
|
-
console.log(` ${icon} ${
|
|
1246
|
+
const icon = result.status === "healthy" ? import_picocolors2.default.green("\u25CF") : import_picocolors2.default.red("\u25CF");
|
|
1247
|
+
console.log(` ${icon} ${import_picocolors2.default.bold(result.serverName)}`);
|
|
663
1248
|
for (const check of result.checks) {
|
|
664
1249
|
const checkIcon = check.skipped ? CHECK_ICON.skip : check.passed ? CHECK_ICON.pass : CHECK_ICON.fail;
|
|
665
1250
|
console.log(` ${checkIcon} ${check.name}: ${check.message}`);
|
|
666
1251
|
if (showFix && !check.passed && !check.skipped && check.fix) {
|
|
667
|
-
console.log(` ${
|
|
1252
|
+
console.log(` ${import_picocolors2.default.yellow("\u2192")} Fix: ${import_picocolors2.default.cyan(check.fix)}`);
|
|
668
1253
|
}
|
|
669
1254
|
}
|
|
670
1255
|
console.log();
|
|
@@ -673,11 +1258,11 @@ async function runParallel(tasks, concurrency) {
|
|
|
673
1258
|
const results = [];
|
|
674
1259
|
const executing = /* @__PURE__ */ new Set();
|
|
675
1260
|
for (const task of tasks) {
|
|
676
|
-
const
|
|
1261
|
+
const p9 = task().then((r) => {
|
|
677
1262
|
results.push(r);
|
|
678
|
-
executing.delete(
|
|
1263
|
+
executing.delete(p9);
|
|
679
1264
|
});
|
|
680
|
-
executing.add(
|
|
1265
|
+
executing.add(p9);
|
|
681
1266
|
if (executing.size >= concurrency) {
|
|
682
1267
|
await Promise.race(executing);
|
|
683
1268
|
}
|
|
@@ -688,72 +1273,9 @@ async function runParallel(tasks, concurrency) {
|
|
|
688
1273
|
|
|
689
1274
|
// src/commands/init.ts
|
|
690
1275
|
init_cjs_shims();
|
|
691
|
-
var
|
|
1276
|
+
var import_citty3 = require("citty");
|
|
692
1277
|
var p = __toESM(require("@clack/prompts"), 1);
|
|
693
|
-
var
|
|
694
|
-
|
|
695
|
-
// src/core/lockfile.ts
|
|
696
|
-
init_cjs_shims();
|
|
697
|
-
var import_node_fs2 = __toESM(require("fs"), 1);
|
|
698
|
-
var import_node_path3 = __toESM(require("path"), 1);
|
|
699
|
-
var import_node_os2 = __toESM(require("os"), 1);
|
|
700
|
-
var LOCKFILE_NAME = "mcpman.lock";
|
|
701
|
-
function findLockfile() {
|
|
702
|
-
let dir = process.cwd();
|
|
703
|
-
while (true) {
|
|
704
|
-
const candidate = import_node_path3.default.join(dir, LOCKFILE_NAME);
|
|
705
|
-
if (import_node_fs2.default.existsSync(candidate)) return candidate;
|
|
706
|
-
const parent = import_node_path3.default.dirname(dir);
|
|
707
|
-
if (parent === dir) break;
|
|
708
|
-
dir = parent;
|
|
709
|
-
}
|
|
710
|
-
return null;
|
|
711
|
-
}
|
|
712
|
-
function getGlobalLockfilePath() {
|
|
713
|
-
return import_node_path3.default.join(import_node_os2.default.homedir(), ".mcpman", LOCKFILE_NAME);
|
|
714
|
-
}
|
|
715
|
-
function resolveLockfilePath() {
|
|
716
|
-
return findLockfile() ?? getGlobalLockfilePath();
|
|
717
|
-
}
|
|
718
|
-
function readLockfile(filePath) {
|
|
719
|
-
const target = filePath ?? resolveLockfilePath();
|
|
720
|
-
if (!import_node_fs2.default.existsSync(target)) {
|
|
721
|
-
return { lockfileVersion: 1, servers: {} };
|
|
722
|
-
}
|
|
723
|
-
try {
|
|
724
|
-
const raw = import_node_fs2.default.readFileSync(target, "utf-8");
|
|
725
|
-
return JSON.parse(raw);
|
|
726
|
-
} catch {
|
|
727
|
-
return { lockfileVersion: 1, servers: {} };
|
|
728
|
-
}
|
|
729
|
-
}
|
|
730
|
-
function serialize(data) {
|
|
731
|
-
const sorted = {
|
|
732
|
-
lockfileVersion: data.lockfileVersion,
|
|
733
|
-
servers: Object.fromEntries(
|
|
734
|
-
Object.entries(data.servers).sort(([a], [b]) => a.localeCompare(b))
|
|
735
|
-
)
|
|
736
|
-
};
|
|
737
|
-
return JSON.stringify(sorted, null, 2) + "\n";
|
|
738
|
-
}
|
|
739
|
-
function writeLockfile(data, filePath) {
|
|
740
|
-
const target = filePath ?? resolveLockfilePath();
|
|
741
|
-
const dir = import_node_path3.default.dirname(target);
|
|
742
|
-
if (!import_node_fs2.default.existsSync(dir)) {
|
|
743
|
-
import_node_fs2.default.mkdirSync(dir, { recursive: true });
|
|
744
|
-
}
|
|
745
|
-
const tmp = `${target}.tmp`;
|
|
746
|
-
import_node_fs2.default.writeFileSync(tmp, serialize(data), "utf-8");
|
|
747
|
-
import_node_fs2.default.renameSync(tmp, target);
|
|
748
|
-
}
|
|
749
|
-
function addEntry(name, entry, filePath) {
|
|
750
|
-
const data = readLockfile(filePath);
|
|
751
|
-
data.servers[name] = entry;
|
|
752
|
-
writeLockfile(data, filePath);
|
|
753
|
-
}
|
|
754
|
-
function createEmptyLockfile(filePath) {
|
|
755
|
-
writeLockfile({ lockfileVersion: 1, servers: {} }, filePath);
|
|
756
|
-
}
|
|
1278
|
+
var import_node_path5 = __toESM(require("path"), 1);
|
|
757
1279
|
|
|
758
1280
|
// src/core/registry.ts
|
|
759
1281
|
init_cjs_shims();
|
|
@@ -865,22 +1387,34 @@ async function resolveFromGitHub(githubUrl) {
|
|
|
865
1387
|
}
|
|
866
1388
|
|
|
867
1389
|
// src/commands/init.ts
|
|
868
|
-
var init_default = (0,
|
|
1390
|
+
var init_default = (0, import_citty3.defineCommand)({
|
|
869
1391
|
meta: {
|
|
870
1392
|
name: "init",
|
|
871
1393
|
description: "Initialize mcpman.lock in the current project"
|
|
872
1394
|
},
|
|
873
|
-
args: {
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
1395
|
+
args: {
|
|
1396
|
+
yes: {
|
|
1397
|
+
type: "boolean",
|
|
1398
|
+
alias: "y",
|
|
1399
|
+
description: "Auto-import all servers without prompting",
|
|
1400
|
+
default: false
|
|
1401
|
+
}
|
|
1402
|
+
},
|
|
1403
|
+
async run({ args }) {
|
|
1404
|
+
const nonInteractive = args.yes || !process.stdout.isTTY;
|
|
1405
|
+
p.intro("mcpman init");
|
|
1406
|
+
const targetPath = import_node_path5.default.join(process.cwd(), LOCKFILE_NAME);
|
|
1407
|
+
const existing = findLockfile();
|
|
878
1408
|
if (existing) {
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
p.
|
|
883
|
-
|
|
1409
|
+
if (nonInteractive) {
|
|
1410
|
+
p.log.warn(`Lockfile already exists: ${existing} \u2014 overwriting (non-interactive).`);
|
|
1411
|
+
} else {
|
|
1412
|
+
p.log.warn(`Lockfile already exists: ${existing}`);
|
|
1413
|
+
const overwrite = await p.confirm({ message: "Overwrite?" });
|
|
1414
|
+
if (p.isCancel(overwrite) || !overwrite) {
|
|
1415
|
+
p.outro("Cancelled.");
|
|
1416
|
+
return;
|
|
1417
|
+
}
|
|
884
1418
|
}
|
|
885
1419
|
}
|
|
886
1420
|
let clients = [];
|
|
@@ -906,20 +1440,26 @@ var init_default = (0, import_citty2.defineCommand)({
|
|
|
906
1440
|
p.outro(`Created ${LOCKFILE_NAME} \u2014 add it to version control!`);
|
|
907
1441
|
return;
|
|
908
1442
|
}
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
1443
|
+
let selected;
|
|
1444
|
+
if (nonInteractive) {
|
|
1445
|
+
selected = clientServers.map((cs) => cs.client.type);
|
|
1446
|
+
p.log.info(`Non-interactive mode: importing all ${clientServers.length} client(s).`);
|
|
1447
|
+
} else {
|
|
1448
|
+
const options = clientServers.map((cs) => ({
|
|
1449
|
+
value: cs.client.type,
|
|
1450
|
+
label: `${cs.client.displayName} (${Object.keys(cs.servers).length} servers)`
|
|
1451
|
+
}));
|
|
1452
|
+
const toImport = await p.multiselect({
|
|
1453
|
+
message: "Import existing servers into lockfile?",
|
|
1454
|
+
options,
|
|
1455
|
+
required: false
|
|
1456
|
+
});
|
|
1457
|
+
if (p.isCancel(toImport)) {
|
|
1458
|
+
p.outro(`Created empty ${LOCKFILE_NAME}`);
|
|
1459
|
+
return;
|
|
1460
|
+
}
|
|
1461
|
+
selected = toImport;
|
|
921
1462
|
}
|
|
922
|
-
const selected = toImport;
|
|
923
1463
|
let importCount = 0;
|
|
924
1464
|
for (const cs of clientServers) {
|
|
925
1465
|
if (!selected.includes(cs.client.type)) continue;
|
|
@@ -949,7 +1489,7 @@ var init_default = (0, import_citty2.defineCommand)({
|
|
|
949
1489
|
|
|
950
1490
|
// src/commands/install.ts
|
|
951
1491
|
init_cjs_shims();
|
|
952
|
-
var
|
|
1492
|
+
var import_citty4 = require("citty");
|
|
953
1493
|
|
|
954
1494
|
// src/core/installer.ts
|
|
955
1495
|
init_cjs_shims();
|
|
@@ -1001,17 +1541,17 @@ async function loadClients() {
|
|
|
1001
1541
|
}
|
|
1002
1542
|
async function installServer(input, options = {}) {
|
|
1003
1543
|
p2.intro("mcpman install");
|
|
1004
|
-
const
|
|
1005
|
-
|
|
1544
|
+
const spinner5 = p2.spinner();
|
|
1545
|
+
spinner5.start("Resolving server...");
|
|
1006
1546
|
let metadata;
|
|
1007
1547
|
try {
|
|
1008
1548
|
metadata = await resolveServer(input);
|
|
1009
1549
|
} catch (err) {
|
|
1010
|
-
|
|
1550
|
+
spinner5.stop("Resolution failed");
|
|
1011
1551
|
p2.log.error(err instanceof Error ? err.message : String(err));
|
|
1012
1552
|
process.exit(1);
|
|
1013
1553
|
}
|
|
1014
|
-
|
|
1554
|
+
spinner5.stop(`Found: ${metadata.name}@${metadata.version}`);
|
|
1015
1555
|
const clients = await loadClients();
|
|
1016
1556
|
if (clients.length === 0) {
|
|
1017
1557
|
p2.log.warn("No supported AI clients detected on this machine.");
|
|
@@ -1065,18 +1605,18 @@ async function installServer(input, options = {}) {
|
|
|
1065
1605
|
args: metadata.args,
|
|
1066
1606
|
...Object.keys(collectedEnv).length > 0 ? { env: collectedEnv } : {}
|
|
1067
1607
|
};
|
|
1068
|
-
|
|
1608
|
+
spinner5.start("Writing config...");
|
|
1069
1609
|
const clientTypes = [];
|
|
1070
1610
|
for (const client of selectedClients) {
|
|
1071
1611
|
try {
|
|
1072
1612
|
await client.addServer(metadata.name, entry);
|
|
1073
1613
|
clientTypes.push(client.type);
|
|
1074
1614
|
} catch (err) {
|
|
1075
|
-
|
|
1615
|
+
spinner5.stop("Partial failure");
|
|
1076
1616
|
p2.log.warn(`Failed to write to ${client.displayName}: ${err instanceof Error ? err.message : String(err)}`);
|
|
1077
1617
|
}
|
|
1078
1618
|
}
|
|
1079
|
-
|
|
1619
|
+
spinner5.stop("Config written");
|
|
1080
1620
|
const source = detectSource(input);
|
|
1081
1621
|
const integrity = computeIntegrity(metadata.resolved);
|
|
1082
1622
|
addEntry(metadata.name, {
|
|
@@ -1098,7 +1638,7 @@ async function installServer(input, options = {}) {
|
|
|
1098
1638
|
|
|
1099
1639
|
// src/utils/logger.ts
|
|
1100
1640
|
init_cjs_shims();
|
|
1101
|
-
var
|
|
1641
|
+
var import_picocolors3 = __toESM(require("picocolors"), 1);
|
|
1102
1642
|
var noColor = process.env.NO_COLOR !== void 0 || process.argv.includes("--no-color");
|
|
1103
1643
|
var isVerbose = process.argv.includes("--verbose");
|
|
1104
1644
|
var isJson = process.argv.includes("--json");
|
|
@@ -1107,11 +1647,11 @@ function colorize(fn, text2) {
|
|
|
1107
1647
|
}
|
|
1108
1648
|
function info(message) {
|
|
1109
1649
|
if (isJson) return;
|
|
1110
|
-
console.log(`${colorize(
|
|
1650
|
+
console.log(`${colorize(import_picocolors3.default.cyan, "i")} ${message}`);
|
|
1111
1651
|
}
|
|
1112
1652
|
function error(message) {
|
|
1113
1653
|
if (isJson) return;
|
|
1114
|
-
console.error(`${colorize(
|
|
1654
|
+
console.error(`${colorize(import_picocolors3.default.red, "\u2717")} ${message}`);
|
|
1115
1655
|
}
|
|
1116
1656
|
function json(data) {
|
|
1117
1657
|
console.log(JSON.stringify(data, null, 2));
|
|
@@ -1119,7 +1659,7 @@ function json(data) {
|
|
|
1119
1659
|
|
|
1120
1660
|
// src/commands/install.ts
|
|
1121
1661
|
var p3 = __toESM(require("@clack/prompts"), 1);
|
|
1122
|
-
var install_default = (0,
|
|
1662
|
+
var install_default = (0, import_citty4.defineCommand)({
|
|
1123
1663
|
meta: {
|
|
1124
1664
|
name: "install",
|
|
1125
1665
|
description: "Install an MCP server into one or more AI clients"
|
|
@@ -1182,14 +1722,14 @@ async function restoreFromLockfile() {
|
|
|
1182
1722
|
|
|
1183
1723
|
// src/commands/list.ts
|
|
1184
1724
|
init_cjs_shims();
|
|
1185
|
-
var
|
|
1186
|
-
var
|
|
1725
|
+
var import_citty5 = require("citty");
|
|
1726
|
+
var import_picocolors4 = __toESM(require("picocolors"), 1);
|
|
1187
1727
|
var STATUS_ICON = {
|
|
1188
|
-
healthy:
|
|
1189
|
-
unhealthy:
|
|
1190
|
-
unknown:
|
|
1728
|
+
healthy: import_picocolors4.default.green("\u25CF"),
|
|
1729
|
+
unhealthy: import_picocolors4.default.red("\u25CF"),
|
|
1730
|
+
unknown: import_picocolors4.default.dim("\u25CB")
|
|
1191
1731
|
};
|
|
1192
|
-
var list_default = (0,
|
|
1732
|
+
var list_default = (0, import_citty5.defineCommand)({
|
|
1193
1733
|
meta: {
|
|
1194
1734
|
name: "list",
|
|
1195
1735
|
description: "List installed MCP servers"
|
|
@@ -1209,13 +1749,13 @@ var list_default = (0, import_citty4.defineCommand)({
|
|
|
1209
1749
|
const servers = await getInstalledServers(args.client);
|
|
1210
1750
|
if (servers.length === 0) {
|
|
1211
1751
|
const filter = args.client ? ` for client "${args.client}"` : "";
|
|
1212
|
-
console.log(
|
|
1752
|
+
console.log(import_picocolors4.default.dim(`No MCP servers installed${filter}. Run ${import_picocolors4.default.cyan("mcpman install <server>")} to get started.`));
|
|
1213
1753
|
return;
|
|
1214
1754
|
}
|
|
1215
1755
|
const withStatus = await Promise.all(
|
|
1216
1756
|
servers.map(async (s) => ({
|
|
1217
1757
|
...s,
|
|
1218
|
-
status: await quickHealthProbe(s.config,
|
|
1758
|
+
status: await quickHealthProbe(s.config, 5e3)
|
|
1219
1759
|
}))
|
|
1220
1760
|
);
|
|
1221
1761
|
if (args.json) {
|
|
@@ -1233,8 +1773,8 @@ var list_default = (0, import_citty4.defineCommand)({
|
|
|
1233
1773
|
const nameWidth = Math.max(4, ...withStatus.map((s) => s.name.length));
|
|
1234
1774
|
const clientsWidth = Math.max(7, ...withStatus.map((s) => formatClients(s.clients).length));
|
|
1235
1775
|
const header = ` ${pad("NAME", nameWidth)} ${pad("CLIENT(S)", clientsWidth)} ${pad("COMMAND", 20)} STATUS`;
|
|
1236
|
-
console.log(
|
|
1237
|
-
console.log(
|
|
1776
|
+
console.log(import_picocolors4.default.dim(header));
|
|
1777
|
+
console.log(import_picocolors4.default.dim(` ${"-".repeat(nameWidth)} ${"-".repeat(clientsWidth)} ${"-".repeat(20)} ------`));
|
|
1238
1778
|
for (const s of withStatus) {
|
|
1239
1779
|
const icon = STATUS_ICON[s.status];
|
|
1240
1780
|
const clientsStr = formatClients(s.clients);
|
|
@@ -1242,7 +1782,7 @@ var list_default = (0, import_citty4.defineCommand)({
|
|
|
1242
1782
|
console.log(` ${pad(s.name, nameWidth)} ${pad(clientsStr, clientsWidth)} ${pad(cmdStr, 20)} ${icon} ${s.status}`);
|
|
1243
1783
|
}
|
|
1244
1784
|
const clientSet = new Set(withStatus.flatMap((s) => s.clients));
|
|
1245
|
-
console.log(
|
|
1785
|
+
console.log(import_picocolors4.default.dim(`
|
|
1246
1786
|
${withStatus.length} server${withStatus.length !== 1 ? "s" : ""} \xB7 ${clientSet.size} client${clientSet.size !== 1 ? "s" : ""}`));
|
|
1247
1787
|
}
|
|
1248
1788
|
});
|
|
@@ -1264,9 +1804,9 @@ function formatClients(clients) {
|
|
|
1264
1804
|
|
|
1265
1805
|
// src/commands/remove.ts
|
|
1266
1806
|
init_cjs_shims();
|
|
1267
|
-
var
|
|
1807
|
+
var import_citty6 = require("citty");
|
|
1268
1808
|
var p4 = __toESM(require("@clack/prompts"), 1);
|
|
1269
|
-
var
|
|
1809
|
+
var import_picocolors5 = __toESM(require("picocolors"), 1);
|
|
1270
1810
|
init_client_detector();
|
|
1271
1811
|
var CLIENT_DISPLAY2 = {
|
|
1272
1812
|
"claude-desktop": "Claude",
|
|
@@ -1277,7 +1817,7 @@ var CLIENT_DISPLAY2 = {
|
|
|
1277
1817
|
function clientDisplayName(type) {
|
|
1278
1818
|
return CLIENT_DISPLAY2[type] ?? type;
|
|
1279
1819
|
}
|
|
1280
|
-
var remove_default = (0,
|
|
1820
|
+
var remove_default = (0, import_citty6.defineCommand)({
|
|
1281
1821
|
meta: {
|
|
1282
1822
|
name: "remove",
|
|
1283
1823
|
description: "Remove an MCP server from one or more AI clients"
|
|
@@ -1304,7 +1844,7 @@ var remove_default = (0, import_citty5.defineCommand)({
|
|
|
1304
1844
|
}
|
|
1305
1845
|
},
|
|
1306
1846
|
async run({ args }) {
|
|
1307
|
-
p4.intro(
|
|
1847
|
+
p4.intro(import_picocolors5.default.bold("mcpman remove"));
|
|
1308
1848
|
const serverName = args.server;
|
|
1309
1849
|
const servers = await getInstalledServers();
|
|
1310
1850
|
const match = servers.find((s) => s.name === serverName);
|
|
@@ -1312,7 +1852,7 @@ var remove_default = (0, import_citty5.defineCommand)({
|
|
|
1312
1852
|
p4.log.warn(`Server "${serverName}" is not installed.`);
|
|
1313
1853
|
const similar = servers.filter((s) => s.name.includes(serverName) || serverName.includes(s.name));
|
|
1314
1854
|
if (similar.length > 0) {
|
|
1315
|
-
p4.log.info(`Did you mean: ${similar.map((s) =>
|
|
1855
|
+
p4.log.info(`Did you mean: ${similar.map((s) => import_picocolors5.default.cyan(s.name)).join(", ")}?`);
|
|
1316
1856
|
}
|
|
1317
1857
|
p4.outro("Nothing to remove.");
|
|
1318
1858
|
return;
|
|
@@ -1347,7 +1887,7 @@ var remove_default = (0, import_citty5.defineCommand)({
|
|
|
1347
1887
|
if (!args.yes) {
|
|
1348
1888
|
const clientNames = targetClients.map(clientDisplayName).join(", ");
|
|
1349
1889
|
const confirmed = await p4.confirm({
|
|
1350
|
-
message: `Remove ${
|
|
1890
|
+
message: `Remove ${import_picocolors5.default.cyan(serverName)} from ${import_picocolors5.default.yellow(clientNames)}?`
|
|
1351
1891
|
});
|
|
1352
1892
|
if (p4.isCancel(confirmed) || !confirmed) {
|
|
1353
1893
|
p4.outro("Cancelled.");
|
|
@@ -1372,17 +1912,697 @@ var remove_default = (0, import_citty5.defineCommand)({
|
|
|
1372
1912
|
}
|
|
1373
1913
|
if (errors.length > 0) {
|
|
1374
1914
|
for (const e of errors) p4.log.error(e);
|
|
1375
|
-
p4.outro(
|
|
1915
|
+
p4.outro(import_picocolors5.default.red("Completed with errors."));
|
|
1916
|
+
process.exit(1);
|
|
1917
|
+
}
|
|
1918
|
+
p4.outro(import_picocolors5.default.green(`Removed "${serverName}" successfully.`));
|
|
1919
|
+
}
|
|
1920
|
+
});
|
|
1921
|
+
|
|
1922
|
+
// src/commands/secrets.ts
|
|
1923
|
+
init_cjs_shims();
|
|
1924
|
+
var import_citty7 = require("citty");
|
|
1925
|
+
var import_picocolors6 = __toESM(require("picocolors"), 1);
|
|
1926
|
+
var p6 = __toESM(require("@clack/prompts"), 1);
|
|
1927
|
+
init_vault_service();
|
|
1928
|
+
function maskValue(value) {
|
|
1929
|
+
if (value.length <= 8) return "***";
|
|
1930
|
+
return `${value.slice(0, 4)}***${value.slice(-3)}`;
|
|
1931
|
+
}
|
|
1932
|
+
function parseKeyValue(input) {
|
|
1933
|
+
const idx = input.indexOf("=");
|
|
1934
|
+
if (idx <= 0) return null;
|
|
1935
|
+
return { key: input.slice(0, idx), value: input.slice(idx + 1) };
|
|
1936
|
+
}
|
|
1937
|
+
var setCommand = (0, import_citty7.defineCommand)({
|
|
1938
|
+
meta: { name: "set", description: "Store an encrypted secret for a server" },
|
|
1939
|
+
args: {
|
|
1940
|
+
server: {
|
|
1941
|
+
type: "positional",
|
|
1942
|
+
description: "Server name (e.g. @modelcontextprotocol/server-github)",
|
|
1943
|
+
required: true
|
|
1944
|
+
},
|
|
1945
|
+
keyvalue: {
|
|
1946
|
+
type: "positional",
|
|
1947
|
+
description: "KEY=VALUE pair to store",
|
|
1948
|
+
required: true
|
|
1949
|
+
}
|
|
1950
|
+
},
|
|
1951
|
+
async run({ args }) {
|
|
1952
|
+
const parsed = parseKeyValue(args.keyvalue);
|
|
1953
|
+
if (!parsed) {
|
|
1954
|
+
console.error(import_picocolors6.default.red("\u2717") + " Invalid format. Expected KEY=VALUE");
|
|
1955
|
+
process.exit(1);
|
|
1956
|
+
}
|
|
1957
|
+
p6.intro(import_picocolors6.default.cyan("mcpman secrets set"));
|
|
1958
|
+
const isNew = listSecrets(args.server).length === 0 || !listSecrets(args.server)[0]?.keys.includes(parsed.key);
|
|
1959
|
+
const vaultPath = (await Promise.resolve().then(() => (init_vault_service(), vault_service_exports))).getVaultPath();
|
|
1960
|
+
const vaultExists = (await import("fs")).existsSync(vaultPath);
|
|
1961
|
+
const password2 = await getMasterPassword(!vaultExists && isNew);
|
|
1962
|
+
const spin = p6.spinner();
|
|
1963
|
+
spin.start("Encrypting secret...");
|
|
1964
|
+
try {
|
|
1965
|
+
setSecret(args.server, parsed.key, parsed.value, password2);
|
|
1966
|
+
spin.stop(
|
|
1967
|
+
`${import_picocolors6.default.green("\u2713")} Stored ${import_picocolors6.default.bold(parsed.key)} for ${import_picocolors6.default.cyan(args.server)}`
|
|
1968
|
+
);
|
|
1969
|
+
} catch (err) {
|
|
1970
|
+
spin.stop(import_picocolors6.default.red("\u2717") + " Failed to store secret");
|
|
1971
|
+
console.error(import_picocolors6.default.dim(String(err)));
|
|
1972
|
+
process.exit(1);
|
|
1973
|
+
}
|
|
1974
|
+
p6.outro(import_picocolors6.default.dim("Secret encrypted and saved to vault."));
|
|
1975
|
+
}
|
|
1976
|
+
});
|
|
1977
|
+
var listCommand = (0, import_citty7.defineCommand)({
|
|
1978
|
+
meta: { name: "list", description: "List secret keys stored in the vault" },
|
|
1979
|
+
args: {
|
|
1980
|
+
server: {
|
|
1981
|
+
type: "positional",
|
|
1982
|
+
description: "Filter by server name (optional)",
|
|
1983
|
+
required: false
|
|
1984
|
+
}
|
|
1985
|
+
},
|
|
1986
|
+
async run({ args }) {
|
|
1987
|
+
const results = listSecrets(args.server || void 0);
|
|
1988
|
+
if (results.length === 0) {
|
|
1989
|
+
const filter = args.server ? ` for ${import_picocolors6.default.cyan(args.server)}` : "";
|
|
1990
|
+
console.log(import_picocolors6.default.dim(`No secrets stored${filter}.`));
|
|
1991
|
+
return;
|
|
1992
|
+
}
|
|
1993
|
+
console.log("");
|
|
1994
|
+
for (const { server, keys } of results) {
|
|
1995
|
+
console.log(import_picocolors6.default.bold(import_picocolors6.default.cyan(server)));
|
|
1996
|
+
for (const key of keys) {
|
|
1997
|
+
console.log(` ${import_picocolors6.default.green("\u25CF")} ${import_picocolors6.default.bold(key)} ${import_picocolors6.default.dim(maskValue("\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022"))}`);
|
|
1998
|
+
}
|
|
1999
|
+
console.log("");
|
|
2000
|
+
}
|
|
2001
|
+
const total = results.reduce((n, r) => n + r.keys.length, 0);
|
|
2002
|
+
console.log(import_picocolors6.default.dim(` ${total} secret${total !== 1 ? "s" : ""} in ${results.length} server${results.length !== 1 ? "s" : ""}`));
|
|
2003
|
+
}
|
|
2004
|
+
});
|
|
2005
|
+
var removeCommand = (0, import_citty7.defineCommand)({
|
|
2006
|
+
meta: { name: "remove", description: "Delete a secret from the vault" },
|
|
2007
|
+
args: {
|
|
2008
|
+
server: {
|
|
2009
|
+
type: "positional",
|
|
2010
|
+
description: "Server name",
|
|
2011
|
+
required: true
|
|
2012
|
+
},
|
|
2013
|
+
key: {
|
|
2014
|
+
type: "positional",
|
|
2015
|
+
description: "Secret key to remove",
|
|
2016
|
+
required: true
|
|
2017
|
+
}
|
|
2018
|
+
},
|
|
2019
|
+
async run({ args }) {
|
|
2020
|
+
const confirmed = await p6.confirm({
|
|
2021
|
+
message: `Remove ${import_picocolors6.default.bold(args.key)} from ${import_picocolors6.default.cyan(args.server)}?`,
|
|
2022
|
+
initialValue: false
|
|
2023
|
+
});
|
|
2024
|
+
if (p6.isCancel(confirmed) || !confirmed) {
|
|
2025
|
+
p6.cancel("Cancelled.");
|
|
2026
|
+
return;
|
|
2027
|
+
}
|
|
2028
|
+
try {
|
|
2029
|
+
removeSecret(args.server, args.key);
|
|
2030
|
+
console.log(`${import_picocolors6.default.green("\u2713")} Removed ${import_picocolors6.default.bold(args.key)} from ${import_picocolors6.default.cyan(args.server)}`);
|
|
2031
|
+
} catch (err) {
|
|
2032
|
+
console.error(import_picocolors6.default.red("\u2717") + " Failed to remove secret");
|
|
2033
|
+
console.error(import_picocolors6.default.dim(String(err)));
|
|
2034
|
+
process.exit(1);
|
|
2035
|
+
}
|
|
2036
|
+
}
|
|
2037
|
+
});
|
|
2038
|
+
var secrets_default = (0, import_citty7.defineCommand)({
|
|
2039
|
+
meta: {
|
|
2040
|
+
name: "secrets",
|
|
2041
|
+
description: "Manage encrypted secrets for MCP servers"
|
|
2042
|
+
},
|
|
2043
|
+
subCommands: {
|
|
2044
|
+
set: setCommand,
|
|
2045
|
+
list: listCommand,
|
|
2046
|
+
remove: removeCommand
|
|
2047
|
+
}
|
|
2048
|
+
});
|
|
2049
|
+
|
|
2050
|
+
// src/commands/sync.ts
|
|
2051
|
+
init_cjs_shims();
|
|
2052
|
+
var import_citty8 = require("citty");
|
|
2053
|
+
var p7 = __toESM(require("@clack/prompts"), 1);
|
|
2054
|
+
var import_picocolors7 = __toESM(require("picocolors"), 1);
|
|
2055
|
+
|
|
2056
|
+
// src/core/config-diff.ts
|
|
2057
|
+
init_cjs_shims();
|
|
2058
|
+
function reconstructServerEntry(lockEntry) {
|
|
2059
|
+
const entry = {
|
|
2060
|
+
command: lockEntry.command
|
|
2061
|
+
};
|
|
2062
|
+
if (lockEntry.args && lockEntry.args.length > 0) {
|
|
2063
|
+
entry.args = lockEntry.args;
|
|
2064
|
+
}
|
|
2065
|
+
if (lockEntry.envVars && lockEntry.envVars.length > 0) {
|
|
2066
|
+
entry.env = Object.fromEntries(lockEntry.envVars.map((k) => [k, ""]));
|
|
2067
|
+
}
|
|
2068
|
+
return entry;
|
|
2069
|
+
}
|
|
2070
|
+
function computeDiff(lockfile, clientConfigs) {
|
|
2071
|
+
const actions = [];
|
|
2072
|
+
for (const [server, lockEntry] of Object.entries(lockfile.servers)) {
|
|
2073
|
+
for (const client of lockEntry.clients) {
|
|
2074
|
+
const config = clientConfigs.get(client);
|
|
2075
|
+
if (!config) continue;
|
|
2076
|
+
if (server in config.servers) {
|
|
2077
|
+
actions.push({ server, client, action: "ok" });
|
|
2078
|
+
} else {
|
|
2079
|
+
actions.push({
|
|
2080
|
+
server,
|
|
2081
|
+
client,
|
|
2082
|
+
action: "add",
|
|
2083
|
+
entry: reconstructServerEntry(lockEntry)
|
|
2084
|
+
});
|
|
2085
|
+
}
|
|
2086
|
+
}
|
|
2087
|
+
}
|
|
2088
|
+
for (const [client, config] of clientConfigs) {
|
|
2089
|
+
for (const server of Object.keys(config.servers)) {
|
|
2090
|
+
if (!(server in lockfile.servers)) {
|
|
2091
|
+
actions.push({ server, client, action: "extra" });
|
|
2092
|
+
}
|
|
2093
|
+
}
|
|
2094
|
+
}
|
|
2095
|
+
return actions;
|
|
2096
|
+
}
|
|
2097
|
+
function computeDiffFromClient(sourceClient, clientConfigs) {
|
|
2098
|
+
const actions = [];
|
|
2099
|
+
const sourceConfig = clientConfigs.get(sourceClient);
|
|
2100
|
+
if (!sourceConfig) return [];
|
|
2101
|
+
for (const [client, config] of clientConfigs) {
|
|
2102
|
+
if (client === sourceClient) continue;
|
|
2103
|
+
for (const [server, entry] of Object.entries(sourceConfig.servers)) {
|
|
2104
|
+
if (server in config.servers) {
|
|
2105
|
+
actions.push({ server, client, action: "ok" });
|
|
2106
|
+
} else {
|
|
2107
|
+
actions.push({ server, client, action: "add", entry });
|
|
2108
|
+
}
|
|
2109
|
+
}
|
|
2110
|
+
for (const server of Object.keys(config.servers)) {
|
|
2111
|
+
if (!(server in sourceConfig.servers)) {
|
|
2112
|
+
actions.push({ server, client, action: "extra" });
|
|
2113
|
+
}
|
|
2114
|
+
}
|
|
2115
|
+
}
|
|
2116
|
+
return actions;
|
|
2117
|
+
}
|
|
2118
|
+
|
|
2119
|
+
// src/core/sync-engine.ts
|
|
2120
|
+
init_cjs_shims();
|
|
2121
|
+
init_client_detector();
|
|
2122
|
+
async function applySyncActions(actions, clients) {
|
|
2123
|
+
const result = { applied: 0, failed: 0, errors: [] };
|
|
2124
|
+
const addActions = actions.filter((a) => a.action === "add" && a.entry);
|
|
2125
|
+
for (const action of addActions) {
|
|
2126
|
+
const handler = clients.get(action.client);
|
|
2127
|
+
if (!handler || !action.entry) {
|
|
2128
|
+
result.failed++;
|
|
2129
|
+
result.errors.push({
|
|
2130
|
+
server: action.server,
|
|
2131
|
+
client: action.client,
|
|
2132
|
+
error: "No handler available for client"
|
|
2133
|
+
});
|
|
2134
|
+
continue;
|
|
2135
|
+
}
|
|
2136
|
+
try {
|
|
2137
|
+
await handler.addServer(action.server, action.entry);
|
|
2138
|
+
result.applied++;
|
|
2139
|
+
} catch (err) {
|
|
2140
|
+
result.failed++;
|
|
2141
|
+
result.errors.push({
|
|
2142
|
+
server: action.server,
|
|
2143
|
+
client: action.client,
|
|
2144
|
+
error: String(err)
|
|
2145
|
+
});
|
|
2146
|
+
}
|
|
2147
|
+
}
|
|
2148
|
+
return result;
|
|
2149
|
+
}
|
|
2150
|
+
async function getClientConfigs() {
|
|
2151
|
+
const configs = /* @__PURE__ */ new Map();
|
|
2152
|
+
const handlers = /* @__PURE__ */ new Map();
|
|
2153
|
+
let installedClients;
|
|
2154
|
+
try {
|
|
2155
|
+
installedClients = await getInstalledClients();
|
|
2156
|
+
} catch {
|
|
2157
|
+
return { configs, handlers };
|
|
2158
|
+
}
|
|
2159
|
+
await Promise.all(
|
|
2160
|
+
installedClients.map(async (handler) => {
|
|
2161
|
+
try {
|
|
2162
|
+
const config = await handler.readConfig();
|
|
2163
|
+
configs.set(handler.type, config);
|
|
2164
|
+
handlers.set(handler.type, handler);
|
|
2165
|
+
} catch (err) {
|
|
2166
|
+
console.warn(`[mcpman] Warning: could not read config for ${handler.displayName}: ${String(err)}`);
|
|
2167
|
+
}
|
|
2168
|
+
})
|
|
2169
|
+
);
|
|
2170
|
+
return { configs, handlers };
|
|
2171
|
+
}
|
|
2172
|
+
|
|
2173
|
+
// src/commands/sync.ts
|
|
2174
|
+
var VALID_CLIENTS = ["claude-desktop", "cursor", "vscode", "windsurf"];
|
|
2175
|
+
var CLIENT_DISPLAY3 = {
|
|
2176
|
+
"claude-desktop": "Claude Desktop",
|
|
2177
|
+
cursor: "Cursor",
|
|
2178
|
+
vscode: "VS Code",
|
|
2179
|
+
windsurf: "Windsurf"
|
|
2180
|
+
};
|
|
2181
|
+
var sync_default = (0, import_citty8.defineCommand)({
|
|
2182
|
+
meta: {
|
|
2183
|
+
name: "sync",
|
|
2184
|
+
description: "Sync MCP server configs across all detected AI clients"
|
|
2185
|
+
},
|
|
2186
|
+
args: {
|
|
2187
|
+
"dry-run": {
|
|
2188
|
+
type: "boolean",
|
|
2189
|
+
description: "Preview changes without applying them",
|
|
2190
|
+
default: false
|
|
2191
|
+
},
|
|
2192
|
+
source: {
|
|
2193
|
+
type: "string",
|
|
2194
|
+
description: "Use a specific client as source of truth (claude-desktop, cursor, vscode, windsurf)"
|
|
2195
|
+
},
|
|
2196
|
+
yes: {
|
|
2197
|
+
type: "boolean",
|
|
2198
|
+
description: "Skip confirmation prompt",
|
|
2199
|
+
default: false
|
|
2200
|
+
}
|
|
2201
|
+
},
|
|
2202
|
+
async run({ args }) {
|
|
2203
|
+
p7.intro(`${import_picocolors7.default.cyan("mcpman sync")}`);
|
|
2204
|
+
const sourceClient = args.source;
|
|
2205
|
+
if (sourceClient && !VALID_CLIENTS.includes(sourceClient)) {
|
|
2206
|
+
p7.log.error(`Invalid --source "${sourceClient}". Must be one of: ${VALID_CLIENTS.join(", ")}`);
|
|
2207
|
+
process.exit(1);
|
|
2208
|
+
}
|
|
2209
|
+
const spinner5 = p7.spinner();
|
|
2210
|
+
spinner5.start("Detecting clients and reading configs...");
|
|
2211
|
+
const { configs, handlers } = await getClientConfigs();
|
|
2212
|
+
spinner5.stop(`Found ${configs.size} client(s)`);
|
|
2213
|
+
if (configs.size === 0) {
|
|
2214
|
+
p7.log.warn("No AI clients detected. Install Claude Desktop, Cursor, VS Code, or Windsurf first.");
|
|
2215
|
+
process.exit(0);
|
|
2216
|
+
}
|
|
2217
|
+
let actions;
|
|
2218
|
+
if (sourceClient) {
|
|
2219
|
+
if (!configs.has(sourceClient)) {
|
|
2220
|
+
p7.log.error(`Source client "${sourceClient}" is not detected or its config is unreadable.`);
|
|
2221
|
+
process.exit(1);
|
|
2222
|
+
}
|
|
2223
|
+
p7.log.info(`Using ${CLIENT_DISPLAY3[sourceClient]} as source of truth`);
|
|
2224
|
+
actions = computeDiffFromClient(sourceClient, configs);
|
|
2225
|
+
} else {
|
|
2226
|
+
const lockfile = readLockfile();
|
|
2227
|
+
actions = computeDiff(lockfile, configs);
|
|
2228
|
+
}
|
|
2229
|
+
printDiffTable(actions);
|
|
2230
|
+
const addCount = actions.filter((a) => a.action === "add").length;
|
|
2231
|
+
const extraCount = actions.filter((a) => a.action === "extra").length;
|
|
2232
|
+
if (addCount === 0 && extraCount === 0) {
|
|
2233
|
+
p7.outro(import_picocolors7.default.green("All clients are in sync."));
|
|
2234
|
+
process.exit(0);
|
|
2235
|
+
}
|
|
2236
|
+
const parts = [];
|
|
2237
|
+
if (addCount > 0) parts.push(import_picocolors7.default.green(`${addCount} to add`));
|
|
2238
|
+
if (extraCount > 0) parts.push(import_picocolors7.default.yellow(`${extraCount} extra (informational)`));
|
|
2239
|
+
p7.log.info(parts.join(" \xB7 "));
|
|
2240
|
+
if (args["dry-run"]) {
|
|
2241
|
+
p7.outro(import_picocolors7.default.dim("Dry run \u2014 no changes applied."));
|
|
2242
|
+
process.exit(1);
|
|
2243
|
+
}
|
|
2244
|
+
if (addCount === 0) {
|
|
2245
|
+
p7.outro(import_picocolors7.default.dim("No additions needed. Extra servers left untouched."));
|
|
2246
|
+
process.exit(1);
|
|
2247
|
+
}
|
|
2248
|
+
if (!args.yes) {
|
|
2249
|
+
const confirmed = await p7.confirm({
|
|
2250
|
+
message: `Apply ${addCount} addition(s) to client configs?`,
|
|
2251
|
+
initialValue: true
|
|
2252
|
+
});
|
|
2253
|
+
if (p7.isCancel(confirmed) || !confirmed) {
|
|
2254
|
+
p7.outro(import_picocolors7.default.dim("Cancelled \u2014 no changes applied."));
|
|
2255
|
+
process.exit(0);
|
|
2256
|
+
}
|
|
2257
|
+
}
|
|
2258
|
+
spinner5.start("Applying sync changes...");
|
|
2259
|
+
const result = await applySyncActions(actions, handlers);
|
|
2260
|
+
spinner5.stop("Done");
|
|
2261
|
+
if (result.applied > 0) {
|
|
2262
|
+
p7.log.success(`Added ${result.applied} server(s) to client configs.`);
|
|
2263
|
+
}
|
|
2264
|
+
if (result.failed > 0) {
|
|
2265
|
+
for (const e of result.errors) {
|
|
2266
|
+
p7.log.error(`Failed to add "${e.server}" to ${e.client}: ${e.error}`);
|
|
2267
|
+
}
|
|
2268
|
+
}
|
|
2269
|
+
p7.outro(result.failed === 0 ? import_picocolors7.default.green("Sync complete.") : import_picocolors7.default.yellow("Sync complete with errors."));
|
|
2270
|
+
process.exit(result.failed > 0 ? 1 : 0);
|
|
2271
|
+
}
|
|
2272
|
+
});
|
|
2273
|
+
function printDiffTable(actions) {
|
|
2274
|
+
if (actions.length === 0) {
|
|
2275
|
+
p7.log.info("No actions to display.");
|
|
2276
|
+
return;
|
|
2277
|
+
}
|
|
2278
|
+
const nameWidth = Math.max(6, ...actions.map((a) => a.server.length));
|
|
2279
|
+
const clientWidth = Math.max(6, ...actions.map((a) => CLIENT_DISPLAY3[a.client]?.length ?? a.client.length));
|
|
2280
|
+
const header = ` ${pad2("SERVER", nameWidth)} ${pad2("CLIENT", clientWidth)} STATUS`;
|
|
2281
|
+
console.log(import_picocolors7.default.dim(header));
|
|
2282
|
+
console.log(import_picocolors7.default.dim(` ${"-".repeat(nameWidth)} ${"-".repeat(clientWidth)} ------`));
|
|
2283
|
+
for (const action of actions) {
|
|
2284
|
+
const clientDisplay = CLIENT_DISPLAY3[action.client] ?? action.client;
|
|
2285
|
+
const [icon, statusText] = formatAction(action.action);
|
|
2286
|
+
console.log(` ${pad2(action.server, nameWidth)} ${pad2(clientDisplay, clientWidth)} ${icon} ${statusText}`);
|
|
2287
|
+
}
|
|
2288
|
+
console.log("");
|
|
2289
|
+
}
|
|
2290
|
+
function formatAction(action) {
|
|
2291
|
+
switch (action) {
|
|
2292
|
+
case "add":
|
|
2293
|
+
return [import_picocolors7.default.green("+"), import_picocolors7.default.green("missing \u2014 will add")];
|
|
2294
|
+
case "extra":
|
|
2295
|
+
return [import_picocolors7.default.yellow("?"), import_picocolors7.default.yellow("extra (not in lockfile)")];
|
|
2296
|
+
case "ok":
|
|
2297
|
+
return [import_picocolors7.default.dim("\xB7"), import_picocolors7.default.dim("in sync")];
|
|
2298
|
+
}
|
|
2299
|
+
}
|
|
2300
|
+
function pad2(s, width) {
|
|
2301
|
+
return s.length >= width ? s : s + " ".repeat(width - s.length);
|
|
2302
|
+
}
|
|
2303
|
+
|
|
2304
|
+
// src/commands/update.ts
|
|
2305
|
+
init_cjs_shims();
|
|
2306
|
+
var import_citty9 = require("citty");
|
|
2307
|
+
var p8 = __toESM(require("@clack/prompts"), 1);
|
|
2308
|
+
var import_picocolors9 = __toESM(require("picocolors"), 1);
|
|
2309
|
+
|
|
2310
|
+
// src/core/version-checker.ts
|
|
2311
|
+
init_cjs_shims();
|
|
2312
|
+
function compareVersions(a, b) {
|
|
2313
|
+
const aParts = a.replace(/^v/, "").split(".").map(Number);
|
|
2314
|
+
const bParts = b.replace(/^v/, "").split(".").map(Number);
|
|
2315
|
+
const len = Math.max(aParts.length, bParts.length);
|
|
2316
|
+
for (let i = 0; i < len; i++) {
|
|
2317
|
+
const aN = aParts[i] ?? 0;
|
|
2318
|
+
const bN = bParts[i] ?? 0;
|
|
2319
|
+
if (Number.isNaN(aN) || Number.isNaN(bN)) return 0;
|
|
2320
|
+
if (aN < bN) return -1;
|
|
2321
|
+
if (aN > bN) return 1;
|
|
2322
|
+
}
|
|
2323
|
+
return 0;
|
|
2324
|
+
}
|
|
2325
|
+
function detectUpdateType(current, latest) {
|
|
2326
|
+
const cParts = current.replace(/^v/, "").split(".").map(Number);
|
|
2327
|
+
const lParts = latest.replace(/^v/, "").split(".").map(Number);
|
|
2328
|
+
if ((lParts[0] ?? 0) > (cParts[0] ?? 0)) return "major";
|
|
2329
|
+
if ((lParts[1] ?? 0) > (cParts[1] ?? 0)) return "minor";
|
|
2330
|
+
return "patch";
|
|
2331
|
+
}
|
|
2332
|
+
async function fetchNpmLatest(packageName) {
|
|
2333
|
+
try {
|
|
2334
|
+
const res = await fetch(
|
|
2335
|
+
`https://registry.npmjs.org/${encodeURIComponent(packageName)}/latest`,
|
|
2336
|
+
{
|
|
2337
|
+
headers: { Accept: "application/json" },
|
|
2338
|
+
signal: AbortSignal.timeout(8e3)
|
|
2339
|
+
}
|
|
2340
|
+
);
|
|
2341
|
+
if (!res.ok) return null;
|
|
2342
|
+
const data = await res.json();
|
|
2343
|
+
return typeof data.version === "string" ? data.version : null;
|
|
2344
|
+
} catch {
|
|
2345
|
+
return null;
|
|
2346
|
+
}
|
|
2347
|
+
}
|
|
2348
|
+
async function fetchSmitheryLatest(name) {
|
|
2349
|
+
try {
|
|
2350
|
+
const res = await fetch(
|
|
2351
|
+
`https://registry.smithery.ai/servers/${encodeURIComponent(name)}`,
|
|
2352
|
+
{
|
|
2353
|
+
headers: { Accept: "application/json" },
|
|
2354
|
+
signal: AbortSignal.timeout(8e3)
|
|
2355
|
+
}
|
|
2356
|
+
);
|
|
2357
|
+
if (!res.ok) return null;
|
|
2358
|
+
const data = await res.json();
|
|
2359
|
+
return typeof data.version === "string" ? data.version : null;
|
|
2360
|
+
} catch {
|
|
2361
|
+
return null;
|
|
2362
|
+
}
|
|
2363
|
+
}
|
|
2364
|
+
async function fetchGithubLatest(resolved) {
|
|
2365
|
+
const match = resolved.match(/github\.com\/([^/]+)\/([^/]+)/);
|
|
2366
|
+
if (!match) return null;
|
|
2367
|
+
const [, owner, repo] = match;
|
|
2368
|
+
try {
|
|
2369
|
+
const res = await fetch(
|
|
2370
|
+
`https://api.github.com/repos/${owner}/${repo}/releases/latest`,
|
|
2371
|
+
{
|
|
2372
|
+
headers: { Accept: "application/json" },
|
|
2373
|
+
signal: AbortSignal.timeout(8e3)
|
|
2374
|
+
}
|
|
2375
|
+
);
|
|
2376
|
+
if (!res.ok) return null;
|
|
2377
|
+
const data = await res.json();
|
|
2378
|
+
return typeof data.tag_name === "string" ? data.tag_name.replace(/^v/, "") : null;
|
|
2379
|
+
} catch {
|
|
2380
|
+
return null;
|
|
2381
|
+
}
|
|
2382
|
+
}
|
|
2383
|
+
async function checkVersion(name, lockEntry) {
|
|
2384
|
+
const current = lockEntry.version;
|
|
2385
|
+
let latest = null;
|
|
2386
|
+
if (lockEntry.source === "npm") {
|
|
2387
|
+
latest = await fetchNpmLatest(name);
|
|
2388
|
+
} else if (lockEntry.source === "smithery") {
|
|
2389
|
+
latest = await fetchSmitheryLatest(name);
|
|
2390
|
+
} else if (lockEntry.source === "github") {
|
|
2391
|
+
latest = await fetchGithubLatest(lockEntry.resolved);
|
|
2392
|
+
}
|
|
2393
|
+
if (!latest || latest === current) {
|
|
2394
|
+
return {
|
|
2395
|
+
server: name,
|
|
2396
|
+
source: lockEntry.source,
|
|
2397
|
+
currentVersion: current,
|
|
2398
|
+
latestVersion: latest ?? current,
|
|
2399
|
+
hasUpdate: false
|
|
2400
|
+
};
|
|
2401
|
+
}
|
|
2402
|
+
const hasUpdate = compareVersions(current, latest) === -1;
|
|
2403
|
+
return {
|
|
2404
|
+
server: name,
|
|
2405
|
+
source: lockEntry.source,
|
|
2406
|
+
currentVersion: current,
|
|
2407
|
+
latestVersion: latest,
|
|
2408
|
+
hasUpdate,
|
|
2409
|
+
updateType: hasUpdate ? detectUpdateType(current, latest) : void 0
|
|
2410
|
+
};
|
|
2411
|
+
}
|
|
2412
|
+
async function checkAllVersions(lockfile) {
|
|
2413
|
+
const entries = Object.entries(lockfile.servers);
|
|
2414
|
+
if (entries.length === 0) return [];
|
|
2415
|
+
const results = [];
|
|
2416
|
+
const executing = /* @__PURE__ */ new Set();
|
|
2417
|
+
for (const [name, entry] of entries) {
|
|
2418
|
+
const p9 = checkVersion(name, entry).then((r) => {
|
|
2419
|
+
results.push(r);
|
|
2420
|
+
executing.delete(p9);
|
|
2421
|
+
});
|
|
2422
|
+
executing.add(p9);
|
|
2423
|
+
if (executing.size >= 5) {
|
|
2424
|
+
await Promise.race(executing);
|
|
2425
|
+
}
|
|
2426
|
+
}
|
|
2427
|
+
await Promise.all(executing);
|
|
2428
|
+
return results;
|
|
2429
|
+
}
|
|
2430
|
+
|
|
2431
|
+
// src/core/update-notifier.ts
|
|
2432
|
+
init_cjs_shims();
|
|
2433
|
+
var import_node_fs5 = __toESM(require("fs"), 1);
|
|
2434
|
+
var import_node_path7 = __toESM(require("path"), 1);
|
|
2435
|
+
var import_node_os5 = __toESM(require("os"), 1);
|
|
2436
|
+
var import_picocolors8 = __toESM(require("picocolors"), 1);
|
|
2437
|
+
var CACHE_FILE = import_node_path7.default.join(import_node_os5.default.homedir(), ".mcpman", ".update-check");
|
|
2438
|
+
var TTL_MS = 24 * 60 * 60 * 1e3;
|
|
2439
|
+
function writeUpdateCache(data) {
|
|
2440
|
+
try {
|
|
2441
|
+
const dir = import_node_path7.default.dirname(CACHE_FILE);
|
|
2442
|
+
if (!import_node_fs5.default.existsSync(dir)) import_node_fs5.default.mkdirSync(dir, { recursive: true });
|
|
2443
|
+
const tmp = `${CACHE_FILE}.tmp`;
|
|
2444
|
+
import_node_fs5.default.writeFileSync(tmp, JSON.stringify(data, null, 2), "utf-8");
|
|
2445
|
+
import_node_fs5.default.renameSync(tmp, CACHE_FILE);
|
|
2446
|
+
} catch {
|
|
2447
|
+
}
|
|
2448
|
+
}
|
|
2449
|
+
|
|
2450
|
+
// src/commands/update.ts
|
|
2451
|
+
async function loadClients2() {
|
|
2452
|
+
try {
|
|
2453
|
+
const mod = await Promise.resolve().then(() => (init_client_detector(), client_detector_exports));
|
|
2454
|
+
return mod.getInstalledClients();
|
|
2455
|
+
} catch {
|
|
2456
|
+
return [];
|
|
2457
|
+
}
|
|
2458
|
+
}
|
|
2459
|
+
function printTable(updates) {
|
|
2460
|
+
const NAME_W = 28;
|
|
2461
|
+
const VER_W = 10;
|
|
2462
|
+
const header = [
|
|
2463
|
+
"NAME".padEnd(NAME_W),
|
|
2464
|
+
"CURRENT".padEnd(VER_W),
|
|
2465
|
+
"LATEST".padEnd(VER_W),
|
|
2466
|
+
"STATUS"
|
|
2467
|
+
].join(" ");
|
|
2468
|
+
console.log(import_picocolors9.default.bold(`
|
|
2469
|
+
${header}`));
|
|
2470
|
+
console.log(import_picocolors9.default.dim(` ${"\u2500".repeat(NAME_W + VER_W * 2 + 20)}`));
|
|
2471
|
+
for (const u of updates) {
|
|
2472
|
+
const nameCol = u.server.slice(0, NAME_W).padEnd(NAME_W);
|
|
2473
|
+
const curCol = u.currentVersion.padEnd(VER_W);
|
|
2474
|
+
const latCol = u.latestVersion.padEnd(VER_W);
|
|
2475
|
+
const statusCol = u.hasUpdate ? import_picocolors9.default.yellow(`Update available${u.updateType ? ` [${u.updateType}]` : ""}`) : import_picocolors9.default.green("Up to date");
|
|
2476
|
+
console.log(` ${nameCol} ${curCol} ${latCol} ${statusCol}`);
|
|
2477
|
+
}
|
|
2478
|
+
console.log();
|
|
2479
|
+
}
|
|
2480
|
+
var update_default = (0, import_citty9.defineCommand)({
|
|
2481
|
+
meta: {
|
|
2482
|
+
name: "update",
|
|
2483
|
+
description: "Check for and apply updates to installed MCP servers"
|
|
2484
|
+
},
|
|
2485
|
+
args: {
|
|
2486
|
+
server: {
|
|
2487
|
+
type: "positional",
|
|
2488
|
+
description: "Server name to update (omit to update all)",
|
|
2489
|
+
required: false
|
|
2490
|
+
},
|
|
2491
|
+
check: {
|
|
2492
|
+
type: "boolean",
|
|
2493
|
+
description: "Check only \u2014 do not apply updates",
|
|
2494
|
+
default: false
|
|
2495
|
+
},
|
|
2496
|
+
yes: {
|
|
2497
|
+
type: "boolean",
|
|
2498
|
+
description: "Skip confirmation prompt",
|
|
2499
|
+
default: false
|
|
2500
|
+
},
|
|
2501
|
+
json: {
|
|
2502
|
+
type: "boolean",
|
|
2503
|
+
description: "Output results as JSON",
|
|
2504
|
+
default: false
|
|
2505
|
+
}
|
|
2506
|
+
},
|
|
2507
|
+
async run({ args }) {
|
|
2508
|
+
const lockfile = readLockfile();
|
|
2509
|
+
const servers = lockfile.servers;
|
|
2510
|
+
const targetEntries = args.server ? Object.entries(servers).filter(([name]) => name === args.server) : Object.entries(servers);
|
|
2511
|
+
if (targetEntries.length === 0) {
|
|
2512
|
+
if (args.server) {
|
|
2513
|
+
console.error(`Server '${args.server}' not found in lockfile.`);
|
|
2514
|
+
} else {
|
|
2515
|
+
console.log("No servers installed. Run mcpman install <server> first.");
|
|
2516
|
+
}
|
|
1376
2517
|
process.exit(1);
|
|
1377
2518
|
}
|
|
1378
|
-
|
|
2519
|
+
const spinner5 = p8.spinner();
|
|
2520
|
+
spinner5.start("Checking versions...");
|
|
2521
|
+
let updates;
|
|
2522
|
+
try {
|
|
2523
|
+
const partialLock = {
|
|
2524
|
+
lockfileVersion: 1,
|
|
2525
|
+
servers: Object.fromEntries(targetEntries)
|
|
2526
|
+
};
|
|
2527
|
+
updates = await checkAllVersions(partialLock);
|
|
2528
|
+
} catch (err) {
|
|
2529
|
+
spinner5.stop("Version check failed");
|
|
2530
|
+
console.error(err instanceof Error ? err.message : String(err));
|
|
2531
|
+
process.exit(1);
|
|
2532
|
+
}
|
|
2533
|
+
spinner5.stop(`Checked ${updates.length} server(s)`);
|
|
2534
|
+
if (args.json) {
|
|
2535
|
+
console.log(JSON.stringify(updates, null, 2));
|
|
2536
|
+
return;
|
|
2537
|
+
}
|
|
2538
|
+
printTable(updates);
|
|
2539
|
+
const outdated = updates.filter((u) => u.hasUpdate);
|
|
2540
|
+
if (outdated.length === 0) {
|
|
2541
|
+
console.log(import_picocolors9.default.green(" All servers are up to date."));
|
|
2542
|
+
return;
|
|
2543
|
+
}
|
|
2544
|
+
if (args.check) {
|
|
2545
|
+
console.log(import_picocolors9.default.yellow(` ${outdated.length} update(s) available. Run mcpman update to apply.`));
|
|
2546
|
+
return;
|
|
2547
|
+
}
|
|
2548
|
+
if (!args.yes) {
|
|
2549
|
+
const confirmed = await p8.confirm({
|
|
2550
|
+
message: `Apply ${outdated.length} update(s)?`,
|
|
2551
|
+
initialValue: true
|
|
2552
|
+
});
|
|
2553
|
+
if (p8.isCancel(confirmed) || !confirmed) {
|
|
2554
|
+
p8.outro("Cancelled.");
|
|
2555
|
+
return;
|
|
2556
|
+
}
|
|
2557
|
+
}
|
|
2558
|
+
const clients = await loadClients2();
|
|
2559
|
+
let successCount = 0;
|
|
2560
|
+
for (const update of outdated) {
|
|
2561
|
+
const lockEntry = servers[update.server];
|
|
2562
|
+
const input = lockEntry.source === "smithery" ? `smithery:${update.server}` : lockEntry.source === "github" ? lockEntry.resolved : update.server;
|
|
2563
|
+
const s = p8.spinner();
|
|
2564
|
+
s.start(`Updating ${update.server}...`);
|
|
2565
|
+
try {
|
|
2566
|
+
const metadata = await resolveServer(input);
|
|
2567
|
+
const integrity = computeIntegrity(metadata.resolved);
|
|
2568
|
+
addEntry(update.server, {
|
|
2569
|
+
...lockEntry,
|
|
2570
|
+
version: metadata.version,
|
|
2571
|
+
resolved: metadata.resolved,
|
|
2572
|
+
integrity,
|
|
2573
|
+
command: metadata.command,
|
|
2574
|
+
args: metadata.args,
|
|
2575
|
+
installedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
2576
|
+
});
|
|
2577
|
+
const entryClients = clients.filter(
|
|
2578
|
+
(c) => lockEntry.clients.includes(c.type)
|
|
2579
|
+
);
|
|
2580
|
+
for (const client of entryClients) {
|
|
2581
|
+
try {
|
|
2582
|
+
await client.addServer(update.server, {
|
|
2583
|
+
command: metadata.command,
|
|
2584
|
+
args: metadata.args
|
|
2585
|
+
});
|
|
2586
|
+
} catch {
|
|
2587
|
+
}
|
|
2588
|
+
}
|
|
2589
|
+
s.stop(`${import_picocolors9.default.green("\u2713")} ${update.server}: ${update.currentVersion} \u2192 ${metadata.version}`);
|
|
2590
|
+
successCount++;
|
|
2591
|
+
} catch (err) {
|
|
2592
|
+
s.stop(`${import_picocolors9.default.red("\u2717")} ${update.server}: ${err instanceof Error ? err.message : String(err)}`);
|
|
2593
|
+
}
|
|
2594
|
+
}
|
|
2595
|
+
const freshLockfile = readLockfile(resolveLockfilePath());
|
|
2596
|
+
const freshUpdates = await checkAllVersions(freshLockfile);
|
|
2597
|
+
writeUpdateCache({ lastCheck: (/* @__PURE__ */ new Date()).toISOString(), updates: freshUpdates });
|
|
2598
|
+
p8.outro(`${successCount} of ${outdated.length} server(s) updated.`);
|
|
1379
2599
|
}
|
|
1380
2600
|
});
|
|
1381
2601
|
|
|
1382
2602
|
// src/utils/constants.ts
|
|
1383
2603
|
init_cjs_shims();
|
|
1384
2604
|
var APP_NAME = "mcpman";
|
|
1385
|
-
var APP_VERSION = "0.
|
|
2605
|
+
var APP_VERSION = "0.2.0";
|
|
1386
2606
|
var APP_DESCRIPTION = "The package manager for MCP servers";
|
|
1387
2607
|
|
|
1388
2608
|
// src/index.ts
|
|
@@ -1390,7 +2610,7 @@ process.on("SIGINT", () => {
|
|
|
1390
2610
|
console.log("\nAborted.");
|
|
1391
2611
|
process.exit(130);
|
|
1392
2612
|
});
|
|
1393
|
-
var main = (0,
|
|
2613
|
+
var main = (0, import_citty10.defineCommand)({
|
|
1394
2614
|
meta: {
|
|
1395
2615
|
name: APP_NAME,
|
|
1396
2616
|
version: APP_VERSION,
|
|
@@ -1401,7 +2621,11 @@ var main = (0, import_citty6.defineCommand)({
|
|
|
1401
2621
|
list: list_default,
|
|
1402
2622
|
remove: remove_default,
|
|
1403
2623
|
doctor: doctor_default,
|
|
1404
|
-
init: init_default
|
|
2624
|
+
init: init_default,
|
|
2625
|
+
secrets: secrets_default,
|
|
2626
|
+
sync: sync_default,
|
|
2627
|
+
audit: audit_default,
|
|
2628
|
+
update: update_default
|
|
1405
2629
|
}
|
|
1406
2630
|
});
|
|
1407
|
-
(0,
|
|
2631
|
+
(0, import_citty10.runMain)(main);
|