mcpman 0.2.0 → 0.4.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 +64 -0
- package/dist/{trust-scorer-LYC6KZCD.js → chunk-RGKHLY5G.js} +1 -0
- package/dist/{chunk-QY22QTBR.js → chunk-RMMEBP2J.js} +7 -0
- package/dist/{client-detector-SUIJSIYM.js → client-detector-UAP2EYZA.js} +1 -1
- package/dist/index.cjs +1489 -710
- package/dist/index.js +1255 -494
- package/dist/trust-scorer-G74WK25Q.js +7 -0
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,19 +1,25 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
|
+
computeTrustScore
|
|
4
|
+
} from "./chunk-RGKHLY5G.js";
|
|
5
|
+
import {
|
|
6
|
+
getConfigPath,
|
|
3
7
|
getInstalledClients
|
|
4
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-RMMEBP2J.js";
|
|
5
9
|
import {
|
|
6
10
|
getMasterPassword,
|
|
11
|
+
getSecretsForServer,
|
|
7
12
|
listSecrets,
|
|
8
13
|
removeSecret,
|
|
9
14
|
setSecret
|
|
10
15
|
} from "./chunk-6X6Q6UZC.js";
|
|
11
16
|
|
|
12
17
|
// src/index.ts
|
|
13
|
-
import { defineCommand as
|
|
18
|
+
import { defineCommand as defineCommand14, runMain } from "citty";
|
|
14
19
|
|
|
15
20
|
// src/commands/audit.ts
|
|
16
21
|
import { defineCommand } from "citty";
|
|
22
|
+
import * as p from "@clack/prompts";
|
|
17
23
|
import pc from "picocolors";
|
|
18
24
|
import { createSpinner } from "nanospinner";
|
|
19
25
|
|
|
@@ -191,8 +197,8 @@ async function scanServer(name, entry) {
|
|
|
191
197
|
fetchNpmMetadata(name),
|
|
192
198
|
fetchVulnerabilities(name, entry.version)
|
|
193
199
|
]);
|
|
194
|
-
const { computeTrustScore } = await import("./trust-scorer-
|
|
195
|
-
const { score, riskLevel } =
|
|
200
|
+
const { computeTrustScore: computeTrustScore2 } = await import("./trust-scorer-G74WK25Q.js");
|
|
201
|
+
const { score, riskLevel } = computeTrustScore2(metadata, vulnerabilities);
|
|
196
202
|
const report = {
|
|
197
203
|
server: name,
|
|
198
204
|
source: "npm",
|
|
@@ -209,17 +215,324 @@ async function scanAllServers(servers, concurrency = 3) {
|
|
|
209
215
|
const results = [];
|
|
210
216
|
const executing = /* @__PURE__ */ new Set();
|
|
211
217
|
for (const [name, entry] of entries) {
|
|
212
|
-
const
|
|
218
|
+
const p11 = scanServer(name, entry).then((r) => {
|
|
213
219
|
results.push(r);
|
|
214
|
-
executing.delete(
|
|
220
|
+
executing.delete(p11);
|
|
215
221
|
});
|
|
216
|
-
executing.add(
|
|
222
|
+
executing.add(p11);
|
|
217
223
|
if (executing.size >= concurrency) await Promise.race(executing);
|
|
218
224
|
}
|
|
219
225
|
await Promise.all(executing);
|
|
220
226
|
return results;
|
|
221
227
|
}
|
|
222
228
|
|
|
229
|
+
// src/core/version-checker.ts
|
|
230
|
+
function compareVersions(a, b) {
|
|
231
|
+
const aParts = a.replace(/^v/, "").split(".").map(Number);
|
|
232
|
+
const bParts = b.replace(/^v/, "").split(".").map(Number);
|
|
233
|
+
const len = Math.max(aParts.length, bParts.length);
|
|
234
|
+
for (let i = 0; i < len; i++) {
|
|
235
|
+
const aN = aParts[i] ?? 0;
|
|
236
|
+
const bN = bParts[i] ?? 0;
|
|
237
|
+
if (Number.isNaN(aN) || Number.isNaN(bN)) return 0;
|
|
238
|
+
if (aN < bN) return -1;
|
|
239
|
+
if (aN > bN) return 1;
|
|
240
|
+
}
|
|
241
|
+
return 0;
|
|
242
|
+
}
|
|
243
|
+
function detectUpdateType(current, latest) {
|
|
244
|
+
const cParts = current.replace(/^v/, "").split(".").map(Number);
|
|
245
|
+
const lParts = latest.replace(/^v/, "").split(".").map(Number);
|
|
246
|
+
if ((lParts[0] ?? 0) > (cParts[0] ?? 0)) return "major";
|
|
247
|
+
if ((lParts[1] ?? 0) > (cParts[1] ?? 0)) return "minor";
|
|
248
|
+
return "patch";
|
|
249
|
+
}
|
|
250
|
+
async function fetchNpmLatest(packageName) {
|
|
251
|
+
try {
|
|
252
|
+
const res = await fetch(
|
|
253
|
+
`https://registry.npmjs.org/${encodeURIComponent(packageName)}/latest`,
|
|
254
|
+
{
|
|
255
|
+
headers: { Accept: "application/json" },
|
|
256
|
+
signal: AbortSignal.timeout(8e3)
|
|
257
|
+
}
|
|
258
|
+
);
|
|
259
|
+
if (!res.ok) return null;
|
|
260
|
+
const data = await res.json();
|
|
261
|
+
return typeof data.version === "string" ? data.version : null;
|
|
262
|
+
} catch {
|
|
263
|
+
return null;
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
async function fetchSmitheryLatest(name) {
|
|
267
|
+
try {
|
|
268
|
+
const res = await fetch(
|
|
269
|
+
`https://registry.smithery.ai/servers/${encodeURIComponent(name)}`,
|
|
270
|
+
{
|
|
271
|
+
headers: { Accept: "application/json" },
|
|
272
|
+
signal: AbortSignal.timeout(8e3)
|
|
273
|
+
}
|
|
274
|
+
);
|
|
275
|
+
if (!res.ok) return null;
|
|
276
|
+
const data = await res.json();
|
|
277
|
+
return typeof data.version === "string" ? data.version : null;
|
|
278
|
+
} catch {
|
|
279
|
+
return null;
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
async function fetchGithubLatest(resolved) {
|
|
283
|
+
const match = resolved.match(/github\.com\/([^/]+)\/([^/]+)/);
|
|
284
|
+
if (!match) return null;
|
|
285
|
+
const [, owner, repo] = match;
|
|
286
|
+
try {
|
|
287
|
+
const res = await fetch(
|
|
288
|
+
`https://api.github.com/repos/${owner}/${repo}/releases/latest`,
|
|
289
|
+
{
|
|
290
|
+
headers: { Accept: "application/json" },
|
|
291
|
+
signal: AbortSignal.timeout(8e3)
|
|
292
|
+
}
|
|
293
|
+
);
|
|
294
|
+
if (!res.ok) return null;
|
|
295
|
+
const data = await res.json();
|
|
296
|
+
return typeof data.tag_name === "string" ? data.tag_name.replace(/^v/, "") : null;
|
|
297
|
+
} catch {
|
|
298
|
+
return null;
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
async function checkVersion(name, lockEntry) {
|
|
302
|
+
const current = lockEntry.version;
|
|
303
|
+
let latest = null;
|
|
304
|
+
if (lockEntry.source === "npm") {
|
|
305
|
+
latest = await fetchNpmLatest(name);
|
|
306
|
+
} else if (lockEntry.source === "smithery") {
|
|
307
|
+
latest = await fetchSmitheryLatest(name);
|
|
308
|
+
} else if (lockEntry.source === "github") {
|
|
309
|
+
latest = await fetchGithubLatest(lockEntry.resolved);
|
|
310
|
+
}
|
|
311
|
+
if (!latest || latest === current) {
|
|
312
|
+
return {
|
|
313
|
+
server: name,
|
|
314
|
+
source: lockEntry.source,
|
|
315
|
+
currentVersion: current,
|
|
316
|
+
latestVersion: latest ?? current,
|
|
317
|
+
hasUpdate: false
|
|
318
|
+
};
|
|
319
|
+
}
|
|
320
|
+
const hasUpdate = compareVersions(current, latest) === -1;
|
|
321
|
+
return {
|
|
322
|
+
server: name,
|
|
323
|
+
source: lockEntry.source,
|
|
324
|
+
currentVersion: current,
|
|
325
|
+
latestVersion: latest,
|
|
326
|
+
hasUpdate,
|
|
327
|
+
updateType: hasUpdate ? detectUpdateType(current, latest) : void 0
|
|
328
|
+
};
|
|
329
|
+
}
|
|
330
|
+
async function checkAllVersions(lockfile) {
|
|
331
|
+
const entries = Object.entries(lockfile.servers);
|
|
332
|
+
if (entries.length === 0) return [];
|
|
333
|
+
const results = [];
|
|
334
|
+
const executing = /* @__PURE__ */ new Set();
|
|
335
|
+
for (const [name, entry] of entries) {
|
|
336
|
+
const p11 = checkVersion(name, entry).then((r) => {
|
|
337
|
+
results.push(r);
|
|
338
|
+
executing.delete(p11);
|
|
339
|
+
});
|
|
340
|
+
executing.add(p11);
|
|
341
|
+
if (executing.size >= 5) {
|
|
342
|
+
await Promise.race(executing);
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
await Promise.all(executing);
|
|
346
|
+
return results;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
// src/core/registry.ts
|
|
350
|
+
import { createHash } from "crypto";
|
|
351
|
+
function computeIntegrity(resolvedUrl) {
|
|
352
|
+
const hash = createHash("sha512").update(resolvedUrl).digest("base64");
|
|
353
|
+
return `sha512-${hash}`;
|
|
354
|
+
}
|
|
355
|
+
async function resolveFromSmithery(name) {
|
|
356
|
+
const url = `https://registry.smithery.ai/servers/${encodeURIComponent(name)}`;
|
|
357
|
+
let data;
|
|
358
|
+
try {
|
|
359
|
+
const res = await fetch(url, {
|
|
360
|
+
headers: { Accept: "application/json" },
|
|
361
|
+
signal: AbortSignal.timeout(8e3)
|
|
362
|
+
});
|
|
363
|
+
if (res.status === 404) {
|
|
364
|
+
throw new Error(`Server '${name}' not found on Smithery registry`);
|
|
365
|
+
}
|
|
366
|
+
if (!res.ok) {
|
|
367
|
+
throw new Error(`Smithery API error: ${res.status}`);
|
|
368
|
+
}
|
|
369
|
+
data = await res.json();
|
|
370
|
+
} catch (err) {
|
|
371
|
+
if (err instanceof Error && err.message.includes("not found")) throw err;
|
|
372
|
+
throw new Error(
|
|
373
|
+
`Cannot reach Smithery registry: ${err instanceof Error ? err.message : String(err)}`
|
|
374
|
+
);
|
|
375
|
+
}
|
|
376
|
+
const version = typeof data.version === "string" ? data.version : "latest";
|
|
377
|
+
const command = typeof data.command === "string" ? data.command : "npx";
|
|
378
|
+
const args = Array.isArray(data.args) ? data.args : ["-y", `${name}@${version}`];
|
|
379
|
+
const envVars = Array.isArray(data.envVars) ? data.envVars : [];
|
|
380
|
+
const resolved = typeof data.resolved === "string" ? data.resolved : `smithery:${name}@${version}`;
|
|
381
|
+
return {
|
|
382
|
+
name,
|
|
383
|
+
version,
|
|
384
|
+
description: typeof data.description === "string" ? data.description : "",
|
|
385
|
+
runtime: "node",
|
|
386
|
+
command,
|
|
387
|
+
args,
|
|
388
|
+
envVars,
|
|
389
|
+
resolved
|
|
390
|
+
};
|
|
391
|
+
}
|
|
392
|
+
async function resolveFromNpm(packageName) {
|
|
393
|
+
const url = `https://registry.npmjs.org/${encodeURIComponent(packageName)}/latest`;
|
|
394
|
+
let data;
|
|
395
|
+
try {
|
|
396
|
+
const res = await fetch(url, {
|
|
397
|
+
headers: { Accept: "application/json" },
|
|
398
|
+
signal: AbortSignal.timeout(8e3)
|
|
399
|
+
});
|
|
400
|
+
if (res.status === 404) {
|
|
401
|
+
throw new Error(`Package '${packageName}' not found on npm`);
|
|
402
|
+
}
|
|
403
|
+
if (!res.ok) {
|
|
404
|
+
throw new Error(`npm registry error: ${res.status}`);
|
|
405
|
+
}
|
|
406
|
+
data = await res.json();
|
|
407
|
+
} catch (err) {
|
|
408
|
+
if (err instanceof Error && err.message.includes("not found")) throw err;
|
|
409
|
+
throw new Error(
|
|
410
|
+
`Cannot reach npm registry: ${err instanceof Error ? err.message : String(err)}`
|
|
411
|
+
);
|
|
412
|
+
}
|
|
413
|
+
const version = typeof data.version === "string" ? data.version : "latest";
|
|
414
|
+
const resolved = `https://registry.npmjs.org/${packageName}/-/${packageName.replace(/^@[^/]+\//, "")}-${version}.tgz`;
|
|
415
|
+
const mcpField = data.mcp && typeof data.mcp === "object" ? data.mcp : null;
|
|
416
|
+
const envVars = mcpField?.envVars ? mcpField.envVars : [];
|
|
417
|
+
return {
|
|
418
|
+
name: packageName,
|
|
419
|
+
version,
|
|
420
|
+
description: typeof data.description === "string" ? data.description : "",
|
|
421
|
+
runtime: "node",
|
|
422
|
+
command: "npx",
|
|
423
|
+
args: ["-y", `${packageName}@${version}`],
|
|
424
|
+
envVars,
|
|
425
|
+
resolved
|
|
426
|
+
};
|
|
427
|
+
}
|
|
428
|
+
async function resolveFromGitHub(githubUrl) {
|
|
429
|
+
const match = githubUrl.match(/github\.com\/([^/]+)\/([^/]+)/);
|
|
430
|
+
if (!match) {
|
|
431
|
+
throw new Error(`Invalid GitHub URL: ${githubUrl}`);
|
|
432
|
+
}
|
|
433
|
+
const [, owner, repo] = match;
|
|
434
|
+
const rawUrl = `https://raw.githubusercontent.com/${owner}/${repo}/main/package.json`;
|
|
435
|
+
let pkgData = {};
|
|
436
|
+
try {
|
|
437
|
+
const res = await fetch(rawUrl, { signal: AbortSignal.timeout(8e3) });
|
|
438
|
+
if (res.ok) {
|
|
439
|
+
pkgData = await res.json();
|
|
440
|
+
}
|
|
441
|
+
} catch {
|
|
442
|
+
}
|
|
443
|
+
const version = typeof pkgData.version === "string" ? pkgData.version : "main";
|
|
444
|
+
const name = typeof pkgData.name === "string" ? pkgData.name : `${owner}/${repo}`;
|
|
445
|
+
return {
|
|
446
|
+
name,
|
|
447
|
+
version,
|
|
448
|
+
description: typeof pkgData.description === "string" ? pkgData.description : "",
|
|
449
|
+
runtime: "node",
|
|
450
|
+
command: "npx",
|
|
451
|
+
args: ["-y", githubUrl],
|
|
452
|
+
envVars: [],
|
|
453
|
+
resolved: githubUrl
|
|
454
|
+
};
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
// src/core/server-resolver.ts
|
|
458
|
+
function detectSource(input) {
|
|
459
|
+
if (input.startsWith("smithery:")) {
|
|
460
|
+
return { type: "smithery", input: input.slice(9) };
|
|
461
|
+
}
|
|
462
|
+
if (input.startsWith("https://github.com/") || input.startsWith("github.com/")) {
|
|
463
|
+
return { type: "github", input };
|
|
464
|
+
}
|
|
465
|
+
return { type: "npm", input };
|
|
466
|
+
}
|
|
467
|
+
function parseEnvFlags(envFlags) {
|
|
468
|
+
if (!envFlags) return {};
|
|
469
|
+
const flags = Array.isArray(envFlags) ? envFlags : [envFlags];
|
|
470
|
+
const result = {};
|
|
471
|
+
for (const flag of flags) {
|
|
472
|
+
const idx = flag.indexOf("=");
|
|
473
|
+
if (idx > 0) {
|
|
474
|
+
result[flag.slice(0, idx)] = flag.slice(idx + 1);
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
return result;
|
|
478
|
+
}
|
|
479
|
+
async function resolveServer(input) {
|
|
480
|
+
const source = detectSource(input);
|
|
481
|
+
switch (source.type) {
|
|
482
|
+
case "smithery":
|
|
483
|
+
return resolveFromSmithery(source.input);
|
|
484
|
+
case "github":
|
|
485
|
+
return resolveFromGitHub(source.input);
|
|
486
|
+
case "npm":
|
|
487
|
+
return resolveFromNpm(source.input);
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
// src/core/server-updater.ts
|
|
492
|
+
async function applyServerUpdate(serverName, lockEntry, clients) {
|
|
493
|
+
const fromVersion = lockEntry.version;
|
|
494
|
+
const input = lockEntry.source === "smithery" ? `smithery:${serverName}` : lockEntry.source === "github" ? lockEntry.resolved : serverName;
|
|
495
|
+
try {
|
|
496
|
+
const metadata = await resolveServer(input);
|
|
497
|
+
const integrity = computeIntegrity(metadata.resolved);
|
|
498
|
+
addEntry(serverName, {
|
|
499
|
+
...lockEntry,
|
|
500
|
+
version: metadata.version,
|
|
501
|
+
resolved: metadata.resolved,
|
|
502
|
+
integrity,
|
|
503
|
+
command: metadata.command,
|
|
504
|
+
args: metadata.args,
|
|
505
|
+
installedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
506
|
+
});
|
|
507
|
+
const targetClients = clients.filter(
|
|
508
|
+
(c) => lockEntry.clients.includes(c.type)
|
|
509
|
+
);
|
|
510
|
+
for (const client of targetClients) {
|
|
511
|
+
try {
|
|
512
|
+
await client.addServer(serverName, {
|
|
513
|
+
command: metadata.command,
|
|
514
|
+
args: metadata.args
|
|
515
|
+
});
|
|
516
|
+
} catch {
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
return {
|
|
520
|
+
server: serverName,
|
|
521
|
+
success: true,
|
|
522
|
+
fromVersion,
|
|
523
|
+
toVersion: metadata.version
|
|
524
|
+
};
|
|
525
|
+
} catch (err) {
|
|
526
|
+
return {
|
|
527
|
+
server: serverName,
|
|
528
|
+
success: false,
|
|
529
|
+
fromVersion,
|
|
530
|
+
toVersion: fromVersion,
|
|
531
|
+
error: err instanceof Error ? err.message : String(err)
|
|
532
|
+
};
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
|
|
223
536
|
// src/commands/audit.ts
|
|
224
537
|
function colorRisk(level, score) {
|
|
225
538
|
const label = score !== null ? `${score}/100 (${level})` : level;
|
|
@@ -290,7 +603,12 @@ var audit_default = defineCommand({
|
|
|
290
603
|
},
|
|
291
604
|
fix: {
|
|
292
605
|
type: "boolean",
|
|
293
|
-
description: "
|
|
606
|
+
description: "Apply updates to fix vulnerable packages",
|
|
607
|
+
default: false
|
|
608
|
+
},
|
|
609
|
+
yes: {
|
|
610
|
+
type: "boolean",
|
|
611
|
+
description: "Skip confirmation prompt (use with --fix)",
|
|
294
612
|
default: false
|
|
295
613
|
}
|
|
296
614
|
},
|
|
@@ -342,30 +660,270 @@ var audit_default = defineCommand({
|
|
|
342
660
|
if (npmReports.length < reports.length) {
|
|
343
661
|
parts.push(pc.dim(`${reports.length - npmReports.length} non-npm (unverified)`));
|
|
344
662
|
}
|
|
345
|
-
if (withIssues.length > 0) {
|
|
346
|
-
parts.push(pc.yellow(`${withIssues.length} with issues`));
|
|
663
|
+
if (withIssues.length > 0) {
|
|
664
|
+
parts.push(pc.yellow(`${withIssues.length} with issues`));
|
|
665
|
+
} else {
|
|
666
|
+
parts.push(pc.green("all clear"));
|
|
667
|
+
}
|
|
668
|
+
console.log(`
|
|
669
|
+
Summary: ${parts.join(" | ")}
|
|
670
|
+
`);
|
|
671
|
+
if (args.fix) {
|
|
672
|
+
await runAuditFix(reports, lockfile.servers, args.yes);
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
});
|
|
676
|
+
async function loadClients() {
|
|
677
|
+
try {
|
|
678
|
+
const mod = await import("./client-detector-UAP2EYZA.js");
|
|
679
|
+
return mod.getInstalledClients();
|
|
680
|
+
} catch {
|
|
681
|
+
return [];
|
|
682
|
+
}
|
|
683
|
+
}
|
|
684
|
+
async function runAuditFix(reports, servers, skipConfirm) {
|
|
685
|
+
const npmWithVulns = reports.filter(
|
|
686
|
+
(r) => r.vulnerabilities.length > 0 && r.source === "npm"
|
|
687
|
+
);
|
|
688
|
+
const nonNpmWithVulns = reports.filter(
|
|
689
|
+
(r) => r.vulnerabilities.length > 0 && r.source !== "npm"
|
|
690
|
+
);
|
|
691
|
+
if (nonNpmWithVulns.length > 0) {
|
|
692
|
+
console.log(pc.yellow(" Non-npm servers require manual update:"));
|
|
693
|
+
for (const r of nonNpmWithVulns) {
|
|
694
|
+
console.log(` ${pc.dim("\u2192")} ${r.server} (${r.source})`);
|
|
695
|
+
}
|
|
696
|
+
console.log();
|
|
697
|
+
}
|
|
698
|
+
if (npmWithVulns.length === 0) {
|
|
699
|
+
console.log(pc.green(" No fixable vulnerabilities found.\n"));
|
|
700
|
+
return;
|
|
701
|
+
}
|
|
702
|
+
const versionSpinner = createSpinner("Checking for available updates...").start();
|
|
703
|
+
const versionChecks = await Promise.all(
|
|
704
|
+
npmWithVulns.map((r) => checkVersion(r.server, servers[r.server]))
|
|
705
|
+
);
|
|
706
|
+
versionSpinner.success({ text: "Version check complete" });
|
|
707
|
+
const updatable = versionChecks.filter((u) => u.hasUpdate);
|
|
708
|
+
if (updatable.length === 0) {
|
|
709
|
+
console.log(pc.yellow(
|
|
710
|
+
" Vulnerable servers have no newer versions available yet.\n Allow time for registry to publish fixes.\n"
|
|
711
|
+
));
|
|
712
|
+
return;
|
|
713
|
+
}
|
|
714
|
+
console.log(pc.bold(`
|
|
715
|
+
${updatable.length} server(s) can be updated to fix vulnerabilities:
|
|
716
|
+
`));
|
|
717
|
+
for (const u of updatable) {
|
|
718
|
+
console.log(` ${pc.cyan("\u2192")} ${u.server} ${pc.dim(u.currentVersion)} \u2192 ${pc.green(u.latestVersion)}`);
|
|
719
|
+
}
|
|
720
|
+
console.log();
|
|
721
|
+
if (!skipConfirm) {
|
|
722
|
+
const confirmed = await p.confirm({
|
|
723
|
+
message: `Update ${updatable.length} vulnerable server(s)?`,
|
|
724
|
+
initialValue: true
|
|
725
|
+
});
|
|
726
|
+
if (p.isCancel(confirmed) || !confirmed) {
|
|
727
|
+
p.outro("Cancelled.");
|
|
728
|
+
return;
|
|
729
|
+
}
|
|
730
|
+
}
|
|
731
|
+
const clients = await loadClients();
|
|
732
|
+
let successCount = 0;
|
|
733
|
+
const results = [];
|
|
734
|
+
for (const u of updatable) {
|
|
735
|
+
const s = createSpinner(`Updating ${u.server}...`).start();
|
|
736
|
+
const result = await applyServerUpdate(u.server, servers[u.server], clients);
|
|
737
|
+
if (result.success) {
|
|
738
|
+
s.success({ text: `${pc.green("\u2713")} ${u.server}: ${result.fromVersion} \u2192 ${result.toVersion}` });
|
|
739
|
+
successCount++;
|
|
740
|
+
} else {
|
|
741
|
+
s.error({ text: `${pc.red("\u2717")} ${u.server}: ${result.error}` });
|
|
742
|
+
}
|
|
743
|
+
results.push({
|
|
744
|
+
server: u.server,
|
|
745
|
+
from: result.fromVersion,
|
|
746
|
+
to: result.toVersion,
|
|
747
|
+
ok: result.success,
|
|
748
|
+
error: result.error
|
|
749
|
+
});
|
|
750
|
+
}
|
|
751
|
+
console.log();
|
|
752
|
+
if (successCount > 0) {
|
|
753
|
+
const updatedNames = results.filter((r) => r.ok).map((r) => r.server);
|
|
754
|
+
const freshLockfile = readLockfile();
|
|
755
|
+
const rescanSpinner = createSpinner("Re-scanning updated servers...").start();
|
|
756
|
+
const afterReports = await Promise.all(
|
|
757
|
+
updatedNames.map((name) => scanServer(name, freshLockfile.servers[name]))
|
|
758
|
+
);
|
|
759
|
+
rescanSpinner.success({ text: "Re-scan complete" });
|
|
760
|
+
console.log(pc.bold("\n Before / After:\n"));
|
|
761
|
+
for (const after of afterReports) {
|
|
762
|
+
const before = reports.find((r) => r.server === after.server);
|
|
763
|
+
const beforeVulns = before?.vulnerabilities.length ?? 0;
|
|
764
|
+
const afterVulns = after.vulnerabilities.length;
|
|
765
|
+
const improved = afterVulns < beforeVulns ? pc.green("improved") : pc.yellow("unchanged");
|
|
766
|
+
console.log(
|
|
767
|
+
` ${pc.bold(after.server)} vulns: ${beforeVulns} \u2192 ${afterVulns} [${improved}]`
|
|
768
|
+
);
|
|
769
|
+
}
|
|
770
|
+
console.log();
|
|
771
|
+
}
|
|
772
|
+
console.log(`
|
|
773
|
+
${successCount} of ${updatable.length} server(s) updated.
|
|
774
|
+
`);
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
// src/commands/config.ts
|
|
778
|
+
import { defineCommand as defineCommand2 } from "citty";
|
|
779
|
+
import pc2 from "picocolors";
|
|
780
|
+
import * as p2 from "@clack/prompts";
|
|
781
|
+
|
|
782
|
+
// src/core/config-service.ts
|
|
783
|
+
import fs3 from "fs";
|
|
784
|
+
import path3 from "path";
|
|
785
|
+
var VALID_KEYS = /* @__PURE__ */ new Set([
|
|
786
|
+
"defaultClient",
|
|
787
|
+
"updateCheckInterval",
|
|
788
|
+
"preferredRegistry",
|
|
789
|
+
"vaultTimeout"
|
|
790
|
+
]);
|
|
791
|
+
function readConfig(configPath = getConfigPath()) {
|
|
792
|
+
try {
|
|
793
|
+
const raw = fs3.readFileSync(configPath, "utf-8");
|
|
794
|
+
const parsed = JSON.parse(raw);
|
|
795
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
796
|
+
return {};
|
|
797
|
+
}
|
|
798
|
+
return parsed;
|
|
799
|
+
} catch {
|
|
800
|
+
return {};
|
|
801
|
+
}
|
|
802
|
+
}
|
|
803
|
+
function writeConfig(data, configPath = getConfigPath()) {
|
|
804
|
+
const dir = path3.dirname(configPath);
|
|
805
|
+
fs3.mkdirSync(dir, { recursive: true });
|
|
806
|
+
const tmp = `${configPath}.tmp`;
|
|
807
|
+
fs3.writeFileSync(tmp, JSON.stringify(data, null, 2), { encoding: "utf-8" });
|
|
808
|
+
fs3.renameSync(tmp, configPath);
|
|
809
|
+
}
|
|
810
|
+
function getConfigValue(key, configPath = getConfigPath()) {
|
|
811
|
+
const data = readConfig(configPath);
|
|
812
|
+
if (!VALID_KEYS.has(key)) return void 0;
|
|
813
|
+
return data[key];
|
|
814
|
+
}
|
|
815
|
+
function setConfigValue(key, value, configPath = getConfigPath()) {
|
|
816
|
+
const data = readConfig(configPath);
|
|
817
|
+
if (!VALID_KEYS.has(key)) {
|
|
818
|
+
throw new Error(`Unknown config key: "${key}". Valid keys: ${[...VALID_KEYS].join(", ")}`);
|
|
819
|
+
}
|
|
820
|
+
data[key] = value;
|
|
821
|
+
writeConfig(data, configPath);
|
|
822
|
+
}
|
|
823
|
+
|
|
824
|
+
// src/commands/config.ts
|
|
825
|
+
function coerceValue(raw) {
|
|
826
|
+
if (raw === "true") return true;
|
|
827
|
+
if (raw === "false") return false;
|
|
828
|
+
const num = Number(raw);
|
|
829
|
+
if (!Number.isNaN(num) && raw.trim() !== "") return num;
|
|
830
|
+
return raw;
|
|
831
|
+
}
|
|
832
|
+
var setCommand = defineCommand2({
|
|
833
|
+
meta: { name: "set", description: "Set a config value" },
|
|
834
|
+
args: {
|
|
835
|
+
key: {
|
|
836
|
+
type: "positional",
|
|
837
|
+
description: "Config key (e.g. defaultClient)",
|
|
838
|
+
required: true
|
|
839
|
+
},
|
|
840
|
+
value: {
|
|
841
|
+
type: "positional",
|
|
842
|
+
description: "Value to set",
|
|
843
|
+
required: true
|
|
844
|
+
}
|
|
845
|
+
},
|
|
846
|
+
run({ args }) {
|
|
847
|
+
try {
|
|
848
|
+
const coerced = coerceValue(args.value);
|
|
849
|
+
setConfigValue(args.key, coerced);
|
|
850
|
+
console.log(
|
|
851
|
+
`${pc2.green("\u2713")} Set ${pc2.bold(args.key)} = ${pc2.cyan(String(coerced))}`
|
|
852
|
+
);
|
|
853
|
+
} catch (err) {
|
|
854
|
+
console.error(`${pc2.red("\u2717")} ${String(err)}`);
|
|
855
|
+
process.exit(1);
|
|
856
|
+
}
|
|
857
|
+
}
|
|
858
|
+
});
|
|
859
|
+
var getCommand = defineCommand2({
|
|
860
|
+
meta: { name: "get", description: "Get a config value" },
|
|
861
|
+
args: {
|
|
862
|
+
key: {
|
|
863
|
+
type: "positional",
|
|
864
|
+
description: "Config key to read",
|
|
865
|
+
required: true
|
|
866
|
+
}
|
|
867
|
+
},
|
|
868
|
+
run({ args }) {
|
|
869
|
+
const val = getConfigValue(args.key);
|
|
870
|
+
if (val === void 0) {
|
|
871
|
+
console.log(pc2.dim(`${args.key}: (not set)`));
|
|
347
872
|
} else {
|
|
348
|
-
|
|
873
|
+
console.log(`${pc2.bold(args.key)}: ${pc2.cyan(String(val))}`);
|
|
349
874
|
}
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
875
|
+
}
|
|
876
|
+
});
|
|
877
|
+
var listCommand = defineCommand2({
|
|
878
|
+
meta: { name: "list", description: "List all config values" },
|
|
879
|
+
run() {
|
|
880
|
+
const data = readConfig();
|
|
881
|
+
const entries = Object.entries(data);
|
|
882
|
+
if (entries.length === 0) {
|
|
883
|
+
console.log(pc2.dim("No config values set. Use `mcpman config set <key> <value>`."));
|
|
884
|
+
return;
|
|
885
|
+
}
|
|
886
|
+
console.log("");
|
|
887
|
+
console.log(pc2.bold("mcpman config:"));
|
|
888
|
+
console.log("");
|
|
889
|
+
for (const [key, val] of entries) {
|
|
890
|
+
console.log(` ${pc2.green("\u25CF")} ${pc2.bold(key)} ${pc2.cyan(String(val))}`);
|
|
891
|
+
}
|
|
892
|
+
console.log("");
|
|
893
|
+
console.log(pc2.dim(` ${entries.length} key${entries.length !== 1 ? "s" : ""} configured`));
|
|
894
|
+
}
|
|
895
|
+
});
|
|
896
|
+
var resetCommand = defineCommand2({
|
|
897
|
+
meta: { name: "reset", description: "Reset config to defaults (removes config file)" },
|
|
898
|
+
async run() {
|
|
899
|
+
const confirmed = await p2.confirm({
|
|
900
|
+
message: "Reset all config values to defaults?",
|
|
901
|
+
initialValue: false
|
|
902
|
+
});
|
|
903
|
+
if (p2.isCancel(confirmed) || !confirmed) {
|
|
904
|
+
p2.cancel("Cancelled.");
|
|
905
|
+
return;
|
|
362
906
|
}
|
|
907
|
+
writeConfig({});
|
|
908
|
+
console.log(`${pc2.green("\u2713")} Config reset to defaults.`);
|
|
909
|
+
}
|
|
910
|
+
});
|
|
911
|
+
var config_default = defineCommand2({
|
|
912
|
+
meta: {
|
|
913
|
+
name: "config",
|
|
914
|
+
description: "Manage mcpman CLI configuration"
|
|
915
|
+
},
|
|
916
|
+
subCommands: {
|
|
917
|
+
set: setCommand,
|
|
918
|
+
get: getCommand,
|
|
919
|
+
list: listCommand,
|
|
920
|
+
reset: resetCommand
|
|
363
921
|
}
|
|
364
922
|
});
|
|
365
923
|
|
|
366
924
|
// src/commands/doctor.ts
|
|
367
|
-
import { defineCommand as
|
|
368
|
-
import
|
|
925
|
+
import { defineCommand as defineCommand3 } from "citty";
|
|
926
|
+
import pc3 from "picocolors";
|
|
369
927
|
|
|
370
928
|
// src/core/server-inventory.ts
|
|
371
929
|
async function getInstalledServers(clientFilter) {
|
|
@@ -629,12 +1187,12 @@ async function quickHealthProbe(config, timeoutMs = 3e3) {
|
|
|
629
1187
|
|
|
630
1188
|
// src/commands/doctor.ts
|
|
631
1189
|
var CHECK_ICON = {
|
|
632
|
-
pass:
|
|
633
|
-
fail:
|
|
634
|
-
skip:
|
|
635
|
-
warn:
|
|
1190
|
+
pass: pc3.green("\u2713"),
|
|
1191
|
+
fail: pc3.red("\u2717"),
|
|
1192
|
+
skip: pc3.dim("-"),
|
|
1193
|
+
warn: pc3.yellow("\u26A0")
|
|
636
1194
|
};
|
|
637
|
-
var doctor_default =
|
|
1195
|
+
var doctor_default = defineCommand3({
|
|
638
1196
|
meta: {
|
|
639
1197
|
name: "doctor",
|
|
640
1198
|
description: "Check MCP server health and configuration"
|
|
@@ -647,10 +1205,10 @@ var doctor_default = defineCommand2({
|
|
|
647
1205
|
}
|
|
648
1206
|
},
|
|
649
1207
|
async run({ args }) {
|
|
650
|
-
console.log(
|
|
1208
|
+
console.log(pc3.bold("\n mcpman doctor\n"));
|
|
651
1209
|
const servers = await getInstalledServers();
|
|
652
1210
|
if (servers.length === 0) {
|
|
653
|
-
console.log(
|
|
1211
|
+
console.log(pc3.dim(" No MCP servers installed. Run mcpman install <server> to get started."));
|
|
654
1212
|
return;
|
|
655
1213
|
}
|
|
656
1214
|
const tasks = servers.map((s) => () => checkServerHealth(s.name, s.config));
|
|
@@ -662,14 +1220,14 @@ var doctor_default = defineCommand2({
|
|
|
662
1220
|
if (result.status === "healthy") passed++;
|
|
663
1221
|
else failed++;
|
|
664
1222
|
}
|
|
665
|
-
console.log(
|
|
1223
|
+
console.log(pc3.dim(" " + "\u2500".repeat(50)));
|
|
666
1224
|
const parts = [];
|
|
667
|
-
if (passed > 0) parts.push(
|
|
668
|
-
if (failed > 0) parts.push(
|
|
1225
|
+
if (passed > 0) parts.push(pc3.green(`${passed} healthy`));
|
|
1226
|
+
if (failed > 0) parts.push(pc3.red(`${failed} unhealthy`));
|
|
669
1227
|
console.log(` Summary: ${parts.join(", ")}`);
|
|
670
1228
|
if (failed > 0) {
|
|
671
1229
|
if (!args.fix) {
|
|
672
|
-
console.log(
|
|
1230
|
+
console.log(pc3.dim(` Run ${pc3.cyan("mcpman doctor --fix")} for fix suggestions.
|
|
673
1231
|
`));
|
|
674
1232
|
}
|
|
675
1233
|
process.exit(1);
|
|
@@ -678,13 +1236,13 @@ var doctor_default = defineCommand2({
|
|
|
678
1236
|
}
|
|
679
1237
|
});
|
|
680
1238
|
function printServerResult(result, showFix) {
|
|
681
|
-
const icon = result.status === "healthy" ?
|
|
682
|
-
console.log(` ${icon} ${
|
|
1239
|
+
const icon = result.status === "healthy" ? pc3.green("\u25CF") : pc3.red("\u25CF");
|
|
1240
|
+
console.log(` ${icon} ${pc3.bold(result.serverName)}`);
|
|
683
1241
|
for (const check of result.checks) {
|
|
684
1242
|
const checkIcon = check.skipped ? CHECK_ICON.skip : check.passed ? CHECK_ICON.pass : CHECK_ICON.fail;
|
|
685
1243
|
console.log(` ${checkIcon} ${check.name}: ${check.message}`);
|
|
686
1244
|
if (showFix && !check.passed && !check.skipped && check.fix) {
|
|
687
|
-
console.log(` ${
|
|
1245
|
+
console.log(` ${pc3.yellow("\u2192")} Fix: ${pc3.cyan(check.fix)}`);
|
|
688
1246
|
}
|
|
689
1247
|
}
|
|
690
1248
|
console.log();
|
|
@@ -693,11 +1251,11 @@ async function runParallel(tasks, concurrency) {
|
|
|
693
1251
|
const results = [];
|
|
694
1252
|
const executing = /* @__PURE__ */ new Set();
|
|
695
1253
|
for (const task of tasks) {
|
|
696
|
-
const
|
|
1254
|
+
const p11 = task().then((r) => {
|
|
697
1255
|
results.push(r);
|
|
698
|
-
executing.delete(
|
|
1256
|
+
executing.delete(p11);
|
|
699
1257
|
});
|
|
700
|
-
executing.add(
|
|
1258
|
+
executing.add(p11);
|
|
701
1259
|
if (executing.size >= concurrency) {
|
|
702
1260
|
await Promise.race(executing);
|
|
703
1261
|
}
|
|
@@ -706,121 +1264,173 @@ async function runParallel(tasks, concurrency) {
|
|
|
706
1264
|
return results;
|
|
707
1265
|
}
|
|
708
1266
|
|
|
709
|
-
// src/commands/
|
|
710
|
-
import { defineCommand as
|
|
711
|
-
import
|
|
712
|
-
import
|
|
1267
|
+
// src/commands/info.ts
|
|
1268
|
+
import { defineCommand as defineCommand4 } from "citty";
|
|
1269
|
+
import pc4 from "picocolors";
|
|
1270
|
+
import { createSpinner as createSpinner2 } from "nanospinner";
|
|
713
1271
|
|
|
714
|
-
// src/core/
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
let
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
1272
|
+
// src/core/package-info.ts
|
|
1273
|
+
async function buildInfo(name, entry, source = "npm") {
|
|
1274
|
+
const resolvedSource = entry?.source ?? source;
|
|
1275
|
+
let weeklyDownloads = 0;
|
|
1276
|
+
let maintainerCount = 0;
|
|
1277
|
+
let packageAge = 0;
|
|
1278
|
+
let lastPublish = "";
|
|
1279
|
+
let deprecated = false;
|
|
1280
|
+
let trustScore = null;
|
|
1281
|
+
let riskLevel = "UNKNOWN";
|
|
1282
|
+
if (resolvedSource === "npm") {
|
|
1283
|
+
const metadata = await fetchNpmMetadata(name);
|
|
1284
|
+
if (!metadata && !entry) {
|
|
1285
|
+
return null;
|
|
1286
|
+
}
|
|
1287
|
+
if (metadata) {
|
|
1288
|
+
weeklyDownloads = metadata.weeklyDownloads;
|
|
1289
|
+
maintainerCount = metadata.maintainerCount;
|
|
1290
|
+
packageAge = metadata.packageAge;
|
|
1291
|
+
lastPublish = metadata.lastPublish;
|
|
1292
|
+
deprecated = metadata.deprecated;
|
|
1293
|
+
const scored = computeTrustScore(metadata, []);
|
|
1294
|
+
trustScore = scored.score;
|
|
1295
|
+
riskLevel = scored.riskLevel;
|
|
733
1296
|
}
|
|
734
|
-
data = await res.json();
|
|
735
|
-
} catch (err) {
|
|
736
|
-
if (err instanceof Error && err.message.includes("not found")) throw err;
|
|
737
|
-
throw new Error(
|
|
738
|
-
`Cannot reach Smithery registry: ${err instanceof Error ? err.message : String(err)}`
|
|
739
|
-
);
|
|
740
1297
|
}
|
|
741
|
-
const version = typeof data.version === "string" ? data.version : "latest";
|
|
742
|
-
const command = typeof data.command === "string" ? data.command : "npx";
|
|
743
|
-
const args = Array.isArray(data.args) ? data.args : ["-y", `${name}@${version}`];
|
|
744
|
-
const envVars = Array.isArray(data.envVars) ? data.envVars : [];
|
|
745
|
-
const resolved = typeof data.resolved === "string" ? data.resolved : `smithery:${name}@${version}`;
|
|
746
1298
|
return {
|
|
747
1299
|
name,
|
|
748
|
-
version,
|
|
749
|
-
description:
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
1300
|
+
version: entry?.version ?? "unknown",
|
|
1301
|
+
description: "",
|
|
1302
|
+
source: resolvedSource,
|
|
1303
|
+
runtime: entry?.runtime ?? "node",
|
|
1304
|
+
envVars: entry?.envVars ?? [],
|
|
1305
|
+
weeklyDownloads,
|
|
1306
|
+
maintainerCount,
|
|
1307
|
+
packageAge,
|
|
1308
|
+
lastPublish,
|
|
1309
|
+
deprecated,
|
|
1310
|
+
trustScore,
|
|
1311
|
+
riskLevel,
|
|
1312
|
+
installedClients: entry?.clients ?? [],
|
|
1313
|
+
isInstalled: entry !== null
|
|
755
1314
|
};
|
|
756
1315
|
}
|
|
757
|
-
async function
|
|
758
|
-
const
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
1316
|
+
async function getPackageInfo(serverName) {
|
|
1317
|
+
const lockfile = readLockfile();
|
|
1318
|
+
const entry = lockfile.servers[serverName] ?? null;
|
|
1319
|
+
return buildInfo(serverName, entry);
|
|
1320
|
+
}
|
|
1321
|
+
|
|
1322
|
+
// src/commands/info.ts
|
|
1323
|
+
function colorRisk2(score, riskLevel) {
|
|
1324
|
+
const label = score !== null ? `${score}/100 (${riskLevel})` : riskLevel;
|
|
1325
|
+
if (riskLevel === "LOW") return pc4.green(label);
|
|
1326
|
+
if (riskLevel === "MEDIUM") return pc4.yellow(label);
|
|
1327
|
+
if (riskLevel === "HIGH") return pc4.red(label);
|
|
1328
|
+
if (riskLevel === "CRITICAL") return pc4.bold(pc4.red(label));
|
|
1329
|
+
return pc4.dim(label);
|
|
1330
|
+
}
|
|
1331
|
+
function formatDaysAgo(isoDate) {
|
|
1332
|
+
if (!isoDate) return "unknown";
|
|
1333
|
+
const days = Math.floor((Date.now() - new Date(isoDate).getTime()) / 864e5);
|
|
1334
|
+
if (days === 0) return "today";
|
|
1335
|
+
if (days === 1) return "1 day ago";
|
|
1336
|
+
return `${days} days ago`;
|
|
1337
|
+
}
|
|
1338
|
+
function printInfo(info2) {
|
|
1339
|
+
const installedBadge = info2.isInstalled ? pc4.green(" [installed]") : pc4.dim(" [not installed]");
|
|
1340
|
+
console.log();
|
|
1341
|
+
console.log(pc4.bold(` ${info2.name}@${info2.version}`) + installedBadge);
|
|
1342
|
+
console.log(pc4.dim(" " + "\u2500".repeat(60)));
|
|
1343
|
+
console.log(` ${pc4.dim("Source:")} ${info2.source}`);
|
|
1344
|
+
console.log(` ${pc4.dim("Runtime:")} ${info2.runtime}`);
|
|
1345
|
+
if (info2.description) {
|
|
1346
|
+
console.log(` ${pc4.dim("Description:")} ${info2.description}`);
|
|
1347
|
+
}
|
|
1348
|
+
if (info2.deprecated) {
|
|
1349
|
+
console.log(` ${pc4.red("[DEPRECATED]")} This package is deprecated`);
|
|
1350
|
+
}
|
|
1351
|
+
console.log();
|
|
1352
|
+
console.log(` ${pc4.bold("Trust & Security")}`);
|
|
1353
|
+
console.log(` ${pc4.dim("Trust score:")} ${colorRisk2(info2.trustScore, info2.riskLevel)}`);
|
|
1354
|
+
if (info2.source === "npm") {
|
|
1355
|
+
console.log(
|
|
1356
|
+
` ${pc4.dim("Downloads:")} ${info2.weeklyDownloads.toLocaleString()}/week ${pc4.dim("|")} ${pc4.dim("Age:")} ${info2.packageAge}d ${pc4.dim("|")} ${pc4.dim("Maintainers:")} ${info2.maintainerCount}`
|
|
776
1357
|
);
|
|
1358
|
+
if (info2.lastPublish) {
|
|
1359
|
+
console.log(` ${pc4.dim("Last publish:")} ${formatDaysAgo(info2.lastPublish)}`);
|
|
1360
|
+
}
|
|
1361
|
+
} else {
|
|
1362
|
+
console.log(pc4.dim(" (Trust data available for npm packages only)"));
|
|
777
1363
|
}
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
runtime: "node",
|
|
787
|
-
command: "npx",
|
|
788
|
-
args: ["-y", `${packageName}@${version}`],
|
|
789
|
-
envVars,
|
|
790
|
-
resolved
|
|
791
|
-
};
|
|
792
|
-
}
|
|
793
|
-
async function resolveFromGitHub(githubUrl) {
|
|
794
|
-
const match = githubUrl.match(/github\.com\/([^/]+)\/([^/]+)/);
|
|
795
|
-
if (!match) {
|
|
796
|
-
throw new Error(`Invalid GitHub URL: ${githubUrl}`);
|
|
1364
|
+
console.log();
|
|
1365
|
+
console.log(` ${pc4.bold("Environment Variables")}`);
|
|
1366
|
+
if (info2.envVars.length > 0) {
|
|
1367
|
+
for (const env of info2.envVars) {
|
|
1368
|
+
console.log(` ${pc4.cyan("\u2022")} ${env}`);
|
|
1369
|
+
}
|
|
1370
|
+
} else {
|
|
1371
|
+
console.log(pc4.dim(" none required"));
|
|
797
1372
|
}
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
if (res.ok) {
|
|
804
|
-
pkgData = await res.json();
|
|
1373
|
+
console.log();
|
|
1374
|
+
console.log(` ${pc4.bold("Installed Clients")}`);
|
|
1375
|
+
if (info2.installedClients.length > 0) {
|
|
1376
|
+
for (const client of info2.installedClients) {
|
|
1377
|
+
console.log(` ${pc4.green("\u2713")} ${client}`);
|
|
805
1378
|
}
|
|
806
|
-
}
|
|
1379
|
+
} else {
|
|
1380
|
+
console.log(pc4.dim(" Not installed in any client"));
|
|
807
1381
|
}
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
name,
|
|
812
|
-
version,
|
|
813
|
-
description: typeof pkgData.description === "string" ? pkgData.description : "",
|
|
814
|
-
runtime: "node",
|
|
815
|
-
command: "npx",
|
|
816
|
-
args: ["-y", githubUrl],
|
|
817
|
-
envVars: [],
|
|
818
|
-
resolved: githubUrl
|
|
819
|
-
};
|
|
1382
|
+
console.log();
|
|
1383
|
+
console.log(pc4.dim(" " + "\u2500".repeat(60)));
|
|
1384
|
+
console.log();
|
|
820
1385
|
}
|
|
1386
|
+
var info_default = defineCommand4({
|
|
1387
|
+
meta: {
|
|
1388
|
+
name: "info",
|
|
1389
|
+
description: "Show detailed metadata for an MCP server (installed or from registry)"
|
|
1390
|
+
},
|
|
1391
|
+
args: {
|
|
1392
|
+
server: {
|
|
1393
|
+
type: "positional",
|
|
1394
|
+
description: "Server name (e.g. @modelcontextprotocol/server-filesystem)",
|
|
1395
|
+
required: true
|
|
1396
|
+
},
|
|
1397
|
+
json: {
|
|
1398
|
+
type: "boolean",
|
|
1399
|
+
description: "Output results as JSON",
|
|
1400
|
+
default: false
|
|
1401
|
+
}
|
|
1402
|
+
},
|
|
1403
|
+
async run({ args }) {
|
|
1404
|
+
const spinner5 = createSpinner2(`Fetching info for ${args.server}...`).start();
|
|
1405
|
+
let info2;
|
|
1406
|
+
try {
|
|
1407
|
+
info2 = await getPackageInfo(args.server);
|
|
1408
|
+
} catch (err) {
|
|
1409
|
+
spinner5.error({ text: "Failed to fetch package info" });
|
|
1410
|
+
console.error(pc4.red(String(err)));
|
|
1411
|
+
process.exit(1);
|
|
1412
|
+
}
|
|
1413
|
+
if (!info2) {
|
|
1414
|
+
spinner5.error({ text: `Package not found: ${args.server}` });
|
|
1415
|
+
console.log(pc4.dim(`
|
|
1416
|
+
"${args.server}" was not found in the npm registry or your lockfile.
|
|
1417
|
+
`));
|
|
1418
|
+
process.exit(1);
|
|
1419
|
+
}
|
|
1420
|
+
spinner5.success({ text: `Found ${args.server}` });
|
|
1421
|
+
if (args.json) {
|
|
1422
|
+
console.log(JSON.stringify(info2, null, 2));
|
|
1423
|
+
return;
|
|
1424
|
+
}
|
|
1425
|
+
printInfo(info2);
|
|
1426
|
+
}
|
|
1427
|
+
});
|
|
821
1428
|
|
|
822
1429
|
// src/commands/init.ts
|
|
823
|
-
|
|
1430
|
+
import { defineCommand as defineCommand5 } from "citty";
|
|
1431
|
+
import * as p3 from "@clack/prompts";
|
|
1432
|
+
import path4 from "path";
|
|
1433
|
+
var init_default = defineCommand5({
|
|
824
1434
|
meta: {
|
|
825
1435
|
name: "init",
|
|
826
1436
|
description: "Initialize mcpman.lock in the current project"
|
|
@@ -835,27 +1445,27 @@ var init_default = defineCommand3({
|
|
|
835
1445
|
},
|
|
836
1446
|
async run({ args }) {
|
|
837
1447
|
const nonInteractive = args.yes || !process.stdout.isTTY;
|
|
838
|
-
|
|
839
|
-
const targetPath =
|
|
1448
|
+
p3.intro("mcpman init");
|
|
1449
|
+
const targetPath = path4.join(process.cwd(), LOCKFILE_NAME);
|
|
840
1450
|
const existing = findLockfile();
|
|
841
1451
|
if (existing) {
|
|
842
1452
|
if (nonInteractive) {
|
|
843
|
-
|
|
1453
|
+
p3.log.warn(`Lockfile already exists: ${existing} \u2014 overwriting (non-interactive).`);
|
|
844
1454
|
} else {
|
|
845
|
-
|
|
846
|
-
const overwrite = await
|
|
847
|
-
if (
|
|
848
|
-
|
|
1455
|
+
p3.log.warn(`Lockfile already exists: ${existing}`);
|
|
1456
|
+
const overwrite = await p3.confirm({ message: "Overwrite?" });
|
|
1457
|
+
if (p3.isCancel(overwrite) || !overwrite) {
|
|
1458
|
+
p3.outro("Cancelled.");
|
|
849
1459
|
return;
|
|
850
1460
|
}
|
|
851
1461
|
}
|
|
852
1462
|
}
|
|
853
1463
|
let clients = [];
|
|
854
1464
|
try {
|
|
855
|
-
const mod = await import("./client-detector-
|
|
1465
|
+
const mod = await import("./client-detector-UAP2EYZA.js");
|
|
856
1466
|
clients = await mod.getInstalledClients();
|
|
857
1467
|
} catch {
|
|
858
|
-
|
|
1468
|
+
p3.log.warn("Could not detect AI clients \u2014 creating empty lockfile.");
|
|
859
1469
|
}
|
|
860
1470
|
const clientServers = [];
|
|
861
1471
|
for (const client of clients) {
|
|
@@ -869,26 +1479,26 @@ var init_default = defineCommand3({
|
|
|
869
1479
|
}
|
|
870
1480
|
createEmptyLockfile(targetPath);
|
|
871
1481
|
if (clientServers.length === 0) {
|
|
872
|
-
|
|
873
|
-
|
|
1482
|
+
p3.log.info("No existing servers found in any client config.");
|
|
1483
|
+
p3.outro(`Created ${LOCKFILE_NAME} \u2014 add it to version control!`);
|
|
874
1484
|
return;
|
|
875
1485
|
}
|
|
876
1486
|
let selected;
|
|
877
1487
|
if (nonInteractive) {
|
|
878
1488
|
selected = clientServers.map((cs) => cs.client.type);
|
|
879
|
-
|
|
1489
|
+
p3.log.info(`Non-interactive mode: importing all ${clientServers.length} client(s).`);
|
|
880
1490
|
} else {
|
|
881
1491
|
const options = clientServers.map((cs) => ({
|
|
882
1492
|
value: cs.client.type,
|
|
883
1493
|
label: `${cs.client.displayName} (${Object.keys(cs.servers).length} servers)`
|
|
884
1494
|
}));
|
|
885
|
-
const toImport = await
|
|
1495
|
+
const toImport = await p3.multiselect({
|
|
886
1496
|
message: "Import existing servers into lockfile?",
|
|
887
1497
|
options,
|
|
888
1498
|
required: false
|
|
889
1499
|
});
|
|
890
|
-
if (
|
|
891
|
-
|
|
1500
|
+
if (p3.isCancel(toImport)) {
|
|
1501
|
+
p3.outro(`Created empty ${LOCKFILE_NAME}`);
|
|
892
1502
|
return;
|
|
893
1503
|
}
|
|
894
1504
|
selected = toImport;
|
|
@@ -914,121 +1524,122 @@ var init_default = defineCommand3({
|
|
|
914
1524
|
importCount++;
|
|
915
1525
|
}
|
|
916
1526
|
}
|
|
917
|
-
|
|
1527
|
+
p3.outro(
|
|
918
1528
|
`Created ${LOCKFILE_NAME} with ${importCount} server(s) \u2014 commit to version control!`
|
|
919
1529
|
);
|
|
920
1530
|
}
|
|
921
1531
|
});
|
|
922
1532
|
|
|
923
1533
|
// src/commands/install.ts
|
|
924
|
-
import { defineCommand as
|
|
1534
|
+
import { defineCommand as defineCommand6 } from "citty";
|
|
925
1535
|
|
|
926
1536
|
// src/core/installer.ts
|
|
927
|
-
import * as
|
|
1537
|
+
import * as p5 from "@clack/prompts";
|
|
928
1538
|
|
|
929
|
-
// src/core/
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
}
|
|
937
|
-
return { type: "npm", input };
|
|
938
|
-
}
|
|
939
|
-
function parseEnvFlags(envFlags) {
|
|
940
|
-
if (!envFlags) return {};
|
|
941
|
-
const flags = Array.isArray(envFlags) ? envFlags : [envFlags];
|
|
942
|
-
const result = {};
|
|
943
|
-
for (const flag of flags) {
|
|
944
|
-
const idx = flag.indexOf("=");
|
|
945
|
-
if (idx > 0) {
|
|
946
|
-
result[flag.slice(0, idx)] = flag.slice(idx + 1);
|
|
1539
|
+
// src/core/installer-vault-helpers.ts
|
|
1540
|
+
import * as p4 from "@clack/prompts";
|
|
1541
|
+
async function tryLoadVaultSecrets(serverName) {
|
|
1542
|
+
try {
|
|
1543
|
+
const entries = listSecrets(serverName);
|
|
1544
|
+
if (entries.length === 0 || entries[0].keys.length === 0) {
|
|
1545
|
+
return {};
|
|
947
1546
|
}
|
|
1547
|
+
const password = await getMasterPassword();
|
|
1548
|
+
return getSecretsForServer(serverName, password);
|
|
1549
|
+
} catch {
|
|
1550
|
+
return {};
|
|
948
1551
|
}
|
|
949
|
-
return result;
|
|
950
1552
|
}
|
|
951
|
-
async function
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
1553
|
+
async function offerVaultSave(serverName, newVars, yes) {
|
|
1554
|
+
if (Object.keys(newVars).length === 0) return;
|
|
1555
|
+
if (yes) return;
|
|
1556
|
+
try {
|
|
1557
|
+
const save = await p4.confirm({
|
|
1558
|
+
message: `Save ${Object.keys(newVars).length} env var(s) to encrypted vault for future installs?`
|
|
1559
|
+
});
|
|
1560
|
+
if (p4.isCancel(save) || !save) return;
|
|
1561
|
+
const password = await getMasterPassword();
|
|
1562
|
+
for (const [key, value] of Object.entries(newVars)) {
|
|
1563
|
+
setSecret(serverName, key, value, password);
|
|
1564
|
+
}
|
|
1565
|
+
p4.log.success(`Credentials saved to vault for '${serverName}'`);
|
|
1566
|
+
} catch (err) {
|
|
1567
|
+
p4.log.warn(`Could not save to vault: ${err instanceof Error ? err.message : String(err)}`);
|
|
960
1568
|
}
|
|
961
1569
|
}
|
|
962
1570
|
|
|
963
1571
|
// src/core/installer.ts
|
|
964
|
-
async function
|
|
1572
|
+
async function loadClients2() {
|
|
965
1573
|
try {
|
|
966
|
-
const mod = await import("./client-detector-
|
|
1574
|
+
const mod = await import("./client-detector-UAP2EYZA.js");
|
|
967
1575
|
return mod.getInstalledClients();
|
|
968
1576
|
} catch {
|
|
969
1577
|
return [];
|
|
970
1578
|
}
|
|
971
1579
|
}
|
|
972
1580
|
async function installServer(input, options = {}) {
|
|
973
|
-
|
|
974
|
-
const spinner5 =
|
|
1581
|
+
p5.intro("mcpman install");
|
|
1582
|
+
const spinner5 = p5.spinner();
|
|
975
1583
|
spinner5.start("Resolving server...");
|
|
976
1584
|
let metadata;
|
|
977
1585
|
try {
|
|
978
1586
|
metadata = await resolveServer(input);
|
|
979
1587
|
} catch (err) {
|
|
980
1588
|
spinner5.stop("Resolution failed");
|
|
981
|
-
|
|
1589
|
+
p5.log.error(err instanceof Error ? err.message : String(err));
|
|
982
1590
|
process.exit(1);
|
|
983
1591
|
}
|
|
984
1592
|
spinner5.stop(`Found: ${metadata.name}@${metadata.version}`);
|
|
985
|
-
const clients = await
|
|
1593
|
+
const clients = await loadClients2();
|
|
986
1594
|
if (clients.length === 0) {
|
|
987
|
-
|
|
988
|
-
|
|
1595
|
+
p5.log.warn("No supported AI clients detected on this machine.");
|
|
1596
|
+
p5.log.info("Supported: Claude Desktop, Cursor, VS Code, Windsurf");
|
|
989
1597
|
process.exit(1);
|
|
990
1598
|
}
|
|
991
1599
|
let selectedClients;
|
|
992
1600
|
if (options.client) {
|
|
993
1601
|
const found = clients.find((c) => c.type === options.client || c.displayName.toLowerCase() === options.client?.toLowerCase());
|
|
994
1602
|
if (!found) {
|
|
995
|
-
|
|
996
|
-
|
|
1603
|
+
p5.log.error(`Client '${options.client}' not found or not installed.`);
|
|
1604
|
+
p5.log.info(`Available: ${clients.map((c) => c.type).join(", ")}`);
|
|
997
1605
|
process.exit(1);
|
|
998
1606
|
}
|
|
999
1607
|
selectedClients = [found];
|
|
1000
1608
|
} else if (options.yes || clients.length === 1) {
|
|
1001
1609
|
selectedClients = clients;
|
|
1002
1610
|
} else {
|
|
1003
|
-
const chosen = await
|
|
1611
|
+
const chosen = await p5.multiselect({
|
|
1004
1612
|
message: "Install to which client(s)?",
|
|
1005
1613
|
options: clients.map((c) => ({ value: c.type, label: c.displayName })),
|
|
1006
1614
|
required: true
|
|
1007
1615
|
});
|
|
1008
|
-
if (
|
|
1009
|
-
|
|
1616
|
+
if (p5.isCancel(chosen)) {
|
|
1617
|
+
p5.outro("Cancelled.");
|
|
1010
1618
|
process.exit(0);
|
|
1011
1619
|
}
|
|
1012
1620
|
selectedClients = clients.filter((c) => chosen.includes(c.type));
|
|
1013
1621
|
}
|
|
1014
1622
|
const providedEnv = parseEnvFlags(options.env);
|
|
1015
|
-
const
|
|
1623
|
+
const vaultEnv = await tryLoadVaultSecrets(metadata.name);
|
|
1624
|
+
const collectedEnv = { ...vaultEnv, ...providedEnv };
|
|
1625
|
+
const newlyEnteredVars = {};
|
|
1016
1626
|
const requiredVars = metadata.envVars.filter((e) => e.required && !(e.name in collectedEnv));
|
|
1017
1627
|
for (const envVar of requiredVars) {
|
|
1018
1628
|
if (options.yes && envVar.default) {
|
|
1019
1629
|
collectedEnv[envVar.name] = envVar.default;
|
|
1020
1630
|
continue;
|
|
1021
1631
|
}
|
|
1022
|
-
const val = await
|
|
1632
|
+
const val = await p5.text({
|
|
1023
1633
|
message: `${envVar.name}${envVar.description ? ` \u2014 ${envVar.description}` : ""}`,
|
|
1024
1634
|
placeholder: envVar.default ?? "",
|
|
1025
1635
|
validate: (v) => envVar.required && !v ? "Required" : void 0
|
|
1026
1636
|
});
|
|
1027
|
-
if (
|
|
1028
|
-
|
|
1637
|
+
if (p5.isCancel(val)) {
|
|
1638
|
+
p5.outro("Cancelled.");
|
|
1029
1639
|
process.exit(0);
|
|
1030
1640
|
}
|
|
1031
1641
|
collectedEnv[envVar.name] = val;
|
|
1642
|
+
newlyEnteredVars[envVar.name] = val;
|
|
1032
1643
|
}
|
|
1033
1644
|
const entry = {
|
|
1034
1645
|
command: metadata.command,
|
|
@@ -1043,7 +1654,7 @@ async function installServer(input, options = {}) {
|
|
|
1043
1654
|
clientTypes.push(client.type);
|
|
1044
1655
|
} catch (err) {
|
|
1045
1656
|
spinner5.stop("Partial failure");
|
|
1046
|
-
|
|
1657
|
+
p5.log.warn(`Failed to write to ${client.displayName}: ${err instanceof Error ? err.message : String(err)}`);
|
|
1047
1658
|
}
|
|
1048
1659
|
}
|
|
1049
1660
|
spinner5.stop("Config written");
|
|
@@ -1062,12 +1673,13 @@ async function installServer(input, options = {}) {
|
|
|
1062
1673
|
clients: clientTypes
|
|
1063
1674
|
});
|
|
1064
1675
|
const lockPath = findLockfile() ?? "mcpman.lock (global)";
|
|
1065
|
-
|
|
1066
|
-
|
|
1676
|
+
p5.log.success(`Lockfile updated: ${lockPath}`);
|
|
1677
|
+
await offerVaultSave(metadata.name, newlyEnteredVars, options.yes ?? false);
|
|
1678
|
+
p5.outro(`${metadata.name}@${metadata.version} installed to ${clientTypes.join(", ")}`);
|
|
1067
1679
|
}
|
|
1068
1680
|
|
|
1069
1681
|
// src/utils/logger.ts
|
|
1070
|
-
import
|
|
1682
|
+
import pc5 from "picocolors";
|
|
1071
1683
|
var noColor = process.env.NO_COLOR !== void 0 || process.argv.includes("--no-color");
|
|
1072
1684
|
var isVerbose = process.argv.includes("--verbose");
|
|
1073
1685
|
var isJson = process.argv.includes("--json");
|
|
@@ -1076,19 +1688,19 @@ function colorize(fn, text2) {
|
|
|
1076
1688
|
}
|
|
1077
1689
|
function info(message) {
|
|
1078
1690
|
if (isJson) return;
|
|
1079
|
-
console.log(`${colorize(
|
|
1691
|
+
console.log(`${colorize(pc5.cyan, "i")} ${message}`);
|
|
1080
1692
|
}
|
|
1081
1693
|
function error(message) {
|
|
1082
1694
|
if (isJson) return;
|
|
1083
|
-
console.error(`${colorize(
|
|
1695
|
+
console.error(`${colorize(pc5.red, "\u2717")} ${message}`);
|
|
1084
1696
|
}
|
|
1085
1697
|
function json(data) {
|
|
1086
1698
|
console.log(JSON.stringify(data, null, 2));
|
|
1087
1699
|
}
|
|
1088
1700
|
|
|
1089
1701
|
// src/commands/install.ts
|
|
1090
|
-
import * as
|
|
1091
|
-
var install_default =
|
|
1702
|
+
import * as p6 from "@clack/prompts";
|
|
1703
|
+
var install_default = defineCommand6({
|
|
1092
1704
|
meta: {
|
|
1093
1705
|
name: "install",
|
|
1094
1706
|
description: "Install an MCP server into one or more AI clients"
|
|
@@ -1137,8 +1749,8 @@ async function restoreFromLockfile() {
|
|
|
1137
1749
|
info("Lockfile is empty \u2014 nothing to restore.");
|
|
1138
1750
|
return;
|
|
1139
1751
|
}
|
|
1140
|
-
|
|
1141
|
-
|
|
1752
|
+
p6.intro(`mcpman install (restore from ${lockPath})`);
|
|
1753
|
+
p6.log.info(`Restoring ${entries.length} server(s)...`);
|
|
1142
1754
|
for (const [name, entry] of entries) {
|
|
1143
1755
|
const input = entry.source === "smithery" ? `smithery:${name}` : entry.source === "github" ? entry.resolved : name;
|
|
1144
1756
|
await installServer(input, {
|
|
@@ -1146,18 +1758,18 @@ async function restoreFromLockfile() {
|
|
|
1146
1758
|
yes: true
|
|
1147
1759
|
});
|
|
1148
1760
|
}
|
|
1149
|
-
|
|
1761
|
+
p6.outro("Restore complete.");
|
|
1150
1762
|
}
|
|
1151
1763
|
|
|
1152
1764
|
// src/commands/list.ts
|
|
1153
|
-
import { defineCommand as
|
|
1154
|
-
import
|
|
1765
|
+
import { defineCommand as defineCommand7 } from "citty";
|
|
1766
|
+
import pc6 from "picocolors";
|
|
1155
1767
|
var STATUS_ICON = {
|
|
1156
|
-
healthy:
|
|
1157
|
-
unhealthy:
|
|
1158
|
-
unknown:
|
|
1768
|
+
healthy: pc6.green("\u25CF"),
|
|
1769
|
+
unhealthy: pc6.red("\u25CF"),
|
|
1770
|
+
unknown: pc6.dim("\u25CB")
|
|
1159
1771
|
};
|
|
1160
|
-
var list_default =
|
|
1772
|
+
var list_default = defineCommand7({
|
|
1161
1773
|
meta: {
|
|
1162
1774
|
name: "list",
|
|
1163
1775
|
description: "List installed MCP servers"
|
|
@@ -1177,7 +1789,7 @@ var list_default = defineCommand5({
|
|
|
1177
1789
|
const servers = await getInstalledServers(args.client);
|
|
1178
1790
|
if (servers.length === 0) {
|
|
1179
1791
|
const filter = args.client ? ` for client "${args.client}"` : "";
|
|
1180
|
-
console.log(
|
|
1792
|
+
console.log(pc6.dim(`No MCP servers installed${filter}. Run ${pc6.cyan("mcpman install <server>")} to get started.`));
|
|
1181
1793
|
return;
|
|
1182
1794
|
}
|
|
1183
1795
|
const withStatus = await Promise.all(
|
|
@@ -1201,8 +1813,8 @@ var list_default = defineCommand5({
|
|
|
1201
1813
|
const nameWidth = Math.max(4, ...withStatus.map((s) => s.name.length));
|
|
1202
1814
|
const clientsWidth = Math.max(7, ...withStatus.map((s) => formatClients(s.clients).length));
|
|
1203
1815
|
const header = ` ${pad("NAME", nameWidth)} ${pad("CLIENT(S)", clientsWidth)} ${pad("COMMAND", 20)} STATUS`;
|
|
1204
|
-
console.log(
|
|
1205
|
-
console.log(
|
|
1816
|
+
console.log(pc6.dim(header));
|
|
1817
|
+
console.log(pc6.dim(` ${"-".repeat(nameWidth)} ${"-".repeat(clientsWidth)} ${"-".repeat(20)} ------`));
|
|
1206
1818
|
for (const s of withStatus) {
|
|
1207
1819
|
const icon = STATUS_ICON[s.status];
|
|
1208
1820
|
const clientsStr = formatClients(s.clients);
|
|
@@ -1210,7 +1822,7 @@ var list_default = defineCommand5({
|
|
|
1210
1822
|
console.log(` ${pad(s.name, nameWidth)} ${pad(clientsStr, clientsWidth)} ${pad(cmdStr, 20)} ${icon} ${s.status}`);
|
|
1211
1823
|
}
|
|
1212
1824
|
const clientSet = new Set(withStatus.flatMap((s) => s.clients));
|
|
1213
|
-
console.log(
|
|
1825
|
+
console.log(pc6.dim(`
|
|
1214
1826
|
${withStatus.length} server${withStatus.length !== 1 ? "s" : ""} \xB7 ${clientSet.size} client${clientSet.size !== 1 ? "s" : ""}`));
|
|
1215
1827
|
}
|
|
1216
1828
|
});
|
|
@@ -1231,9 +1843,9 @@ function formatClients(clients) {
|
|
|
1231
1843
|
}
|
|
1232
1844
|
|
|
1233
1845
|
// src/commands/remove.ts
|
|
1234
|
-
import { defineCommand as
|
|
1235
|
-
import * as
|
|
1236
|
-
import
|
|
1846
|
+
import { defineCommand as defineCommand8 } from "citty";
|
|
1847
|
+
import * as p7 from "@clack/prompts";
|
|
1848
|
+
import pc7 from "picocolors";
|
|
1237
1849
|
var CLIENT_DISPLAY2 = {
|
|
1238
1850
|
"claude-desktop": "Claude",
|
|
1239
1851
|
cursor: "Cursor",
|
|
@@ -1243,7 +1855,7 @@ var CLIENT_DISPLAY2 = {
|
|
|
1243
1855
|
function clientDisplayName(type) {
|
|
1244
1856
|
return CLIENT_DISPLAY2[type] ?? type;
|
|
1245
1857
|
}
|
|
1246
|
-
var remove_default =
|
|
1858
|
+
var remove_default = defineCommand8({
|
|
1247
1859
|
meta: {
|
|
1248
1860
|
name: "remove",
|
|
1249
1861
|
description: "Remove an MCP server from one or more AI clients"
|
|
@@ -1270,17 +1882,17 @@ var remove_default = defineCommand6({
|
|
|
1270
1882
|
}
|
|
1271
1883
|
},
|
|
1272
1884
|
async run({ args }) {
|
|
1273
|
-
|
|
1885
|
+
p7.intro(pc7.bold("mcpman remove"));
|
|
1274
1886
|
const serverName = args.server;
|
|
1275
1887
|
const servers = await getInstalledServers();
|
|
1276
1888
|
const match = servers.find((s) => s.name === serverName);
|
|
1277
1889
|
if (!match) {
|
|
1278
|
-
|
|
1890
|
+
p7.log.warn(`Server "${serverName}" is not installed.`);
|
|
1279
1891
|
const similar = servers.filter((s) => s.name.includes(serverName) || serverName.includes(s.name));
|
|
1280
1892
|
if (similar.length > 0) {
|
|
1281
|
-
|
|
1893
|
+
p7.log.info(`Did you mean: ${similar.map((s) => pc7.cyan(s.name)).join(", ")}?`);
|
|
1282
1894
|
}
|
|
1283
|
-
|
|
1895
|
+
p7.outro("Nothing to remove.");
|
|
1284
1896
|
return;
|
|
1285
1897
|
}
|
|
1286
1898
|
let targetClients;
|
|
@@ -1288,15 +1900,15 @@ var remove_default = defineCommand6({
|
|
|
1288
1900
|
targetClients = match.clients;
|
|
1289
1901
|
} else if (args.client) {
|
|
1290
1902
|
if (!match.clients.includes(args.client)) {
|
|
1291
|
-
|
|
1292
|
-
|
|
1903
|
+
p7.log.warn(`Server "${serverName}" is not installed in client "${args.client}".`);
|
|
1904
|
+
p7.outro("Nothing to remove.");
|
|
1293
1905
|
return;
|
|
1294
1906
|
}
|
|
1295
1907
|
targetClients = [args.client];
|
|
1296
1908
|
} else if (match.clients.length === 1) {
|
|
1297
1909
|
targetClients = match.clients;
|
|
1298
1910
|
} else {
|
|
1299
|
-
const selected = await
|
|
1911
|
+
const selected = await p7.multiselect({
|
|
1300
1912
|
message: `Remove "${serverName}" from which clients?`,
|
|
1301
1913
|
options: match.clients.map((c) => ({
|
|
1302
1914
|
value: c,
|
|
@@ -1304,19 +1916,19 @@ var remove_default = defineCommand6({
|
|
|
1304
1916
|
})),
|
|
1305
1917
|
required: true
|
|
1306
1918
|
});
|
|
1307
|
-
if (
|
|
1308
|
-
|
|
1919
|
+
if (p7.isCancel(selected)) {
|
|
1920
|
+
p7.outro("Cancelled.");
|
|
1309
1921
|
process.exit(0);
|
|
1310
1922
|
}
|
|
1311
1923
|
targetClients = selected;
|
|
1312
1924
|
}
|
|
1313
1925
|
if (!args.yes) {
|
|
1314
1926
|
const clientNames = targetClients.map(clientDisplayName).join(", ");
|
|
1315
|
-
const confirmed = await
|
|
1316
|
-
message: `Remove ${
|
|
1927
|
+
const confirmed = await p7.confirm({
|
|
1928
|
+
message: `Remove ${pc7.cyan(serverName)} from ${pc7.yellow(clientNames)}?`
|
|
1317
1929
|
});
|
|
1318
|
-
if (
|
|
1319
|
-
|
|
1930
|
+
if (p7.isCancel(confirmed) || !confirmed) {
|
|
1931
|
+
p7.outro("Cancelled.");
|
|
1320
1932
|
return;
|
|
1321
1933
|
}
|
|
1322
1934
|
}
|
|
@@ -1330,25 +1942,268 @@ var remove_default = defineCommand6({
|
|
|
1330
1942
|
}
|
|
1331
1943
|
try {
|
|
1332
1944
|
await handler.removeServer(serverName);
|
|
1333
|
-
|
|
1945
|
+
p7.log.success(`Removed from ${clientDisplayName(clientType)}`);
|
|
1334
1946
|
} catch (err) {
|
|
1335
1947
|
const msg = err instanceof Error ? err.message : String(err);
|
|
1336
1948
|
errors.push(`${clientDisplayName(clientType)}: ${msg}`);
|
|
1337
1949
|
}
|
|
1338
1950
|
}
|
|
1339
1951
|
if (errors.length > 0) {
|
|
1340
|
-
for (const e of errors)
|
|
1341
|
-
|
|
1952
|
+
for (const e of errors) p7.log.error(e);
|
|
1953
|
+
p7.outro(pc7.red("Completed with errors."));
|
|
1342
1954
|
process.exit(1);
|
|
1343
1955
|
}
|
|
1344
|
-
|
|
1956
|
+
p7.outro(pc7.green(`Removed "${serverName}" successfully.`));
|
|
1957
|
+
}
|
|
1958
|
+
});
|
|
1959
|
+
|
|
1960
|
+
// src/commands/run.ts
|
|
1961
|
+
import { defineCommand as defineCommand9 } from "citty";
|
|
1962
|
+
import { spawn as spawn2 } from "child_process";
|
|
1963
|
+
import pc8 from "picocolors";
|
|
1964
|
+
var run_default = defineCommand9({
|
|
1965
|
+
meta: {
|
|
1966
|
+
name: "run",
|
|
1967
|
+
description: "Run an installed MCP server with vault secrets injected"
|
|
1968
|
+
},
|
|
1969
|
+
args: {
|
|
1970
|
+
server: {
|
|
1971
|
+
type: "positional",
|
|
1972
|
+
description: "Server name to run (as installed in lockfile)",
|
|
1973
|
+
required: true
|
|
1974
|
+
},
|
|
1975
|
+
env: {
|
|
1976
|
+
type: "string",
|
|
1977
|
+
description: "Override env var KEY=VAL (repeatable)",
|
|
1978
|
+
alias: "e"
|
|
1979
|
+
}
|
|
1980
|
+
},
|
|
1981
|
+
async run({ args }) {
|
|
1982
|
+
const serverName = args.server;
|
|
1983
|
+
const lockfile = readLockfile();
|
|
1984
|
+
const entry = lockfile.servers[serverName];
|
|
1985
|
+
if (!entry) {
|
|
1986
|
+
console.error(pc8.red(` Error: Server '${serverName}' is not installed.`));
|
|
1987
|
+
console.error(pc8.dim(` Run ${pc8.cyan("mcpman install <server>")} to install it first.`));
|
|
1988
|
+
process.exit(1);
|
|
1989
|
+
}
|
|
1990
|
+
const lockfileEnv = parseEnvFlags(entry.envVars);
|
|
1991
|
+
const vaultEnv = await loadVaultSecrets(serverName);
|
|
1992
|
+
const cliEnv = parseEnvFlags(args.env);
|
|
1993
|
+
const finalEnv = {
|
|
1994
|
+
...process.env,
|
|
1995
|
+
...lockfileEnv,
|
|
1996
|
+
...vaultEnv,
|
|
1997
|
+
...cliEnv
|
|
1998
|
+
};
|
|
1999
|
+
console.log(pc8.dim(` Running ${pc8.cyan(serverName)}...`));
|
|
2000
|
+
const child = spawn2(entry.command, entry.args, {
|
|
2001
|
+
env: finalEnv,
|
|
2002
|
+
stdio: "inherit"
|
|
2003
|
+
});
|
|
2004
|
+
const forwardSignal = (signal) => {
|
|
2005
|
+
if (!child.killed) {
|
|
2006
|
+
child.kill(signal);
|
|
2007
|
+
}
|
|
2008
|
+
};
|
|
2009
|
+
process.on("SIGINT", () => forwardSignal("SIGINT"));
|
|
2010
|
+
process.on("SIGTERM", () => forwardSignal("SIGTERM"));
|
|
2011
|
+
await new Promise((resolve) => {
|
|
2012
|
+
child.on("close", (code) => {
|
|
2013
|
+
process.exit(code ?? 0);
|
|
2014
|
+
resolve();
|
|
2015
|
+
});
|
|
2016
|
+
child.on("error", (err) => {
|
|
2017
|
+
console.error(pc8.red(` Failed to start '${serverName}': ${err.message}`));
|
|
2018
|
+
process.exit(1);
|
|
2019
|
+
resolve();
|
|
2020
|
+
});
|
|
2021
|
+
});
|
|
2022
|
+
}
|
|
2023
|
+
});
|
|
2024
|
+
async function loadVaultSecrets(serverName) {
|
|
2025
|
+
try {
|
|
2026
|
+
const entries = listSecrets(serverName);
|
|
2027
|
+
if (entries.length === 0 || entries[0].keys.length === 0) {
|
|
2028
|
+
return {};
|
|
2029
|
+
}
|
|
2030
|
+
const password = await getMasterPassword();
|
|
2031
|
+
return getSecretsForServer(serverName, password);
|
|
2032
|
+
} catch {
|
|
2033
|
+
console.warn(pc8.yellow(" Warning: Could not load vault secrets, continuing without them."));
|
|
2034
|
+
return {};
|
|
2035
|
+
}
|
|
2036
|
+
}
|
|
2037
|
+
|
|
2038
|
+
// src/commands/search.ts
|
|
2039
|
+
import { defineCommand as defineCommand10 } from "citty";
|
|
2040
|
+
import pc9 from "picocolors";
|
|
2041
|
+
import { createSpinner as createSpinner3 } from "nanospinner";
|
|
2042
|
+
|
|
2043
|
+
// src/core/registry-search.ts
|
|
2044
|
+
var SEARCH_TIMEOUT_MS = 1e4;
|
|
2045
|
+
async function searchNpm(query, limit = 20) {
|
|
2046
|
+
const cap = Math.min(limit, 100);
|
|
2047
|
+
const url = `https://registry.npmjs.org/-/v1/search?text=mcp+${encodeURIComponent(query)}&size=${cap}`;
|
|
2048
|
+
try {
|
|
2049
|
+
const res = await fetch(url, { signal: AbortSignal.timeout(SEARCH_TIMEOUT_MS) });
|
|
2050
|
+
if (!res.ok) return [];
|
|
2051
|
+
const data = await res.json();
|
|
2052
|
+
const objects = Array.isArray(data["objects"]) ? data["objects"] : [];
|
|
2053
|
+
return objects.map((obj) => {
|
|
2054
|
+
const pkg = obj["package"] ?? {};
|
|
2055
|
+
const dl = obj["downloads"];
|
|
2056
|
+
return {
|
|
2057
|
+
name: typeof pkg["name"] === "string" ? pkg["name"] : "",
|
|
2058
|
+
description: typeof pkg["description"] === "string" ? pkg["description"] : "",
|
|
2059
|
+
version: typeof pkg["version"] === "string" ? pkg["version"] : "",
|
|
2060
|
+
date: typeof pkg["date"] === "string" ? pkg["date"] : "",
|
|
2061
|
+
downloads: typeof dl?.["weekly"] === "number" ? dl["weekly"] : 0,
|
|
2062
|
+
keywords: Array.isArray(pkg["keywords"]) ? pkg["keywords"] : []
|
|
2063
|
+
};
|
|
2064
|
+
}).filter((r) => r.name !== "");
|
|
2065
|
+
} catch {
|
|
2066
|
+
return [];
|
|
2067
|
+
}
|
|
2068
|
+
}
|
|
2069
|
+
async function searchSmithery(query, limit = 20) {
|
|
2070
|
+
const cap = Math.min(limit, 100);
|
|
2071
|
+
const url = `https://api.smithery.ai/v1/servers?q=${encodeURIComponent(query)}&limit=${cap}`;
|
|
2072
|
+
try {
|
|
2073
|
+
const res = await fetch(url, { signal: AbortSignal.timeout(SEARCH_TIMEOUT_MS) });
|
|
2074
|
+
if (!res.ok) return [];
|
|
2075
|
+
const data = await res.json();
|
|
2076
|
+
const servers = Array.isArray(data["servers"]) ? data["servers"] : [];
|
|
2077
|
+
return servers.map((s) => ({
|
|
2078
|
+
name: typeof s["name"] === "string" ? s["name"] : "",
|
|
2079
|
+
description: typeof s["description"] === "string" ? s["description"] : "",
|
|
2080
|
+
version: typeof s["version"] === "string" ? s["version"] : "latest",
|
|
2081
|
+
runtime: typeof s["runtime"] === "string" ? s["runtime"] : "node"
|
|
2082
|
+
})).filter((r) => r.name !== "");
|
|
2083
|
+
} catch {
|
|
2084
|
+
return [];
|
|
2085
|
+
}
|
|
2086
|
+
}
|
|
2087
|
+
|
|
2088
|
+
// src/commands/search.ts
|
|
2089
|
+
function truncate2(s, max) {
|
|
2090
|
+
return s.length > max ? s.slice(0, max - 1) + "\u2026" : s;
|
|
2091
|
+
}
|
|
2092
|
+
function pad2(s, width) {
|
|
2093
|
+
return s.length >= width ? s : s + " ".repeat(width - s.length);
|
|
2094
|
+
}
|
|
2095
|
+
function highlightMatch(name, query) {
|
|
2096
|
+
const idx = name.toLowerCase().indexOf(query.toLowerCase());
|
|
2097
|
+
if (idx === -1) return name;
|
|
2098
|
+
return name.slice(0, idx) + pc9.yellow(name.slice(idx, idx + query.length)) + name.slice(idx + query.length);
|
|
2099
|
+
}
|
|
2100
|
+
function formatDownloads(n) {
|
|
2101
|
+
if (!n) return pc9.dim("\u2014");
|
|
2102
|
+
if (n >= 1e6) return `${(n / 1e6).toFixed(1)}M`;
|
|
2103
|
+
if (n >= 1e3) return `${(n / 1e3).toFixed(1)}k`;
|
|
2104
|
+
return String(n);
|
|
2105
|
+
}
|
|
2106
|
+
function printNpmResults(results, query) {
|
|
2107
|
+
const nameWidth = Math.max(4, ...results.map((r) => r.name.length), 20);
|
|
2108
|
+
const verWidth = Math.max(7, ...results.map((r) => r.version.length));
|
|
2109
|
+
const dlWidth = 9;
|
|
2110
|
+
const descMax = 50;
|
|
2111
|
+
const header = ` ${pad2("NAME", nameWidth)} ${pad2("VERSION", verWidth)} ${pad2("DOWNLOADS", dlWidth)} DESCRIPTION`;
|
|
2112
|
+
console.log(pc9.dim(header));
|
|
2113
|
+
console.log(pc9.dim(` ${"-".repeat(nameWidth)} ${"-".repeat(verWidth)} ${"-".repeat(dlWidth)} ${"-".repeat(descMax)}`));
|
|
2114
|
+
for (const r of results) {
|
|
2115
|
+
const name = highlightMatch(pad2(r.name, nameWidth), query);
|
|
2116
|
+
const ver = pad2(r.version, verWidth);
|
|
2117
|
+
const dl = pad2(formatDownloads(r.downloads), dlWidth);
|
|
2118
|
+
const desc = truncate2(r.description || pc9.dim("(no description)"), descMax);
|
|
2119
|
+
console.log(` ${name} ${pc9.dim(ver)} ${dl} ${desc}`);
|
|
2120
|
+
}
|
|
2121
|
+
}
|
|
2122
|
+
function printSmitheryResults(results, query) {
|
|
2123
|
+
const nameWidth = Math.max(4, ...results.map((r) => r.name.length), 20);
|
|
2124
|
+
const verWidth = Math.max(7, ...results.map((r) => r.version.length));
|
|
2125
|
+
const descMax = 50;
|
|
2126
|
+
const header = ` ${pad2("NAME", nameWidth)} ${pad2("VERSION", verWidth)} DESCRIPTION`;
|
|
2127
|
+
console.log(pc9.dim(header));
|
|
2128
|
+
console.log(pc9.dim(` ${"-".repeat(nameWidth)} ${"-".repeat(verWidth)} ${"-".repeat(descMax)}`));
|
|
2129
|
+
for (const r of results) {
|
|
2130
|
+
const name = highlightMatch(pad2(r.name, nameWidth), query);
|
|
2131
|
+
const ver = pad2(r.version, verWidth);
|
|
2132
|
+
const desc = truncate2(r.description || pc9.dim("(no description)"), descMax);
|
|
2133
|
+
console.log(` ${name} ${pc9.dim(ver)} ${desc}`);
|
|
2134
|
+
}
|
|
2135
|
+
}
|
|
2136
|
+
var search_default = defineCommand10({
|
|
2137
|
+
meta: {
|
|
2138
|
+
name: "search",
|
|
2139
|
+
description: "Search for MCP servers on npm or Smithery registry"
|
|
2140
|
+
},
|
|
2141
|
+
args: {
|
|
2142
|
+
query: {
|
|
2143
|
+
type: "positional",
|
|
2144
|
+
description: "Search query",
|
|
2145
|
+
required: true
|
|
2146
|
+
},
|
|
2147
|
+
registry: {
|
|
2148
|
+
type: "string",
|
|
2149
|
+
description: "Registry to search: npm or smithery (default: npm)",
|
|
2150
|
+
default: "npm"
|
|
2151
|
+
},
|
|
2152
|
+
limit: {
|
|
2153
|
+
type: "string",
|
|
2154
|
+
description: "Maximum number of results (default: 20, max: 100)",
|
|
2155
|
+
default: "20"
|
|
2156
|
+
}
|
|
2157
|
+
},
|
|
2158
|
+
async run({ args }) {
|
|
2159
|
+
const query = args.query;
|
|
2160
|
+
const registry = args.registry.toLowerCase();
|
|
2161
|
+
const limit = Math.min(Math.max(1, Number.parseInt(args.limit, 10) || 20), 100);
|
|
2162
|
+
if (registry !== "npm" && registry !== "smithery") {
|
|
2163
|
+
console.error(pc9.red(` Unknown registry "${registry}". Use "npm" or "smithery".`));
|
|
2164
|
+
process.exit(1);
|
|
2165
|
+
}
|
|
2166
|
+
const spinner5 = createSpinner3(`Searching ${registry} for "${query}"...`).start();
|
|
2167
|
+
if (registry === "npm") {
|
|
2168
|
+
const results2 = await searchNpm(query, limit);
|
|
2169
|
+
spinner5.stop();
|
|
2170
|
+
if (results2.length === 0) {
|
|
2171
|
+
console.log(pc9.dim(`
|
|
2172
|
+
No results found for "${query}" on npm.
|
|
2173
|
+
`));
|
|
2174
|
+
return;
|
|
2175
|
+
}
|
|
2176
|
+
console.log(pc9.bold(`
|
|
2177
|
+
mcpman search \u2014 npm (${results2.length} result${results2.length !== 1 ? "s" : ""})
|
|
2178
|
+
`));
|
|
2179
|
+
printNpmResults(results2, query);
|
|
2180
|
+
console.log(pc9.dim(`
|
|
2181
|
+
Install with: mcpman install <name>
|
|
2182
|
+
`));
|
|
2183
|
+
return;
|
|
2184
|
+
}
|
|
2185
|
+
const results = await searchSmithery(query, limit);
|
|
2186
|
+
spinner5.stop();
|
|
2187
|
+
if (results.length === 0) {
|
|
2188
|
+
console.log(pc9.dim(`
|
|
2189
|
+
No results found for "${query}" on Smithery.
|
|
2190
|
+
`));
|
|
2191
|
+
return;
|
|
2192
|
+
}
|
|
2193
|
+
console.log(pc9.bold(`
|
|
2194
|
+
mcpman search \u2014 Smithery (${results.length} result${results.length !== 1 ? "s" : ""})
|
|
2195
|
+
`));
|
|
2196
|
+
printSmitheryResults(results, query);
|
|
2197
|
+
console.log(pc9.dim(`
|
|
2198
|
+
Install with: mcpman install <name>
|
|
2199
|
+
`));
|
|
1345
2200
|
}
|
|
1346
2201
|
});
|
|
1347
2202
|
|
|
1348
2203
|
// src/commands/secrets.ts
|
|
1349
|
-
import { defineCommand as
|
|
1350
|
-
import
|
|
1351
|
-
import * as
|
|
2204
|
+
import { defineCommand as defineCommand11 } from "citty";
|
|
2205
|
+
import pc10 from "picocolors";
|
|
2206
|
+
import * as p8 from "@clack/prompts";
|
|
1352
2207
|
function maskValue(value) {
|
|
1353
2208
|
if (value.length <= 8) return "***";
|
|
1354
2209
|
return `${value.slice(0, 4)}***${value.slice(-3)}`;
|
|
@@ -1358,7 +2213,7 @@ function parseKeyValue(input) {
|
|
|
1358
2213
|
if (idx <= 0) return null;
|
|
1359
2214
|
return { key: input.slice(0, idx), value: input.slice(idx + 1) };
|
|
1360
2215
|
}
|
|
1361
|
-
var
|
|
2216
|
+
var setCommand2 = defineCommand11({
|
|
1362
2217
|
meta: { name: "set", description: "Store an encrypted secret for a server" },
|
|
1363
2218
|
args: {
|
|
1364
2219
|
server: {
|
|
@@ -1375,30 +2230,30 @@ var setCommand = defineCommand7({
|
|
|
1375
2230
|
async run({ args }) {
|
|
1376
2231
|
const parsed = parseKeyValue(args.keyvalue);
|
|
1377
2232
|
if (!parsed) {
|
|
1378
|
-
console.error(
|
|
2233
|
+
console.error(pc10.red("\u2717") + " Invalid format. Expected KEY=VALUE");
|
|
1379
2234
|
process.exit(1);
|
|
1380
2235
|
}
|
|
1381
|
-
|
|
2236
|
+
p8.intro(pc10.cyan("mcpman secrets set"));
|
|
1382
2237
|
const isNew = listSecrets(args.server).length === 0 || !listSecrets(args.server)[0]?.keys.includes(parsed.key);
|
|
1383
2238
|
const vaultPath = (await import("./vault-service-UTZAV6N6.js")).getVaultPath();
|
|
1384
2239
|
const vaultExists = (await import("fs")).existsSync(vaultPath);
|
|
1385
2240
|
const password = await getMasterPassword(!vaultExists && isNew);
|
|
1386
|
-
const spin =
|
|
2241
|
+
const spin = p8.spinner();
|
|
1387
2242
|
spin.start("Encrypting secret...");
|
|
1388
2243
|
try {
|
|
1389
2244
|
setSecret(args.server, parsed.key, parsed.value, password);
|
|
1390
2245
|
spin.stop(
|
|
1391
|
-
`${
|
|
2246
|
+
`${pc10.green("\u2713")} Stored ${pc10.bold(parsed.key)} for ${pc10.cyan(args.server)}`
|
|
1392
2247
|
);
|
|
1393
2248
|
} catch (err) {
|
|
1394
|
-
spin.stop(
|
|
1395
|
-
console.error(
|
|
2249
|
+
spin.stop(pc10.red("\u2717") + " Failed to store secret");
|
|
2250
|
+
console.error(pc10.dim(String(err)));
|
|
1396
2251
|
process.exit(1);
|
|
1397
2252
|
}
|
|
1398
|
-
|
|
2253
|
+
p8.outro(pc10.dim("Secret encrypted and saved to vault."));
|
|
1399
2254
|
}
|
|
1400
2255
|
});
|
|
1401
|
-
var
|
|
2256
|
+
var listCommand2 = defineCommand11({
|
|
1402
2257
|
meta: { name: "list", description: "List secret keys stored in the vault" },
|
|
1403
2258
|
args: {
|
|
1404
2259
|
server: {
|
|
@@ -1410,23 +2265,23 @@ var listCommand = defineCommand7({
|
|
|
1410
2265
|
async run({ args }) {
|
|
1411
2266
|
const results = listSecrets(args.server || void 0);
|
|
1412
2267
|
if (results.length === 0) {
|
|
1413
|
-
const filter = args.server ? ` for ${
|
|
1414
|
-
console.log(
|
|
2268
|
+
const filter = args.server ? ` for ${pc10.cyan(args.server)}` : "";
|
|
2269
|
+
console.log(pc10.dim(`No secrets stored${filter}.`));
|
|
1415
2270
|
return;
|
|
1416
2271
|
}
|
|
1417
2272
|
console.log("");
|
|
1418
2273
|
for (const { server, keys } of results) {
|
|
1419
|
-
console.log(
|
|
2274
|
+
console.log(pc10.bold(pc10.cyan(server)));
|
|
1420
2275
|
for (const key of keys) {
|
|
1421
|
-
console.log(` ${
|
|
2276
|
+
console.log(` ${pc10.green("\u25CF")} ${pc10.bold(key)} ${pc10.dim(maskValue("\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022"))}`);
|
|
1422
2277
|
}
|
|
1423
2278
|
console.log("");
|
|
1424
2279
|
}
|
|
1425
2280
|
const total = results.reduce((n, r) => n + r.keys.length, 0);
|
|
1426
|
-
console.log(
|
|
2281
|
+
console.log(pc10.dim(` ${total} secret${total !== 1 ? "s" : ""} in ${results.length} server${results.length !== 1 ? "s" : ""}`));
|
|
1427
2282
|
}
|
|
1428
2283
|
});
|
|
1429
|
-
var removeCommand =
|
|
2284
|
+
var removeCommand = defineCommand11({
|
|
1430
2285
|
meta: { name: "remove", description: "Delete a secret from the vault" },
|
|
1431
2286
|
args: {
|
|
1432
2287
|
server: {
|
|
@@ -1441,40 +2296,40 @@ var removeCommand = defineCommand7({
|
|
|
1441
2296
|
}
|
|
1442
2297
|
},
|
|
1443
2298
|
async run({ args }) {
|
|
1444
|
-
const confirmed = await
|
|
1445
|
-
message: `Remove ${
|
|
2299
|
+
const confirmed = await p8.confirm({
|
|
2300
|
+
message: `Remove ${pc10.bold(args.key)} from ${pc10.cyan(args.server)}?`,
|
|
1446
2301
|
initialValue: false
|
|
1447
2302
|
});
|
|
1448
|
-
if (
|
|
1449
|
-
|
|
2303
|
+
if (p8.isCancel(confirmed) || !confirmed) {
|
|
2304
|
+
p8.cancel("Cancelled.");
|
|
1450
2305
|
return;
|
|
1451
2306
|
}
|
|
1452
2307
|
try {
|
|
1453
2308
|
removeSecret(args.server, args.key);
|
|
1454
|
-
console.log(`${
|
|
2309
|
+
console.log(`${pc10.green("\u2713")} Removed ${pc10.bold(args.key)} from ${pc10.cyan(args.server)}`);
|
|
1455
2310
|
} catch (err) {
|
|
1456
|
-
console.error(
|
|
1457
|
-
console.error(
|
|
2311
|
+
console.error(pc10.red("\u2717") + " Failed to remove secret");
|
|
2312
|
+
console.error(pc10.dim(String(err)));
|
|
1458
2313
|
process.exit(1);
|
|
1459
2314
|
}
|
|
1460
2315
|
}
|
|
1461
2316
|
});
|
|
1462
|
-
var secrets_default =
|
|
2317
|
+
var secrets_default = defineCommand11({
|
|
1463
2318
|
meta: {
|
|
1464
2319
|
name: "secrets",
|
|
1465
2320
|
description: "Manage encrypted secrets for MCP servers"
|
|
1466
2321
|
},
|
|
1467
2322
|
subCommands: {
|
|
1468
|
-
set:
|
|
1469
|
-
list:
|
|
2323
|
+
set: setCommand2,
|
|
2324
|
+
list: listCommand2,
|
|
1470
2325
|
remove: removeCommand
|
|
1471
2326
|
}
|
|
1472
2327
|
});
|
|
1473
2328
|
|
|
1474
2329
|
// src/commands/sync.ts
|
|
1475
|
-
import { defineCommand as
|
|
1476
|
-
import * as
|
|
1477
|
-
import
|
|
2330
|
+
import { defineCommand as defineCommand12 } from "citty";
|
|
2331
|
+
import * as p9 from "@clack/prompts";
|
|
2332
|
+
import pc11 from "picocolors";
|
|
1478
2333
|
|
|
1479
2334
|
// src/core/config-diff.ts
|
|
1480
2335
|
function reconstructServerEntry(lockEntry) {
|
|
@@ -1489,7 +2344,7 @@ function reconstructServerEntry(lockEntry) {
|
|
|
1489
2344
|
}
|
|
1490
2345
|
return entry;
|
|
1491
2346
|
}
|
|
1492
|
-
function computeDiff(lockfile, clientConfigs) {
|
|
2347
|
+
function computeDiff(lockfile, clientConfigs, options = {}) {
|
|
1493
2348
|
const actions = [];
|
|
1494
2349
|
for (const [server, lockEntry] of Object.entries(lockfile.servers)) {
|
|
1495
2350
|
for (const client of lockEntry.clients) {
|
|
@@ -1507,19 +2362,21 @@ function computeDiff(lockfile, clientConfigs) {
|
|
|
1507
2362
|
}
|
|
1508
2363
|
}
|
|
1509
2364
|
}
|
|
2365
|
+
const extraAction = options.remove ? "remove" : "extra";
|
|
1510
2366
|
for (const [client, config] of clientConfigs) {
|
|
1511
2367
|
for (const server of Object.keys(config.servers)) {
|
|
1512
2368
|
if (!(server in lockfile.servers)) {
|
|
1513
|
-
actions.push({ server, client, action:
|
|
2369
|
+
actions.push({ server, client, action: extraAction });
|
|
1514
2370
|
}
|
|
1515
2371
|
}
|
|
1516
2372
|
}
|
|
1517
2373
|
return actions;
|
|
1518
2374
|
}
|
|
1519
|
-
function computeDiffFromClient(sourceClient, clientConfigs) {
|
|
2375
|
+
function computeDiffFromClient(sourceClient, clientConfigs, options = {}) {
|
|
1520
2376
|
const actions = [];
|
|
1521
2377
|
const sourceConfig = clientConfigs.get(sourceClient);
|
|
1522
2378
|
if (!sourceConfig) return [];
|
|
2379
|
+
const extraAction = options.remove ? "remove" : "extra";
|
|
1523
2380
|
for (const [client, config] of clientConfigs) {
|
|
1524
2381
|
if (client === sourceClient) continue;
|
|
1525
2382
|
for (const [server, entry] of Object.entries(sourceConfig.servers)) {
|
|
@@ -1531,7 +2388,7 @@ function computeDiffFromClient(sourceClient, clientConfigs) {
|
|
|
1531
2388
|
}
|
|
1532
2389
|
for (const server of Object.keys(config.servers)) {
|
|
1533
2390
|
if (!(server in sourceConfig.servers)) {
|
|
1534
|
-
actions.push({ server, client, action:
|
|
2391
|
+
actions.push({ server, client, action: extraAction });
|
|
1535
2392
|
}
|
|
1536
2393
|
}
|
|
1537
2394
|
}
|
|
@@ -1540,7 +2397,7 @@ function computeDiffFromClient(sourceClient, clientConfigs) {
|
|
|
1540
2397
|
|
|
1541
2398
|
// src/core/sync-engine.ts
|
|
1542
2399
|
async function applySyncActions(actions, clients) {
|
|
1543
|
-
const result = { applied: 0, failed: 0, errors: [] };
|
|
2400
|
+
const result = { applied: 0, removed: 0, failed: 0, errors: [] };
|
|
1544
2401
|
const addActions = actions.filter((a) => a.action === "add" && a.entry);
|
|
1545
2402
|
for (const action of addActions) {
|
|
1546
2403
|
const handler = clients.get(action.client);
|
|
@@ -1565,6 +2422,30 @@ async function applySyncActions(actions, clients) {
|
|
|
1565
2422
|
});
|
|
1566
2423
|
}
|
|
1567
2424
|
}
|
|
2425
|
+
const removeActions = actions.filter((a) => a.action === "remove");
|
|
2426
|
+
for (const action of removeActions) {
|
|
2427
|
+
const handler = clients.get(action.client);
|
|
2428
|
+
if (!handler) {
|
|
2429
|
+
result.failed++;
|
|
2430
|
+
result.errors.push({
|
|
2431
|
+
server: action.server,
|
|
2432
|
+
client: action.client,
|
|
2433
|
+
error: "No handler available for client"
|
|
2434
|
+
});
|
|
2435
|
+
continue;
|
|
2436
|
+
}
|
|
2437
|
+
try {
|
|
2438
|
+
await handler.removeServer(action.server);
|
|
2439
|
+
result.removed++;
|
|
2440
|
+
} catch (err) {
|
|
2441
|
+
result.failed++;
|
|
2442
|
+
result.errors.push({
|
|
2443
|
+
server: action.server,
|
|
2444
|
+
client: action.client,
|
|
2445
|
+
error: String(err)
|
|
2446
|
+
});
|
|
2447
|
+
}
|
|
2448
|
+
}
|
|
1568
2449
|
return result;
|
|
1569
2450
|
}
|
|
1570
2451
|
async function getClientConfigs() {
|
|
@@ -1598,7 +2479,7 @@ var CLIENT_DISPLAY3 = {
|
|
|
1598
2479
|
vscode: "VS Code",
|
|
1599
2480
|
windsurf: "Windsurf"
|
|
1600
2481
|
};
|
|
1601
|
-
var sync_default =
|
|
2482
|
+
var sync_default = defineCommand12({
|
|
1602
2483
|
meta: {
|
|
1603
2484
|
name: "sync",
|
|
1604
2485
|
description: "Sync MCP server configs across all detected AI clients"
|
|
@@ -1609,6 +2490,11 @@ var sync_default = defineCommand8({
|
|
|
1609
2490
|
description: "Preview changes without applying them",
|
|
1610
2491
|
default: false
|
|
1611
2492
|
},
|
|
2493
|
+
remove: {
|
|
2494
|
+
type: "boolean",
|
|
2495
|
+
description: "Remove extra servers not in lockfile",
|
|
2496
|
+
default: false
|
|
2497
|
+
},
|
|
1612
2498
|
source: {
|
|
1613
2499
|
type: "string",
|
|
1614
2500
|
description: "Use a specific client as source of truth (claude-desktop, cursor, vscode, windsurf)"
|
|
@@ -1620,58 +2506,64 @@ var sync_default = defineCommand8({
|
|
|
1620
2506
|
}
|
|
1621
2507
|
},
|
|
1622
2508
|
async run({ args }) {
|
|
1623
|
-
|
|
2509
|
+
p9.intro(`${pc11.cyan("mcpman sync")}`);
|
|
1624
2510
|
const sourceClient = args.source;
|
|
1625
2511
|
if (sourceClient && !VALID_CLIENTS.includes(sourceClient)) {
|
|
1626
|
-
|
|
2512
|
+
p9.log.error(`Invalid --source "${sourceClient}". Must be one of: ${VALID_CLIENTS.join(", ")}`);
|
|
1627
2513
|
process.exit(1);
|
|
1628
2514
|
}
|
|
1629
|
-
const spinner5 =
|
|
2515
|
+
const spinner5 = p9.spinner();
|
|
1630
2516
|
spinner5.start("Detecting clients and reading configs...");
|
|
1631
2517
|
const { configs, handlers } = await getClientConfigs();
|
|
1632
2518
|
spinner5.stop(`Found ${configs.size} client(s)`);
|
|
1633
2519
|
if (configs.size === 0) {
|
|
1634
|
-
|
|
2520
|
+
p9.log.warn("No AI clients detected. Install Claude Desktop, Cursor, VS Code, or Windsurf first.");
|
|
1635
2521
|
process.exit(0);
|
|
1636
2522
|
}
|
|
2523
|
+
const diffOptions = { remove: args.remove };
|
|
1637
2524
|
let actions;
|
|
1638
2525
|
if (sourceClient) {
|
|
1639
2526
|
if (!configs.has(sourceClient)) {
|
|
1640
|
-
|
|
2527
|
+
p9.log.error(`Source client "${sourceClient}" is not detected or its config is unreadable.`);
|
|
1641
2528
|
process.exit(1);
|
|
1642
2529
|
}
|
|
1643
|
-
|
|
1644
|
-
actions = computeDiffFromClient(sourceClient, configs);
|
|
2530
|
+
p9.log.info(`Using ${CLIENT_DISPLAY3[sourceClient]} as source of truth`);
|
|
2531
|
+
actions = computeDiffFromClient(sourceClient, configs, diffOptions);
|
|
1645
2532
|
} else {
|
|
1646
2533
|
const lockfile = readLockfile();
|
|
1647
|
-
actions = computeDiff(lockfile, configs);
|
|
2534
|
+
actions = computeDiff(lockfile, configs, diffOptions);
|
|
1648
2535
|
}
|
|
1649
2536
|
printDiffTable(actions);
|
|
1650
2537
|
const addCount = actions.filter((a) => a.action === "add").length;
|
|
1651
2538
|
const extraCount = actions.filter((a) => a.action === "extra").length;
|
|
1652
|
-
|
|
1653
|
-
|
|
2539
|
+
const removeCount = actions.filter((a) => a.action === "remove").length;
|
|
2540
|
+
if (addCount === 0 && removeCount === 0 && extraCount === 0) {
|
|
2541
|
+
p9.outro(pc11.green("All clients are in sync."));
|
|
1654
2542
|
process.exit(0);
|
|
1655
2543
|
}
|
|
1656
2544
|
const parts = [];
|
|
1657
|
-
if (addCount > 0) parts.push(
|
|
1658
|
-
if (
|
|
1659
|
-
|
|
2545
|
+
if (addCount > 0) parts.push(pc11.green(`${addCount} to add`));
|
|
2546
|
+
if (removeCount > 0) parts.push(pc11.red(`${removeCount} to remove`));
|
|
2547
|
+
if (extraCount > 0) parts.push(pc11.yellow(`${extraCount} extra (informational)`));
|
|
2548
|
+
p9.log.info(parts.join(" \xB7 "));
|
|
1660
2549
|
if (args["dry-run"]) {
|
|
1661
|
-
|
|
2550
|
+
p9.outro(pc11.dim("Dry run \u2014 no changes applied."));
|
|
1662
2551
|
process.exit(1);
|
|
1663
2552
|
}
|
|
1664
|
-
if (addCount === 0) {
|
|
1665
|
-
|
|
2553
|
+
if (addCount === 0 && removeCount === 0) {
|
|
2554
|
+
p9.outro(pc11.dim("No additions needed. Extra servers left untouched."));
|
|
1666
2555
|
process.exit(1);
|
|
1667
2556
|
}
|
|
1668
2557
|
if (!args.yes) {
|
|
1669
|
-
const
|
|
1670
|
-
|
|
2558
|
+
const actionParts = [];
|
|
2559
|
+
if (addCount > 0) actionParts.push(`${addCount} addition(s)`);
|
|
2560
|
+
if (removeCount > 0) actionParts.push(`${removeCount} removal(s)`);
|
|
2561
|
+
const confirmed = await p9.confirm({
|
|
2562
|
+
message: `Apply ${actionParts.join(" and ")} to client configs?`,
|
|
1671
2563
|
initialValue: true
|
|
1672
2564
|
});
|
|
1673
|
-
if (
|
|
1674
|
-
|
|
2565
|
+
if (p9.isCancel(confirmed) || !confirmed) {
|
|
2566
|
+
p9.outro(pc11.dim("Cancelled \u2014 no changes applied."));
|
|
1675
2567
|
process.exit(0);
|
|
1676
2568
|
}
|
|
1677
2569
|
}
|
|
@@ -1679,195 +2571,80 @@ var sync_default = defineCommand8({
|
|
|
1679
2571
|
const result = await applySyncActions(actions, handlers);
|
|
1680
2572
|
spinner5.stop("Done");
|
|
1681
2573
|
if (result.applied > 0) {
|
|
1682
|
-
|
|
2574
|
+
p9.log.success(`Added ${result.applied} server(s) to client configs.`);
|
|
2575
|
+
}
|
|
2576
|
+
if (result.removed > 0) {
|
|
2577
|
+
p9.log.success(`Removed ${result.removed} server(s) from client configs.`);
|
|
1683
2578
|
}
|
|
1684
2579
|
if (result.failed > 0) {
|
|
1685
2580
|
for (const e of result.errors) {
|
|
1686
|
-
|
|
2581
|
+
p9.log.error(`Failed to sync "${e.server}" on ${e.client}: ${e.error}`);
|
|
1687
2582
|
}
|
|
1688
2583
|
}
|
|
1689
|
-
|
|
2584
|
+
p9.outro(result.failed === 0 ? pc11.green("Sync complete.") : pc11.yellow("Sync complete with errors."));
|
|
1690
2585
|
process.exit(result.failed > 0 ? 1 : 0);
|
|
1691
2586
|
}
|
|
1692
2587
|
});
|
|
1693
2588
|
function printDiffTable(actions) {
|
|
1694
2589
|
if (actions.length === 0) {
|
|
1695
|
-
|
|
2590
|
+
p9.log.info("No actions to display.");
|
|
1696
2591
|
return;
|
|
1697
2592
|
}
|
|
1698
2593
|
const nameWidth = Math.max(6, ...actions.map((a) => a.server.length));
|
|
1699
2594
|
const clientWidth = Math.max(6, ...actions.map((a) => CLIENT_DISPLAY3[a.client]?.length ?? a.client.length));
|
|
1700
|
-
const header = ` ${
|
|
1701
|
-
console.log(
|
|
1702
|
-
console.log(
|
|
2595
|
+
const header = ` ${pad3("SERVER", nameWidth)} ${pad3("CLIENT", clientWidth)} STATUS`;
|
|
2596
|
+
console.log(pc11.dim(header));
|
|
2597
|
+
console.log(pc11.dim(` ${"-".repeat(nameWidth)} ${"-".repeat(clientWidth)} ------`));
|
|
1703
2598
|
for (const action of actions) {
|
|
1704
2599
|
const clientDisplay = CLIENT_DISPLAY3[action.client] ?? action.client;
|
|
1705
2600
|
const [icon, statusText] = formatAction(action.action);
|
|
1706
|
-
console.log(` ${
|
|
2601
|
+
console.log(` ${pad3(action.server, nameWidth)} ${pad3(clientDisplay, clientWidth)} ${icon} ${statusText}`);
|
|
1707
2602
|
}
|
|
1708
2603
|
console.log("");
|
|
1709
2604
|
}
|
|
1710
2605
|
function formatAction(action) {
|
|
1711
2606
|
switch (action) {
|
|
1712
2607
|
case "add":
|
|
1713
|
-
return [
|
|
2608
|
+
return [pc11.green("+"), pc11.green("missing \u2014 will add")];
|
|
1714
2609
|
case "extra":
|
|
1715
|
-
return [
|
|
2610
|
+
return [pc11.yellow("?"), pc11.yellow("extra (not in lockfile)")];
|
|
2611
|
+
case "remove":
|
|
2612
|
+
return [pc11.red("\u2013"), pc11.red("extra \u2014 will remove")];
|
|
1716
2613
|
case "ok":
|
|
1717
|
-
return [
|
|
2614
|
+
return [pc11.dim("\xB7"), pc11.dim("in sync")];
|
|
1718
2615
|
}
|
|
1719
2616
|
}
|
|
1720
|
-
function
|
|
2617
|
+
function pad3(s, width) {
|
|
1721
2618
|
return s.length >= width ? s : s + " ".repeat(width - s.length);
|
|
1722
2619
|
}
|
|
1723
2620
|
|
|
1724
2621
|
// src/commands/update.ts
|
|
1725
|
-
import { defineCommand as
|
|
1726
|
-
import * as
|
|
1727
|
-
import
|
|
1728
|
-
|
|
1729
|
-
// src/core/version-checker.ts
|
|
1730
|
-
function compareVersions(a, b) {
|
|
1731
|
-
const aParts = a.replace(/^v/, "").split(".").map(Number);
|
|
1732
|
-
const bParts = b.replace(/^v/, "").split(".").map(Number);
|
|
1733
|
-
const len = Math.max(aParts.length, bParts.length);
|
|
1734
|
-
for (let i = 0; i < len; i++) {
|
|
1735
|
-
const aN = aParts[i] ?? 0;
|
|
1736
|
-
const bN = bParts[i] ?? 0;
|
|
1737
|
-
if (Number.isNaN(aN) || Number.isNaN(bN)) return 0;
|
|
1738
|
-
if (aN < bN) return -1;
|
|
1739
|
-
if (aN > bN) return 1;
|
|
1740
|
-
}
|
|
1741
|
-
return 0;
|
|
1742
|
-
}
|
|
1743
|
-
function detectUpdateType(current, latest) {
|
|
1744
|
-
const cParts = current.replace(/^v/, "").split(".").map(Number);
|
|
1745
|
-
const lParts = latest.replace(/^v/, "").split(".").map(Number);
|
|
1746
|
-
if ((lParts[0] ?? 0) > (cParts[0] ?? 0)) return "major";
|
|
1747
|
-
if ((lParts[1] ?? 0) > (cParts[1] ?? 0)) return "minor";
|
|
1748
|
-
return "patch";
|
|
1749
|
-
}
|
|
1750
|
-
async function fetchNpmLatest(packageName) {
|
|
1751
|
-
try {
|
|
1752
|
-
const res = await fetch(
|
|
1753
|
-
`https://registry.npmjs.org/${encodeURIComponent(packageName)}/latest`,
|
|
1754
|
-
{
|
|
1755
|
-
headers: { Accept: "application/json" },
|
|
1756
|
-
signal: AbortSignal.timeout(8e3)
|
|
1757
|
-
}
|
|
1758
|
-
);
|
|
1759
|
-
if (!res.ok) return null;
|
|
1760
|
-
const data = await res.json();
|
|
1761
|
-
return typeof data.version === "string" ? data.version : null;
|
|
1762
|
-
} catch {
|
|
1763
|
-
return null;
|
|
1764
|
-
}
|
|
1765
|
-
}
|
|
1766
|
-
async function fetchSmitheryLatest(name) {
|
|
1767
|
-
try {
|
|
1768
|
-
const res = await fetch(
|
|
1769
|
-
`https://registry.smithery.ai/servers/${encodeURIComponent(name)}`,
|
|
1770
|
-
{
|
|
1771
|
-
headers: { Accept: "application/json" },
|
|
1772
|
-
signal: AbortSignal.timeout(8e3)
|
|
1773
|
-
}
|
|
1774
|
-
);
|
|
1775
|
-
if (!res.ok) return null;
|
|
1776
|
-
const data = await res.json();
|
|
1777
|
-
return typeof data.version === "string" ? data.version : null;
|
|
1778
|
-
} catch {
|
|
1779
|
-
return null;
|
|
1780
|
-
}
|
|
1781
|
-
}
|
|
1782
|
-
async function fetchGithubLatest(resolved) {
|
|
1783
|
-
const match = resolved.match(/github\.com\/([^/]+)\/([^/]+)/);
|
|
1784
|
-
if (!match) return null;
|
|
1785
|
-
const [, owner, repo] = match;
|
|
1786
|
-
try {
|
|
1787
|
-
const res = await fetch(
|
|
1788
|
-
`https://api.github.com/repos/${owner}/${repo}/releases/latest`,
|
|
1789
|
-
{
|
|
1790
|
-
headers: { Accept: "application/json" },
|
|
1791
|
-
signal: AbortSignal.timeout(8e3)
|
|
1792
|
-
}
|
|
1793
|
-
);
|
|
1794
|
-
if (!res.ok) return null;
|
|
1795
|
-
const data = await res.json();
|
|
1796
|
-
return typeof data.tag_name === "string" ? data.tag_name.replace(/^v/, "") : null;
|
|
1797
|
-
} catch {
|
|
1798
|
-
return null;
|
|
1799
|
-
}
|
|
1800
|
-
}
|
|
1801
|
-
async function checkVersion(name, lockEntry) {
|
|
1802
|
-
const current = lockEntry.version;
|
|
1803
|
-
let latest = null;
|
|
1804
|
-
if (lockEntry.source === "npm") {
|
|
1805
|
-
latest = await fetchNpmLatest(name);
|
|
1806
|
-
} else if (lockEntry.source === "smithery") {
|
|
1807
|
-
latest = await fetchSmitheryLatest(name);
|
|
1808
|
-
} else if (lockEntry.source === "github") {
|
|
1809
|
-
latest = await fetchGithubLatest(lockEntry.resolved);
|
|
1810
|
-
}
|
|
1811
|
-
if (!latest || latest === current) {
|
|
1812
|
-
return {
|
|
1813
|
-
server: name,
|
|
1814
|
-
source: lockEntry.source,
|
|
1815
|
-
currentVersion: current,
|
|
1816
|
-
latestVersion: latest ?? current,
|
|
1817
|
-
hasUpdate: false
|
|
1818
|
-
};
|
|
1819
|
-
}
|
|
1820
|
-
const hasUpdate = compareVersions(current, latest) === -1;
|
|
1821
|
-
return {
|
|
1822
|
-
server: name,
|
|
1823
|
-
source: lockEntry.source,
|
|
1824
|
-
currentVersion: current,
|
|
1825
|
-
latestVersion: latest,
|
|
1826
|
-
hasUpdate,
|
|
1827
|
-
updateType: hasUpdate ? detectUpdateType(current, latest) : void 0
|
|
1828
|
-
};
|
|
1829
|
-
}
|
|
1830
|
-
async function checkAllVersions(lockfile) {
|
|
1831
|
-
const entries = Object.entries(lockfile.servers);
|
|
1832
|
-
if (entries.length === 0) return [];
|
|
1833
|
-
const results = [];
|
|
1834
|
-
const executing = /* @__PURE__ */ new Set();
|
|
1835
|
-
for (const [name, entry] of entries) {
|
|
1836
|
-
const p8 = checkVersion(name, entry).then((r) => {
|
|
1837
|
-
results.push(r);
|
|
1838
|
-
executing.delete(p8);
|
|
1839
|
-
});
|
|
1840
|
-
executing.add(p8);
|
|
1841
|
-
if (executing.size >= 5) {
|
|
1842
|
-
await Promise.race(executing);
|
|
1843
|
-
}
|
|
1844
|
-
}
|
|
1845
|
-
await Promise.all(executing);
|
|
1846
|
-
return results;
|
|
1847
|
-
}
|
|
2622
|
+
import { defineCommand as defineCommand13 } from "citty";
|
|
2623
|
+
import * as p10 from "@clack/prompts";
|
|
2624
|
+
import pc13 from "picocolors";
|
|
1848
2625
|
|
|
1849
2626
|
// src/core/update-notifier.ts
|
|
1850
|
-
import
|
|
1851
|
-
import
|
|
2627
|
+
import fs4 from "fs";
|
|
2628
|
+
import path5 from "path";
|
|
1852
2629
|
import os3 from "os";
|
|
1853
|
-
import
|
|
1854
|
-
var CACHE_FILE =
|
|
2630
|
+
import pc12 from "picocolors";
|
|
2631
|
+
var CACHE_FILE = path5.join(os3.homedir(), ".mcpman", ".update-check");
|
|
1855
2632
|
var TTL_MS = 24 * 60 * 60 * 1e3;
|
|
1856
2633
|
function writeUpdateCache(data) {
|
|
1857
2634
|
try {
|
|
1858
|
-
const dir =
|
|
1859
|
-
if (!
|
|
2635
|
+
const dir = path5.dirname(CACHE_FILE);
|
|
2636
|
+
if (!fs4.existsSync(dir)) fs4.mkdirSync(dir, { recursive: true });
|
|
1860
2637
|
const tmp = `${CACHE_FILE}.tmp`;
|
|
1861
|
-
|
|
1862
|
-
|
|
2638
|
+
fs4.writeFileSync(tmp, JSON.stringify(data, null, 2), "utf-8");
|
|
2639
|
+
fs4.renameSync(tmp, CACHE_FILE);
|
|
1863
2640
|
} catch {
|
|
1864
2641
|
}
|
|
1865
2642
|
}
|
|
1866
2643
|
|
|
1867
2644
|
// src/commands/update.ts
|
|
1868
|
-
async function
|
|
2645
|
+
async function loadClients3() {
|
|
1869
2646
|
try {
|
|
1870
|
-
const mod = await import("./client-detector-
|
|
2647
|
+
const mod = await import("./client-detector-UAP2EYZA.js");
|
|
1871
2648
|
return mod.getInstalledClients();
|
|
1872
2649
|
} catch {
|
|
1873
2650
|
return [];
|
|
@@ -1882,19 +2659,19 @@ function printTable(updates) {
|
|
|
1882
2659
|
"LATEST".padEnd(VER_W),
|
|
1883
2660
|
"STATUS"
|
|
1884
2661
|
].join(" ");
|
|
1885
|
-
console.log(
|
|
2662
|
+
console.log(pc13.bold(`
|
|
1886
2663
|
${header}`));
|
|
1887
|
-
console.log(
|
|
2664
|
+
console.log(pc13.dim(` ${"\u2500".repeat(NAME_W + VER_W * 2 + 20)}`));
|
|
1888
2665
|
for (const u of updates) {
|
|
1889
2666
|
const nameCol = u.server.slice(0, NAME_W).padEnd(NAME_W);
|
|
1890
2667
|
const curCol = u.currentVersion.padEnd(VER_W);
|
|
1891
2668
|
const latCol = u.latestVersion.padEnd(VER_W);
|
|
1892
|
-
const statusCol = u.hasUpdate ?
|
|
2669
|
+
const statusCol = u.hasUpdate ? pc13.yellow(`Update available${u.updateType ? ` [${u.updateType}]` : ""}`) : pc13.green("Up to date");
|
|
1893
2670
|
console.log(` ${nameCol} ${curCol} ${latCol} ${statusCol}`);
|
|
1894
2671
|
}
|
|
1895
2672
|
console.log();
|
|
1896
2673
|
}
|
|
1897
|
-
var update_default =
|
|
2674
|
+
var update_default = defineCommand13({
|
|
1898
2675
|
meta: {
|
|
1899
2676
|
name: "update",
|
|
1900
2677
|
description: "Check for and apply updates to installed MCP servers"
|
|
@@ -1933,7 +2710,7 @@ var update_default = defineCommand9({
|
|
|
1933
2710
|
}
|
|
1934
2711
|
process.exit(1);
|
|
1935
2712
|
}
|
|
1936
|
-
const spinner5 =
|
|
2713
|
+
const spinner5 = p10.spinner();
|
|
1937
2714
|
spinner5.start("Checking versions...");
|
|
1938
2715
|
let updates;
|
|
1939
2716
|
try {
|
|
@@ -1955,70 +2732,50 @@ var update_default = defineCommand9({
|
|
|
1955
2732
|
printTable(updates);
|
|
1956
2733
|
const outdated = updates.filter((u) => u.hasUpdate);
|
|
1957
2734
|
if (outdated.length === 0) {
|
|
1958
|
-
console.log(
|
|
2735
|
+
console.log(pc13.green(" All servers are up to date."));
|
|
1959
2736
|
return;
|
|
1960
2737
|
}
|
|
1961
2738
|
if (args.check) {
|
|
1962
|
-
console.log(
|
|
2739
|
+
console.log(pc13.yellow(` ${outdated.length} update(s) available. Run mcpman update to apply.`));
|
|
1963
2740
|
return;
|
|
1964
2741
|
}
|
|
1965
2742
|
if (!args.yes) {
|
|
1966
|
-
const confirmed = await
|
|
2743
|
+
const confirmed = await p10.confirm({
|
|
1967
2744
|
message: `Apply ${outdated.length} update(s)?`,
|
|
1968
2745
|
initialValue: true
|
|
1969
2746
|
});
|
|
1970
|
-
if (
|
|
1971
|
-
|
|
2747
|
+
if (p10.isCancel(confirmed) || !confirmed) {
|
|
2748
|
+
p10.outro("Cancelled.");
|
|
1972
2749
|
return;
|
|
1973
2750
|
}
|
|
1974
2751
|
}
|
|
1975
|
-
const clients = await
|
|
2752
|
+
const clients = await loadClients3();
|
|
1976
2753
|
let successCount = 0;
|
|
1977
2754
|
for (const update of outdated) {
|
|
1978
|
-
const
|
|
1979
|
-
const input = lockEntry.source === "smithery" ? `smithery:${update.server}` : lockEntry.source === "github" ? lockEntry.resolved : update.server;
|
|
1980
|
-
const s = p7.spinner();
|
|
2755
|
+
const s = p10.spinner();
|
|
1981
2756
|
s.start(`Updating ${update.server}...`);
|
|
1982
|
-
|
|
1983
|
-
|
|
1984
|
-
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
|
|
1989
|
-
integrity,
|
|
1990
|
-
command: metadata.command,
|
|
1991
|
-
args: metadata.args,
|
|
1992
|
-
installedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1993
|
-
});
|
|
1994
|
-
const entryClients = clients.filter(
|
|
1995
|
-
(c) => lockEntry.clients.includes(c.type)
|
|
1996
|
-
);
|
|
1997
|
-
for (const client of entryClients) {
|
|
1998
|
-
try {
|
|
1999
|
-
await client.addServer(update.server, {
|
|
2000
|
-
command: metadata.command,
|
|
2001
|
-
args: metadata.args
|
|
2002
|
-
});
|
|
2003
|
-
} catch {
|
|
2004
|
-
}
|
|
2005
|
-
}
|
|
2006
|
-
s.stop(`${pc9.green("\u2713")} ${update.server}: ${update.currentVersion} \u2192 ${metadata.version}`);
|
|
2757
|
+
const result = await applyServerUpdate(
|
|
2758
|
+
update.server,
|
|
2759
|
+
servers[update.server],
|
|
2760
|
+
clients
|
|
2761
|
+
);
|
|
2762
|
+
if (result.success) {
|
|
2763
|
+
s.stop(`${pc13.green("\u2713")} ${update.server}: ${result.fromVersion} \u2192 ${result.toVersion}`);
|
|
2007
2764
|
successCount++;
|
|
2008
|
-
}
|
|
2009
|
-
s.stop(`${
|
|
2765
|
+
} else {
|
|
2766
|
+
s.stop(`${pc13.red("\u2717")} ${update.server}: ${result.error}`);
|
|
2010
2767
|
}
|
|
2011
2768
|
}
|
|
2012
2769
|
const freshLockfile = readLockfile(resolveLockfilePath());
|
|
2013
2770
|
const freshUpdates = await checkAllVersions(freshLockfile);
|
|
2014
2771
|
writeUpdateCache({ lastCheck: (/* @__PURE__ */ new Date()).toISOString(), updates: freshUpdates });
|
|
2015
|
-
|
|
2772
|
+
p10.outro(`${successCount} of ${outdated.length} server(s) updated.`);
|
|
2016
2773
|
}
|
|
2017
2774
|
});
|
|
2018
2775
|
|
|
2019
2776
|
// src/utils/constants.ts
|
|
2020
2777
|
var APP_NAME = "mcpman";
|
|
2021
|
-
var APP_VERSION = "0.
|
|
2778
|
+
var APP_VERSION = "0.4.0";
|
|
2022
2779
|
var APP_DESCRIPTION = "The package manager for MCP servers";
|
|
2023
2780
|
|
|
2024
2781
|
// src/index.ts
|
|
@@ -2026,7 +2783,7 @@ process.on("SIGINT", () => {
|
|
|
2026
2783
|
console.log("\nAborted.");
|
|
2027
2784
|
process.exit(130);
|
|
2028
2785
|
});
|
|
2029
|
-
var main =
|
|
2786
|
+
var main = defineCommand14({
|
|
2030
2787
|
meta: {
|
|
2031
2788
|
name: APP_NAME,
|
|
2032
2789
|
version: APP_VERSION,
|
|
@@ -2041,7 +2798,11 @@ var main = defineCommand10({
|
|
|
2041
2798
|
secrets: secrets_default,
|
|
2042
2799
|
sync: sync_default,
|
|
2043
2800
|
audit: audit_default,
|
|
2044
|
-
update: update_default
|
|
2801
|
+
update: update_default,
|
|
2802
|
+
config: config_default,
|
|
2803
|
+
search: search_default,
|
|
2804
|
+
info: info_default,
|
|
2805
|
+
run: run_default
|
|
2045
2806
|
}
|
|
2046
2807
|
});
|
|
2047
2808
|
runMain(main);
|