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.cjs
CHANGED
|
@@ -159,9 +159,9 @@ async function atomicWrite(filePath, content) {
|
|
|
159
159
|
throw err;
|
|
160
160
|
}
|
|
161
161
|
}
|
|
162
|
-
async function pathExists(
|
|
162
|
+
async function pathExists(p12) {
|
|
163
163
|
try {
|
|
164
|
-
await import_node_fs3.default.promises.access(
|
|
164
|
+
await import_node_fs3.default.promises.access(p12);
|
|
165
165
|
return true;
|
|
166
166
|
} catch {
|
|
167
167
|
return false;
|
|
@@ -237,6 +237,12 @@ var init_base_client_handler = __esm({
|
|
|
237
237
|
function getHomedir() {
|
|
238
238
|
return import_node_os3.default.homedir();
|
|
239
239
|
}
|
|
240
|
+
function getMcpmanDir() {
|
|
241
|
+
return import_node_path4.default.join(import_node_os3.default.homedir(), ".mcpman");
|
|
242
|
+
}
|
|
243
|
+
function getConfigPath() {
|
|
244
|
+
return import_node_path4.default.join(getMcpmanDir(), "config.json");
|
|
245
|
+
}
|
|
240
246
|
function getAppDataDir() {
|
|
241
247
|
const home = getHomedir();
|
|
242
248
|
if (process.platform === "darwin") {
|
|
@@ -432,12 +438,12 @@ __export(vault_service_exports, {
|
|
|
432
438
|
writeVault: () => writeVault
|
|
433
439
|
});
|
|
434
440
|
function getVaultPath() {
|
|
435
|
-
return
|
|
441
|
+
return import_node_path7.default.join(import_node_os4.default.homedir(), ".mcpman", "vault.enc");
|
|
436
442
|
}
|
|
437
443
|
function readVault(vaultPath = getVaultPath()) {
|
|
438
444
|
const empty = { version: 1, servers: {} };
|
|
439
445
|
try {
|
|
440
|
-
const raw =
|
|
446
|
+
const raw = import_node_fs5.default.readFileSync(vaultPath, "utf-8");
|
|
441
447
|
const parsed = JSON.parse(raw);
|
|
442
448
|
if (parsed.version !== 1 || typeof parsed.servers !== "object") return empty;
|
|
443
449
|
return parsed;
|
|
@@ -446,16 +452,16 @@ function readVault(vaultPath = getVaultPath()) {
|
|
|
446
452
|
}
|
|
447
453
|
}
|
|
448
454
|
function writeVault(data, vaultPath = getVaultPath()) {
|
|
449
|
-
const dir =
|
|
450
|
-
|
|
455
|
+
const dir = import_node_path7.default.dirname(vaultPath);
|
|
456
|
+
import_node_fs5.default.mkdirSync(dir, { recursive: true });
|
|
451
457
|
const tmp = `${vaultPath}.tmp`;
|
|
452
|
-
|
|
458
|
+
import_node_fs5.default.writeFileSync(tmp, JSON.stringify(data, null, 2), { mode: 384 });
|
|
453
459
|
if (process.platform !== "win32") {
|
|
454
|
-
|
|
460
|
+
import_node_fs5.default.chmodSync(tmp, 384);
|
|
455
461
|
}
|
|
456
|
-
|
|
462
|
+
import_node_fs5.default.renameSync(tmp, vaultPath);
|
|
457
463
|
if (process.platform !== "win32") {
|
|
458
|
-
|
|
464
|
+
import_node_fs5.default.chmodSync(vaultPath, 384);
|
|
459
465
|
}
|
|
460
466
|
}
|
|
461
467
|
function encrypt(value, password2) {
|
|
@@ -482,20 +488,20 @@ function decrypt(entry, password2) {
|
|
|
482
488
|
]);
|
|
483
489
|
return decrypted.toString("utf-8");
|
|
484
490
|
}
|
|
485
|
-
async function getMasterPassword(
|
|
491
|
+
async function getMasterPassword(confirm9 = false) {
|
|
486
492
|
if (_cachedPassword) return _cachedPassword;
|
|
487
|
-
const password2 = await
|
|
493
|
+
const password2 = await p4.password({
|
|
488
494
|
message: "Enter vault master password:",
|
|
489
495
|
validate: (v) => v.length < 8 ? "Password must be at least 8 characters" : void 0
|
|
490
496
|
});
|
|
491
|
-
if (
|
|
492
|
-
|
|
497
|
+
if (p4.isCancel(password2)) {
|
|
498
|
+
p4.cancel("Vault access cancelled.");
|
|
493
499
|
process.exit(0);
|
|
494
500
|
}
|
|
495
|
-
if (
|
|
496
|
-
const confirm22 = await
|
|
497
|
-
if (
|
|
498
|
-
|
|
501
|
+
if (confirm9) {
|
|
502
|
+
const confirm22 = await p4.password({ message: "Confirm master password:" });
|
|
503
|
+
if (p4.isCancel(confirm22) || confirm22 !== password2) {
|
|
504
|
+
p4.cancel("Passwords do not match.");
|
|
499
505
|
process.exit(1);
|
|
500
506
|
}
|
|
501
507
|
}
|
|
@@ -545,16 +551,16 @@ function listSecrets(server, vaultPath = getVaultPath()) {
|
|
|
545
551
|
keys: Object.keys(keys)
|
|
546
552
|
}));
|
|
547
553
|
}
|
|
548
|
-
var import_node_crypto2,
|
|
554
|
+
var import_node_crypto2, import_node_fs5, import_node_os4, import_node_path7, p4, _cachedPassword;
|
|
549
555
|
var init_vault_service = __esm({
|
|
550
556
|
"src/core/vault-service.ts"() {
|
|
551
557
|
"use strict";
|
|
552
558
|
init_cjs_shims();
|
|
553
559
|
import_node_crypto2 = __toESM(require("crypto"), 1);
|
|
554
|
-
|
|
560
|
+
import_node_fs5 = __toESM(require("fs"), 1);
|
|
555
561
|
import_node_os4 = __toESM(require("os"), 1);
|
|
556
|
-
|
|
557
|
-
|
|
562
|
+
import_node_path7 = __toESM(require("path"), 1);
|
|
563
|
+
p4 = __toESM(require("@clack/prompts"), 1);
|
|
558
564
|
_cachedPassword = null;
|
|
559
565
|
process.on("exit", () => {
|
|
560
566
|
_cachedPassword = null;
|
|
@@ -564,11 +570,12 @@ var init_vault_service = __esm({
|
|
|
564
570
|
|
|
565
571
|
// src/index.ts
|
|
566
572
|
init_cjs_shims();
|
|
567
|
-
var
|
|
573
|
+
var import_citty14 = require("citty");
|
|
568
574
|
|
|
569
575
|
// src/commands/audit.ts
|
|
570
576
|
init_cjs_shims();
|
|
571
577
|
var import_citty = require("citty");
|
|
578
|
+
var p = __toESM(require("@clack/prompts"), 1);
|
|
572
579
|
var import_picocolors = __toESM(require("picocolors"), 1);
|
|
573
580
|
var import_nanospinner = require("nanospinner");
|
|
574
581
|
|
|
@@ -766,181 +773,744 @@ async function scanAllServers(servers, concurrency = 3) {
|
|
|
766
773
|
const results = [];
|
|
767
774
|
const executing = /* @__PURE__ */ new Set();
|
|
768
775
|
for (const [name, entry] of entries) {
|
|
769
|
-
const
|
|
776
|
+
const p12 = scanServer(name, entry).then((r) => {
|
|
770
777
|
results.push(r);
|
|
771
|
-
executing.delete(
|
|
778
|
+
executing.delete(p12);
|
|
772
779
|
});
|
|
773
|
-
executing.add(
|
|
780
|
+
executing.add(p12);
|
|
774
781
|
if (executing.size >= concurrency) await Promise.race(executing);
|
|
775
782
|
}
|
|
776
783
|
await Promise.all(executing);
|
|
777
784
|
return results;
|
|
778
785
|
}
|
|
779
786
|
|
|
780
|
-
// src/
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
return
|
|
787
|
+
// src/core/version-checker.ts
|
|
788
|
+
init_cjs_shims();
|
|
789
|
+
function compareVersions(a, b) {
|
|
790
|
+
const aParts = a.replace(/^v/, "").split(".").map(Number);
|
|
791
|
+
const bParts = b.replace(/^v/, "").split(".").map(Number);
|
|
792
|
+
const len = Math.max(aParts.length, bParts.length);
|
|
793
|
+
for (let i = 0; i < len; i++) {
|
|
794
|
+
const aN = aParts[i] ?? 0;
|
|
795
|
+
const bN = bParts[i] ?? 0;
|
|
796
|
+
if (Number.isNaN(aN) || Number.isNaN(bN)) return 0;
|
|
797
|
+
if (aN < bN) return -1;
|
|
798
|
+
if (aN > bN) return 1;
|
|
799
|
+
}
|
|
800
|
+
return 0;
|
|
794
801
|
}
|
|
795
|
-
function
|
|
796
|
-
const
|
|
797
|
-
|
|
798
|
-
if (
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
if (c.high) parts.push(import_picocolors.default.red(`${c.high} high`));
|
|
802
|
-
if (c.moderate) parts.push(import_picocolors.default.yellow(`${c.moderate} moderate`));
|
|
803
|
-
if (c.low) parts.push(import_picocolors.default.dim(`${c.low} low`));
|
|
804
|
-
return parts.join(", ");
|
|
802
|
+
function detectUpdateType(current, latest) {
|
|
803
|
+
const cParts = current.replace(/^v/, "").split(".").map(Number);
|
|
804
|
+
const lParts = latest.replace(/^v/, "").split(".").map(Number);
|
|
805
|
+
if ((lParts[0] ?? 0) > (cParts[0] ?? 0)) return "major";
|
|
806
|
+
if ((lParts[1] ?? 0) > (cParts[1] ?? 0)) return "minor";
|
|
807
|
+
return "patch";
|
|
805
808
|
}
|
|
806
|
-
function
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
}
|
|
815
|
-
if (report.metadata) {
|
|
816
|
-
const { weeklyDownloads, packageAge, lastPublish, maintainerCount, deprecated } = report.metadata;
|
|
817
|
-
const dlStr = weeklyDownloads.toLocaleString();
|
|
818
|
-
console.log(
|
|
819
|
-
` ${import_picocolors.default.dim("Downloads:")} ${dlStr}/week ${import_picocolors.default.dim("|")} ${import_picocolors.default.dim("Age:")} ${packageAge}d ${import_picocolors.default.dim("|")} ${import_picocolors.default.dim("Last publish:")} ${daysAgo(lastPublish)} ${import_picocolors.default.dim("|")} ${import_picocolors.default.dim("Maintainers:")} ${maintainerCount}` + (deprecated ? import_picocolors.default.red(" [DEPRECATED]") : "")
|
|
809
|
+
async function fetchNpmLatest(packageName) {
|
|
810
|
+
try {
|
|
811
|
+
const res = await fetch(
|
|
812
|
+
`https://registry.npmjs.org/${encodeURIComponent(packageName)}/latest`,
|
|
813
|
+
{
|
|
814
|
+
headers: { Accept: "application/json" },
|
|
815
|
+
signal: AbortSignal.timeout(8e3)
|
|
816
|
+
}
|
|
820
817
|
);
|
|
818
|
+
if (!res.ok) return null;
|
|
819
|
+
const data = await res.json();
|
|
820
|
+
return typeof data.version === "string" ? data.version : null;
|
|
821
|
+
} catch {
|
|
822
|
+
return null;
|
|
821
823
|
}
|
|
822
|
-
console.log(` ${import_picocolors.default.dim("Vulnerabilities:")} ${countVulns(report.vulnerabilities)}`);
|
|
823
|
-
if (report.vulnerabilities.length > 0) {
|
|
824
|
-
for (const v of report.vulnerabilities) {
|
|
825
|
-
const sevColor = v.severity === "critical" || v.severity === "high" ? import_picocolors.default.red : import_picocolors.default.yellow;
|
|
826
|
-
const url = v.url ? import_picocolors.default.dim(` ${v.url}`) : "";
|
|
827
|
-
console.log(` ${sevColor("\u25B8")} [${v.severity}] ${v.title}${url}`);
|
|
828
|
-
}
|
|
829
|
-
}
|
|
830
|
-
console.log();
|
|
831
824
|
}
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
type: "positional",
|
|
840
|
-
description: "Specific server to audit (omit to audit all)",
|
|
841
|
-
required: false
|
|
842
|
-
},
|
|
843
|
-
json: {
|
|
844
|
-
type: "boolean",
|
|
845
|
-
description: "Output results as JSON",
|
|
846
|
-
default: false
|
|
847
|
-
},
|
|
848
|
-
fix: {
|
|
849
|
-
type: "boolean",
|
|
850
|
-
description: "Show available fix versions for vulnerable packages",
|
|
851
|
-
default: false
|
|
852
|
-
}
|
|
853
|
-
},
|
|
854
|
-
async run({ args }) {
|
|
855
|
-
const lockfile = readLockfile();
|
|
856
|
-
const { servers } = lockfile;
|
|
857
|
-
if (Object.keys(servers).length === 0) {
|
|
858
|
-
console.log(import_picocolors.default.dim("\n No MCP servers installed. Run mcpman install <server> to get started.\n"));
|
|
859
|
-
return;
|
|
860
|
-
}
|
|
861
|
-
const targets = {};
|
|
862
|
-
if (args.server) {
|
|
863
|
-
if (!servers[args.server]) {
|
|
864
|
-
console.error(import_picocolors.default.red(`
|
|
865
|
-
Server "${args.server}" not found in lockfile.
|
|
866
|
-
`));
|
|
867
|
-
process.exit(1);
|
|
825
|
+
async function fetchSmitheryLatest(name) {
|
|
826
|
+
try {
|
|
827
|
+
const res = await fetch(
|
|
828
|
+
`https://registry.smithery.ai/servers/${encodeURIComponent(name)}`,
|
|
829
|
+
{
|
|
830
|
+
headers: { Accept: "application/json" },
|
|
831
|
+
signal: AbortSignal.timeout(8e3)
|
|
868
832
|
}
|
|
869
|
-
targets[args.server] = servers[args.server];
|
|
870
|
-
} else {
|
|
871
|
-
Object.assign(targets, servers);
|
|
872
|
-
}
|
|
873
|
-
const spinner5 = (0, import_nanospinner.createSpinner)(`Scanning ${Object.keys(targets).length} server(s)...`).start();
|
|
874
|
-
let reports;
|
|
875
|
-
try {
|
|
876
|
-
reports = args.server ? [await scanServer(args.server, targets[args.server])] : await scanAllServers(targets);
|
|
877
|
-
} catch (err) {
|
|
878
|
-
spinner5.error({ text: "Scan failed" });
|
|
879
|
-
console.error(import_picocolors.default.red(String(err)));
|
|
880
|
-
process.exit(1);
|
|
881
|
-
}
|
|
882
|
-
spinner5.success({ text: `Scanned ${reports.length} server(s)` });
|
|
883
|
-
if (args.json) {
|
|
884
|
-
console.log(JSON.stringify(reports, null, 2));
|
|
885
|
-
return;
|
|
886
|
-
}
|
|
887
|
-
console.log(import_picocolors.default.bold("\n mcpman audit\n"));
|
|
888
|
-
console.log(import_picocolors.default.dim(" " + "\u2500".repeat(60)));
|
|
889
|
-
for (const report of reports) {
|
|
890
|
-
printReport(report);
|
|
891
|
-
}
|
|
892
|
-
console.log(import_picocolors.default.dim(" " + "\u2500".repeat(60)));
|
|
893
|
-
const withIssues = reports.filter(
|
|
894
|
-
(r) => r.riskLevel !== "LOW" && r.riskLevel !== "UNKNOWN"
|
|
895
833
|
);
|
|
896
|
-
|
|
897
|
-
const
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
`
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
console.log(import_picocolors.default.bold(" Fix suggestions:"));
|
|
914
|
-
for (const r of withVulns) {
|
|
915
|
-
console.log(` ${import_picocolors.default.cyan("\u2192")} Run ${import_picocolors.default.cyan(`mcpman install ${r.server}@latest`)} to update`);
|
|
916
|
-
}
|
|
917
|
-
console.log();
|
|
834
|
+
if (!res.ok) return null;
|
|
835
|
+
const data = await res.json();
|
|
836
|
+
return typeof data.version === "string" ? data.version : null;
|
|
837
|
+
} catch {
|
|
838
|
+
return null;
|
|
839
|
+
}
|
|
840
|
+
}
|
|
841
|
+
async function fetchGithubLatest(resolved) {
|
|
842
|
+
const match = resolved.match(/github\.com\/([^/]+)\/([^/]+)/);
|
|
843
|
+
if (!match) return null;
|
|
844
|
+
const [, owner, repo] = match;
|
|
845
|
+
try {
|
|
846
|
+
const res = await fetch(
|
|
847
|
+
`https://api.github.com/repos/${owner}/${repo}/releases/latest`,
|
|
848
|
+
{
|
|
849
|
+
headers: { Accept: "application/json" },
|
|
850
|
+
signal: AbortSignal.timeout(8e3)
|
|
918
851
|
}
|
|
852
|
+
);
|
|
853
|
+
if (!res.ok) return null;
|
|
854
|
+
const data = await res.json();
|
|
855
|
+
return typeof data.tag_name === "string" ? data.tag_name.replace(/^v/, "") : null;
|
|
856
|
+
} catch {
|
|
857
|
+
return null;
|
|
858
|
+
}
|
|
859
|
+
}
|
|
860
|
+
async function checkVersion(name, lockEntry) {
|
|
861
|
+
const current = lockEntry.version;
|
|
862
|
+
let latest = null;
|
|
863
|
+
if (lockEntry.source === "npm") {
|
|
864
|
+
latest = await fetchNpmLatest(name);
|
|
865
|
+
} else if (lockEntry.source === "smithery") {
|
|
866
|
+
latest = await fetchSmitheryLatest(name);
|
|
867
|
+
} else if (lockEntry.source === "github") {
|
|
868
|
+
latest = await fetchGithubLatest(lockEntry.resolved);
|
|
869
|
+
}
|
|
870
|
+
if (!latest || latest === current) {
|
|
871
|
+
return {
|
|
872
|
+
server: name,
|
|
873
|
+
source: lockEntry.source,
|
|
874
|
+
currentVersion: current,
|
|
875
|
+
latestVersion: latest ?? current,
|
|
876
|
+
hasUpdate: false
|
|
877
|
+
};
|
|
878
|
+
}
|
|
879
|
+
const hasUpdate = compareVersions(current, latest) === -1;
|
|
880
|
+
return {
|
|
881
|
+
server: name,
|
|
882
|
+
source: lockEntry.source,
|
|
883
|
+
currentVersion: current,
|
|
884
|
+
latestVersion: latest,
|
|
885
|
+
hasUpdate,
|
|
886
|
+
updateType: hasUpdate ? detectUpdateType(current, latest) : void 0
|
|
887
|
+
};
|
|
888
|
+
}
|
|
889
|
+
async function checkAllVersions(lockfile) {
|
|
890
|
+
const entries = Object.entries(lockfile.servers);
|
|
891
|
+
if (entries.length === 0) return [];
|
|
892
|
+
const results = [];
|
|
893
|
+
const executing = /* @__PURE__ */ new Set();
|
|
894
|
+
for (const [name, entry] of entries) {
|
|
895
|
+
const p12 = checkVersion(name, entry).then((r) => {
|
|
896
|
+
results.push(r);
|
|
897
|
+
executing.delete(p12);
|
|
898
|
+
});
|
|
899
|
+
executing.add(p12);
|
|
900
|
+
if (executing.size >= 5) {
|
|
901
|
+
await Promise.race(executing);
|
|
919
902
|
}
|
|
920
903
|
}
|
|
921
|
-
|
|
904
|
+
await Promise.all(executing);
|
|
905
|
+
return results;
|
|
906
|
+
}
|
|
922
907
|
|
|
923
|
-
// src/
|
|
908
|
+
// src/core/server-updater.ts
|
|
924
909
|
init_cjs_shims();
|
|
925
|
-
var import_citty2 = require("citty");
|
|
926
|
-
var import_picocolors2 = __toESM(require("picocolors"), 1);
|
|
927
910
|
|
|
928
|
-
// src/core/server-
|
|
911
|
+
// src/core/server-resolver.ts
|
|
929
912
|
init_cjs_shims();
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
913
|
+
|
|
914
|
+
// src/core/registry.ts
|
|
915
|
+
init_cjs_shims();
|
|
916
|
+
var import_node_crypto = require("crypto");
|
|
917
|
+
function computeIntegrity(resolvedUrl) {
|
|
918
|
+
const hash = (0, import_node_crypto.createHash)("sha512").update(resolvedUrl).digest("base64");
|
|
919
|
+
return `sha512-${hash}`;
|
|
920
|
+
}
|
|
921
|
+
async function resolveFromSmithery(name) {
|
|
922
|
+
const url = `https://registry.smithery.ai/servers/${encodeURIComponent(name)}`;
|
|
923
|
+
let data;
|
|
924
|
+
try {
|
|
925
|
+
const res = await fetch(url, {
|
|
926
|
+
headers: { Accept: "application/json" },
|
|
927
|
+
signal: AbortSignal.timeout(8e3)
|
|
928
|
+
});
|
|
929
|
+
if (res.status === 404) {
|
|
930
|
+
throw new Error(`Server '${name}' not found on Smithery registry`);
|
|
931
|
+
}
|
|
932
|
+
if (!res.ok) {
|
|
933
|
+
throw new Error(`Smithery API error: ${res.status}`);
|
|
934
|
+
}
|
|
935
|
+
data = await res.json();
|
|
936
|
+
} catch (err) {
|
|
937
|
+
if (err instanceof Error && err.message.includes("not found")) throw err;
|
|
938
|
+
throw new Error(
|
|
939
|
+
`Cannot reach Smithery registry: ${err instanceof Error ? err.message : String(err)}`
|
|
940
|
+
);
|
|
941
|
+
}
|
|
942
|
+
const version = typeof data.version === "string" ? data.version : "latest";
|
|
943
|
+
const command = typeof data.command === "string" ? data.command : "npx";
|
|
944
|
+
const args = Array.isArray(data.args) ? data.args : ["-y", `${name}@${version}`];
|
|
945
|
+
const envVars = Array.isArray(data.envVars) ? data.envVars : [];
|
|
946
|
+
const resolved = typeof data.resolved === "string" ? data.resolved : `smithery:${name}@${version}`;
|
|
947
|
+
return {
|
|
948
|
+
name,
|
|
949
|
+
version,
|
|
950
|
+
description: typeof data.description === "string" ? data.description : "",
|
|
951
|
+
runtime: "node",
|
|
952
|
+
command,
|
|
953
|
+
args,
|
|
954
|
+
envVars,
|
|
955
|
+
resolved
|
|
956
|
+
};
|
|
957
|
+
}
|
|
958
|
+
async function resolveFromNpm(packageName) {
|
|
959
|
+
const url = `https://registry.npmjs.org/${encodeURIComponent(packageName)}/latest`;
|
|
960
|
+
let data;
|
|
961
|
+
try {
|
|
962
|
+
const res = await fetch(url, {
|
|
963
|
+
headers: { Accept: "application/json" },
|
|
964
|
+
signal: AbortSignal.timeout(8e3)
|
|
965
|
+
});
|
|
966
|
+
if (res.status === 404) {
|
|
967
|
+
throw new Error(`Package '${packageName}' not found on npm`);
|
|
968
|
+
}
|
|
969
|
+
if (!res.ok) {
|
|
970
|
+
throw new Error(`npm registry error: ${res.status}`);
|
|
971
|
+
}
|
|
972
|
+
data = await res.json();
|
|
973
|
+
} catch (err) {
|
|
974
|
+
if (err instanceof Error && err.message.includes("not found")) throw err;
|
|
975
|
+
throw new Error(
|
|
976
|
+
`Cannot reach npm registry: ${err instanceof Error ? err.message : String(err)}`
|
|
977
|
+
);
|
|
978
|
+
}
|
|
979
|
+
const version = typeof data.version === "string" ? data.version : "latest";
|
|
980
|
+
const resolved = `https://registry.npmjs.org/${packageName}/-/${packageName.replace(/^@[^/]+\//, "")}-${version}.tgz`;
|
|
981
|
+
const mcpField = data.mcp && typeof data.mcp === "object" ? data.mcp : null;
|
|
982
|
+
const envVars = mcpField?.envVars ? mcpField.envVars : [];
|
|
983
|
+
return {
|
|
984
|
+
name: packageName,
|
|
985
|
+
version,
|
|
986
|
+
description: typeof data.description === "string" ? data.description : "",
|
|
987
|
+
runtime: "node",
|
|
988
|
+
command: "npx",
|
|
989
|
+
args: ["-y", `${packageName}@${version}`],
|
|
990
|
+
envVars,
|
|
991
|
+
resolved
|
|
992
|
+
};
|
|
993
|
+
}
|
|
994
|
+
async function resolveFromGitHub(githubUrl) {
|
|
995
|
+
const match = githubUrl.match(/github\.com\/([^/]+)\/([^/]+)/);
|
|
996
|
+
if (!match) {
|
|
997
|
+
throw new Error(`Invalid GitHub URL: ${githubUrl}`);
|
|
998
|
+
}
|
|
999
|
+
const [, owner, repo] = match;
|
|
1000
|
+
const rawUrl = `https://raw.githubusercontent.com/${owner}/${repo}/main/package.json`;
|
|
1001
|
+
let pkgData = {};
|
|
1002
|
+
try {
|
|
1003
|
+
const res = await fetch(rawUrl, { signal: AbortSignal.timeout(8e3) });
|
|
1004
|
+
if (res.ok) {
|
|
1005
|
+
pkgData = await res.json();
|
|
1006
|
+
}
|
|
1007
|
+
} catch {
|
|
1008
|
+
}
|
|
1009
|
+
const version = typeof pkgData.version === "string" ? pkgData.version : "main";
|
|
1010
|
+
const name = typeof pkgData.name === "string" ? pkgData.name : `${owner}/${repo}`;
|
|
1011
|
+
return {
|
|
1012
|
+
name,
|
|
1013
|
+
version,
|
|
1014
|
+
description: typeof pkgData.description === "string" ? pkgData.description : "",
|
|
1015
|
+
runtime: "node",
|
|
1016
|
+
command: "npx",
|
|
1017
|
+
args: ["-y", githubUrl],
|
|
1018
|
+
envVars: [],
|
|
1019
|
+
resolved: githubUrl
|
|
1020
|
+
};
|
|
1021
|
+
}
|
|
1022
|
+
|
|
1023
|
+
// src/core/server-resolver.ts
|
|
1024
|
+
function detectSource(input) {
|
|
1025
|
+
if (input.startsWith("smithery:")) {
|
|
1026
|
+
return { type: "smithery", input: input.slice(9) };
|
|
1027
|
+
}
|
|
1028
|
+
if (input.startsWith("https://github.com/") || input.startsWith("github.com/")) {
|
|
1029
|
+
return { type: "github", input };
|
|
1030
|
+
}
|
|
1031
|
+
return { type: "npm", input };
|
|
1032
|
+
}
|
|
1033
|
+
function parseEnvFlags(envFlags) {
|
|
1034
|
+
if (!envFlags) return {};
|
|
1035
|
+
const flags = Array.isArray(envFlags) ? envFlags : [envFlags];
|
|
1036
|
+
const result = {};
|
|
1037
|
+
for (const flag of flags) {
|
|
1038
|
+
const idx = flag.indexOf("=");
|
|
1039
|
+
if (idx > 0) {
|
|
1040
|
+
result[flag.slice(0, idx)] = flag.slice(idx + 1);
|
|
1041
|
+
}
|
|
1042
|
+
}
|
|
1043
|
+
return result;
|
|
1044
|
+
}
|
|
1045
|
+
async function resolveServer(input) {
|
|
1046
|
+
const source = detectSource(input);
|
|
1047
|
+
switch (source.type) {
|
|
1048
|
+
case "smithery":
|
|
1049
|
+
return resolveFromSmithery(source.input);
|
|
1050
|
+
case "github":
|
|
1051
|
+
return resolveFromGitHub(source.input);
|
|
1052
|
+
case "npm":
|
|
1053
|
+
return resolveFromNpm(source.input);
|
|
1054
|
+
}
|
|
1055
|
+
}
|
|
1056
|
+
|
|
1057
|
+
// src/core/server-updater.ts
|
|
1058
|
+
async function applyServerUpdate(serverName, lockEntry, clients) {
|
|
1059
|
+
const fromVersion = lockEntry.version;
|
|
1060
|
+
const input = lockEntry.source === "smithery" ? `smithery:${serverName}` : lockEntry.source === "github" ? lockEntry.resolved : serverName;
|
|
1061
|
+
try {
|
|
1062
|
+
const metadata = await resolveServer(input);
|
|
1063
|
+
const integrity = computeIntegrity(metadata.resolved);
|
|
1064
|
+
addEntry(serverName, {
|
|
1065
|
+
...lockEntry,
|
|
1066
|
+
version: metadata.version,
|
|
1067
|
+
resolved: metadata.resolved,
|
|
1068
|
+
integrity,
|
|
1069
|
+
command: metadata.command,
|
|
1070
|
+
args: metadata.args,
|
|
1071
|
+
installedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1072
|
+
});
|
|
1073
|
+
const targetClients = clients.filter(
|
|
1074
|
+
(c) => lockEntry.clients.includes(c.type)
|
|
1075
|
+
);
|
|
1076
|
+
for (const client of targetClients) {
|
|
1077
|
+
try {
|
|
1078
|
+
await client.addServer(serverName, {
|
|
1079
|
+
command: metadata.command,
|
|
1080
|
+
args: metadata.args
|
|
1081
|
+
});
|
|
1082
|
+
} catch {
|
|
1083
|
+
}
|
|
1084
|
+
}
|
|
1085
|
+
return {
|
|
1086
|
+
server: serverName,
|
|
1087
|
+
success: true,
|
|
1088
|
+
fromVersion,
|
|
1089
|
+
toVersion: metadata.version
|
|
1090
|
+
};
|
|
1091
|
+
} catch (err) {
|
|
1092
|
+
return {
|
|
1093
|
+
server: serverName,
|
|
1094
|
+
success: false,
|
|
1095
|
+
fromVersion,
|
|
1096
|
+
toVersion: fromVersion,
|
|
1097
|
+
error: err instanceof Error ? err.message : String(err)
|
|
1098
|
+
};
|
|
1099
|
+
}
|
|
1100
|
+
}
|
|
1101
|
+
|
|
1102
|
+
// src/commands/audit.ts
|
|
1103
|
+
function colorRisk(level, score) {
|
|
1104
|
+
const label = score !== null ? `${score}/100 (${level})` : level;
|
|
1105
|
+
if (level === "LOW") return import_picocolors.default.green(label);
|
|
1106
|
+
if (level === "MEDIUM") return import_picocolors.default.yellow(label);
|
|
1107
|
+
if (level === "HIGH") return import_picocolors.default.red(label);
|
|
1108
|
+
if (level === "CRITICAL") return import_picocolors.default.bold(import_picocolors.default.red(label));
|
|
1109
|
+
return import_picocolors.default.dim(label);
|
|
1110
|
+
}
|
|
1111
|
+
function daysAgo(isoDate) {
|
|
1112
|
+
const days = Math.floor((Date.now() - new Date(isoDate).getTime()) / 864e5);
|
|
1113
|
+
if (days === 0) return "today";
|
|
1114
|
+
if (days === 1) return "1 day ago";
|
|
1115
|
+
return `${days} days ago`;
|
|
1116
|
+
}
|
|
1117
|
+
function countVulns(vulns) {
|
|
1118
|
+
const c = { critical: 0, high: 0, moderate: 0, low: 0 };
|
|
1119
|
+
for (const v of vulns) c[v.severity]++;
|
|
1120
|
+
if (vulns.length === 0) return import_picocolors.default.green("none");
|
|
1121
|
+
const parts = [];
|
|
1122
|
+
if (c.critical) parts.push(import_picocolors.default.bold(import_picocolors.default.red(`${c.critical} critical`)));
|
|
1123
|
+
if (c.high) parts.push(import_picocolors.default.red(`${c.high} high`));
|
|
1124
|
+
if (c.moderate) parts.push(import_picocolors.default.yellow(`${c.moderate} moderate`));
|
|
1125
|
+
if (c.low) parts.push(import_picocolors.default.dim(`${c.low} low`));
|
|
1126
|
+
return parts.join(", ");
|
|
1127
|
+
}
|
|
1128
|
+
function printReport(report) {
|
|
1129
|
+
const riskColored = colorRisk(report.riskLevel, report.score);
|
|
1130
|
+
const icon = report.riskLevel === "LOW" ? import_picocolors.default.green("\u25CF") : report.riskLevel === "MEDIUM" ? import_picocolors.default.yellow("\u25CF") : report.riskLevel === "UNKNOWN" ? import_picocolors.default.dim("\u25CB") : import_picocolors.default.red("\u25CF");
|
|
1131
|
+
console.log(` ${icon} ${import_picocolors.default.bold(report.server)} Score: ${riskColored}`);
|
|
1132
|
+
if (report.source !== "npm") {
|
|
1133
|
+
console.log(` ${import_picocolors.default.dim("Non-npm source \u2014 security data unavailable")}`);
|
|
1134
|
+
console.log();
|
|
1135
|
+
return;
|
|
1136
|
+
}
|
|
1137
|
+
if (report.metadata) {
|
|
1138
|
+
const { weeklyDownloads, packageAge, lastPublish, maintainerCount, deprecated } = report.metadata;
|
|
1139
|
+
const dlStr = weeklyDownloads.toLocaleString();
|
|
1140
|
+
console.log(
|
|
1141
|
+
` ${import_picocolors.default.dim("Downloads:")} ${dlStr}/week ${import_picocolors.default.dim("|")} ${import_picocolors.default.dim("Age:")} ${packageAge}d ${import_picocolors.default.dim("|")} ${import_picocolors.default.dim("Last publish:")} ${daysAgo(lastPublish)} ${import_picocolors.default.dim("|")} ${import_picocolors.default.dim("Maintainers:")} ${maintainerCount}` + (deprecated ? import_picocolors.default.red(" [DEPRECATED]") : "")
|
|
1142
|
+
);
|
|
1143
|
+
}
|
|
1144
|
+
console.log(` ${import_picocolors.default.dim("Vulnerabilities:")} ${countVulns(report.vulnerabilities)}`);
|
|
1145
|
+
if (report.vulnerabilities.length > 0) {
|
|
1146
|
+
for (const v of report.vulnerabilities) {
|
|
1147
|
+
const sevColor = v.severity === "critical" || v.severity === "high" ? import_picocolors.default.red : import_picocolors.default.yellow;
|
|
1148
|
+
const url = v.url ? import_picocolors.default.dim(` ${v.url}`) : "";
|
|
1149
|
+
console.log(` ${sevColor("\u25B8")} [${v.severity}] ${v.title}${url}`);
|
|
1150
|
+
}
|
|
1151
|
+
}
|
|
1152
|
+
console.log();
|
|
1153
|
+
}
|
|
1154
|
+
var audit_default = (0, import_citty.defineCommand)({
|
|
1155
|
+
meta: {
|
|
1156
|
+
name: "audit",
|
|
1157
|
+
description: "Scan installed MCP servers for security vulnerabilities and trust scores"
|
|
1158
|
+
},
|
|
1159
|
+
args: {
|
|
1160
|
+
server: {
|
|
1161
|
+
type: "positional",
|
|
1162
|
+
description: "Specific server to audit (omit to audit all)",
|
|
1163
|
+
required: false
|
|
1164
|
+
},
|
|
1165
|
+
json: {
|
|
1166
|
+
type: "boolean",
|
|
1167
|
+
description: "Output results as JSON",
|
|
1168
|
+
default: false
|
|
1169
|
+
},
|
|
1170
|
+
fix: {
|
|
1171
|
+
type: "boolean",
|
|
1172
|
+
description: "Apply updates to fix vulnerable packages",
|
|
1173
|
+
default: false
|
|
1174
|
+
},
|
|
1175
|
+
yes: {
|
|
1176
|
+
type: "boolean",
|
|
1177
|
+
description: "Skip confirmation prompt (use with --fix)",
|
|
1178
|
+
default: false
|
|
1179
|
+
}
|
|
1180
|
+
},
|
|
1181
|
+
async run({ args }) {
|
|
1182
|
+
const lockfile = readLockfile();
|
|
1183
|
+
const { servers } = lockfile;
|
|
1184
|
+
if (Object.keys(servers).length === 0) {
|
|
1185
|
+
console.log(import_picocolors.default.dim("\n No MCP servers installed. Run mcpman install <server> to get started.\n"));
|
|
1186
|
+
return;
|
|
1187
|
+
}
|
|
1188
|
+
const targets = {};
|
|
1189
|
+
if (args.server) {
|
|
1190
|
+
if (!servers[args.server]) {
|
|
1191
|
+
console.error(import_picocolors.default.red(`
|
|
1192
|
+
Server "${args.server}" not found in lockfile.
|
|
1193
|
+
`));
|
|
1194
|
+
process.exit(1);
|
|
1195
|
+
}
|
|
1196
|
+
targets[args.server] = servers[args.server];
|
|
1197
|
+
} else {
|
|
1198
|
+
Object.assign(targets, servers);
|
|
1199
|
+
}
|
|
1200
|
+
const spinner5 = (0, import_nanospinner.createSpinner)(`Scanning ${Object.keys(targets).length} server(s)...`).start();
|
|
1201
|
+
let reports;
|
|
1202
|
+
try {
|
|
1203
|
+
reports = args.server ? [await scanServer(args.server, targets[args.server])] : await scanAllServers(targets);
|
|
1204
|
+
} catch (err) {
|
|
1205
|
+
spinner5.error({ text: "Scan failed" });
|
|
1206
|
+
console.error(import_picocolors.default.red(String(err)));
|
|
1207
|
+
process.exit(1);
|
|
1208
|
+
}
|
|
1209
|
+
spinner5.success({ text: `Scanned ${reports.length} server(s)` });
|
|
1210
|
+
if (args.json) {
|
|
1211
|
+
console.log(JSON.stringify(reports, null, 2));
|
|
1212
|
+
return;
|
|
1213
|
+
}
|
|
1214
|
+
console.log(import_picocolors.default.bold("\n mcpman audit\n"));
|
|
1215
|
+
console.log(import_picocolors.default.dim(" " + "\u2500".repeat(60)));
|
|
1216
|
+
for (const report of reports) {
|
|
1217
|
+
printReport(report);
|
|
1218
|
+
}
|
|
1219
|
+
console.log(import_picocolors.default.dim(" " + "\u2500".repeat(60)));
|
|
1220
|
+
const withIssues = reports.filter(
|
|
1221
|
+
(r) => r.riskLevel !== "LOW" && r.riskLevel !== "UNKNOWN"
|
|
1222
|
+
);
|
|
1223
|
+
const npmReports = reports.filter((r) => r.source === "npm");
|
|
1224
|
+
const parts = [];
|
|
1225
|
+
parts.push(`${reports.length} server(s) scanned`);
|
|
1226
|
+
if (npmReports.length < reports.length) {
|
|
1227
|
+
parts.push(import_picocolors.default.dim(`${reports.length - npmReports.length} non-npm (unverified)`));
|
|
1228
|
+
}
|
|
1229
|
+
if (withIssues.length > 0) {
|
|
1230
|
+
parts.push(import_picocolors.default.yellow(`${withIssues.length} with issues`));
|
|
1231
|
+
} else {
|
|
1232
|
+
parts.push(import_picocolors.default.green("all clear"));
|
|
1233
|
+
}
|
|
1234
|
+
console.log(`
|
|
1235
|
+
Summary: ${parts.join(" | ")}
|
|
1236
|
+
`);
|
|
1237
|
+
if (args.fix) {
|
|
1238
|
+
await runAuditFix(reports, lockfile.servers, args.yes);
|
|
1239
|
+
}
|
|
1240
|
+
}
|
|
1241
|
+
});
|
|
1242
|
+
async function loadClients() {
|
|
1243
|
+
try {
|
|
1244
|
+
const mod = await Promise.resolve().then(() => (init_client_detector(), client_detector_exports));
|
|
1245
|
+
return mod.getInstalledClients();
|
|
1246
|
+
} catch {
|
|
1247
|
+
return [];
|
|
1248
|
+
}
|
|
1249
|
+
}
|
|
1250
|
+
async function runAuditFix(reports, servers, skipConfirm) {
|
|
1251
|
+
const npmWithVulns = reports.filter(
|
|
1252
|
+
(r) => r.vulnerabilities.length > 0 && r.source === "npm"
|
|
1253
|
+
);
|
|
1254
|
+
const nonNpmWithVulns = reports.filter(
|
|
1255
|
+
(r) => r.vulnerabilities.length > 0 && r.source !== "npm"
|
|
1256
|
+
);
|
|
1257
|
+
if (nonNpmWithVulns.length > 0) {
|
|
1258
|
+
console.log(import_picocolors.default.yellow(" Non-npm servers require manual update:"));
|
|
1259
|
+
for (const r of nonNpmWithVulns) {
|
|
1260
|
+
console.log(` ${import_picocolors.default.dim("\u2192")} ${r.server} (${r.source})`);
|
|
1261
|
+
}
|
|
1262
|
+
console.log();
|
|
1263
|
+
}
|
|
1264
|
+
if (npmWithVulns.length === 0) {
|
|
1265
|
+
console.log(import_picocolors.default.green(" No fixable vulnerabilities found.\n"));
|
|
1266
|
+
return;
|
|
1267
|
+
}
|
|
1268
|
+
const versionSpinner = (0, import_nanospinner.createSpinner)("Checking for available updates...").start();
|
|
1269
|
+
const versionChecks = await Promise.all(
|
|
1270
|
+
npmWithVulns.map((r) => checkVersion(r.server, servers[r.server]))
|
|
1271
|
+
);
|
|
1272
|
+
versionSpinner.success({ text: "Version check complete" });
|
|
1273
|
+
const updatable = versionChecks.filter((u) => u.hasUpdate);
|
|
1274
|
+
if (updatable.length === 0) {
|
|
1275
|
+
console.log(import_picocolors.default.yellow(
|
|
1276
|
+
" Vulnerable servers have no newer versions available yet.\n Allow time for registry to publish fixes.\n"
|
|
1277
|
+
));
|
|
1278
|
+
return;
|
|
1279
|
+
}
|
|
1280
|
+
console.log(import_picocolors.default.bold(`
|
|
1281
|
+
${updatable.length} server(s) can be updated to fix vulnerabilities:
|
|
1282
|
+
`));
|
|
1283
|
+
for (const u of updatable) {
|
|
1284
|
+
console.log(` ${import_picocolors.default.cyan("\u2192")} ${u.server} ${import_picocolors.default.dim(u.currentVersion)} \u2192 ${import_picocolors.default.green(u.latestVersion)}`);
|
|
1285
|
+
}
|
|
1286
|
+
console.log();
|
|
1287
|
+
if (!skipConfirm) {
|
|
1288
|
+
const confirmed = await p.confirm({
|
|
1289
|
+
message: `Update ${updatable.length} vulnerable server(s)?`,
|
|
1290
|
+
initialValue: true
|
|
1291
|
+
});
|
|
1292
|
+
if (p.isCancel(confirmed) || !confirmed) {
|
|
1293
|
+
p.outro("Cancelled.");
|
|
1294
|
+
return;
|
|
1295
|
+
}
|
|
1296
|
+
}
|
|
1297
|
+
const clients = await loadClients();
|
|
1298
|
+
let successCount = 0;
|
|
1299
|
+
const results = [];
|
|
1300
|
+
for (const u of updatable) {
|
|
1301
|
+
const s = (0, import_nanospinner.createSpinner)(`Updating ${u.server}...`).start();
|
|
1302
|
+
const result = await applyServerUpdate(u.server, servers[u.server], clients);
|
|
1303
|
+
if (result.success) {
|
|
1304
|
+
s.success({ text: `${import_picocolors.default.green("\u2713")} ${u.server}: ${result.fromVersion} \u2192 ${result.toVersion}` });
|
|
1305
|
+
successCount++;
|
|
1306
|
+
} else {
|
|
1307
|
+
s.error({ text: `${import_picocolors.default.red("\u2717")} ${u.server}: ${result.error}` });
|
|
1308
|
+
}
|
|
1309
|
+
results.push({
|
|
1310
|
+
server: u.server,
|
|
1311
|
+
from: result.fromVersion,
|
|
1312
|
+
to: result.toVersion,
|
|
1313
|
+
ok: result.success,
|
|
1314
|
+
error: result.error
|
|
1315
|
+
});
|
|
1316
|
+
}
|
|
1317
|
+
console.log();
|
|
1318
|
+
if (successCount > 0) {
|
|
1319
|
+
const updatedNames = results.filter((r) => r.ok).map((r) => r.server);
|
|
1320
|
+
const freshLockfile = readLockfile();
|
|
1321
|
+
const rescanSpinner = (0, import_nanospinner.createSpinner)("Re-scanning updated servers...").start();
|
|
1322
|
+
const afterReports = await Promise.all(
|
|
1323
|
+
updatedNames.map((name) => scanServer(name, freshLockfile.servers[name]))
|
|
1324
|
+
);
|
|
1325
|
+
rescanSpinner.success({ text: "Re-scan complete" });
|
|
1326
|
+
console.log(import_picocolors.default.bold("\n Before / After:\n"));
|
|
1327
|
+
for (const after of afterReports) {
|
|
1328
|
+
const before = reports.find((r) => r.server === after.server);
|
|
1329
|
+
const beforeVulns = before?.vulnerabilities.length ?? 0;
|
|
1330
|
+
const afterVulns = after.vulnerabilities.length;
|
|
1331
|
+
const improved = afterVulns < beforeVulns ? import_picocolors.default.green("improved") : import_picocolors.default.yellow("unchanged");
|
|
1332
|
+
console.log(
|
|
1333
|
+
` ${import_picocolors.default.bold(after.server)} vulns: ${beforeVulns} \u2192 ${afterVulns} [${improved}]`
|
|
1334
|
+
);
|
|
1335
|
+
}
|
|
1336
|
+
console.log();
|
|
1337
|
+
}
|
|
1338
|
+
console.log(`
|
|
1339
|
+
${successCount} of ${updatable.length} server(s) updated.
|
|
1340
|
+
`);
|
|
1341
|
+
}
|
|
1342
|
+
|
|
1343
|
+
// src/commands/config.ts
|
|
1344
|
+
init_cjs_shims();
|
|
1345
|
+
var import_citty2 = require("citty");
|
|
1346
|
+
var import_picocolors2 = __toESM(require("picocolors"), 1);
|
|
1347
|
+
var p2 = __toESM(require("@clack/prompts"), 1);
|
|
1348
|
+
|
|
1349
|
+
// src/core/config-service.ts
|
|
1350
|
+
init_cjs_shims();
|
|
1351
|
+
var import_node_fs4 = __toESM(require("fs"), 1);
|
|
1352
|
+
var import_node_path5 = __toESM(require("path"), 1);
|
|
1353
|
+
init_paths();
|
|
1354
|
+
var VALID_KEYS = /* @__PURE__ */ new Set([
|
|
1355
|
+
"defaultClient",
|
|
1356
|
+
"updateCheckInterval",
|
|
1357
|
+
"preferredRegistry",
|
|
1358
|
+
"vaultTimeout"
|
|
1359
|
+
]);
|
|
1360
|
+
function readConfig(configPath = getConfigPath()) {
|
|
1361
|
+
try {
|
|
1362
|
+
const raw = import_node_fs4.default.readFileSync(configPath, "utf-8");
|
|
1363
|
+
const parsed = JSON.parse(raw);
|
|
1364
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
1365
|
+
return {};
|
|
1366
|
+
}
|
|
1367
|
+
return parsed;
|
|
1368
|
+
} catch {
|
|
1369
|
+
return {};
|
|
1370
|
+
}
|
|
1371
|
+
}
|
|
1372
|
+
function writeConfig(data, configPath = getConfigPath()) {
|
|
1373
|
+
const dir = import_node_path5.default.dirname(configPath);
|
|
1374
|
+
import_node_fs4.default.mkdirSync(dir, { recursive: true });
|
|
1375
|
+
const tmp = `${configPath}.tmp`;
|
|
1376
|
+
import_node_fs4.default.writeFileSync(tmp, JSON.stringify(data, null, 2), { encoding: "utf-8" });
|
|
1377
|
+
import_node_fs4.default.renameSync(tmp, configPath);
|
|
1378
|
+
}
|
|
1379
|
+
function getConfigValue(key, configPath = getConfigPath()) {
|
|
1380
|
+
const data = readConfig(configPath);
|
|
1381
|
+
if (!VALID_KEYS.has(key)) return void 0;
|
|
1382
|
+
return data[key];
|
|
1383
|
+
}
|
|
1384
|
+
function setConfigValue(key, value, configPath = getConfigPath()) {
|
|
1385
|
+
const data = readConfig(configPath);
|
|
1386
|
+
if (!VALID_KEYS.has(key)) {
|
|
1387
|
+
throw new Error(`Unknown config key: "${key}". Valid keys: ${[...VALID_KEYS].join(", ")}`);
|
|
1388
|
+
}
|
|
1389
|
+
data[key] = value;
|
|
1390
|
+
writeConfig(data, configPath);
|
|
1391
|
+
}
|
|
1392
|
+
|
|
1393
|
+
// src/commands/config.ts
|
|
1394
|
+
function coerceValue(raw) {
|
|
1395
|
+
if (raw === "true") return true;
|
|
1396
|
+
if (raw === "false") return false;
|
|
1397
|
+
const num = Number(raw);
|
|
1398
|
+
if (!Number.isNaN(num) && raw.trim() !== "") return num;
|
|
1399
|
+
return raw;
|
|
1400
|
+
}
|
|
1401
|
+
var setCommand = (0, import_citty2.defineCommand)({
|
|
1402
|
+
meta: { name: "set", description: "Set a config value" },
|
|
1403
|
+
args: {
|
|
1404
|
+
key: {
|
|
1405
|
+
type: "positional",
|
|
1406
|
+
description: "Config key (e.g. defaultClient)",
|
|
1407
|
+
required: true
|
|
1408
|
+
},
|
|
1409
|
+
value: {
|
|
1410
|
+
type: "positional",
|
|
1411
|
+
description: "Value to set",
|
|
1412
|
+
required: true
|
|
1413
|
+
}
|
|
1414
|
+
},
|
|
1415
|
+
run({ args }) {
|
|
1416
|
+
try {
|
|
1417
|
+
const coerced = coerceValue(args.value);
|
|
1418
|
+
setConfigValue(args.key, coerced);
|
|
1419
|
+
console.log(
|
|
1420
|
+
`${import_picocolors2.default.green("\u2713")} Set ${import_picocolors2.default.bold(args.key)} = ${import_picocolors2.default.cyan(String(coerced))}`
|
|
1421
|
+
);
|
|
1422
|
+
} catch (err) {
|
|
1423
|
+
console.error(`${import_picocolors2.default.red("\u2717")} ${String(err)}`);
|
|
1424
|
+
process.exit(1);
|
|
1425
|
+
}
|
|
1426
|
+
}
|
|
1427
|
+
});
|
|
1428
|
+
var getCommand = (0, import_citty2.defineCommand)({
|
|
1429
|
+
meta: { name: "get", description: "Get a config value" },
|
|
1430
|
+
args: {
|
|
1431
|
+
key: {
|
|
1432
|
+
type: "positional",
|
|
1433
|
+
description: "Config key to read",
|
|
1434
|
+
required: true
|
|
1435
|
+
}
|
|
1436
|
+
},
|
|
1437
|
+
run({ args }) {
|
|
1438
|
+
const val = getConfigValue(args.key);
|
|
1439
|
+
if (val === void 0) {
|
|
1440
|
+
console.log(import_picocolors2.default.dim(`${args.key}: (not set)`));
|
|
1441
|
+
} else {
|
|
1442
|
+
console.log(`${import_picocolors2.default.bold(args.key)}: ${import_picocolors2.default.cyan(String(val))}`);
|
|
1443
|
+
}
|
|
1444
|
+
}
|
|
1445
|
+
});
|
|
1446
|
+
var listCommand = (0, import_citty2.defineCommand)({
|
|
1447
|
+
meta: { name: "list", description: "List all config values" },
|
|
1448
|
+
run() {
|
|
1449
|
+
const data = readConfig();
|
|
1450
|
+
const entries = Object.entries(data);
|
|
1451
|
+
if (entries.length === 0) {
|
|
1452
|
+
console.log(import_picocolors2.default.dim("No config values set. Use `mcpman config set <key> <value>`."));
|
|
1453
|
+
return;
|
|
1454
|
+
}
|
|
1455
|
+
console.log("");
|
|
1456
|
+
console.log(import_picocolors2.default.bold("mcpman config:"));
|
|
1457
|
+
console.log("");
|
|
1458
|
+
for (const [key, val] of entries) {
|
|
1459
|
+
console.log(` ${import_picocolors2.default.green("\u25CF")} ${import_picocolors2.default.bold(key)} ${import_picocolors2.default.cyan(String(val))}`);
|
|
1460
|
+
}
|
|
1461
|
+
console.log("");
|
|
1462
|
+
console.log(import_picocolors2.default.dim(` ${entries.length} key${entries.length !== 1 ? "s" : ""} configured`));
|
|
1463
|
+
}
|
|
1464
|
+
});
|
|
1465
|
+
var resetCommand = (0, import_citty2.defineCommand)({
|
|
1466
|
+
meta: { name: "reset", description: "Reset config to defaults (removes config file)" },
|
|
1467
|
+
async run() {
|
|
1468
|
+
const confirmed = await p2.confirm({
|
|
1469
|
+
message: "Reset all config values to defaults?",
|
|
1470
|
+
initialValue: false
|
|
1471
|
+
});
|
|
1472
|
+
if (p2.isCancel(confirmed) || !confirmed) {
|
|
1473
|
+
p2.cancel("Cancelled.");
|
|
1474
|
+
return;
|
|
1475
|
+
}
|
|
1476
|
+
writeConfig({});
|
|
1477
|
+
console.log(`${import_picocolors2.default.green("\u2713")} Config reset to defaults.`);
|
|
1478
|
+
}
|
|
1479
|
+
});
|
|
1480
|
+
var config_default = (0, import_citty2.defineCommand)({
|
|
1481
|
+
meta: {
|
|
1482
|
+
name: "config",
|
|
1483
|
+
description: "Manage mcpman CLI configuration"
|
|
1484
|
+
},
|
|
1485
|
+
subCommands: {
|
|
1486
|
+
set: setCommand,
|
|
1487
|
+
get: getCommand,
|
|
1488
|
+
list: listCommand,
|
|
1489
|
+
reset: resetCommand
|
|
1490
|
+
}
|
|
1491
|
+
});
|
|
1492
|
+
|
|
1493
|
+
// src/commands/doctor.ts
|
|
1494
|
+
init_cjs_shims();
|
|
1495
|
+
var import_citty3 = require("citty");
|
|
1496
|
+
var import_picocolors3 = __toESM(require("picocolors"), 1);
|
|
1497
|
+
|
|
1498
|
+
// src/core/server-inventory.ts
|
|
1499
|
+
init_cjs_shims();
|
|
1500
|
+
init_client_detector();
|
|
1501
|
+
async function getInstalledServers(clientFilter) {
|
|
1502
|
+
const clients = await getInstalledClients();
|
|
1503
|
+
const filtered = clientFilter ? clients.filter((c) => c.type === clientFilter) : clients;
|
|
1504
|
+
const serverMap = /* @__PURE__ */ new Map();
|
|
1505
|
+
for (const client of filtered) {
|
|
1506
|
+
let config;
|
|
1507
|
+
try {
|
|
1508
|
+
config = await client.readConfig();
|
|
1509
|
+
} catch {
|
|
1510
|
+
continue;
|
|
1511
|
+
}
|
|
1512
|
+
for (const [name, entry] of Object.entries(config.servers)) {
|
|
1513
|
+
const existing = serverMap.get(name);
|
|
944
1514
|
if (existing) {
|
|
945
1515
|
if (!existing.clients.includes(client.type)) {
|
|
946
1516
|
existing.clients.push(client.type);
|
|
@@ -1194,12 +1764,12 @@ async function quickHealthProbe(config, timeoutMs = 3e3) {
|
|
|
1194
1764
|
|
|
1195
1765
|
// src/commands/doctor.ts
|
|
1196
1766
|
var CHECK_ICON = {
|
|
1197
|
-
pass:
|
|
1198
|
-
fail:
|
|
1199
|
-
skip:
|
|
1200
|
-
warn:
|
|
1767
|
+
pass: import_picocolors3.default.green("\u2713"),
|
|
1768
|
+
fail: import_picocolors3.default.red("\u2717"),
|
|
1769
|
+
skip: import_picocolors3.default.dim("-"),
|
|
1770
|
+
warn: import_picocolors3.default.yellow("\u26A0")
|
|
1201
1771
|
};
|
|
1202
|
-
var doctor_default = (0,
|
|
1772
|
+
var doctor_default = (0, import_citty3.defineCommand)({
|
|
1203
1773
|
meta: {
|
|
1204
1774
|
name: "doctor",
|
|
1205
1775
|
description: "Check MCP server health and configuration"
|
|
@@ -1212,10 +1782,10 @@ var doctor_default = (0, import_citty2.defineCommand)({
|
|
|
1212
1782
|
}
|
|
1213
1783
|
},
|
|
1214
1784
|
async run({ args }) {
|
|
1215
|
-
console.log(
|
|
1785
|
+
console.log(import_picocolors3.default.bold("\n mcpman doctor\n"));
|
|
1216
1786
|
const servers = await getInstalledServers();
|
|
1217
1787
|
if (servers.length === 0) {
|
|
1218
|
-
console.log(
|
|
1788
|
+
console.log(import_picocolors3.default.dim(" No MCP servers installed. Run mcpman install <server> to get started."));
|
|
1219
1789
|
return;
|
|
1220
1790
|
}
|
|
1221
1791
|
const tasks = servers.map((s) => () => checkServerHealth(s.name, s.config));
|
|
@@ -1227,14 +1797,14 @@ var doctor_default = (0, import_citty2.defineCommand)({
|
|
|
1227
1797
|
if (result.status === "healthy") passed++;
|
|
1228
1798
|
else failed++;
|
|
1229
1799
|
}
|
|
1230
|
-
console.log(
|
|
1800
|
+
console.log(import_picocolors3.default.dim(" " + "\u2500".repeat(50)));
|
|
1231
1801
|
const parts = [];
|
|
1232
|
-
if (passed > 0) parts.push(
|
|
1233
|
-
if (failed > 0) parts.push(
|
|
1802
|
+
if (passed > 0) parts.push(import_picocolors3.default.green(`${passed} healthy`));
|
|
1803
|
+
if (failed > 0) parts.push(import_picocolors3.default.red(`${failed} unhealthy`));
|
|
1234
1804
|
console.log(` Summary: ${parts.join(", ")}`);
|
|
1235
1805
|
if (failed > 0) {
|
|
1236
1806
|
if (!args.fix) {
|
|
1237
|
-
console.log(
|
|
1807
|
+
console.log(import_picocolors3.default.dim(` Run ${import_picocolors3.default.cyan("mcpman doctor --fix")} for fix suggestions.
|
|
1238
1808
|
`));
|
|
1239
1809
|
}
|
|
1240
1810
|
process.exit(1);
|
|
@@ -1243,13 +1813,13 @@ var doctor_default = (0, import_citty2.defineCommand)({
|
|
|
1243
1813
|
}
|
|
1244
1814
|
});
|
|
1245
1815
|
function printServerResult(result, showFix) {
|
|
1246
|
-
const icon = result.status === "healthy" ?
|
|
1247
|
-
console.log(` ${icon} ${
|
|
1816
|
+
const icon = result.status === "healthy" ? import_picocolors3.default.green("\u25CF") : import_picocolors3.default.red("\u25CF");
|
|
1817
|
+
console.log(` ${icon} ${import_picocolors3.default.bold(result.serverName)}`);
|
|
1248
1818
|
for (const check of result.checks) {
|
|
1249
1819
|
const checkIcon = check.skipped ? CHECK_ICON.skip : check.passed ? CHECK_ICON.pass : CHECK_ICON.fail;
|
|
1250
1820
|
console.log(` ${checkIcon} ${check.name}: ${check.message}`);
|
|
1251
1821
|
if (showFix && !check.passed && !check.skipped && check.fix) {
|
|
1252
|
-
console.log(` ${
|
|
1822
|
+
console.log(` ${import_picocolors3.default.yellow("\u2192")} Fix: ${import_picocolors3.default.cyan(check.fix)}`);
|
|
1253
1823
|
}
|
|
1254
1824
|
}
|
|
1255
1825
|
console.log();
|
|
@@ -1258,11 +1828,11 @@ async function runParallel(tasks, concurrency) {
|
|
|
1258
1828
|
const results = [];
|
|
1259
1829
|
const executing = /* @__PURE__ */ new Set();
|
|
1260
1830
|
for (const task of tasks) {
|
|
1261
|
-
const
|
|
1831
|
+
const p12 = task().then((r) => {
|
|
1262
1832
|
results.push(r);
|
|
1263
|
-
executing.delete(
|
|
1833
|
+
executing.delete(p12);
|
|
1264
1834
|
});
|
|
1265
|
-
executing.add(
|
|
1835
|
+
executing.add(p12);
|
|
1266
1836
|
if (executing.size >= concurrency) {
|
|
1267
1837
|
await Promise.race(executing);
|
|
1268
1838
|
}
|
|
@@ -1271,123 +1841,177 @@ async function runParallel(tasks, concurrency) {
|
|
|
1271
1841
|
return results;
|
|
1272
1842
|
}
|
|
1273
1843
|
|
|
1274
|
-
// src/commands/
|
|
1844
|
+
// src/commands/info.ts
|
|
1275
1845
|
init_cjs_shims();
|
|
1276
|
-
var
|
|
1277
|
-
var
|
|
1278
|
-
var
|
|
1846
|
+
var import_citty4 = require("citty");
|
|
1847
|
+
var import_picocolors4 = __toESM(require("picocolors"), 1);
|
|
1848
|
+
var import_nanospinner2 = require("nanospinner");
|
|
1279
1849
|
|
|
1280
|
-
// src/core/
|
|
1850
|
+
// src/core/package-info.ts
|
|
1281
1851
|
init_cjs_shims();
|
|
1282
|
-
|
|
1283
|
-
function
|
|
1284
|
-
const
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
let
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1852
|
+
init_trust_scorer();
|
|
1853
|
+
async function buildInfo(name, entry, source = "npm") {
|
|
1854
|
+
const resolvedSource = entry?.source ?? source;
|
|
1855
|
+
let weeklyDownloads = 0;
|
|
1856
|
+
let maintainerCount = 0;
|
|
1857
|
+
let packageAge = 0;
|
|
1858
|
+
let lastPublish = "";
|
|
1859
|
+
let deprecated = false;
|
|
1860
|
+
let trustScore = null;
|
|
1861
|
+
let riskLevel = "UNKNOWN";
|
|
1862
|
+
if (resolvedSource === "npm") {
|
|
1863
|
+
const metadata = await fetchNpmMetadata(name);
|
|
1864
|
+
if (!metadata && !entry) {
|
|
1865
|
+
return null;
|
|
1866
|
+
}
|
|
1867
|
+
if (metadata) {
|
|
1868
|
+
weeklyDownloads = metadata.weeklyDownloads;
|
|
1869
|
+
maintainerCount = metadata.maintainerCount;
|
|
1870
|
+
packageAge = metadata.packageAge;
|
|
1871
|
+
lastPublish = metadata.lastPublish;
|
|
1872
|
+
deprecated = metadata.deprecated;
|
|
1873
|
+
const scored = computeTrustScore(metadata, []);
|
|
1874
|
+
trustScore = scored.score;
|
|
1875
|
+
riskLevel = scored.riskLevel;
|
|
1300
1876
|
}
|
|
1301
|
-
data = await res.json();
|
|
1302
|
-
} catch (err) {
|
|
1303
|
-
if (err instanceof Error && err.message.includes("not found")) throw err;
|
|
1304
|
-
throw new Error(
|
|
1305
|
-
`Cannot reach Smithery registry: ${err instanceof Error ? err.message : String(err)}`
|
|
1306
|
-
);
|
|
1307
1877
|
}
|
|
1308
|
-
const version = typeof data.version === "string" ? data.version : "latest";
|
|
1309
|
-
const command = typeof data.command === "string" ? data.command : "npx";
|
|
1310
|
-
const args = Array.isArray(data.args) ? data.args : ["-y", `${name}@${version}`];
|
|
1311
|
-
const envVars = Array.isArray(data.envVars) ? data.envVars : [];
|
|
1312
|
-
const resolved = typeof data.resolved === "string" ? data.resolved : `smithery:${name}@${version}`;
|
|
1313
1878
|
return {
|
|
1314
1879
|
name,
|
|
1315
|
-
version,
|
|
1316
|
-
description:
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1880
|
+
version: entry?.version ?? "unknown",
|
|
1881
|
+
description: "",
|
|
1882
|
+
source: resolvedSource,
|
|
1883
|
+
runtime: entry?.runtime ?? "node",
|
|
1884
|
+
envVars: entry?.envVars ?? [],
|
|
1885
|
+
weeklyDownloads,
|
|
1886
|
+
maintainerCount,
|
|
1887
|
+
packageAge,
|
|
1888
|
+
lastPublish,
|
|
1889
|
+
deprecated,
|
|
1890
|
+
trustScore,
|
|
1891
|
+
riskLevel,
|
|
1892
|
+
installedClients: entry?.clients ?? [],
|
|
1893
|
+
isInstalled: entry !== null
|
|
1322
1894
|
};
|
|
1323
1895
|
}
|
|
1324
|
-
async function
|
|
1325
|
-
const
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
const res = await fetch(url, {
|
|
1329
|
-
headers: { Accept: "application/json" },
|
|
1330
|
-
signal: AbortSignal.timeout(8e3)
|
|
1331
|
-
});
|
|
1332
|
-
if (res.status === 404) {
|
|
1333
|
-
throw new Error(`Package '${packageName}' not found on npm`);
|
|
1334
|
-
}
|
|
1335
|
-
if (!res.ok) {
|
|
1336
|
-
throw new Error(`npm registry error: ${res.status}`);
|
|
1337
|
-
}
|
|
1338
|
-
data = await res.json();
|
|
1339
|
-
} catch (err) {
|
|
1340
|
-
if (err instanceof Error && err.message.includes("not found")) throw err;
|
|
1341
|
-
throw new Error(
|
|
1342
|
-
`Cannot reach npm registry: ${err instanceof Error ? err.message : String(err)}`
|
|
1343
|
-
);
|
|
1344
|
-
}
|
|
1345
|
-
const version = typeof data.version === "string" ? data.version : "latest";
|
|
1346
|
-
const resolved = `https://registry.npmjs.org/${packageName}/-/${packageName.replace(/^@[^/]+\//, "")}-${version}.tgz`;
|
|
1347
|
-
const mcpField = data.mcp && typeof data.mcp === "object" ? data.mcp : null;
|
|
1348
|
-
const envVars = mcpField?.envVars ? mcpField.envVars : [];
|
|
1349
|
-
return {
|
|
1350
|
-
name: packageName,
|
|
1351
|
-
version,
|
|
1352
|
-
description: typeof data.description === "string" ? data.description : "",
|
|
1353
|
-
runtime: "node",
|
|
1354
|
-
command: "npx",
|
|
1355
|
-
args: ["-y", `${packageName}@${version}`],
|
|
1356
|
-
envVars,
|
|
1357
|
-
resolved
|
|
1358
|
-
};
|
|
1896
|
+
async function getPackageInfo(serverName) {
|
|
1897
|
+
const lockfile = readLockfile();
|
|
1898
|
+
const entry = lockfile.servers[serverName] ?? null;
|
|
1899
|
+
return buildInfo(serverName, entry);
|
|
1359
1900
|
}
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1901
|
+
|
|
1902
|
+
// src/commands/info.ts
|
|
1903
|
+
function colorRisk2(score, riskLevel) {
|
|
1904
|
+
const label = score !== null ? `${score}/100 (${riskLevel})` : riskLevel;
|
|
1905
|
+
if (riskLevel === "LOW") return import_picocolors4.default.green(label);
|
|
1906
|
+
if (riskLevel === "MEDIUM") return import_picocolors4.default.yellow(label);
|
|
1907
|
+
if (riskLevel === "HIGH") return import_picocolors4.default.red(label);
|
|
1908
|
+
if (riskLevel === "CRITICAL") return import_picocolors4.default.bold(import_picocolors4.default.red(label));
|
|
1909
|
+
return import_picocolors4.default.dim(label);
|
|
1910
|
+
}
|
|
1911
|
+
function formatDaysAgo(isoDate) {
|
|
1912
|
+
if (!isoDate) return "unknown";
|
|
1913
|
+
const days = Math.floor((Date.now() - new Date(isoDate).getTime()) / 864e5);
|
|
1914
|
+
if (days === 0) return "today";
|
|
1915
|
+
if (days === 1) return "1 day ago";
|
|
1916
|
+
return `${days} days ago`;
|
|
1917
|
+
}
|
|
1918
|
+
function printInfo(info2) {
|
|
1919
|
+
const installedBadge = info2.isInstalled ? import_picocolors4.default.green(" [installed]") : import_picocolors4.default.dim(" [not installed]");
|
|
1920
|
+
console.log();
|
|
1921
|
+
console.log(import_picocolors4.default.bold(` ${info2.name}@${info2.version}`) + installedBadge);
|
|
1922
|
+
console.log(import_picocolors4.default.dim(" " + "\u2500".repeat(60)));
|
|
1923
|
+
console.log(` ${import_picocolors4.default.dim("Source:")} ${info2.source}`);
|
|
1924
|
+
console.log(` ${import_picocolors4.default.dim("Runtime:")} ${info2.runtime}`);
|
|
1925
|
+
if (info2.description) {
|
|
1926
|
+
console.log(` ${import_picocolors4.default.dim("Description:")} ${info2.description}`);
|
|
1927
|
+
}
|
|
1928
|
+
if (info2.deprecated) {
|
|
1929
|
+
console.log(` ${import_picocolors4.default.red("[DEPRECATED]")} This package is deprecated`);
|
|
1930
|
+
}
|
|
1931
|
+
console.log();
|
|
1932
|
+
console.log(` ${import_picocolors4.default.bold("Trust & Security")}`);
|
|
1933
|
+
console.log(` ${import_picocolors4.default.dim("Trust score:")} ${colorRisk2(info2.trustScore, info2.riskLevel)}`);
|
|
1934
|
+
if (info2.source === "npm") {
|
|
1935
|
+
console.log(
|
|
1936
|
+
` ${import_picocolors4.default.dim("Downloads:")} ${info2.weeklyDownloads.toLocaleString()}/week ${import_picocolors4.default.dim("|")} ${import_picocolors4.default.dim("Age:")} ${info2.packageAge}d ${import_picocolors4.default.dim("|")} ${import_picocolors4.default.dim("Maintainers:")} ${info2.maintainerCount}`
|
|
1937
|
+
);
|
|
1938
|
+
if (info2.lastPublish) {
|
|
1939
|
+
console.log(` ${import_picocolors4.default.dim("Last publish:")} ${formatDaysAgo(info2.lastPublish)}`);
|
|
1940
|
+
}
|
|
1941
|
+
} else {
|
|
1942
|
+
console.log(import_picocolors4.default.dim(" (Trust data available for npm packages only)"));
|
|
1364
1943
|
}
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
if (res.ok) {
|
|
1371
|
-
pkgData = await res.json();
|
|
1944
|
+
console.log();
|
|
1945
|
+
console.log(` ${import_picocolors4.default.bold("Environment Variables")}`);
|
|
1946
|
+
if (info2.envVars.length > 0) {
|
|
1947
|
+
for (const env of info2.envVars) {
|
|
1948
|
+
console.log(` ${import_picocolors4.default.cyan("\u2022")} ${env}`);
|
|
1372
1949
|
}
|
|
1373
|
-
}
|
|
1950
|
+
} else {
|
|
1951
|
+
console.log(import_picocolors4.default.dim(" none required"));
|
|
1374
1952
|
}
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1953
|
+
console.log();
|
|
1954
|
+
console.log(` ${import_picocolors4.default.bold("Installed Clients")}`);
|
|
1955
|
+
if (info2.installedClients.length > 0) {
|
|
1956
|
+
for (const client of info2.installedClients) {
|
|
1957
|
+
console.log(` ${import_picocolors4.default.green("\u2713")} ${client}`);
|
|
1958
|
+
}
|
|
1959
|
+
} else {
|
|
1960
|
+
console.log(import_picocolors4.default.dim(" Not installed in any client"));
|
|
1961
|
+
}
|
|
1962
|
+
console.log();
|
|
1963
|
+
console.log(import_picocolors4.default.dim(" " + "\u2500".repeat(60)));
|
|
1964
|
+
console.log();
|
|
1387
1965
|
}
|
|
1966
|
+
var info_default = (0, import_citty4.defineCommand)({
|
|
1967
|
+
meta: {
|
|
1968
|
+
name: "info",
|
|
1969
|
+
description: "Show detailed metadata for an MCP server (installed or from registry)"
|
|
1970
|
+
},
|
|
1971
|
+
args: {
|
|
1972
|
+
server: {
|
|
1973
|
+
type: "positional",
|
|
1974
|
+
description: "Server name (e.g. @modelcontextprotocol/server-filesystem)",
|
|
1975
|
+
required: true
|
|
1976
|
+
},
|
|
1977
|
+
json: {
|
|
1978
|
+
type: "boolean",
|
|
1979
|
+
description: "Output results as JSON",
|
|
1980
|
+
default: false
|
|
1981
|
+
}
|
|
1982
|
+
},
|
|
1983
|
+
async run({ args }) {
|
|
1984
|
+
const spinner5 = (0, import_nanospinner2.createSpinner)(`Fetching info for ${args.server}...`).start();
|
|
1985
|
+
let info2;
|
|
1986
|
+
try {
|
|
1987
|
+
info2 = await getPackageInfo(args.server);
|
|
1988
|
+
} catch (err) {
|
|
1989
|
+
spinner5.error({ text: "Failed to fetch package info" });
|
|
1990
|
+
console.error(import_picocolors4.default.red(String(err)));
|
|
1991
|
+
process.exit(1);
|
|
1992
|
+
}
|
|
1993
|
+
if (!info2) {
|
|
1994
|
+
spinner5.error({ text: `Package not found: ${args.server}` });
|
|
1995
|
+
console.log(import_picocolors4.default.dim(`
|
|
1996
|
+
"${args.server}" was not found in the npm registry or your lockfile.
|
|
1997
|
+
`));
|
|
1998
|
+
process.exit(1);
|
|
1999
|
+
}
|
|
2000
|
+
spinner5.success({ text: `Found ${args.server}` });
|
|
2001
|
+
if (args.json) {
|
|
2002
|
+
console.log(JSON.stringify(info2, null, 2));
|
|
2003
|
+
return;
|
|
2004
|
+
}
|
|
2005
|
+
printInfo(info2);
|
|
2006
|
+
}
|
|
2007
|
+
});
|
|
1388
2008
|
|
|
1389
2009
|
// src/commands/init.ts
|
|
1390
|
-
|
|
2010
|
+
init_cjs_shims();
|
|
2011
|
+
var import_citty5 = require("citty");
|
|
2012
|
+
var p3 = __toESM(require("@clack/prompts"), 1);
|
|
2013
|
+
var import_node_path6 = __toESM(require("path"), 1);
|
|
2014
|
+
var init_default = (0, import_citty5.defineCommand)({
|
|
1391
2015
|
meta: {
|
|
1392
2016
|
name: "init",
|
|
1393
2017
|
description: "Initialize mcpman.lock in the current project"
|
|
@@ -1402,17 +2026,17 @@ var init_default = (0, import_citty3.defineCommand)({
|
|
|
1402
2026
|
},
|
|
1403
2027
|
async run({ args }) {
|
|
1404
2028
|
const nonInteractive = args.yes || !process.stdout.isTTY;
|
|
1405
|
-
|
|
1406
|
-
const targetPath =
|
|
2029
|
+
p3.intro("mcpman init");
|
|
2030
|
+
const targetPath = import_node_path6.default.join(process.cwd(), LOCKFILE_NAME);
|
|
1407
2031
|
const existing = findLockfile();
|
|
1408
2032
|
if (existing) {
|
|
1409
2033
|
if (nonInteractive) {
|
|
1410
|
-
|
|
2034
|
+
p3.log.warn(`Lockfile already exists: ${existing} \u2014 overwriting (non-interactive).`);
|
|
1411
2035
|
} else {
|
|
1412
|
-
|
|
1413
|
-
const overwrite = await
|
|
1414
|
-
if (
|
|
1415
|
-
|
|
2036
|
+
p3.log.warn(`Lockfile already exists: ${existing}`);
|
|
2037
|
+
const overwrite = await p3.confirm({ message: "Overwrite?" });
|
|
2038
|
+
if (p3.isCancel(overwrite) || !overwrite) {
|
|
2039
|
+
p3.outro("Cancelled.");
|
|
1416
2040
|
return;
|
|
1417
2041
|
}
|
|
1418
2042
|
}
|
|
@@ -1422,7 +2046,7 @@ var init_default = (0, import_citty3.defineCommand)({
|
|
|
1422
2046
|
const mod = await Promise.resolve().then(() => (init_client_detector(), client_detector_exports));
|
|
1423
2047
|
clients = await mod.getInstalledClients();
|
|
1424
2048
|
} catch {
|
|
1425
|
-
|
|
2049
|
+
p3.log.warn("Could not detect AI clients \u2014 creating empty lockfile.");
|
|
1426
2050
|
}
|
|
1427
2051
|
const clientServers = [];
|
|
1428
2052
|
for (const client of clients) {
|
|
@@ -1436,26 +2060,26 @@ var init_default = (0, import_citty3.defineCommand)({
|
|
|
1436
2060
|
}
|
|
1437
2061
|
createEmptyLockfile(targetPath);
|
|
1438
2062
|
if (clientServers.length === 0) {
|
|
1439
|
-
|
|
1440
|
-
|
|
2063
|
+
p3.log.info("No existing servers found in any client config.");
|
|
2064
|
+
p3.outro(`Created ${LOCKFILE_NAME} \u2014 add it to version control!`);
|
|
1441
2065
|
return;
|
|
1442
2066
|
}
|
|
1443
2067
|
let selected;
|
|
1444
2068
|
if (nonInteractive) {
|
|
1445
2069
|
selected = clientServers.map((cs) => cs.client.type);
|
|
1446
|
-
|
|
2070
|
+
p3.log.info(`Non-interactive mode: importing all ${clientServers.length} client(s).`);
|
|
1447
2071
|
} else {
|
|
1448
2072
|
const options = clientServers.map((cs) => ({
|
|
1449
2073
|
value: cs.client.type,
|
|
1450
2074
|
label: `${cs.client.displayName} (${Object.keys(cs.servers).length} servers)`
|
|
1451
2075
|
}));
|
|
1452
|
-
const toImport = await
|
|
2076
|
+
const toImport = await p3.multiselect({
|
|
1453
2077
|
message: "Import existing servers into lockfile?",
|
|
1454
2078
|
options,
|
|
1455
2079
|
required: false
|
|
1456
2080
|
});
|
|
1457
|
-
if (
|
|
1458
|
-
|
|
2081
|
+
if (p3.isCancel(toImport)) {
|
|
2082
|
+
p3.outro(`Created empty ${LOCKFILE_NAME}`);
|
|
1459
2083
|
return;
|
|
1460
2084
|
}
|
|
1461
2085
|
selected = toImport;
|
|
@@ -1481,7 +2105,7 @@ var init_default = (0, import_citty3.defineCommand)({
|
|
|
1481
2105
|
importCount++;
|
|
1482
2106
|
}
|
|
1483
2107
|
}
|
|
1484
|
-
|
|
2108
|
+
p3.outro(
|
|
1485
2109
|
`Created ${LOCKFILE_NAME} with ${importCount} server(s) \u2014 commit to version control!`
|
|
1486
2110
|
);
|
|
1487
2111
|
}
|
|
@@ -1489,49 +2113,48 @@ var init_default = (0, import_citty3.defineCommand)({
|
|
|
1489
2113
|
|
|
1490
2114
|
// src/commands/install.ts
|
|
1491
2115
|
init_cjs_shims();
|
|
1492
|
-
var
|
|
2116
|
+
var import_citty6 = require("citty");
|
|
1493
2117
|
|
|
1494
2118
|
// src/core/installer.ts
|
|
1495
2119
|
init_cjs_shims();
|
|
1496
|
-
var
|
|
2120
|
+
var p6 = __toESM(require("@clack/prompts"), 1);
|
|
1497
2121
|
|
|
1498
|
-
// src/core/
|
|
2122
|
+
// src/core/installer-vault-helpers.ts
|
|
1499
2123
|
init_cjs_shims();
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
return { type: "npm", input };
|
|
1508
|
-
}
|
|
1509
|
-
function parseEnvFlags(envFlags) {
|
|
1510
|
-
if (!envFlags) return {};
|
|
1511
|
-
const flags = Array.isArray(envFlags) ? envFlags : [envFlags];
|
|
1512
|
-
const result = {};
|
|
1513
|
-
for (const flag of flags) {
|
|
1514
|
-
const idx = flag.indexOf("=");
|
|
1515
|
-
if (idx > 0) {
|
|
1516
|
-
result[flag.slice(0, idx)] = flag.slice(idx + 1);
|
|
2124
|
+
var p5 = __toESM(require("@clack/prompts"), 1);
|
|
2125
|
+
init_vault_service();
|
|
2126
|
+
async function tryLoadVaultSecrets(serverName) {
|
|
2127
|
+
try {
|
|
2128
|
+
const entries = listSecrets(serverName);
|
|
2129
|
+
if (entries.length === 0 || entries[0].keys.length === 0) {
|
|
2130
|
+
return {};
|
|
1517
2131
|
}
|
|
2132
|
+
const password2 = await getMasterPassword();
|
|
2133
|
+
return getSecretsForServer(serverName, password2);
|
|
2134
|
+
} catch {
|
|
2135
|
+
return {};
|
|
1518
2136
|
}
|
|
1519
|
-
return result;
|
|
1520
2137
|
}
|
|
1521
|
-
async function
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
2138
|
+
async function offerVaultSave(serverName, newVars, yes) {
|
|
2139
|
+
if (Object.keys(newVars).length === 0) return;
|
|
2140
|
+
if (yes) return;
|
|
2141
|
+
try {
|
|
2142
|
+
const save = await p5.confirm({
|
|
2143
|
+
message: `Save ${Object.keys(newVars).length} env var(s) to encrypted vault for future installs?`
|
|
2144
|
+
});
|
|
2145
|
+
if (p5.isCancel(save) || !save) return;
|
|
2146
|
+
const password2 = await getMasterPassword();
|
|
2147
|
+
for (const [key, value] of Object.entries(newVars)) {
|
|
2148
|
+
setSecret(serverName, key, value, password2);
|
|
2149
|
+
}
|
|
2150
|
+
p5.log.success(`Credentials saved to vault for '${serverName}'`);
|
|
2151
|
+
} catch (err) {
|
|
2152
|
+
p5.log.warn(`Could not save to vault: ${err instanceof Error ? err.message : String(err)}`);
|
|
1530
2153
|
}
|
|
1531
2154
|
}
|
|
1532
2155
|
|
|
1533
2156
|
// src/core/installer.ts
|
|
1534
|
-
async function
|
|
2157
|
+
async function loadClients2() {
|
|
1535
2158
|
try {
|
|
1536
2159
|
const mod = await Promise.resolve().then(() => (init_client_detector(), client_detector_exports));
|
|
1537
2160
|
return mod.getInstalledClients();
|
|
@@ -1540,65 +2163,68 @@ async function loadClients() {
|
|
|
1540
2163
|
}
|
|
1541
2164
|
}
|
|
1542
2165
|
async function installServer(input, options = {}) {
|
|
1543
|
-
|
|
1544
|
-
const spinner5 =
|
|
2166
|
+
p6.intro("mcpman install");
|
|
2167
|
+
const spinner5 = p6.spinner();
|
|
1545
2168
|
spinner5.start("Resolving server...");
|
|
1546
2169
|
let metadata;
|
|
1547
2170
|
try {
|
|
1548
2171
|
metadata = await resolveServer(input);
|
|
1549
2172
|
} catch (err) {
|
|
1550
2173
|
spinner5.stop("Resolution failed");
|
|
1551
|
-
|
|
2174
|
+
p6.log.error(err instanceof Error ? err.message : String(err));
|
|
1552
2175
|
process.exit(1);
|
|
1553
2176
|
}
|
|
1554
2177
|
spinner5.stop(`Found: ${metadata.name}@${metadata.version}`);
|
|
1555
|
-
const clients = await
|
|
2178
|
+
const clients = await loadClients2();
|
|
1556
2179
|
if (clients.length === 0) {
|
|
1557
|
-
|
|
1558
|
-
|
|
2180
|
+
p6.log.warn("No supported AI clients detected on this machine.");
|
|
2181
|
+
p6.log.info("Supported: Claude Desktop, Cursor, VS Code, Windsurf");
|
|
1559
2182
|
process.exit(1);
|
|
1560
2183
|
}
|
|
1561
2184
|
let selectedClients;
|
|
1562
2185
|
if (options.client) {
|
|
1563
2186
|
const found = clients.find((c) => c.type === options.client || c.displayName.toLowerCase() === options.client?.toLowerCase());
|
|
1564
2187
|
if (!found) {
|
|
1565
|
-
|
|
1566
|
-
|
|
2188
|
+
p6.log.error(`Client '${options.client}' not found or not installed.`);
|
|
2189
|
+
p6.log.info(`Available: ${clients.map((c) => c.type).join(", ")}`);
|
|
1567
2190
|
process.exit(1);
|
|
1568
2191
|
}
|
|
1569
2192
|
selectedClients = [found];
|
|
1570
2193
|
} else if (options.yes || clients.length === 1) {
|
|
1571
2194
|
selectedClients = clients;
|
|
1572
2195
|
} else {
|
|
1573
|
-
const chosen = await
|
|
2196
|
+
const chosen = await p6.multiselect({
|
|
1574
2197
|
message: "Install to which client(s)?",
|
|
1575
2198
|
options: clients.map((c) => ({ value: c.type, label: c.displayName })),
|
|
1576
2199
|
required: true
|
|
1577
2200
|
});
|
|
1578
|
-
if (
|
|
1579
|
-
|
|
2201
|
+
if (p6.isCancel(chosen)) {
|
|
2202
|
+
p6.outro("Cancelled.");
|
|
1580
2203
|
process.exit(0);
|
|
1581
2204
|
}
|
|
1582
2205
|
selectedClients = clients.filter((c) => chosen.includes(c.type));
|
|
1583
2206
|
}
|
|
1584
2207
|
const providedEnv = parseEnvFlags(options.env);
|
|
1585
|
-
const
|
|
2208
|
+
const vaultEnv = await tryLoadVaultSecrets(metadata.name);
|
|
2209
|
+
const collectedEnv = { ...vaultEnv, ...providedEnv };
|
|
2210
|
+
const newlyEnteredVars = {};
|
|
1586
2211
|
const requiredVars = metadata.envVars.filter((e) => e.required && !(e.name in collectedEnv));
|
|
1587
2212
|
for (const envVar of requiredVars) {
|
|
1588
2213
|
if (options.yes && envVar.default) {
|
|
1589
2214
|
collectedEnv[envVar.name] = envVar.default;
|
|
1590
2215
|
continue;
|
|
1591
2216
|
}
|
|
1592
|
-
const val = await
|
|
2217
|
+
const val = await p6.text({
|
|
1593
2218
|
message: `${envVar.name}${envVar.description ? ` \u2014 ${envVar.description}` : ""}`,
|
|
1594
2219
|
placeholder: envVar.default ?? "",
|
|
1595
2220
|
validate: (v) => envVar.required && !v ? "Required" : void 0
|
|
1596
2221
|
});
|
|
1597
|
-
if (
|
|
1598
|
-
|
|
2222
|
+
if (p6.isCancel(val)) {
|
|
2223
|
+
p6.outro("Cancelled.");
|
|
1599
2224
|
process.exit(0);
|
|
1600
2225
|
}
|
|
1601
2226
|
collectedEnv[envVar.name] = val;
|
|
2227
|
+
newlyEnteredVars[envVar.name] = val;
|
|
1602
2228
|
}
|
|
1603
2229
|
const entry = {
|
|
1604
2230
|
command: metadata.command,
|
|
@@ -1613,7 +2239,7 @@ async function installServer(input, options = {}) {
|
|
|
1613
2239
|
clientTypes.push(client.type);
|
|
1614
2240
|
} catch (err) {
|
|
1615
2241
|
spinner5.stop("Partial failure");
|
|
1616
|
-
|
|
2242
|
+
p6.log.warn(`Failed to write to ${client.displayName}: ${err instanceof Error ? err.message : String(err)}`);
|
|
1617
2243
|
}
|
|
1618
2244
|
}
|
|
1619
2245
|
spinner5.stop("Config written");
|
|
@@ -1632,13 +2258,14 @@ async function installServer(input, options = {}) {
|
|
|
1632
2258
|
clients: clientTypes
|
|
1633
2259
|
});
|
|
1634
2260
|
const lockPath = findLockfile() ?? "mcpman.lock (global)";
|
|
1635
|
-
|
|
1636
|
-
|
|
2261
|
+
p6.log.success(`Lockfile updated: ${lockPath}`);
|
|
2262
|
+
await offerVaultSave(metadata.name, newlyEnteredVars, options.yes ?? false);
|
|
2263
|
+
p6.outro(`${metadata.name}@${metadata.version} installed to ${clientTypes.join(", ")}`);
|
|
1637
2264
|
}
|
|
1638
2265
|
|
|
1639
2266
|
// src/utils/logger.ts
|
|
1640
2267
|
init_cjs_shims();
|
|
1641
|
-
var
|
|
2268
|
+
var import_picocolors5 = __toESM(require("picocolors"), 1);
|
|
1642
2269
|
var noColor = process.env.NO_COLOR !== void 0 || process.argv.includes("--no-color");
|
|
1643
2270
|
var isVerbose = process.argv.includes("--verbose");
|
|
1644
2271
|
var isJson = process.argv.includes("--json");
|
|
@@ -1647,19 +2274,19 @@ function colorize(fn, text2) {
|
|
|
1647
2274
|
}
|
|
1648
2275
|
function info(message) {
|
|
1649
2276
|
if (isJson) return;
|
|
1650
|
-
console.log(`${colorize(
|
|
2277
|
+
console.log(`${colorize(import_picocolors5.default.cyan, "i")} ${message}`);
|
|
1651
2278
|
}
|
|
1652
2279
|
function error(message) {
|
|
1653
2280
|
if (isJson) return;
|
|
1654
|
-
console.error(`${colorize(
|
|
2281
|
+
console.error(`${colorize(import_picocolors5.default.red, "\u2717")} ${message}`);
|
|
1655
2282
|
}
|
|
1656
2283
|
function json(data) {
|
|
1657
2284
|
console.log(JSON.stringify(data, null, 2));
|
|
1658
2285
|
}
|
|
1659
2286
|
|
|
1660
2287
|
// src/commands/install.ts
|
|
1661
|
-
var
|
|
1662
|
-
var install_default = (0,
|
|
2288
|
+
var p7 = __toESM(require("@clack/prompts"), 1);
|
|
2289
|
+
var install_default = (0, import_citty6.defineCommand)({
|
|
1663
2290
|
meta: {
|
|
1664
2291
|
name: "install",
|
|
1665
2292
|
description: "Install an MCP server into one or more AI clients"
|
|
@@ -1708,8 +2335,8 @@ async function restoreFromLockfile() {
|
|
|
1708
2335
|
info("Lockfile is empty \u2014 nothing to restore.");
|
|
1709
2336
|
return;
|
|
1710
2337
|
}
|
|
1711
|
-
|
|
1712
|
-
|
|
2338
|
+
p7.intro(`mcpman install (restore from ${lockPath})`);
|
|
2339
|
+
p7.log.info(`Restoring ${entries.length} server(s)...`);
|
|
1713
2340
|
for (const [name, entry] of entries) {
|
|
1714
2341
|
const input = entry.source === "smithery" ? `smithery:${name}` : entry.source === "github" ? entry.resolved : name;
|
|
1715
2342
|
await installServer(input, {
|
|
@@ -1717,19 +2344,19 @@ async function restoreFromLockfile() {
|
|
|
1717
2344
|
yes: true
|
|
1718
2345
|
});
|
|
1719
2346
|
}
|
|
1720
|
-
|
|
2347
|
+
p7.outro("Restore complete.");
|
|
1721
2348
|
}
|
|
1722
2349
|
|
|
1723
2350
|
// src/commands/list.ts
|
|
1724
2351
|
init_cjs_shims();
|
|
1725
|
-
var
|
|
1726
|
-
var
|
|
2352
|
+
var import_citty7 = require("citty");
|
|
2353
|
+
var import_picocolors6 = __toESM(require("picocolors"), 1);
|
|
1727
2354
|
var STATUS_ICON = {
|
|
1728
|
-
healthy:
|
|
1729
|
-
unhealthy:
|
|
1730
|
-
unknown:
|
|
2355
|
+
healthy: import_picocolors6.default.green("\u25CF"),
|
|
2356
|
+
unhealthy: import_picocolors6.default.red("\u25CF"),
|
|
2357
|
+
unknown: import_picocolors6.default.dim("\u25CB")
|
|
1731
2358
|
};
|
|
1732
|
-
var list_default = (0,
|
|
2359
|
+
var list_default = (0, import_citty7.defineCommand)({
|
|
1733
2360
|
meta: {
|
|
1734
2361
|
name: "list",
|
|
1735
2362
|
description: "List installed MCP servers"
|
|
@@ -1749,7 +2376,7 @@ var list_default = (0, import_citty5.defineCommand)({
|
|
|
1749
2376
|
const servers = await getInstalledServers(args.client);
|
|
1750
2377
|
if (servers.length === 0) {
|
|
1751
2378
|
const filter = args.client ? ` for client "${args.client}"` : "";
|
|
1752
|
-
console.log(
|
|
2379
|
+
console.log(import_picocolors6.default.dim(`No MCP servers installed${filter}. Run ${import_picocolors6.default.cyan("mcpman install <server>")} to get started.`));
|
|
1753
2380
|
return;
|
|
1754
2381
|
}
|
|
1755
2382
|
const withStatus = await Promise.all(
|
|
@@ -1773,8 +2400,8 @@ var list_default = (0, import_citty5.defineCommand)({
|
|
|
1773
2400
|
const nameWidth = Math.max(4, ...withStatus.map((s) => s.name.length));
|
|
1774
2401
|
const clientsWidth = Math.max(7, ...withStatus.map((s) => formatClients(s.clients).length));
|
|
1775
2402
|
const header = ` ${pad("NAME", nameWidth)} ${pad("CLIENT(S)", clientsWidth)} ${pad("COMMAND", 20)} STATUS`;
|
|
1776
|
-
console.log(
|
|
1777
|
-
console.log(
|
|
2403
|
+
console.log(import_picocolors6.default.dim(header));
|
|
2404
|
+
console.log(import_picocolors6.default.dim(` ${"-".repeat(nameWidth)} ${"-".repeat(clientsWidth)} ${"-".repeat(20)} ------`));
|
|
1778
2405
|
for (const s of withStatus) {
|
|
1779
2406
|
const icon = STATUS_ICON[s.status];
|
|
1780
2407
|
const clientsStr = formatClients(s.clients);
|
|
@@ -1782,7 +2409,7 @@ var list_default = (0, import_citty5.defineCommand)({
|
|
|
1782
2409
|
console.log(` ${pad(s.name, nameWidth)} ${pad(clientsStr, clientsWidth)} ${pad(cmdStr, 20)} ${icon} ${s.status}`);
|
|
1783
2410
|
}
|
|
1784
2411
|
const clientSet = new Set(withStatus.flatMap((s) => s.clients));
|
|
1785
|
-
console.log(
|
|
2412
|
+
console.log(import_picocolors6.default.dim(`
|
|
1786
2413
|
${withStatus.length} server${withStatus.length !== 1 ? "s" : ""} \xB7 ${clientSet.size} client${clientSet.size !== 1 ? "s" : ""}`));
|
|
1787
2414
|
}
|
|
1788
2415
|
});
|
|
@@ -1804,9 +2431,9 @@ function formatClients(clients) {
|
|
|
1804
2431
|
|
|
1805
2432
|
// src/commands/remove.ts
|
|
1806
2433
|
init_cjs_shims();
|
|
1807
|
-
var
|
|
1808
|
-
var
|
|
1809
|
-
var
|
|
2434
|
+
var import_citty8 = require("citty");
|
|
2435
|
+
var p8 = __toESM(require("@clack/prompts"), 1);
|
|
2436
|
+
var import_picocolors7 = __toESM(require("picocolors"), 1);
|
|
1810
2437
|
init_client_detector();
|
|
1811
2438
|
var CLIENT_DISPLAY2 = {
|
|
1812
2439
|
"claude-desktop": "Claude",
|
|
@@ -1817,113 +2444,360 @@ var CLIENT_DISPLAY2 = {
|
|
|
1817
2444
|
function clientDisplayName(type) {
|
|
1818
2445
|
return CLIENT_DISPLAY2[type] ?? type;
|
|
1819
2446
|
}
|
|
1820
|
-
var remove_default = (0,
|
|
2447
|
+
var remove_default = (0, import_citty8.defineCommand)({
|
|
2448
|
+
meta: {
|
|
2449
|
+
name: "remove",
|
|
2450
|
+
description: "Remove an MCP server from one or more AI clients"
|
|
2451
|
+
},
|
|
2452
|
+
args: {
|
|
2453
|
+
server: {
|
|
2454
|
+
type: "positional",
|
|
2455
|
+
description: "Server name to remove",
|
|
2456
|
+
required: true
|
|
2457
|
+
},
|
|
2458
|
+
client: {
|
|
2459
|
+
type: "string",
|
|
2460
|
+
description: "Target client (claude, cursor, vscode, windsurf)"
|
|
2461
|
+
},
|
|
2462
|
+
all: {
|
|
2463
|
+
type: "boolean",
|
|
2464
|
+
description: "Remove from all clients",
|
|
2465
|
+
default: false
|
|
2466
|
+
},
|
|
2467
|
+
yes: {
|
|
2468
|
+
type: "boolean",
|
|
2469
|
+
description: "Skip confirmation prompt",
|
|
2470
|
+
default: false
|
|
2471
|
+
}
|
|
2472
|
+
},
|
|
2473
|
+
async run({ args }) {
|
|
2474
|
+
p8.intro(import_picocolors7.default.bold("mcpman remove"));
|
|
2475
|
+
const serverName = args.server;
|
|
2476
|
+
const servers = await getInstalledServers();
|
|
2477
|
+
const match = servers.find((s) => s.name === serverName);
|
|
2478
|
+
if (!match) {
|
|
2479
|
+
p8.log.warn(`Server "${serverName}" is not installed.`);
|
|
2480
|
+
const similar = servers.filter((s) => s.name.includes(serverName) || serverName.includes(s.name));
|
|
2481
|
+
if (similar.length > 0) {
|
|
2482
|
+
p8.log.info(`Did you mean: ${similar.map((s) => import_picocolors7.default.cyan(s.name)).join(", ")}?`);
|
|
2483
|
+
}
|
|
2484
|
+
p8.outro("Nothing to remove.");
|
|
2485
|
+
return;
|
|
2486
|
+
}
|
|
2487
|
+
let targetClients;
|
|
2488
|
+
if (args.all) {
|
|
2489
|
+
targetClients = match.clients;
|
|
2490
|
+
} else if (args.client) {
|
|
2491
|
+
if (!match.clients.includes(args.client)) {
|
|
2492
|
+
p8.log.warn(`Server "${serverName}" is not installed in client "${args.client}".`);
|
|
2493
|
+
p8.outro("Nothing to remove.");
|
|
2494
|
+
return;
|
|
2495
|
+
}
|
|
2496
|
+
targetClients = [args.client];
|
|
2497
|
+
} else if (match.clients.length === 1) {
|
|
2498
|
+
targetClients = match.clients;
|
|
2499
|
+
} else {
|
|
2500
|
+
const selected = await p8.multiselect({
|
|
2501
|
+
message: `Remove "${serverName}" from which clients?`,
|
|
2502
|
+
options: match.clients.map((c) => ({
|
|
2503
|
+
value: c,
|
|
2504
|
+
label: clientDisplayName(c)
|
|
2505
|
+
})),
|
|
2506
|
+
required: true
|
|
2507
|
+
});
|
|
2508
|
+
if (p8.isCancel(selected)) {
|
|
2509
|
+
p8.outro("Cancelled.");
|
|
2510
|
+
process.exit(0);
|
|
2511
|
+
}
|
|
2512
|
+
targetClients = selected;
|
|
2513
|
+
}
|
|
2514
|
+
if (!args.yes) {
|
|
2515
|
+
const clientNames = targetClients.map(clientDisplayName).join(", ");
|
|
2516
|
+
const confirmed = await p8.confirm({
|
|
2517
|
+
message: `Remove ${import_picocolors7.default.cyan(serverName)} from ${import_picocolors7.default.yellow(clientNames)}?`
|
|
2518
|
+
});
|
|
2519
|
+
if (p8.isCancel(confirmed) || !confirmed) {
|
|
2520
|
+
p8.outro("Cancelled.");
|
|
2521
|
+
return;
|
|
2522
|
+
}
|
|
2523
|
+
}
|
|
2524
|
+
const installedClients = await getInstalledClients();
|
|
2525
|
+
const errors = [];
|
|
2526
|
+
for (const clientType of targetClients) {
|
|
2527
|
+
const handler = installedClients.find((c) => c.type === clientType);
|
|
2528
|
+
if (!handler) {
|
|
2529
|
+
errors.push(`Client "${clientType}" not found`);
|
|
2530
|
+
continue;
|
|
2531
|
+
}
|
|
2532
|
+
try {
|
|
2533
|
+
await handler.removeServer(serverName);
|
|
2534
|
+
p8.log.success(`Removed from ${clientDisplayName(clientType)}`);
|
|
2535
|
+
} catch (err) {
|
|
2536
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
2537
|
+
errors.push(`${clientDisplayName(clientType)}: ${msg}`);
|
|
2538
|
+
}
|
|
2539
|
+
}
|
|
2540
|
+
if (errors.length > 0) {
|
|
2541
|
+
for (const e of errors) p8.log.error(e);
|
|
2542
|
+
p8.outro(import_picocolors7.default.red("Completed with errors."));
|
|
2543
|
+
process.exit(1);
|
|
2544
|
+
}
|
|
2545
|
+
p8.outro(import_picocolors7.default.green(`Removed "${serverName}" successfully.`));
|
|
2546
|
+
}
|
|
2547
|
+
});
|
|
2548
|
+
|
|
2549
|
+
// src/commands/run.ts
|
|
2550
|
+
init_cjs_shims();
|
|
2551
|
+
var import_citty9 = require("citty");
|
|
2552
|
+
var import_node_child_process3 = require("child_process");
|
|
2553
|
+
var import_picocolors8 = __toESM(require("picocolors"), 1);
|
|
2554
|
+
init_vault_service();
|
|
2555
|
+
var run_default = (0, import_citty9.defineCommand)({
|
|
2556
|
+
meta: {
|
|
2557
|
+
name: "run",
|
|
2558
|
+
description: "Run an installed MCP server with vault secrets injected"
|
|
2559
|
+
},
|
|
2560
|
+
args: {
|
|
2561
|
+
server: {
|
|
2562
|
+
type: "positional",
|
|
2563
|
+
description: "Server name to run (as installed in lockfile)",
|
|
2564
|
+
required: true
|
|
2565
|
+
},
|
|
2566
|
+
env: {
|
|
2567
|
+
type: "string",
|
|
2568
|
+
description: "Override env var KEY=VAL (repeatable)",
|
|
2569
|
+
alias: "e"
|
|
2570
|
+
}
|
|
2571
|
+
},
|
|
2572
|
+
async run({ args }) {
|
|
2573
|
+
const serverName = args.server;
|
|
2574
|
+
const lockfile = readLockfile();
|
|
2575
|
+
const entry = lockfile.servers[serverName];
|
|
2576
|
+
if (!entry) {
|
|
2577
|
+
console.error(import_picocolors8.default.red(` Error: Server '${serverName}' is not installed.`));
|
|
2578
|
+
console.error(import_picocolors8.default.dim(` Run ${import_picocolors8.default.cyan("mcpman install <server>")} to install it first.`));
|
|
2579
|
+
process.exit(1);
|
|
2580
|
+
}
|
|
2581
|
+
const lockfileEnv = parseEnvFlags(entry.envVars);
|
|
2582
|
+
const vaultEnv = await loadVaultSecrets(serverName);
|
|
2583
|
+
const cliEnv = parseEnvFlags(args.env);
|
|
2584
|
+
const finalEnv = {
|
|
2585
|
+
...process.env,
|
|
2586
|
+
...lockfileEnv,
|
|
2587
|
+
...vaultEnv,
|
|
2588
|
+
...cliEnv
|
|
2589
|
+
};
|
|
2590
|
+
console.log(import_picocolors8.default.dim(` Running ${import_picocolors8.default.cyan(serverName)}...`));
|
|
2591
|
+
const child = (0, import_node_child_process3.spawn)(entry.command, entry.args, {
|
|
2592
|
+
env: finalEnv,
|
|
2593
|
+
stdio: "inherit"
|
|
2594
|
+
});
|
|
2595
|
+
const forwardSignal = (signal) => {
|
|
2596
|
+
if (!child.killed) {
|
|
2597
|
+
child.kill(signal);
|
|
2598
|
+
}
|
|
2599
|
+
};
|
|
2600
|
+
process.on("SIGINT", () => forwardSignal("SIGINT"));
|
|
2601
|
+
process.on("SIGTERM", () => forwardSignal("SIGTERM"));
|
|
2602
|
+
await new Promise((resolve) => {
|
|
2603
|
+
child.on("close", (code) => {
|
|
2604
|
+
process.exit(code ?? 0);
|
|
2605
|
+
resolve();
|
|
2606
|
+
});
|
|
2607
|
+
child.on("error", (err) => {
|
|
2608
|
+
console.error(import_picocolors8.default.red(` Failed to start '${serverName}': ${err.message}`));
|
|
2609
|
+
process.exit(1);
|
|
2610
|
+
resolve();
|
|
2611
|
+
});
|
|
2612
|
+
});
|
|
2613
|
+
}
|
|
2614
|
+
});
|
|
2615
|
+
async function loadVaultSecrets(serverName) {
|
|
2616
|
+
try {
|
|
2617
|
+
const entries = listSecrets(serverName);
|
|
2618
|
+
if (entries.length === 0 || entries[0].keys.length === 0) {
|
|
2619
|
+
return {};
|
|
2620
|
+
}
|
|
2621
|
+
const password2 = await getMasterPassword();
|
|
2622
|
+
return getSecretsForServer(serverName, password2);
|
|
2623
|
+
} catch {
|
|
2624
|
+
console.warn(import_picocolors8.default.yellow(" Warning: Could not load vault secrets, continuing without them."));
|
|
2625
|
+
return {};
|
|
2626
|
+
}
|
|
2627
|
+
}
|
|
2628
|
+
|
|
2629
|
+
// src/commands/search.ts
|
|
2630
|
+
init_cjs_shims();
|
|
2631
|
+
var import_citty10 = require("citty");
|
|
2632
|
+
var import_picocolors9 = __toESM(require("picocolors"), 1);
|
|
2633
|
+
var import_nanospinner3 = require("nanospinner");
|
|
2634
|
+
|
|
2635
|
+
// src/core/registry-search.ts
|
|
2636
|
+
init_cjs_shims();
|
|
2637
|
+
var SEARCH_TIMEOUT_MS = 1e4;
|
|
2638
|
+
async function searchNpm(query, limit = 20) {
|
|
2639
|
+
const cap = Math.min(limit, 100);
|
|
2640
|
+
const url = `https://registry.npmjs.org/-/v1/search?text=mcp+${encodeURIComponent(query)}&size=${cap}`;
|
|
2641
|
+
try {
|
|
2642
|
+
const res = await fetch(url, { signal: AbortSignal.timeout(SEARCH_TIMEOUT_MS) });
|
|
2643
|
+
if (!res.ok) return [];
|
|
2644
|
+
const data = await res.json();
|
|
2645
|
+
const objects = Array.isArray(data["objects"]) ? data["objects"] : [];
|
|
2646
|
+
return objects.map((obj) => {
|
|
2647
|
+
const pkg = obj["package"] ?? {};
|
|
2648
|
+
const dl = obj["downloads"];
|
|
2649
|
+
return {
|
|
2650
|
+
name: typeof pkg["name"] === "string" ? pkg["name"] : "",
|
|
2651
|
+
description: typeof pkg["description"] === "string" ? pkg["description"] : "",
|
|
2652
|
+
version: typeof pkg["version"] === "string" ? pkg["version"] : "",
|
|
2653
|
+
date: typeof pkg["date"] === "string" ? pkg["date"] : "",
|
|
2654
|
+
downloads: typeof dl?.["weekly"] === "number" ? dl["weekly"] : 0,
|
|
2655
|
+
keywords: Array.isArray(pkg["keywords"]) ? pkg["keywords"] : []
|
|
2656
|
+
};
|
|
2657
|
+
}).filter((r) => r.name !== "");
|
|
2658
|
+
} catch {
|
|
2659
|
+
return [];
|
|
2660
|
+
}
|
|
2661
|
+
}
|
|
2662
|
+
async function searchSmithery(query, limit = 20) {
|
|
2663
|
+
const cap = Math.min(limit, 100);
|
|
2664
|
+
const url = `https://api.smithery.ai/v1/servers?q=${encodeURIComponent(query)}&limit=${cap}`;
|
|
2665
|
+
try {
|
|
2666
|
+
const res = await fetch(url, { signal: AbortSignal.timeout(SEARCH_TIMEOUT_MS) });
|
|
2667
|
+
if (!res.ok) return [];
|
|
2668
|
+
const data = await res.json();
|
|
2669
|
+
const servers = Array.isArray(data["servers"]) ? data["servers"] : [];
|
|
2670
|
+
return servers.map((s) => ({
|
|
2671
|
+
name: typeof s["name"] === "string" ? s["name"] : "",
|
|
2672
|
+
description: typeof s["description"] === "string" ? s["description"] : "",
|
|
2673
|
+
version: typeof s["version"] === "string" ? s["version"] : "latest",
|
|
2674
|
+
runtime: typeof s["runtime"] === "string" ? s["runtime"] : "node"
|
|
2675
|
+
})).filter((r) => r.name !== "");
|
|
2676
|
+
} catch {
|
|
2677
|
+
return [];
|
|
2678
|
+
}
|
|
2679
|
+
}
|
|
2680
|
+
|
|
2681
|
+
// src/commands/search.ts
|
|
2682
|
+
function truncate2(s, max) {
|
|
2683
|
+
return s.length > max ? s.slice(0, max - 1) + "\u2026" : s;
|
|
2684
|
+
}
|
|
2685
|
+
function pad2(s, width) {
|
|
2686
|
+
return s.length >= width ? s : s + " ".repeat(width - s.length);
|
|
2687
|
+
}
|
|
2688
|
+
function highlightMatch(name, query) {
|
|
2689
|
+
const idx = name.toLowerCase().indexOf(query.toLowerCase());
|
|
2690
|
+
if (idx === -1) return name;
|
|
2691
|
+
return name.slice(0, idx) + import_picocolors9.default.yellow(name.slice(idx, idx + query.length)) + name.slice(idx + query.length);
|
|
2692
|
+
}
|
|
2693
|
+
function formatDownloads(n) {
|
|
2694
|
+
if (!n) return import_picocolors9.default.dim("\u2014");
|
|
2695
|
+
if (n >= 1e6) return `${(n / 1e6).toFixed(1)}M`;
|
|
2696
|
+
if (n >= 1e3) return `${(n / 1e3).toFixed(1)}k`;
|
|
2697
|
+
return String(n);
|
|
2698
|
+
}
|
|
2699
|
+
function printNpmResults(results, query) {
|
|
2700
|
+
const nameWidth = Math.max(4, ...results.map((r) => r.name.length), 20);
|
|
2701
|
+
const verWidth = Math.max(7, ...results.map((r) => r.version.length));
|
|
2702
|
+
const dlWidth = 9;
|
|
2703
|
+
const descMax = 50;
|
|
2704
|
+
const header = ` ${pad2("NAME", nameWidth)} ${pad2("VERSION", verWidth)} ${pad2("DOWNLOADS", dlWidth)} DESCRIPTION`;
|
|
2705
|
+
console.log(import_picocolors9.default.dim(header));
|
|
2706
|
+
console.log(import_picocolors9.default.dim(` ${"-".repeat(nameWidth)} ${"-".repeat(verWidth)} ${"-".repeat(dlWidth)} ${"-".repeat(descMax)}`));
|
|
2707
|
+
for (const r of results) {
|
|
2708
|
+
const name = highlightMatch(pad2(r.name, nameWidth), query);
|
|
2709
|
+
const ver = pad2(r.version, verWidth);
|
|
2710
|
+
const dl = pad2(formatDownloads(r.downloads), dlWidth);
|
|
2711
|
+
const desc = truncate2(r.description || import_picocolors9.default.dim("(no description)"), descMax);
|
|
2712
|
+
console.log(` ${name} ${import_picocolors9.default.dim(ver)} ${dl} ${desc}`);
|
|
2713
|
+
}
|
|
2714
|
+
}
|
|
2715
|
+
function printSmitheryResults(results, query) {
|
|
2716
|
+
const nameWidth = Math.max(4, ...results.map((r) => r.name.length), 20);
|
|
2717
|
+
const verWidth = Math.max(7, ...results.map((r) => r.version.length));
|
|
2718
|
+
const descMax = 50;
|
|
2719
|
+
const header = ` ${pad2("NAME", nameWidth)} ${pad2("VERSION", verWidth)} DESCRIPTION`;
|
|
2720
|
+
console.log(import_picocolors9.default.dim(header));
|
|
2721
|
+
console.log(import_picocolors9.default.dim(` ${"-".repeat(nameWidth)} ${"-".repeat(verWidth)} ${"-".repeat(descMax)}`));
|
|
2722
|
+
for (const r of results) {
|
|
2723
|
+
const name = highlightMatch(pad2(r.name, nameWidth), query);
|
|
2724
|
+
const ver = pad2(r.version, verWidth);
|
|
2725
|
+
const desc = truncate2(r.description || import_picocolors9.default.dim("(no description)"), descMax);
|
|
2726
|
+
console.log(` ${name} ${import_picocolors9.default.dim(ver)} ${desc}`);
|
|
2727
|
+
}
|
|
2728
|
+
}
|
|
2729
|
+
var search_default = (0, import_citty10.defineCommand)({
|
|
1821
2730
|
meta: {
|
|
1822
|
-
name: "
|
|
1823
|
-
description: "
|
|
2731
|
+
name: "search",
|
|
2732
|
+
description: "Search for MCP servers on npm or Smithery registry"
|
|
1824
2733
|
},
|
|
1825
2734
|
args: {
|
|
1826
|
-
|
|
2735
|
+
query: {
|
|
1827
2736
|
type: "positional",
|
|
1828
|
-
description: "
|
|
2737
|
+
description: "Search query",
|
|
1829
2738
|
required: true
|
|
1830
2739
|
},
|
|
1831
|
-
|
|
2740
|
+
registry: {
|
|
1832
2741
|
type: "string",
|
|
1833
|
-
description: "
|
|
1834
|
-
|
|
1835
|
-
all: {
|
|
1836
|
-
type: "boolean",
|
|
1837
|
-
description: "Remove from all clients",
|
|
1838
|
-
default: false
|
|
2742
|
+
description: "Registry to search: npm or smithery (default: npm)",
|
|
2743
|
+
default: "npm"
|
|
1839
2744
|
},
|
|
1840
|
-
|
|
1841
|
-
type: "
|
|
1842
|
-
description: "
|
|
1843
|
-
default:
|
|
2745
|
+
limit: {
|
|
2746
|
+
type: "string",
|
|
2747
|
+
description: "Maximum number of results (default: 20, max: 100)",
|
|
2748
|
+
default: "20"
|
|
1844
2749
|
}
|
|
1845
2750
|
},
|
|
1846
2751
|
async run({ args }) {
|
|
1847
|
-
|
|
1848
|
-
const
|
|
1849
|
-
const
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
const similar = servers.filter((s) => s.name.includes(serverName) || serverName.includes(s.name));
|
|
1854
|
-
if (similar.length > 0) {
|
|
1855
|
-
p4.log.info(`Did you mean: ${similar.map((s) => import_picocolors5.default.cyan(s.name)).join(", ")}?`);
|
|
1856
|
-
}
|
|
1857
|
-
p4.outro("Nothing to remove.");
|
|
1858
|
-
return;
|
|
1859
|
-
}
|
|
1860
|
-
let targetClients;
|
|
1861
|
-
if (args.all) {
|
|
1862
|
-
targetClients = match.clients;
|
|
1863
|
-
} else if (args.client) {
|
|
1864
|
-
if (!match.clients.includes(args.client)) {
|
|
1865
|
-
p4.log.warn(`Server "${serverName}" is not installed in client "${args.client}".`);
|
|
1866
|
-
p4.outro("Nothing to remove.");
|
|
1867
|
-
return;
|
|
1868
|
-
}
|
|
1869
|
-
targetClients = [args.client];
|
|
1870
|
-
} else if (match.clients.length === 1) {
|
|
1871
|
-
targetClients = match.clients;
|
|
1872
|
-
} else {
|
|
1873
|
-
const selected = await p4.multiselect({
|
|
1874
|
-
message: `Remove "${serverName}" from which clients?`,
|
|
1875
|
-
options: match.clients.map((c) => ({
|
|
1876
|
-
value: c,
|
|
1877
|
-
label: clientDisplayName(c)
|
|
1878
|
-
})),
|
|
1879
|
-
required: true
|
|
1880
|
-
});
|
|
1881
|
-
if (p4.isCancel(selected)) {
|
|
1882
|
-
p4.outro("Cancelled.");
|
|
1883
|
-
process.exit(0);
|
|
1884
|
-
}
|
|
1885
|
-
targetClients = selected;
|
|
2752
|
+
const query = args.query;
|
|
2753
|
+
const registry = args.registry.toLowerCase();
|
|
2754
|
+
const limit = Math.min(Math.max(1, Number.parseInt(args.limit, 10) || 20), 100);
|
|
2755
|
+
if (registry !== "npm" && registry !== "smithery") {
|
|
2756
|
+
console.error(import_picocolors9.default.red(` Unknown registry "${registry}". Use "npm" or "smithery".`));
|
|
2757
|
+
process.exit(1);
|
|
1886
2758
|
}
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
|
-
const
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
|
|
2759
|
+
const spinner5 = (0, import_nanospinner3.createSpinner)(`Searching ${registry} for "${query}"...`).start();
|
|
2760
|
+
if (registry === "npm") {
|
|
2761
|
+
const results2 = await searchNpm(query, limit);
|
|
2762
|
+
spinner5.stop();
|
|
2763
|
+
if (results2.length === 0) {
|
|
2764
|
+
console.log(import_picocolors9.default.dim(`
|
|
2765
|
+
No results found for "${query}" on npm.
|
|
2766
|
+
`));
|
|
1894
2767
|
return;
|
|
1895
2768
|
}
|
|
2769
|
+
console.log(import_picocolors9.default.bold(`
|
|
2770
|
+
mcpman search \u2014 npm (${results2.length} result${results2.length !== 1 ? "s" : ""})
|
|
2771
|
+
`));
|
|
2772
|
+
printNpmResults(results2, query);
|
|
2773
|
+
console.log(import_picocolors9.default.dim(`
|
|
2774
|
+
Install with: mcpman install <name>
|
|
2775
|
+
`));
|
|
2776
|
+
return;
|
|
1896
2777
|
}
|
|
1897
|
-
const
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
}
|
|
1905
|
-
try {
|
|
1906
|
-
await handler.removeServer(serverName);
|
|
1907
|
-
p4.log.success(`Removed from ${clientDisplayName(clientType)}`);
|
|
1908
|
-
} catch (err) {
|
|
1909
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
1910
|
-
errors.push(`${clientDisplayName(clientType)}: ${msg}`);
|
|
1911
|
-
}
|
|
1912
|
-
}
|
|
1913
|
-
if (errors.length > 0) {
|
|
1914
|
-
for (const e of errors) p4.log.error(e);
|
|
1915
|
-
p4.outro(import_picocolors5.default.red("Completed with errors."));
|
|
1916
|
-
process.exit(1);
|
|
2778
|
+
const results = await searchSmithery(query, limit);
|
|
2779
|
+
spinner5.stop();
|
|
2780
|
+
if (results.length === 0) {
|
|
2781
|
+
console.log(import_picocolors9.default.dim(`
|
|
2782
|
+
No results found for "${query}" on Smithery.
|
|
2783
|
+
`));
|
|
2784
|
+
return;
|
|
1917
2785
|
}
|
|
1918
|
-
|
|
2786
|
+
console.log(import_picocolors9.default.bold(`
|
|
2787
|
+
mcpman search \u2014 Smithery (${results.length} result${results.length !== 1 ? "s" : ""})
|
|
2788
|
+
`));
|
|
2789
|
+
printSmitheryResults(results, query);
|
|
2790
|
+
console.log(import_picocolors9.default.dim(`
|
|
2791
|
+
Install with: mcpman install <name>
|
|
2792
|
+
`));
|
|
1919
2793
|
}
|
|
1920
2794
|
});
|
|
1921
2795
|
|
|
1922
2796
|
// src/commands/secrets.ts
|
|
1923
2797
|
init_cjs_shims();
|
|
1924
|
-
var
|
|
1925
|
-
var
|
|
1926
|
-
var
|
|
2798
|
+
var import_citty11 = require("citty");
|
|
2799
|
+
var import_picocolors10 = __toESM(require("picocolors"), 1);
|
|
2800
|
+
var p9 = __toESM(require("@clack/prompts"), 1);
|
|
1927
2801
|
init_vault_service();
|
|
1928
2802
|
function maskValue(value) {
|
|
1929
2803
|
if (value.length <= 8) return "***";
|
|
@@ -1934,7 +2808,7 @@ function parseKeyValue(input) {
|
|
|
1934
2808
|
if (idx <= 0) return null;
|
|
1935
2809
|
return { key: input.slice(0, idx), value: input.slice(idx + 1) };
|
|
1936
2810
|
}
|
|
1937
|
-
var
|
|
2811
|
+
var setCommand2 = (0, import_citty11.defineCommand)({
|
|
1938
2812
|
meta: { name: "set", description: "Store an encrypted secret for a server" },
|
|
1939
2813
|
args: {
|
|
1940
2814
|
server: {
|
|
@@ -1951,30 +2825,30 @@ var setCommand = (0, import_citty7.defineCommand)({
|
|
|
1951
2825
|
async run({ args }) {
|
|
1952
2826
|
const parsed = parseKeyValue(args.keyvalue);
|
|
1953
2827
|
if (!parsed) {
|
|
1954
|
-
console.error(
|
|
2828
|
+
console.error(import_picocolors10.default.red("\u2717") + " Invalid format. Expected KEY=VALUE");
|
|
1955
2829
|
process.exit(1);
|
|
1956
2830
|
}
|
|
1957
|
-
|
|
2831
|
+
p9.intro(import_picocolors10.default.cyan("mcpman secrets set"));
|
|
1958
2832
|
const isNew = listSecrets(args.server).length === 0 || !listSecrets(args.server)[0]?.keys.includes(parsed.key);
|
|
1959
2833
|
const vaultPath = (await Promise.resolve().then(() => (init_vault_service(), vault_service_exports))).getVaultPath();
|
|
1960
2834
|
const vaultExists = (await import("fs")).existsSync(vaultPath);
|
|
1961
2835
|
const password2 = await getMasterPassword(!vaultExists && isNew);
|
|
1962
|
-
const spin =
|
|
2836
|
+
const spin = p9.spinner();
|
|
1963
2837
|
spin.start("Encrypting secret...");
|
|
1964
2838
|
try {
|
|
1965
2839
|
setSecret(args.server, parsed.key, parsed.value, password2);
|
|
1966
2840
|
spin.stop(
|
|
1967
|
-
`${
|
|
2841
|
+
`${import_picocolors10.default.green("\u2713")} Stored ${import_picocolors10.default.bold(parsed.key)} for ${import_picocolors10.default.cyan(args.server)}`
|
|
1968
2842
|
);
|
|
1969
2843
|
} catch (err) {
|
|
1970
|
-
spin.stop(
|
|
1971
|
-
console.error(
|
|
2844
|
+
spin.stop(import_picocolors10.default.red("\u2717") + " Failed to store secret");
|
|
2845
|
+
console.error(import_picocolors10.default.dim(String(err)));
|
|
1972
2846
|
process.exit(1);
|
|
1973
2847
|
}
|
|
1974
|
-
|
|
2848
|
+
p9.outro(import_picocolors10.default.dim("Secret encrypted and saved to vault."));
|
|
1975
2849
|
}
|
|
1976
2850
|
});
|
|
1977
|
-
var
|
|
2851
|
+
var listCommand2 = (0, import_citty11.defineCommand)({
|
|
1978
2852
|
meta: { name: "list", description: "List secret keys stored in the vault" },
|
|
1979
2853
|
args: {
|
|
1980
2854
|
server: {
|
|
@@ -1986,23 +2860,23 @@ var listCommand = (0, import_citty7.defineCommand)({
|
|
|
1986
2860
|
async run({ args }) {
|
|
1987
2861
|
const results = listSecrets(args.server || void 0);
|
|
1988
2862
|
if (results.length === 0) {
|
|
1989
|
-
const filter = args.server ? ` for ${
|
|
1990
|
-
console.log(
|
|
2863
|
+
const filter = args.server ? ` for ${import_picocolors10.default.cyan(args.server)}` : "";
|
|
2864
|
+
console.log(import_picocolors10.default.dim(`No secrets stored${filter}.`));
|
|
1991
2865
|
return;
|
|
1992
2866
|
}
|
|
1993
2867
|
console.log("");
|
|
1994
2868
|
for (const { server, keys } of results) {
|
|
1995
|
-
console.log(
|
|
2869
|
+
console.log(import_picocolors10.default.bold(import_picocolors10.default.cyan(server)));
|
|
1996
2870
|
for (const key of keys) {
|
|
1997
|
-
console.log(` ${
|
|
2871
|
+
console.log(` ${import_picocolors10.default.green("\u25CF")} ${import_picocolors10.default.bold(key)} ${import_picocolors10.default.dim(maskValue("\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022"))}`);
|
|
1998
2872
|
}
|
|
1999
2873
|
console.log("");
|
|
2000
2874
|
}
|
|
2001
2875
|
const total = results.reduce((n, r) => n + r.keys.length, 0);
|
|
2002
|
-
console.log(
|
|
2876
|
+
console.log(import_picocolors10.default.dim(` ${total} secret${total !== 1 ? "s" : ""} in ${results.length} server${results.length !== 1 ? "s" : ""}`));
|
|
2003
2877
|
}
|
|
2004
2878
|
});
|
|
2005
|
-
var removeCommand = (0,
|
|
2879
|
+
var removeCommand = (0, import_citty11.defineCommand)({
|
|
2006
2880
|
meta: { name: "remove", description: "Delete a secret from the vault" },
|
|
2007
2881
|
args: {
|
|
2008
2882
|
server: {
|
|
@@ -2017,41 +2891,41 @@ var removeCommand = (0, import_citty7.defineCommand)({
|
|
|
2017
2891
|
}
|
|
2018
2892
|
},
|
|
2019
2893
|
async run({ args }) {
|
|
2020
|
-
const confirmed = await
|
|
2021
|
-
message: `Remove ${
|
|
2894
|
+
const confirmed = await p9.confirm({
|
|
2895
|
+
message: `Remove ${import_picocolors10.default.bold(args.key)} from ${import_picocolors10.default.cyan(args.server)}?`,
|
|
2022
2896
|
initialValue: false
|
|
2023
2897
|
});
|
|
2024
|
-
if (
|
|
2025
|
-
|
|
2898
|
+
if (p9.isCancel(confirmed) || !confirmed) {
|
|
2899
|
+
p9.cancel("Cancelled.");
|
|
2026
2900
|
return;
|
|
2027
2901
|
}
|
|
2028
2902
|
try {
|
|
2029
2903
|
removeSecret(args.server, args.key);
|
|
2030
|
-
console.log(`${
|
|
2904
|
+
console.log(`${import_picocolors10.default.green("\u2713")} Removed ${import_picocolors10.default.bold(args.key)} from ${import_picocolors10.default.cyan(args.server)}`);
|
|
2031
2905
|
} catch (err) {
|
|
2032
|
-
console.error(
|
|
2033
|
-
console.error(
|
|
2906
|
+
console.error(import_picocolors10.default.red("\u2717") + " Failed to remove secret");
|
|
2907
|
+
console.error(import_picocolors10.default.dim(String(err)));
|
|
2034
2908
|
process.exit(1);
|
|
2035
2909
|
}
|
|
2036
2910
|
}
|
|
2037
2911
|
});
|
|
2038
|
-
var secrets_default = (0,
|
|
2912
|
+
var secrets_default = (0, import_citty11.defineCommand)({
|
|
2039
2913
|
meta: {
|
|
2040
2914
|
name: "secrets",
|
|
2041
2915
|
description: "Manage encrypted secrets for MCP servers"
|
|
2042
2916
|
},
|
|
2043
2917
|
subCommands: {
|
|
2044
|
-
set:
|
|
2045
|
-
list:
|
|
2918
|
+
set: setCommand2,
|
|
2919
|
+
list: listCommand2,
|
|
2046
2920
|
remove: removeCommand
|
|
2047
2921
|
}
|
|
2048
2922
|
});
|
|
2049
2923
|
|
|
2050
2924
|
// src/commands/sync.ts
|
|
2051
2925
|
init_cjs_shims();
|
|
2052
|
-
var
|
|
2053
|
-
var
|
|
2054
|
-
var
|
|
2926
|
+
var import_citty12 = require("citty");
|
|
2927
|
+
var p10 = __toESM(require("@clack/prompts"), 1);
|
|
2928
|
+
var import_picocolors11 = __toESM(require("picocolors"), 1);
|
|
2055
2929
|
|
|
2056
2930
|
// src/core/config-diff.ts
|
|
2057
2931
|
init_cjs_shims();
|
|
@@ -2067,7 +2941,7 @@ function reconstructServerEntry(lockEntry) {
|
|
|
2067
2941
|
}
|
|
2068
2942
|
return entry;
|
|
2069
2943
|
}
|
|
2070
|
-
function computeDiff(lockfile, clientConfigs) {
|
|
2944
|
+
function computeDiff(lockfile, clientConfigs, options = {}) {
|
|
2071
2945
|
const actions = [];
|
|
2072
2946
|
for (const [server, lockEntry] of Object.entries(lockfile.servers)) {
|
|
2073
2947
|
for (const client of lockEntry.clients) {
|
|
@@ -2085,19 +2959,21 @@ function computeDiff(lockfile, clientConfigs) {
|
|
|
2085
2959
|
}
|
|
2086
2960
|
}
|
|
2087
2961
|
}
|
|
2962
|
+
const extraAction = options.remove ? "remove" : "extra";
|
|
2088
2963
|
for (const [client, config] of clientConfigs) {
|
|
2089
2964
|
for (const server of Object.keys(config.servers)) {
|
|
2090
2965
|
if (!(server in lockfile.servers)) {
|
|
2091
|
-
actions.push({ server, client, action:
|
|
2966
|
+
actions.push({ server, client, action: extraAction });
|
|
2092
2967
|
}
|
|
2093
2968
|
}
|
|
2094
2969
|
}
|
|
2095
2970
|
return actions;
|
|
2096
2971
|
}
|
|
2097
|
-
function computeDiffFromClient(sourceClient, clientConfigs) {
|
|
2972
|
+
function computeDiffFromClient(sourceClient, clientConfigs, options = {}) {
|
|
2098
2973
|
const actions = [];
|
|
2099
2974
|
const sourceConfig = clientConfigs.get(sourceClient);
|
|
2100
2975
|
if (!sourceConfig) return [];
|
|
2976
|
+
const extraAction = options.remove ? "remove" : "extra";
|
|
2101
2977
|
for (const [client, config] of clientConfigs) {
|
|
2102
2978
|
if (client === sourceClient) continue;
|
|
2103
2979
|
for (const [server, entry] of Object.entries(sourceConfig.servers)) {
|
|
@@ -2109,7 +2985,7 @@ function computeDiffFromClient(sourceClient, clientConfigs) {
|
|
|
2109
2985
|
}
|
|
2110
2986
|
for (const server of Object.keys(config.servers)) {
|
|
2111
2987
|
if (!(server in sourceConfig.servers)) {
|
|
2112
|
-
actions.push({ server, client, action:
|
|
2988
|
+
actions.push({ server, client, action: extraAction });
|
|
2113
2989
|
}
|
|
2114
2990
|
}
|
|
2115
2991
|
}
|
|
@@ -2120,7 +2996,7 @@ function computeDiffFromClient(sourceClient, clientConfigs) {
|
|
|
2120
2996
|
init_cjs_shims();
|
|
2121
2997
|
init_client_detector();
|
|
2122
2998
|
async function applySyncActions(actions, clients) {
|
|
2123
|
-
const result = { applied: 0, failed: 0, errors: [] };
|
|
2999
|
+
const result = { applied: 0, removed: 0, failed: 0, errors: [] };
|
|
2124
3000
|
const addActions = actions.filter((a) => a.action === "add" && a.entry);
|
|
2125
3001
|
for (const action of addActions) {
|
|
2126
3002
|
const handler = clients.get(action.client);
|
|
@@ -2145,6 +3021,30 @@ async function applySyncActions(actions, clients) {
|
|
|
2145
3021
|
});
|
|
2146
3022
|
}
|
|
2147
3023
|
}
|
|
3024
|
+
const removeActions = actions.filter((a) => a.action === "remove");
|
|
3025
|
+
for (const action of removeActions) {
|
|
3026
|
+
const handler = clients.get(action.client);
|
|
3027
|
+
if (!handler) {
|
|
3028
|
+
result.failed++;
|
|
3029
|
+
result.errors.push({
|
|
3030
|
+
server: action.server,
|
|
3031
|
+
client: action.client,
|
|
3032
|
+
error: "No handler available for client"
|
|
3033
|
+
});
|
|
3034
|
+
continue;
|
|
3035
|
+
}
|
|
3036
|
+
try {
|
|
3037
|
+
await handler.removeServer(action.server);
|
|
3038
|
+
result.removed++;
|
|
3039
|
+
} catch (err) {
|
|
3040
|
+
result.failed++;
|
|
3041
|
+
result.errors.push({
|
|
3042
|
+
server: action.server,
|
|
3043
|
+
client: action.client,
|
|
3044
|
+
error: String(err)
|
|
3045
|
+
});
|
|
3046
|
+
}
|
|
3047
|
+
}
|
|
2148
3048
|
return result;
|
|
2149
3049
|
}
|
|
2150
3050
|
async function getClientConfigs() {
|
|
@@ -2178,7 +3078,7 @@ var CLIENT_DISPLAY3 = {
|
|
|
2178
3078
|
vscode: "VS Code",
|
|
2179
3079
|
windsurf: "Windsurf"
|
|
2180
3080
|
};
|
|
2181
|
-
var sync_default = (0,
|
|
3081
|
+
var sync_default = (0, import_citty12.defineCommand)({
|
|
2182
3082
|
meta: {
|
|
2183
3083
|
name: "sync",
|
|
2184
3084
|
description: "Sync MCP server configs across all detected AI clients"
|
|
@@ -2189,6 +3089,11 @@ var sync_default = (0, import_citty8.defineCommand)({
|
|
|
2189
3089
|
description: "Preview changes without applying them",
|
|
2190
3090
|
default: false
|
|
2191
3091
|
},
|
|
3092
|
+
remove: {
|
|
3093
|
+
type: "boolean",
|
|
3094
|
+
description: "Remove extra servers not in lockfile",
|
|
3095
|
+
default: false
|
|
3096
|
+
},
|
|
2192
3097
|
source: {
|
|
2193
3098
|
type: "string",
|
|
2194
3099
|
description: "Use a specific client as source of truth (claude-desktop, cursor, vscode, windsurf)"
|
|
@@ -2200,58 +3105,64 @@ var sync_default = (0, import_citty8.defineCommand)({
|
|
|
2200
3105
|
}
|
|
2201
3106
|
},
|
|
2202
3107
|
async run({ args }) {
|
|
2203
|
-
|
|
3108
|
+
p10.intro(`${import_picocolors11.default.cyan("mcpman sync")}`);
|
|
2204
3109
|
const sourceClient = args.source;
|
|
2205
3110
|
if (sourceClient && !VALID_CLIENTS.includes(sourceClient)) {
|
|
2206
|
-
|
|
3111
|
+
p10.log.error(`Invalid --source "${sourceClient}". Must be one of: ${VALID_CLIENTS.join(", ")}`);
|
|
2207
3112
|
process.exit(1);
|
|
2208
3113
|
}
|
|
2209
|
-
const spinner5 =
|
|
3114
|
+
const spinner5 = p10.spinner();
|
|
2210
3115
|
spinner5.start("Detecting clients and reading configs...");
|
|
2211
3116
|
const { configs, handlers } = await getClientConfigs();
|
|
2212
3117
|
spinner5.stop(`Found ${configs.size} client(s)`);
|
|
2213
3118
|
if (configs.size === 0) {
|
|
2214
|
-
|
|
3119
|
+
p10.log.warn("No AI clients detected. Install Claude Desktop, Cursor, VS Code, or Windsurf first.");
|
|
2215
3120
|
process.exit(0);
|
|
2216
3121
|
}
|
|
3122
|
+
const diffOptions = { remove: args.remove };
|
|
2217
3123
|
let actions;
|
|
2218
3124
|
if (sourceClient) {
|
|
2219
3125
|
if (!configs.has(sourceClient)) {
|
|
2220
|
-
|
|
3126
|
+
p10.log.error(`Source client "${sourceClient}" is not detected or its config is unreadable.`);
|
|
2221
3127
|
process.exit(1);
|
|
2222
3128
|
}
|
|
2223
|
-
|
|
2224
|
-
actions = computeDiffFromClient(sourceClient, configs);
|
|
3129
|
+
p10.log.info(`Using ${CLIENT_DISPLAY3[sourceClient]} as source of truth`);
|
|
3130
|
+
actions = computeDiffFromClient(sourceClient, configs, diffOptions);
|
|
2225
3131
|
} else {
|
|
2226
3132
|
const lockfile = readLockfile();
|
|
2227
|
-
actions = computeDiff(lockfile, configs);
|
|
3133
|
+
actions = computeDiff(lockfile, configs, diffOptions);
|
|
2228
3134
|
}
|
|
2229
3135
|
printDiffTable(actions);
|
|
2230
3136
|
const addCount = actions.filter((a) => a.action === "add").length;
|
|
2231
3137
|
const extraCount = actions.filter((a) => a.action === "extra").length;
|
|
2232
|
-
|
|
2233
|
-
|
|
3138
|
+
const removeCount = actions.filter((a) => a.action === "remove").length;
|
|
3139
|
+
if (addCount === 0 && removeCount === 0 && extraCount === 0) {
|
|
3140
|
+
p10.outro(import_picocolors11.default.green("All clients are in sync."));
|
|
2234
3141
|
process.exit(0);
|
|
2235
3142
|
}
|
|
2236
3143
|
const parts = [];
|
|
2237
|
-
if (addCount > 0) parts.push(
|
|
2238
|
-
if (
|
|
2239
|
-
|
|
3144
|
+
if (addCount > 0) parts.push(import_picocolors11.default.green(`${addCount} to add`));
|
|
3145
|
+
if (removeCount > 0) parts.push(import_picocolors11.default.red(`${removeCount} to remove`));
|
|
3146
|
+
if (extraCount > 0) parts.push(import_picocolors11.default.yellow(`${extraCount} extra (informational)`));
|
|
3147
|
+
p10.log.info(parts.join(" \xB7 "));
|
|
2240
3148
|
if (args["dry-run"]) {
|
|
2241
|
-
|
|
3149
|
+
p10.outro(import_picocolors11.default.dim("Dry run \u2014 no changes applied."));
|
|
2242
3150
|
process.exit(1);
|
|
2243
3151
|
}
|
|
2244
|
-
if (addCount === 0) {
|
|
2245
|
-
|
|
3152
|
+
if (addCount === 0 && removeCount === 0) {
|
|
3153
|
+
p10.outro(import_picocolors11.default.dim("No additions needed. Extra servers left untouched."));
|
|
2246
3154
|
process.exit(1);
|
|
2247
3155
|
}
|
|
2248
3156
|
if (!args.yes) {
|
|
2249
|
-
const
|
|
2250
|
-
|
|
3157
|
+
const actionParts = [];
|
|
3158
|
+
if (addCount > 0) actionParts.push(`${addCount} addition(s)`);
|
|
3159
|
+
if (removeCount > 0) actionParts.push(`${removeCount} removal(s)`);
|
|
3160
|
+
const confirmed = await p10.confirm({
|
|
3161
|
+
message: `Apply ${actionParts.join(" and ")} to client configs?`,
|
|
2251
3162
|
initialValue: true
|
|
2252
3163
|
});
|
|
2253
|
-
if (
|
|
2254
|
-
|
|
3164
|
+
if (p10.isCancel(confirmed) || !confirmed) {
|
|
3165
|
+
p10.outro(import_picocolors11.default.dim("Cancelled \u2014 no changes applied."));
|
|
2255
3166
|
process.exit(0);
|
|
2256
3167
|
}
|
|
2257
3168
|
}
|
|
@@ -2259,196 +3170,80 @@ var sync_default = (0, import_citty8.defineCommand)({
|
|
|
2259
3170
|
const result = await applySyncActions(actions, handlers);
|
|
2260
3171
|
spinner5.stop("Done");
|
|
2261
3172
|
if (result.applied > 0) {
|
|
2262
|
-
|
|
3173
|
+
p10.log.success(`Added ${result.applied} server(s) to client configs.`);
|
|
3174
|
+
}
|
|
3175
|
+
if (result.removed > 0) {
|
|
3176
|
+
p10.log.success(`Removed ${result.removed} server(s) from client configs.`);
|
|
2263
3177
|
}
|
|
2264
3178
|
if (result.failed > 0) {
|
|
2265
3179
|
for (const e of result.errors) {
|
|
2266
|
-
|
|
3180
|
+
p10.log.error(`Failed to sync "${e.server}" on ${e.client}: ${e.error}`);
|
|
2267
3181
|
}
|
|
2268
3182
|
}
|
|
2269
|
-
|
|
3183
|
+
p10.outro(result.failed === 0 ? import_picocolors11.default.green("Sync complete.") : import_picocolors11.default.yellow("Sync complete with errors."));
|
|
2270
3184
|
process.exit(result.failed > 0 ? 1 : 0);
|
|
2271
3185
|
}
|
|
2272
3186
|
});
|
|
2273
3187
|
function printDiffTable(actions) {
|
|
2274
3188
|
if (actions.length === 0) {
|
|
2275
|
-
|
|
3189
|
+
p10.log.info("No actions to display.");
|
|
2276
3190
|
return;
|
|
2277
3191
|
}
|
|
2278
3192
|
const nameWidth = Math.max(6, ...actions.map((a) => a.server.length));
|
|
2279
3193
|
const clientWidth = Math.max(6, ...actions.map((a) => CLIENT_DISPLAY3[a.client]?.length ?? a.client.length));
|
|
2280
|
-
const header = ` ${
|
|
2281
|
-
console.log(
|
|
2282
|
-
console.log(
|
|
3194
|
+
const header = ` ${pad3("SERVER", nameWidth)} ${pad3("CLIENT", clientWidth)} STATUS`;
|
|
3195
|
+
console.log(import_picocolors11.default.dim(header));
|
|
3196
|
+
console.log(import_picocolors11.default.dim(` ${"-".repeat(nameWidth)} ${"-".repeat(clientWidth)} ------`));
|
|
2283
3197
|
for (const action of actions) {
|
|
2284
3198
|
const clientDisplay = CLIENT_DISPLAY3[action.client] ?? action.client;
|
|
2285
3199
|
const [icon, statusText] = formatAction(action.action);
|
|
2286
|
-
console.log(` ${
|
|
3200
|
+
console.log(` ${pad3(action.server, nameWidth)} ${pad3(clientDisplay, clientWidth)} ${icon} ${statusText}`);
|
|
2287
3201
|
}
|
|
2288
3202
|
console.log("");
|
|
2289
3203
|
}
|
|
2290
3204
|
function formatAction(action) {
|
|
2291
3205
|
switch (action) {
|
|
2292
3206
|
case "add":
|
|
2293
|
-
return [
|
|
3207
|
+
return [import_picocolors11.default.green("+"), import_picocolors11.default.green("missing \u2014 will add")];
|
|
2294
3208
|
case "extra":
|
|
2295
|
-
return [
|
|
3209
|
+
return [import_picocolors11.default.yellow("?"), import_picocolors11.default.yellow("extra (not in lockfile)")];
|
|
3210
|
+
case "remove":
|
|
3211
|
+
return [import_picocolors11.default.red("\u2013"), import_picocolors11.default.red("extra \u2014 will remove")];
|
|
2296
3212
|
case "ok":
|
|
2297
|
-
return [
|
|
3213
|
+
return [import_picocolors11.default.dim("\xB7"), import_picocolors11.default.dim("in sync")];
|
|
2298
3214
|
}
|
|
2299
3215
|
}
|
|
2300
|
-
function
|
|
3216
|
+
function pad3(s, width) {
|
|
2301
3217
|
return s.length >= width ? s : s + " ".repeat(width - s.length);
|
|
2302
3218
|
}
|
|
2303
3219
|
|
|
2304
3220
|
// src/commands/update.ts
|
|
2305
3221
|
init_cjs_shims();
|
|
2306
|
-
var
|
|
2307
|
-
var
|
|
2308
|
-
var
|
|
2309
|
-
|
|
2310
|
-
// src/core/version-checker.ts
|
|
2311
|
-
init_cjs_shims();
|
|
2312
|
-
function compareVersions(a, b) {
|
|
2313
|
-
const aParts = a.replace(/^v/, "").split(".").map(Number);
|
|
2314
|
-
const bParts = b.replace(/^v/, "").split(".").map(Number);
|
|
2315
|
-
const len = Math.max(aParts.length, bParts.length);
|
|
2316
|
-
for (let i = 0; i < len; i++) {
|
|
2317
|
-
const aN = aParts[i] ?? 0;
|
|
2318
|
-
const bN = bParts[i] ?? 0;
|
|
2319
|
-
if (Number.isNaN(aN) || Number.isNaN(bN)) return 0;
|
|
2320
|
-
if (aN < bN) return -1;
|
|
2321
|
-
if (aN > bN) return 1;
|
|
2322
|
-
}
|
|
2323
|
-
return 0;
|
|
2324
|
-
}
|
|
2325
|
-
function detectUpdateType(current, latest) {
|
|
2326
|
-
const cParts = current.replace(/^v/, "").split(".").map(Number);
|
|
2327
|
-
const lParts = latest.replace(/^v/, "").split(".").map(Number);
|
|
2328
|
-
if ((lParts[0] ?? 0) > (cParts[0] ?? 0)) return "major";
|
|
2329
|
-
if ((lParts[1] ?? 0) > (cParts[1] ?? 0)) return "minor";
|
|
2330
|
-
return "patch";
|
|
2331
|
-
}
|
|
2332
|
-
async function fetchNpmLatest(packageName) {
|
|
2333
|
-
try {
|
|
2334
|
-
const res = await fetch(
|
|
2335
|
-
`https://registry.npmjs.org/${encodeURIComponent(packageName)}/latest`,
|
|
2336
|
-
{
|
|
2337
|
-
headers: { Accept: "application/json" },
|
|
2338
|
-
signal: AbortSignal.timeout(8e3)
|
|
2339
|
-
}
|
|
2340
|
-
);
|
|
2341
|
-
if (!res.ok) return null;
|
|
2342
|
-
const data = await res.json();
|
|
2343
|
-
return typeof data.version === "string" ? data.version : null;
|
|
2344
|
-
} catch {
|
|
2345
|
-
return null;
|
|
2346
|
-
}
|
|
2347
|
-
}
|
|
2348
|
-
async function fetchSmitheryLatest(name) {
|
|
2349
|
-
try {
|
|
2350
|
-
const res = await fetch(
|
|
2351
|
-
`https://registry.smithery.ai/servers/${encodeURIComponent(name)}`,
|
|
2352
|
-
{
|
|
2353
|
-
headers: { Accept: "application/json" },
|
|
2354
|
-
signal: AbortSignal.timeout(8e3)
|
|
2355
|
-
}
|
|
2356
|
-
);
|
|
2357
|
-
if (!res.ok) return null;
|
|
2358
|
-
const data = await res.json();
|
|
2359
|
-
return typeof data.version === "string" ? data.version : null;
|
|
2360
|
-
} catch {
|
|
2361
|
-
return null;
|
|
2362
|
-
}
|
|
2363
|
-
}
|
|
2364
|
-
async function fetchGithubLatest(resolved) {
|
|
2365
|
-
const match = resolved.match(/github\.com\/([^/]+)\/([^/]+)/);
|
|
2366
|
-
if (!match) return null;
|
|
2367
|
-
const [, owner, repo] = match;
|
|
2368
|
-
try {
|
|
2369
|
-
const res = await fetch(
|
|
2370
|
-
`https://api.github.com/repos/${owner}/${repo}/releases/latest`,
|
|
2371
|
-
{
|
|
2372
|
-
headers: { Accept: "application/json" },
|
|
2373
|
-
signal: AbortSignal.timeout(8e3)
|
|
2374
|
-
}
|
|
2375
|
-
);
|
|
2376
|
-
if (!res.ok) return null;
|
|
2377
|
-
const data = await res.json();
|
|
2378
|
-
return typeof data.tag_name === "string" ? data.tag_name.replace(/^v/, "") : null;
|
|
2379
|
-
} catch {
|
|
2380
|
-
return null;
|
|
2381
|
-
}
|
|
2382
|
-
}
|
|
2383
|
-
async function checkVersion(name, lockEntry) {
|
|
2384
|
-
const current = lockEntry.version;
|
|
2385
|
-
let latest = null;
|
|
2386
|
-
if (lockEntry.source === "npm") {
|
|
2387
|
-
latest = await fetchNpmLatest(name);
|
|
2388
|
-
} else if (lockEntry.source === "smithery") {
|
|
2389
|
-
latest = await fetchSmitheryLatest(name);
|
|
2390
|
-
} else if (lockEntry.source === "github") {
|
|
2391
|
-
latest = await fetchGithubLatest(lockEntry.resolved);
|
|
2392
|
-
}
|
|
2393
|
-
if (!latest || latest === current) {
|
|
2394
|
-
return {
|
|
2395
|
-
server: name,
|
|
2396
|
-
source: lockEntry.source,
|
|
2397
|
-
currentVersion: current,
|
|
2398
|
-
latestVersion: latest ?? current,
|
|
2399
|
-
hasUpdate: false
|
|
2400
|
-
};
|
|
2401
|
-
}
|
|
2402
|
-
const hasUpdate = compareVersions(current, latest) === -1;
|
|
2403
|
-
return {
|
|
2404
|
-
server: name,
|
|
2405
|
-
source: lockEntry.source,
|
|
2406
|
-
currentVersion: current,
|
|
2407
|
-
latestVersion: latest,
|
|
2408
|
-
hasUpdate,
|
|
2409
|
-
updateType: hasUpdate ? detectUpdateType(current, latest) : void 0
|
|
2410
|
-
};
|
|
2411
|
-
}
|
|
2412
|
-
async function checkAllVersions(lockfile) {
|
|
2413
|
-
const entries = Object.entries(lockfile.servers);
|
|
2414
|
-
if (entries.length === 0) return [];
|
|
2415
|
-
const results = [];
|
|
2416
|
-
const executing = /* @__PURE__ */ new Set();
|
|
2417
|
-
for (const [name, entry] of entries) {
|
|
2418
|
-
const p9 = checkVersion(name, entry).then((r) => {
|
|
2419
|
-
results.push(r);
|
|
2420
|
-
executing.delete(p9);
|
|
2421
|
-
});
|
|
2422
|
-
executing.add(p9);
|
|
2423
|
-
if (executing.size >= 5) {
|
|
2424
|
-
await Promise.race(executing);
|
|
2425
|
-
}
|
|
2426
|
-
}
|
|
2427
|
-
await Promise.all(executing);
|
|
2428
|
-
return results;
|
|
2429
|
-
}
|
|
3222
|
+
var import_citty13 = require("citty");
|
|
3223
|
+
var p11 = __toESM(require("@clack/prompts"), 1);
|
|
3224
|
+
var import_picocolors13 = __toESM(require("picocolors"), 1);
|
|
2430
3225
|
|
|
2431
3226
|
// src/core/update-notifier.ts
|
|
2432
3227
|
init_cjs_shims();
|
|
2433
|
-
var
|
|
2434
|
-
var
|
|
3228
|
+
var import_node_fs6 = __toESM(require("fs"), 1);
|
|
3229
|
+
var import_node_path8 = __toESM(require("path"), 1);
|
|
2435
3230
|
var import_node_os5 = __toESM(require("os"), 1);
|
|
2436
|
-
var
|
|
2437
|
-
var CACHE_FILE =
|
|
3231
|
+
var import_picocolors12 = __toESM(require("picocolors"), 1);
|
|
3232
|
+
var CACHE_FILE = import_node_path8.default.join(import_node_os5.default.homedir(), ".mcpman", ".update-check");
|
|
2438
3233
|
var TTL_MS = 24 * 60 * 60 * 1e3;
|
|
2439
3234
|
function writeUpdateCache(data) {
|
|
2440
3235
|
try {
|
|
2441
|
-
const dir =
|
|
2442
|
-
if (!
|
|
3236
|
+
const dir = import_node_path8.default.dirname(CACHE_FILE);
|
|
3237
|
+
if (!import_node_fs6.default.existsSync(dir)) import_node_fs6.default.mkdirSync(dir, { recursive: true });
|
|
2443
3238
|
const tmp = `${CACHE_FILE}.tmp`;
|
|
2444
|
-
|
|
2445
|
-
|
|
3239
|
+
import_node_fs6.default.writeFileSync(tmp, JSON.stringify(data, null, 2), "utf-8");
|
|
3240
|
+
import_node_fs6.default.renameSync(tmp, CACHE_FILE);
|
|
2446
3241
|
} catch {
|
|
2447
3242
|
}
|
|
2448
3243
|
}
|
|
2449
3244
|
|
|
2450
3245
|
// src/commands/update.ts
|
|
2451
|
-
async function
|
|
3246
|
+
async function loadClients3() {
|
|
2452
3247
|
try {
|
|
2453
3248
|
const mod = await Promise.resolve().then(() => (init_client_detector(), client_detector_exports));
|
|
2454
3249
|
return mod.getInstalledClients();
|
|
@@ -2465,19 +3260,19 @@ function printTable(updates) {
|
|
|
2465
3260
|
"LATEST".padEnd(VER_W),
|
|
2466
3261
|
"STATUS"
|
|
2467
3262
|
].join(" ");
|
|
2468
|
-
console.log(
|
|
3263
|
+
console.log(import_picocolors13.default.bold(`
|
|
2469
3264
|
${header}`));
|
|
2470
|
-
console.log(
|
|
3265
|
+
console.log(import_picocolors13.default.dim(` ${"\u2500".repeat(NAME_W + VER_W * 2 + 20)}`));
|
|
2471
3266
|
for (const u of updates) {
|
|
2472
3267
|
const nameCol = u.server.slice(0, NAME_W).padEnd(NAME_W);
|
|
2473
3268
|
const curCol = u.currentVersion.padEnd(VER_W);
|
|
2474
3269
|
const latCol = u.latestVersion.padEnd(VER_W);
|
|
2475
|
-
const statusCol = u.hasUpdate ?
|
|
3270
|
+
const statusCol = u.hasUpdate ? import_picocolors13.default.yellow(`Update available${u.updateType ? ` [${u.updateType}]` : ""}`) : import_picocolors13.default.green("Up to date");
|
|
2476
3271
|
console.log(` ${nameCol} ${curCol} ${latCol} ${statusCol}`);
|
|
2477
3272
|
}
|
|
2478
3273
|
console.log();
|
|
2479
3274
|
}
|
|
2480
|
-
var update_default = (0,
|
|
3275
|
+
var update_default = (0, import_citty13.defineCommand)({
|
|
2481
3276
|
meta: {
|
|
2482
3277
|
name: "update",
|
|
2483
3278
|
description: "Check for and apply updates to installed MCP servers"
|
|
@@ -2516,7 +3311,7 @@ var update_default = (0, import_citty9.defineCommand)({
|
|
|
2516
3311
|
}
|
|
2517
3312
|
process.exit(1);
|
|
2518
3313
|
}
|
|
2519
|
-
const spinner5 =
|
|
3314
|
+
const spinner5 = p11.spinner();
|
|
2520
3315
|
spinner5.start("Checking versions...");
|
|
2521
3316
|
let updates;
|
|
2522
3317
|
try {
|
|
@@ -2538,71 +3333,51 @@ var update_default = (0, import_citty9.defineCommand)({
|
|
|
2538
3333
|
printTable(updates);
|
|
2539
3334
|
const outdated = updates.filter((u) => u.hasUpdate);
|
|
2540
3335
|
if (outdated.length === 0) {
|
|
2541
|
-
console.log(
|
|
3336
|
+
console.log(import_picocolors13.default.green(" All servers are up to date."));
|
|
2542
3337
|
return;
|
|
2543
3338
|
}
|
|
2544
3339
|
if (args.check) {
|
|
2545
|
-
console.log(
|
|
3340
|
+
console.log(import_picocolors13.default.yellow(` ${outdated.length} update(s) available. Run mcpman update to apply.`));
|
|
2546
3341
|
return;
|
|
2547
3342
|
}
|
|
2548
3343
|
if (!args.yes) {
|
|
2549
|
-
const confirmed = await
|
|
3344
|
+
const confirmed = await p11.confirm({
|
|
2550
3345
|
message: `Apply ${outdated.length} update(s)?`,
|
|
2551
3346
|
initialValue: true
|
|
2552
3347
|
});
|
|
2553
|
-
if (
|
|
2554
|
-
|
|
3348
|
+
if (p11.isCancel(confirmed) || !confirmed) {
|
|
3349
|
+
p11.outro("Cancelled.");
|
|
2555
3350
|
return;
|
|
2556
3351
|
}
|
|
2557
3352
|
}
|
|
2558
|
-
const clients = await
|
|
3353
|
+
const clients = await loadClients3();
|
|
2559
3354
|
let successCount = 0;
|
|
2560
3355
|
for (const update of outdated) {
|
|
2561
|
-
const
|
|
2562
|
-
const input = lockEntry.source === "smithery" ? `smithery:${update.server}` : lockEntry.source === "github" ? lockEntry.resolved : update.server;
|
|
2563
|
-
const s = p8.spinner();
|
|
3356
|
+
const s = p11.spinner();
|
|
2564
3357
|
s.start(`Updating ${update.server}...`);
|
|
2565
|
-
|
|
2566
|
-
|
|
2567
|
-
|
|
2568
|
-
|
|
2569
|
-
|
|
2570
|
-
|
|
2571
|
-
|
|
2572
|
-
integrity,
|
|
2573
|
-
command: metadata.command,
|
|
2574
|
-
args: metadata.args,
|
|
2575
|
-
installedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
2576
|
-
});
|
|
2577
|
-
const entryClients = clients.filter(
|
|
2578
|
-
(c) => lockEntry.clients.includes(c.type)
|
|
2579
|
-
);
|
|
2580
|
-
for (const client of entryClients) {
|
|
2581
|
-
try {
|
|
2582
|
-
await client.addServer(update.server, {
|
|
2583
|
-
command: metadata.command,
|
|
2584
|
-
args: metadata.args
|
|
2585
|
-
});
|
|
2586
|
-
} catch {
|
|
2587
|
-
}
|
|
2588
|
-
}
|
|
2589
|
-
s.stop(`${import_picocolors9.default.green("\u2713")} ${update.server}: ${update.currentVersion} \u2192 ${metadata.version}`);
|
|
3358
|
+
const result = await applyServerUpdate(
|
|
3359
|
+
update.server,
|
|
3360
|
+
servers[update.server],
|
|
3361
|
+
clients
|
|
3362
|
+
);
|
|
3363
|
+
if (result.success) {
|
|
3364
|
+
s.stop(`${import_picocolors13.default.green("\u2713")} ${update.server}: ${result.fromVersion} \u2192 ${result.toVersion}`);
|
|
2590
3365
|
successCount++;
|
|
2591
|
-
}
|
|
2592
|
-
s.stop(`${
|
|
3366
|
+
} else {
|
|
3367
|
+
s.stop(`${import_picocolors13.default.red("\u2717")} ${update.server}: ${result.error}`);
|
|
2593
3368
|
}
|
|
2594
3369
|
}
|
|
2595
3370
|
const freshLockfile = readLockfile(resolveLockfilePath());
|
|
2596
3371
|
const freshUpdates = await checkAllVersions(freshLockfile);
|
|
2597
3372
|
writeUpdateCache({ lastCheck: (/* @__PURE__ */ new Date()).toISOString(), updates: freshUpdates });
|
|
2598
|
-
|
|
3373
|
+
p11.outro(`${successCount} of ${outdated.length} server(s) updated.`);
|
|
2599
3374
|
}
|
|
2600
3375
|
});
|
|
2601
3376
|
|
|
2602
3377
|
// src/utils/constants.ts
|
|
2603
3378
|
init_cjs_shims();
|
|
2604
3379
|
var APP_NAME = "mcpman";
|
|
2605
|
-
var APP_VERSION = "0.
|
|
3380
|
+
var APP_VERSION = "0.4.0";
|
|
2606
3381
|
var APP_DESCRIPTION = "The package manager for MCP servers";
|
|
2607
3382
|
|
|
2608
3383
|
// src/index.ts
|
|
@@ -2610,7 +3385,7 @@ process.on("SIGINT", () => {
|
|
|
2610
3385
|
console.log("\nAborted.");
|
|
2611
3386
|
process.exit(130);
|
|
2612
3387
|
});
|
|
2613
|
-
var main = (0,
|
|
3388
|
+
var main = (0, import_citty14.defineCommand)({
|
|
2614
3389
|
meta: {
|
|
2615
3390
|
name: APP_NAME,
|
|
2616
3391
|
version: APP_VERSION,
|
|
@@ -2625,7 +3400,11 @@ var main = (0, import_citty10.defineCommand)({
|
|
|
2625
3400
|
secrets: secrets_default,
|
|
2626
3401
|
sync: sync_default,
|
|
2627
3402
|
audit: audit_default,
|
|
2628
|
-
update: update_default
|
|
3403
|
+
update: update_default,
|
|
3404
|
+
config: config_default,
|
|
3405
|
+
search: search_default,
|
|
3406
|
+
info: info_default,
|
|
3407
|
+
run: run_default
|
|
2629
3408
|
}
|
|
2630
3409
|
});
|
|
2631
|
-
(0,
|
|
3410
|
+
(0, import_citty14.runMain)(main);
|