@yawlabs/mcph 0.39.0 → 0.40.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/CHANGELOG.md +4 -0
- package/dist/index.js +325 -173
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to `@yawlabs/mcph` are documented here. This project uses [semantic versioning](https://semver.org) and a CI-gated release flow: pushing a `vX.Y.Z` tag triggers `.github/workflows/release.yml`, which publishes to npm.
|
|
4
4
|
|
|
5
|
+
## 0.40.0 — 2026-04-18
|
|
6
|
+
|
|
7
|
+
- **`mcph bundles` CLI subcommand** — CLI counterpart to the `mcp_connect_bundles` meta-tool (v0.28.0). Two actions mirror the meta-tool's `action` parameter: `list` prints every curated bundle grouped by category with activate hints (static, no network, no token needed — good for browsing or sharing in onboarding docs), and `match` partitions the curated set against the user's enabled servers from the backend into ready-to-activate vs partially-installed, so a human can see in the terminal what the LLM-facing tool would suggest. The LLM tool has always been primary surface, but "what bundles exist?" is a frequent enough support question that surfacing them in the CLI earns its keep. Match only counts `isActive: true` servers — disabled ones don't auto-activate, so they shouldn't count toward "ready" — matching the LLM tool's filter so both surfaces agree. Partial bundles sort fewest-missing first to match the discover inline hint ranking. `--json` emits machine-readable output (`{bundles}` for list, `{installed, ready, partial}` for match). Exit codes: 0 success, 1 match needs a token and none resolved, 2 match couldn't reach the backend.
|
|
8
|
+
|
|
5
9
|
## 0.39.0 — 2026-04-18
|
|
6
10
|
|
|
7
11
|
- **`mcph servers` CLI subcommand** — Lists the servers currently configured for your account in the mcp.hosting dashboard, hitting the same `/api/connect/config` endpoint that `runServer` polls at startup. Fills a gap between `mcph doctor` (local state: config files, clients, state.json) and the web dashboard: users can sanity-check their dashboard edits from the terminal, support engineers can ask for `mcph servers --json` output in a ticket, and scripts can pick a namespace up-front before piping into `mcph compliance` or `mcph install`. Table view groups the relevant columns (namespace, name, type, enabled/disabled, compliance grade, cached tool count) and is sorted alphabetically by namespace for diffable re-runs; `--json` emits the raw backend response verbatim. Exit codes: 0 success, 1 no token, 2 fetch error.
|
package/dist/index.js
CHANGED
|
@@ -1,104 +1,76 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
// src/
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
3
|
+
// src/bundles.ts
|
|
4
|
+
var CURATED_BUNDLES = [
|
|
5
|
+
{
|
|
6
|
+
id: "devops-incident",
|
|
7
|
+
name: "DevOps Incident Triage",
|
|
8
|
+
description: "GitHub + PagerDuty + Slack for on-call triage",
|
|
9
|
+
namespaces: ["github", "pagerduty", "slack"],
|
|
10
|
+
category: "ops"
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
id: "pr-review",
|
|
14
|
+
name: "PR Review",
|
|
15
|
+
description: "GitHub + Linear for issue-to-PR traceability",
|
|
16
|
+
namespaces: ["github", "linear"],
|
|
17
|
+
category: "dev"
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
id: "growth-stack",
|
|
21
|
+
name: "Growth Stack",
|
|
22
|
+
description: "HubSpot + Slack + GA for lifecycle + funnel signals",
|
|
23
|
+
namespaces: ["hubspot", "slack", "ga"],
|
|
24
|
+
category: "growth"
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
id: "data-ops",
|
|
28
|
+
name: "Data Ops",
|
|
29
|
+
description: "Postgres + S3 + Snowflake for pipeline debugging",
|
|
30
|
+
namespaces: ["postgres", "s3", "snowflake"],
|
|
31
|
+
category: "data"
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
id: "product-release",
|
|
35
|
+
name: "Product Release",
|
|
36
|
+
description: "GitHub + Linear + Slack for ship-day coordination",
|
|
37
|
+
namespaces: ["github", "linear", "slack"],
|
|
38
|
+
category: "dev"
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
id: "support-ops",
|
|
42
|
+
name: "Support Ops",
|
|
43
|
+
description: "Zendesk + Slack + HubSpot for escalation handoffs",
|
|
44
|
+
namespaces: ["zendesk", "slack", "hubspot"],
|
|
45
|
+
category: "ops"
|
|
14
46
|
}
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
if (result.deleteToken) {
|
|
28
|
-
process.stdout.write(`
|
|
29
|
-
Delete token (save this): ${result.deleteToken}
|
|
30
|
-
`);
|
|
47
|
+
];
|
|
48
|
+
function matchBundles(installedNamespaces) {
|
|
49
|
+
const installed = new Set(installedNamespaces);
|
|
50
|
+
const ready = [];
|
|
51
|
+
const partial = [];
|
|
52
|
+
for (const bundle of CURATED_BUNDLES) {
|
|
53
|
+
const have = bundle.namespaces.filter((ns) => installed.has(ns));
|
|
54
|
+
const missing = bundle.namespaces.filter((ns) => !installed.has(ns));
|
|
55
|
+
if (missing.length === 0) {
|
|
56
|
+
ready.push(bundle);
|
|
57
|
+
} else if (have.length > 0) {
|
|
58
|
+
partial.push({ bundle, have, missing });
|
|
31
59
|
}
|
|
32
60
|
}
|
|
33
|
-
return
|
|
34
|
-
}
|
|
35
|
-
function runTest(args) {
|
|
36
|
-
return new Promise((resolve4) => {
|
|
37
|
-
const child = spawn("npx", ["-y", "@yawlabs/mcp-compliance", "test", "--format", "json", ...args], {
|
|
38
|
-
stdio: ["ignore", "pipe", "inherit"],
|
|
39
|
-
shell: process.platform === "win32"
|
|
40
|
-
});
|
|
41
|
-
let stdout = "";
|
|
42
|
-
child.stdout.on("data", (chunk) => {
|
|
43
|
-
stdout += chunk.toString();
|
|
44
|
-
});
|
|
45
|
-
child.on("error", (err) => {
|
|
46
|
-
process.stderr.write(`
|
|
47
|
-
Failed to launch mcp-compliance: ${err.message}
|
|
48
|
-
`);
|
|
49
|
-
resolve4(null);
|
|
50
|
-
});
|
|
51
|
-
child.on("close", (code) => {
|
|
52
|
-
try {
|
|
53
|
-
const parsed = JSON.parse(stdout);
|
|
54
|
-
if (!parsed.grade || !parsed.summary) {
|
|
55
|
-
process.stderr.write(`
|
|
56
|
-
mcp-compliance returned unexpected JSON (exit ${code}).
|
|
57
|
-
`);
|
|
58
|
-
resolve4(null);
|
|
59
|
-
return;
|
|
60
|
-
}
|
|
61
|
-
resolve4(parsed);
|
|
62
|
-
} catch {
|
|
63
|
-
process.stderr.write(`
|
|
64
|
-
mcp-compliance exited ${code} without valid JSON output.
|
|
65
|
-
`);
|
|
66
|
-
resolve4(null);
|
|
67
|
-
}
|
|
68
|
-
});
|
|
69
|
-
});
|
|
61
|
+
return { ready, partial };
|
|
70
62
|
}
|
|
71
|
-
function
|
|
72
|
-
|
|
73
|
-
process.stdout.write(
|
|
74
|
-
`
|
|
75
|
-
Compliance: ${grade} (${score.toFixed(1)}%) \u2014 ${summary.passed}/${summary.total} passed, ${summary.requiredPassed}/${summary.required} required
|
|
76
|
-
Target: ${url}
|
|
77
|
-
`
|
|
78
|
-
);
|
|
63
|
+
function bundleActivateHint(bundle) {
|
|
64
|
+
return `mcp_connect_activate({ namespaces: ${JSON.stringify(bundle.namespaces)} })`;
|
|
79
65
|
}
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
const body = await res.body.text().catch(() => "");
|
|
89
|
-
process.stderr.write(`
|
|
90
|
-
Publish failed: HTTP ${res.statusCode}${body ? ` \u2014 ${body}` : ""}
|
|
91
|
-
`);
|
|
92
|
-
return null;
|
|
93
|
-
}
|
|
94
|
-
const parsed = await res.body.json();
|
|
95
|
-
return parsed;
|
|
96
|
-
} catch (err) {
|
|
97
|
-
process.stderr.write(`
|
|
98
|
-
Publish failed: ${err?.message ?? String(err)}
|
|
99
|
-
`);
|
|
100
|
-
return null;
|
|
101
|
-
}
|
|
66
|
+
function topPartialBundles(installedNamespaces, limit) {
|
|
67
|
+
if (limit <= 0) return [];
|
|
68
|
+
const { partial } = matchBundles(installedNamespaces);
|
|
69
|
+
return partial.slice().sort((a, b) => {
|
|
70
|
+
if (a.missing.length !== b.missing.length) return a.missing.length - b.missing.length;
|
|
71
|
+
if (a.have.length !== b.have.length) return b.have.length - a.have.length;
|
|
72
|
+
return a.bundle.id.localeCompare(b.bundle.id);
|
|
73
|
+
}).slice(0, limit);
|
|
102
74
|
}
|
|
103
75
|
|
|
104
76
|
// src/config-loader.ts
|
|
@@ -468,7 +440,7 @@ function profileAllows(profile, namespace) {
|
|
|
468
440
|
}
|
|
469
441
|
|
|
470
442
|
// src/config.ts
|
|
471
|
-
import { request
|
|
443
|
+
import { request } from "undici";
|
|
472
444
|
async function fetchConfig(apiUrl6, token6, currentVersion) {
|
|
473
445
|
const url = `${apiUrl6.replace(/\/$/, "")}/api/connect/config`;
|
|
474
446
|
const headers = {
|
|
@@ -478,7 +450,7 @@ async function fetchConfig(apiUrl6, token6, currentVersion) {
|
|
|
478
450
|
if (currentVersion) {
|
|
479
451
|
headers["If-None-Match"] = `"${currentVersion}"`;
|
|
480
452
|
}
|
|
481
|
-
const res = await
|
|
453
|
+
const res = await request(url, {
|
|
482
454
|
method: "GET",
|
|
483
455
|
headers,
|
|
484
456
|
headersTimeout: 1e4,
|
|
@@ -534,6 +506,249 @@ var ConfigError = class extends Error {
|
|
|
534
506
|
fatal;
|
|
535
507
|
};
|
|
536
508
|
|
|
509
|
+
// src/bundles-cmd.ts
|
|
510
|
+
var BUNDLES_USAGE = `Usage: mcph bundles [list|match] [--json]
|
|
511
|
+
|
|
512
|
+
Curated multi-server bundles \u2014 hand-picked stacks you can activate in one step.
|
|
513
|
+
|
|
514
|
+
list List every curated bundle (default, no network).
|
|
515
|
+
match Partition bundles against your installed servers (reads the backend).
|
|
516
|
+
|
|
517
|
+
--json Emit machine-readable JSON instead of a table.`;
|
|
518
|
+
function parseBundlesArgs(argv) {
|
|
519
|
+
let action = "list";
|
|
520
|
+
let json = false;
|
|
521
|
+
let actionSet = false;
|
|
522
|
+
for (const a of argv) {
|
|
523
|
+
if (a === "--json") {
|
|
524
|
+
json = true;
|
|
525
|
+
} else if (a === "--help" || a === "-h") {
|
|
526
|
+
return { ok: false, error: BUNDLES_USAGE };
|
|
527
|
+
} else if (a === "list" || a === "match") {
|
|
528
|
+
if (actionSet) {
|
|
529
|
+
return { ok: false, error: `mcph bundles: action already set to "${action}" (got "${a}")
|
|
530
|
+
|
|
531
|
+
${BUNDLES_USAGE}` };
|
|
532
|
+
}
|
|
533
|
+
action = a;
|
|
534
|
+
actionSet = true;
|
|
535
|
+
} else {
|
|
536
|
+
return { ok: false, error: `mcph bundles: unknown argument "${a}"
|
|
537
|
+
|
|
538
|
+
${BUNDLES_USAGE}` };
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
return { ok: true, options: { action, json } };
|
|
542
|
+
}
|
|
543
|
+
async function runBundlesCommand(opts = {}) {
|
|
544
|
+
const write = opts.out ?? ((s) => process.stdout.write(s));
|
|
545
|
+
const writeErr = opts.err ?? ((s) => process.stderr.write(s));
|
|
546
|
+
const lines = [];
|
|
547
|
+
const print = (s = "") => {
|
|
548
|
+
lines.push(s);
|
|
549
|
+
write(`${s}
|
|
550
|
+
`);
|
|
551
|
+
};
|
|
552
|
+
const printErr = (s) => {
|
|
553
|
+
lines.push(s);
|
|
554
|
+
writeErr(`${s}
|
|
555
|
+
`);
|
|
556
|
+
};
|
|
557
|
+
const action = opts.action ?? "list";
|
|
558
|
+
if (action === "list") {
|
|
559
|
+
if (opts.json) {
|
|
560
|
+
print(JSON.stringify({ bundles: CURATED_BUNDLES }, null, 2));
|
|
561
|
+
} else {
|
|
562
|
+
renderList(print);
|
|
563
|
+
}
|
|
564
|
+
return { exitCode: 0, lines };
|
|
565
|
+
}
|
|
566
|
+
const config = await loadMcphConfig({
|
|
567
|
+
cwd: opts.cwd,
|
|
568
|
+
home: opts.home,
|
|
569
|
+
env: opts.env
|
|
570
|
+
});
|
|
571
|
+
if (!config.token) {
|
|
572
|
+
printErr("mcph bundles match: no token resolved. Run `mcph install <client> --token mcp_pat_\u2026` or set MCPH_TOKEN.");
|
|
573
|
+
return { exitCode: 1, lines };
|
|
574
|
+
}
|
|
575
|
+
const fetcher = opts.fetcher ?? fetchConfig;
|
|
576
|
+
let backend;
|
|
577
|
+
try {
|
|
578
|
+
backend = await fetcher(config.apiBase, config.token);
|
|
579
|
+
} catch (err) {
|
|
580
|
+
const msg = err instanceof ConfigError || err instanceof Error ? err.message : String(err);
|
|
581
|
+
printErr(`mcph bundles match: ${msg}`);
|
|
582
|
+
return { exitCode: 2, lines };
|
|
583
|
+
}
|
|
584
|
+
if (!backend) {
|
|
585
|
+
printErr("mcph bundles match: backend returned no data (unexpected 304).");
|
|
586
|
+
return { exitCode: 2, lines };
|
|
587
|
+
}
|
|
588
|
+
const installed = backend.servers.filter((s) => s.isActive).map((s) => s.namespace);
|
|
589
|
+
const match = matchBundles(installed);
|
|
590
|
+
if (opts.json) {
|
|
591
|
+
print(JSON.stringify({ installed, ...match }, null, 2));
|
|
592
|
+
return { exitCode: 0, lines };
|
|
593
|
+
}
|
|
594
|
+
renderMatch(match, installed, print);
|
|
595
|
+
return { exitCode: 0, lines };
|
|
596
|
+
}
|
|
597
|
+
function renderList(print) {
|
|
598
|
+
print(`${CURATED_BUNDLES.length} curated bundles`);
|
|
599
|
+
print("");
|
|
600
|
+
const byCategory = /* @__PURE__ */ new Map();
|
|
601
|
+
for (const b of CURATED_BUNDLES) {
|
|
602
|
+
const list = byCategory.get(b.category) ?? [];
|
|
603
|
+
list.push(b);
|
|
604
|
+
byCategory.set(b.category, list);
|
|
605
|
+
}
|
|
606
|
+
const categories = [...byCategory.keys()].sort();
|
|
607
|
+
for (const cat of categories) {
|
|
608
|
+
const list = (byCategory.get(cat) ?? []).slice().sort((a, b) => a.id.localeCompare(b.id));
|
|
609
|
+
print(` [${cat}]`);
|
|
610
|
+
for (const b of list) {
|
|
611
|
+
print(` ${b.id.padEnd(18)} ${b.name}`);
|
|
612
|
+
print(` ${b.description}`);
|
|
613
|
+
print(` \u2192 ${bundleActivateHint(b)}`);
|
|
614
|
+
}
|
|
615
|
+
print("");
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
function renderMatch(match, installed, print) {
|
|
619
|
+
const installedList = installed.length === 0 ? "(none)" : installed.slice().sort().join(", ");
|
|
620
|
+
print(`Checked ${CURATED_BUNDLES.length} bundles against ${installed.length} enabled servers: ${installedList}`);
|
|
621
|
+
print("");
|
|
622
|
+
if (match.ready.length === 0 && match.partial.length === 0) {
|
|
623
|
+
print("No curated bundles match your current config.");
|
|
624
|
+
print("Run `mcph bundles list` to see the full catalog.");
|
|
625
|
+
return;
|
|
626
|
+
}
|
|
627
|
+
if (match.ready.length > 0) {
|
|
628
|
+
print("Ready to activate (every namespace installed):");
|
|
629
|
+
for (const b of match.ready.slice().sort((a, c) => a.id.localeCompare(c.id))) {
|
|
630
|
+
print(` ${b.id.padEnd(18)} ${b.description}`);
|
|
631
|
+
print(` \u2192 ${bundleActivateHint(b)}`);
|
|
632
|
+
}
|
|
633
|
+
print("");
|
|
634
|
+
}
|
|
635
|
+
if (match.partial.length > 0) {
|
|
636
|
+
const sorted = match.partial.slice().sort((a, b) => {
|
|
637
|
+
if (a.missing.length !== b.missing.length) return a.missing.length - b.missing.length;
|
|
638
|
+
if (a.have.length !== b.have.length) return b.have.length - a.have.length;
|
|
639
|
+
return a.bundle.id.localeCompare(b.bundle.id);
|
|
640
|
+
});
|
|
641
|
+
print("Partially installed (install more to complete):");
|
|
642
|
+
for (const entry of sorted) {
|
|
643
|
+
const have = entry.have.join(", ");
|
|
644
|
+
const missing = entry.missing.join(", ");
|
|
645
|
+
print(` ${entry.bundle.id.padEnd(18)} have: ${have}; missing: ${missing}`);
|
|
646
|
+
}
|
|
647
|
+
print("");
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
// src/compliance-cmd.ts
|
|
652
|
+
import { spawn } from "child_process";
|
|
653
|
+
import { request as request2 } from "undici";
|
|
654
|
+
async function runComplianceCommand(argv) {
|
|
655
|
+
const publish = argv.includes("--publish");
|
|
656
|
+
const args = argv.filter((a) => a !== "--publish");
|
|
657
|
+
if (args.length === 0) {
|
|
658
|
+
process.stderr.write(
|
|
659
|
+
'\n Usage: mcph compliance <target> [extraArgs...] [--publish]\n\n Examples:\n mcph compliance "npx -y @modelcontextprotocol/server-filesystem /tmp"\n mcph compliance https://example.com/mcp --publish\n\n'
|
|
660
|
+
);
|
|
661
|
+
return 1;
|
|
662
|
+
}
|
|
663
|
+
const apiUrl6 = process.env.MCPH_URL ?? "https://mcp.hosting";
|
|
664
|
+
const report = await runTest(args);
|
|
665
|
+
if (!report) return 1;
|
|
666
|
+
printSummary(report);
|
|
667
|
+
if (publish) {
|
|
668
|
+
const result = await publishReport(apiUrl6, report);
|
|
669
|
+
if (!result) return 1;
|
|
670
|
+
process.stdout.write(`
|
|
671
|
+
Published: ${result.reportUrl}
|
|
672
|
+
`);
|
|
673
|
+
process.stdout.write(`Badge: ${result.badgeUrl}
|
|
674
|
+
`);
|
|
675
|
+
if (result.deleteToken) {
|
|
676
|
+
process.stdout.write(`
|
|
677
|
+
Delete token (save this): ${result.deleteToken}
|
|
678
|
+
`);
|
|
679
|
+
}
|
|
680
|
+
}
|
|
681
|
+
return 0;
|
|
682
|
+
}
|
|
683
|
+
function runTest(args) {
|
|
684
|
+
return new Promise((resolve4) => {
|
|
685
|
+
const child = spawn("npx", ["-y", "@yawlabs/mcp-compliance", "test", "--format", "json", ...args], {
|
|
686
|
+
stdio: ["ignore", "pipe", "inherit"],
|
|
687
|
+
shell: process.platform === "win32"
|
|
688
|
+
});
|
|
689
|
+
let stdout = "";
|
|
690
|
+
child.stdout.on("data", (chunk) => {
|
|
691
|
+
stdout += chunk.toString();
|
|
692
|
+
});
|
|
693
|
+
child.on("error", (err) => {
|
|
694
|
+
process.stderr.write(`
|
|
695
|
+
Failed to launch mcp-compliance: ${err.message}
|
|
696
|
+
`);
|
|
697
|
+
resolve4(null);
|
|
698
|
+
});
|
|
699
|
+
child.on("close", (code) => {
|
|
700
|
+
try {
|
|
701
|
+
const parsed = JSON.parse(stdout);
|
|
702
|
+
if (!parsed.grade || !parsed.summary) {
|
|
703
|
+
process.stderr.write(`
|
|
704
|
+
mcp-compliance returned unexpected JSON (exit ${code}).
|
|
705
|
+
`);
|
|
706
|
+
resolve4(null);
|
|
707
|
+
return;
|
|
708
|
+
}
|
|
709
|
+
resolve4(parsed);
|
|
710
|
+
} catch {
|
|
711
|
+
process.stderr.write(`
|
|
712
|
+
mcp-compliance exited ${code} without valid JSON output.
|
|
713
|
+
`);
|
|
714
|
+
resolve4(null);
|
|
715
|
+
}
|
|
716
|
+
});
|
|
717
|
+
});
|
|
718
|
+
}
|
|
719
|
+
function printSummary(report) {
|
|
720
|
+
const { grade, score, summary, url } = report;
|
|
721
|
+
process.stdout.write(
|
|
722
|
+
`
|
|
723
|
+
Compliance: ${grade} (${score.toFixed(1)}%) \u2014 ${summary.passed}/${summary.total} passed, ${summary.requiredPassed}/${summary.required} required
|
|
724
|
+
Target: ${url}
|
|
725
|
+
`
|
|
726
|
+
);
|
|
727
|
+
}
|
|
728
|
+
async function publishReport(apiUrl6, report) {
|
|
729
|
+
try {
|
|
730
|
+
const res = await request2(`${apiUrl6.replace(/\/$/, "")}/api/compliance/ext`, {
|
|
731
|
+
method: "POST",
|
|
732
|
+
headers: { "Content-Type": "application/json" },
|
|
733
|
+
body: JSON.stringify(report)
|
|
734
|
+
});
|
|
735
|
+
if (res.statusCode !== 200) {
|
|
736
|
+
const body = await res.body.text().catch(() => "");
|
|
737
|
+
process.stderr.write(`
|
|
738
|
+
Publish failed: HTTP ${res.statusCode}${body ? ` \u2014 ${body}` : ""}
|
|
739
|
+
`);
|
|
740
|
+
return null;
|
|
741
|
+
}
|
|
742
|
+
const parsed = await res.body.json();
|
|
743
|
+
return parsed;
|
|
744
|
+
} catch (err) {
|
|
745
|
+
process.stderr.write(`
|
|
746
|
+
Publish failed: ${err?.message ?? String(err)}
|
|
747
|
+
`);
|
|
748
|
+
return null;
|
|
749
|
+
}
|
|
750
|
+
}
|
|
751
|
+
|
|
537
752
|
// src/doctor-cmd.ts
|
|
538
753
|
import { existsSync, readFileSync, statSync } from "fs";
|
|
539
754
|
import { readFile as readFile3 } from "fs/promises";
|
|
@@ -1004,7 +1219,7 @@ function selectFlakyNamespaces(entries, limit) {
|
|
|
1004
1219
|
}
|
|
1005
1220
|
|
|
1006
1221
|
// src/doctor-cmd.ts
|
|
1007
|
-
var VERSION = true ? "0.
|
|
1222
|
+
var VERSION = true ? "0.40.0" : "dev";
|
|
1008
1223
|
async function runDoctor(opts = {}) {
|
|
1009
1224
|
const lines = [];
|
|
1010
1225
|
const write = opts.out ?? ((s) => process.stdout.write(s));
|
|
@@ -1965,79 +2180,6 @@ async function shutdownAnalytics() {
|
|
|
1965
2180
|
dispatchBuffer.length = 0;
|
|
1966
2181
|
}
|
|
1967
2182
|
|
|
1968
|
-
// src/bundles.ts
|
|
1969
|
-
var CURATED_BUNDLES = [
|
|
1970
|
-
{
|
|
1971
|
-
id: "devops-incident",
|
|
1972
|
-
name: "DevOps Incident Triage",
|
|
1973
|
-
description: "GitHub + PagerDuty + Slack for on-call triage",
|
|
1974
|
-
namespaces: ["github", "pagerduty", "slack"],
|
|
1975
|
-
category: "ops"
|
|
1976
|
-
},
|
|
1977
|
-
{
|
|
1978
|
-
id: "pr-review",
|
|
1979
|
-
name: "PR Review",
|
|
1980
|
-
description: "GitHub + Linear for issue-to-PR traceability",
|
|
1981
|
-
namespaces: ["github", "linear"],
|
|
1982
|
-
category: "dev"
|
|
1983
|
-
},
|
|
1984
|
-
{
|
|
1985
|
-
id: "growth-stack",
|
|
1986
|
-
name: "Growth Stack",
|
|
1987
|
-
description: "HubSpot + Slack + GA for lifecycle + funnel signals",
|
|
1988
|
-
namespaces: ["hubspot", "slack", "ga"],
|
|
1989
|
-
category: "growth"
|
|
1990
|
-
},
|
|
1991
|
-
{
|
|
1992
|
-
id: "data-ops",
|
|
1993
|
-
name: "Data Ops",
|
|
1994
|
-
description: "Postgres + S3 + Snowflake for pipeline debugging",
|
|
1995
|
-
namespaces: ["postgres", "s3", "snowflake"],
|
|
1996
|
-
category: "data"
|
|
1997
|
-
},
|
|
1998
|
-
{
|
|
1999
|
-
id: "product-release",
|
|
2000
|
-
name: "Product Release",
|
|
2001
|
-
description: "GitHub + Linear + Slack for ship-day coordination",
|
|
2002
|
-
namespaces: ["github", "linear", "slack"],
|
|
2003
|
-
category: "dev"
|
|
2004
|
-
},
|
|
2005
|
-
{
|
|
2006
|
-
id: "support-ops",
|
|
2007
|
-
name: "Support Ops",
|
|
2008
|
-
description: "Zendesk + Slack + HubSpot for escalation handoffs",
|
|
2009
|
-
namespaces: ["zendesk", "slack", "hubspot"],
|
|
2010
|
-
category: "ops"
|
|
2011
|
-
}
|
|
2012
|
-
];
|
|
2013
|
-
function matchBundles(installedNamespaces) {
|
|
2014
|
-
const installed = new Set(installedNamespaces);
|
|
2015
|
-
const ready = [];
|
|
2016
|
-
const partial = [];
|
|
2017
|
-
for (const bundle of CURATED_BUNDLES) {
|
|
2018
|
-
const have = bundle.namespaces.filter((ns) => installed.has(ns));
|
|
2019
|
-
const missing = bundle.namespaces.filter((ns) => !installed.has(ns));
|
|
2020
|
-
if (missing.length === 0) {
|
|
2021
|
-
ready.push(bundle);
|
|
2022
|
-
} else if (have.length > 0) {
|
|
2023
|
-
partial.push({ bundle, have, missing });
|
|
2024
|
-
}
|
|
2025
|
-
}
|
|
2026
|
-
return { ready, partial };
|
|
2027
|
-
}
|
|
2028
|
-
function bundleActivateHint(bundle) {
|
|
2029
|
-
return `mcp_connect_activate({ namespaces: ${JSON.stringify(bundle.namespaces)} })`;
|
|
2030
|
-
}
|
|
2031
|
-
function topPartialBundles(installedNamespaces, limit) {
|
|
2032
|
-
if (limit <= 0) return [];
|
|
2033
|
-
const { partial } = matchBundles(installedNamespaces);
|
|
2034
|
-
return partial.slice().sort((a, b) => {
|
|
2035
|
-
if (a.missing.length !== b.missing.length) return a.missing.length - b.missing.length;
|
|
2036
|
-
if (a.have.length !== b.have.length) return b.have.length - a.have.length;
|
|
2037
|
-
return a.bundle.id.localeCompare(b.bundle.id);
|
|
2038
|
-
}).slice(0, limit);
|
|
2039
|
-
}
|
|
2040
|
-
|
|
2041
2183
|
// src/compliance.ts
|
|
2042
2184
|
var GRADE_ORDER = {
|
|
2043
2185
|
A: 4,
|
|
@@ -4065,7 +4207,7 @@ function categorizeSpawnError(err) {
|
|
|
4065
4207
|
}
|
|
4066
4208
|
async function connectToUpstream(config, onDisconnect, onListChanged) {
|
|
4067
4209
|
const client = new Client(
|
|
4068
|
-
{ name: "mcph", version: true ? "0.
|
|
4210
|
+
{ name: "mcph", version: true ? "0.40.0" : "dev" },
|
|
4069
4211
|
{ capabilities: {} }
|
|
4070
4212
|
);
|
|
4071
4213
|
let transport;
|
|
@@ -4546,7 +4688,7 @@ var ConnectServer = class _ConnectServer {
|
|
|
4546
4688
|
this.apiUrl = apiUrl6;
|
|
4547
4689
|
this.token = token6;
|
|
4548
4690
|
this.server = new Server(
|
|
4549
|
-
{ name: "mcph", version: true ? "0.
|
|
4691
|
+
{ name: "mcph", version: true ? "0.40.0" : "dev" },
|
|
4550
4692
|
{
|
|
4551
4693
|
capabilities: {
|
|
4552
4694
|
tools: { listChanged: true },
|
|
@@ -6709,6 +6851,7 @@ var KNOWN_SUBCOMMANDS = [
|
|
|
6709
6851
|
"doctor",
|
|
6710
6852
|
"reset-learning",
|
|
6711
6853
|
"servers",
|
|
6854
|
+
"bundles",
|
|
6712
6855
|
"help",
|
|
6713
6856
|
"--help",
|
|
6714
6857
|
"-h",
|
|
@@ -6738,6 +6881,14 @@ if (subcommand === "compliance") {
|
|
|
6738
6881
|
process.exit(2);
|
|
6739
6882
|
}
|
|
6740
6883
|
runServersCommand(parsed.options).then((r) => process.exit(r.exitCode));
|
|
6884
|
+
} else if (subcommand === "bundles") {
|
|
6885
|
+
const parsed = parseBundlesArgs(process.argv.slice(3));
|
|
6886
|
+
if (!parsed.ok) {
|
|
6887
|
+
process.stderr.write(`${parsed.error}
|
|
6888
|
+
`);
|
|
6889
|
+
process.exit(2);
|
|
6890
|
+
}
|
|
6891
|
+
runBundlesCommand(parsed.options).then((r) => process.exit(r.exitCode));
|
|
6741
6892
|
} else if (subcommand === "--help" || subcommand === "-h" || subcommand === "help") {
|
|
6742
6893
|
const installBlock = ` ${INSTALL_USAGE.replace(/^Usage: /, "").replace(/\n/g, "\n ")}`;
|
|
6743
6894
|
process.stdout.write(
|
|
@@ -6749,6 +6900,7 @@ if (subcommand === "compliance") {
|
|
|
6749
6900
|
mcph install <client> [flags] Auto-edit an MCP client's config to launch mcph
|
|
6750
6901
|
mcph doctor Print loaded config + detected clients (support diagnostic)
|
|
6751
6902
|
mcph servers [--json] List servers configured in your mcp.hosting dashboard
|
|
6903
|
+
mcph bundles [list|match] Browse curated multi-server bundles
|
|
6752
6904
|
mcph compliance <target> [flags] Run the compliance suite against an MCP server
|
|
6753
6905
|
mcph reset-learning Clear cross-session learning history (~/.mcph/state.json)
|
|
6754
6906
|
mcph --version Print version
|
|
@@ -6771,7 +6923,7 @@ ${installBlock}
|
|
|
6771
6923
|
);
|
|
6772
6924
|
process.exit(0);
|
|
6773
6925
|
} else if (subcommand === "--version" || subcommand === "-V") {
|
|
6774
|
-
process.stdout.write(`mcph ${true ? "0.
|
|
6926
|
+
process.stdout.write(`mcph ${true ? "0.40.0" : "dev"}
|
|
6775
6927
|
`);
|
|
6776
6928
|
process.exit(0);
|
|
6777
6929
|
} else if (subcommand && !subcommand.startsWith("-")) {
|
package/package.json
CHANGED