@yawlabs/mcph 0.37.0 → 0.39.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 +8 -0
- package/dist/index.js +187 -8
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,14 @@
|
|
|
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.39.0 — 2026-04-18
|
|
6
|
+
|
|
7
|
+
- **`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.
|
|
8
|
+
|
|
9
|
+
## 0.38.0 — 2026-04-18
|
|
10
|
+
|
|
11
|
+
- **`mcph reset-learning` CLI subcommand** — Deletes `~/.mcph/state.json` so cross-session learning starts fresh; prints the entry counts that were cleared. Pairs with v0.37.0's doctor RELIABILITY section: once a namespace has been flagged flaky, the dispatch penalty branch (v0.36.0) keeps suppressing it until enough new successes pile up — but if the user has since fixed the underlying cause (rotated a token, swapped the upstream, re-authed), that history is stale and the penalty has overstayed its welcome. This gives them a direct CLI lever to clear it. Scope is all-or-nothing by design; a per-namespace flag is footgunny (user clears one, forgets the others, keeps getting silently mis-ranked). No-op with an explanatory message when `MCPH_DISABLE_PERSISTENCE` is set or the file doesn't exist, so `mcph reset-learning` never surprises. Exit 0 on success or no-op, exit 1 on I/O error (permissions, disk).
|
|
12
|
+
|
|
5
13
|
## 0.37.0 — 2026-04-18
|
|
6
14
|
|
|
7
15
|
- **`mcph doctor` RELIABILITY section** — New block surfaces flaky dormant namespaces pulled directly from `~/.mcph/state.json`, using the same ≥3-dispatches / <80%-success definition as `mcp_connect_health`'s cross-session reliability block — so the CLI diagnostic and the LLM-facing health tool agree on what "flaky" means. Sorted worst-rate first, capped at 5. Silently omitted when no namespace qualifies, state.json doesn't exist yet, or `MCPH_DISABLE_PERSISTENCE` is set. Threshold constants + sort logic extracted into `selectFlakyNamespaces` so handleHealth and doctor can't drift apart.
|
package/dist/index.js
CHANGED
|
@@ -1004,7 +1004,7 @@ function selectFlakyNamespaces(entries, limit) {
|
|
|
1004
1004
|
}
|
|
1005
1005
|
|
|
1006
1006
|
// src/doctor-cmd.ts
|
|
1007
|
-
var VERSION = true ? "0.
|
|
1007
|
+
var VERSION = true ? "0.39.0" : "dev";
|
|
1008
1008
|
async function runDoctor(opts = {}) {
|
|
1009
1009
|
const lines = [];
|
|
1010
1010
|
const write = opts.out ?? ((s) => process.stdout.write(s));
|
|
@@ -1791,9 +1791,61 @@ ${USAGE}` };
|
|
|
1791
1791
|
}
|
|
1792
1792
|
var INSTALL_USAGE = USAGE;
|
|
1793
1793
|
|
|
1794
|
+
// src/reset-learning-cmd.ts
|
|
1795
|
+
import { unlink } from "fs/promises";
|
|
1796
|
+
import { homedir as homedir6 } from "os";
|
|
1797
|
+
import { join as join6 } from "path";
|
|
1798
|
+
async function runResetLearning(opts = {}) {
|
|
1799
|
+
const home = opts.home ?? homedir6();
|
|
1800
|
+
const env = opts.env ?? process.env;
|
|
1801
|
+
const write = opts.out ?? ((s) => process.stdout.write(s));
|
|
1802
|
+
const writeErr = opts.err ?? ((s) => process.stderr.write(s));
|
|
1803
|
+
const lines = [];
|
|
1804
|
+
const print = (s = "") => {
|
|
1805
|
+
lines.push(s);
|
|
1806
|
+
write(`${s}
|
|
1807
|
+
`);
|
|
1808
|
+
};
|
|
1809
|
+
const printErr = (s) => {
|
|
1810
|
+
lines.push(s);
|
|
1811
|
+
writeErr(`${s}
|
|
1812
|
+
`);
|
|
1813
|
+
};
|
|
1814
|
+
const filePath = join6(userConfigDir(home), STATE_FILENAME);
|
|
1815
|
+
const raw = env.MCPH_DISABLE_PERSISTENCE;
|
|
1816
|
+
const disabled = raw !== void 0 && raw !== "" && (raw === "1" || raw.toLowerCase() === "true");
|
|
1817
|
+
if (disabled) {
|
|
1818
|
+
print("mcph reset-learning: persistence is disabled (MCPH_DISABLE_PERSISTENCE) \u2014 nothing to clear.");
|
|
1819
|
+
return { exitCode: 0, lines, removed: false, path: filePath };
|
|
1820
|
+
}
|
|
1821
|
+
const persisted = await loadState(filePath);
|
|
1822
|
+
const learningCount = Object.keys(persisted.learning).length;
|
|
1823
|
+
const packCount = persisted.packHistory.length;
|
|
1824
|
+
try {
|
|
1825
|
+
await unlink(filePath);
|
|
1826
|
+
} catch (err) {
|
|
1827
|
+
if (isFileNotFound2(err)) {
|
|
1828
|
+
print("mcph reset-learning: no persisted state to reset.");
|
|
1829
|
+
print(` path: ${filePath}`);
|
|
1830
|
+
return { exitCode: 0, lines, removed: false, path: filePath };
|
|
1831
|
+
}
|
|
1832
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
1833
|
+
printErr(`mcph reset-learning: failed to remove ${filePath}: ${msg}`);
|
|
1834
|
+
return { exitCode: 1, lines, removed: false, path: filePath };
|
|
1835
|
+
}
|
|
1836
|
+
print("mcph reset-learning: cleared persisted state.");
|
|
1837
|
+
print(` path: ${filePath}`);
|
|
1838
|
+
print(` learning entries removed: ${learningCount}`);
|
|
1839
|
+
print(` pack history entries removed: ${packCount}`);
|
|
1840
|
+
return { exitCode: 0, lines, removed: true, path: filePath };
|
|
1841
|
+
}
|
|
1842
|
+
function isFileNotFound2(err) {
|
|
1843
|
+
return !!err && typeof err === "object" && "code" in err && err.code === "ENOENT";
|
|
1844
|
+
}
|
|
1845
|
+
|
|
1794
1846
|
// src/server.ts
|
|
1795
1847
|
import { readFile as readFile6 } from "fs/promises";
|
|
1796
|
-
import { homedir as
|
|
1848
|
+
import { homedir as homedir7 } from "os";
|
|
1797
1849
|
import { isAbsolute, relative, resolve as resolve3 } from "path";
|
|
1798
1850
|
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
1799
1851
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
@@ -4013,7 +4065,7 @@ function categorizeSpawnError(err) {
|
|
|
4013
4065
|
}
|
|
4014
4066
|
async function connectToUpstream(config, onDisconnect, onListChanged) {
|
|
4015
4067
|
const client = new Client(
|
|
4016
|
-
{ name: "mcph", version: true ? "0.
|
|
4068
|
+
{ name: "mcph", version: true ? "0.39.0" : "dev" },
|
|
4017
4069
|
{ capabilities: {} }
|
|
4018
4070
|
);
|
|
4019
4071
|
let transport;
|
|
@@ -4494,7 +4546,7 @@ var ConnectServer = class _ConnectServer {
|
|
|
4494
4546
|
this.apiUrl = apiUrl6;
|
|
4495
4547
|
this.token = token6;
|
|
4496
4548
|
this.server = new Server(
|
|
4497
|
-
{ name: "mcph", version: true ? "0.
|
|
4549
|
+
{ name: "mcph", version: true ? "0.39.0" : "dev" },
|
|
4498
4550
|
{
|
|
4499
4551
|
capabilities: {
|
|
4500
4552
|
tools: { listChanged: true },
|
|
@@ -5929,7 +5981,7 @@ ${activeCount} loaded in this session, ${totalTools} tools in context${tokenSumm
|
|
|
5929
5981
|
}
|
|
5930
5982
|
const ALLOWED_FILENAMES = ["claude_desktop_config.json", "mcp.json", "settings.json", "mcp_config.json"];
|
|
5931
5983
|
try {
|
|
5932
|
-
const resolved = filepath.startsWith("~/") || filepath.startsWith("~\\") ? resolve3(
|
|
5984
|
+
const resolved = filepath.startsWith("~/") || filepath.startsWith("~\\") ? resolve3(homedir7(), filepath.slice(2)) : resolve3(filepath);
|
|
5933
5985
|
const resolvedBasename = resolved.split(/[/\\]/).pop() || "";
|
|
5934
5986
|
if (!ALLOWED_FILENAMES.includes(resolvedBasename)) {
|
|
5935
5987
|
return {
|
|
@@ -5946,7 +5998,7 @@ ${activeCount} loaded in this session, ${totalTools} tools in context${tokenSumm
|
|
|
5946
5998
|
const rel = relative(base, p);
|
|
5947
5999
|
return rel === "" || !rel.startsWith("..") && !isAbsolute(rel);
|
|
5948
6000
|
};
|
|
5949
|
-
if (!isUnder(
|
|
6001
|
+
if (!isUnder(homedir7(), resolved) && !isUnder(process.cwd(), resolved)) {
|
|
5950
6002
|
return {
|
|
5951
6003
|
content: [
|
|
5952
6004
|
{ type: "text", text: "Import path must be under your home directory or the current working directory." }
|
|
@@ -6546,8 +6598,123 @@ To load the top pack in one step, call \`mcp_connect_activate\` with namespaces=
|
|
|
6546
6598
|
}
|
|
6547
6599
|
};
|
|
6548
6600
|
|
|
6601
|
+
// src/servers-cmd.ts
|
|
6602
|
+
function parseServersArgs(argv) {
|
|
6603
|
+
let json = false;
|
|
6604
|
+
for (const a of argv) {
|
|
6605
|
+
if (a === "--json") {
|
|
6606
|
+
json = true;
|
|
6607
|
+
} else if (a === "--help" || a === "-h") {
|
|
6608
|
+
return { ok: false, error: SERVERS_USAGE };
|
|
6609
|
+
} else {
|
|
6610
|
+
return { ok: false, error: `mcph servers: unknown argument "${a}"
|
|
6611
|
+
|
|
6612
|
+
${SERVERS_USAGE}` };
|
|
6613
|
+
}
|
|
6614
|
+
}
|
|
6615
|
+
return { ok: true, options: { json } };
|
|
6616
|
+
}
|
|
6617
|
+
var SERVERS_USAGE = `Usage: mcph servers [--json]
|
|
6618
|
+
|
|
6619
|
+
List the servers configured in your mcp.hosting dashboard.
|
|
6620
|
+
|
|
6621
|
+
--json Emit machine-readable JSON instead of a table.`;
|
|
6622
|
+
async function runServersCommand(opts = {}) {
|
|
6623
|
+
const write = opts.out ?? ((s) => process.stdout.write(s));
|
|
6624
|
+
const writeErr = opts.err ?? ((s) => process.stderr.write(s));
|
|
6625
|
+
const lines = [];
|
|
6626
|
+
const print = (s = "") => {
|
|
6627
|
+
lines.push(s);
|
|
6628
|
+
write(`${s}
|
|
6629
|
+
`);
|
|
6630
|
+
};
|
|
6631
|
+
const printErr = (s) => {
|
|
6632
|
+
lines.push(s);
|
|
6633
|
+
writeErr(`${s}
|
|
6634
|
+
`);
|
|
6635
|
+
};
|
|
6636
|
+
const config = await loadMcphConfig({
|
|
6637
|
+
cwd: opts.cwd,
|
|
6638
|
+
home: opts.home,
|
|
6639
|
+
env: opts.env
|
|
6640
|
+
});
|
|
6641
|
+
if (!config.token) {
|
|
6642
|
+
printErr("mcph servers: no token resolved. Run `mcph install <client> --token mcp_pat_\u2026` or set MCPH_TOKEN.");
|
|
6643
|
+
return { exitCode: 1, lines };
|
|
6644
|
+
}
|
|
6645
|
+
const fetcher = opts.fetcher ?? fetchConfig;
|
|
6646
|
+
let backend;
|
|
6647
|
+
try {
|
|
6648
|
+
backend = await fetcher(config.apiBase, config.token);
|
|
6649
|
+
} catch (err) {
|
|
6650
|
+
const msg = err instanceof ConfigError || err instanceof Error ? err.message : String(err);
|
|
6651
|
+
printErr(`mcph servers: ${msg}`);
|
|
6652
|
+
return { exitCode: 2, lines };
|
|
6653
|
+
}
|
|
6654
|
+
if (!backend) {
|
|
6655
|
+
printErr("mcph servers: backend returned no data (unexpected 304).");
|
|
6656
|
+
return { exitCode: 2, lines };
|
|
6657
|
+
}
|
|
6658
|
+
if (opts.json) {
|
|
6659
|
+
print(JSON.stringify(backend, null, 2));
|
|
6660
|
+
return { exitCode: 0, lines };
|
|
6661
|
+
}
|
|
6662
|
+
renderTable(backend, print);
|
|
6663
|
+
return { exitCode: 0, lines };
|
|
6664
|
+
}
|
|
6665
|
+
function renderTable(cfg, print) {
|
|
6666
|
+
const servers = cfg.servers;
|
|
6667
|
+
if (servers.length === 0) {
|
|
6668
|
+
print("No servers configured yet. Visit https://mcp.hosting to add one.");
|
|
6669
|
+
return;
|
|
6670
|
+
}
|
|
6671
|
+
const version = cfg.configVersion ? ` (config ${truncateVersion(cfg.configVersion)})` : "";
|
|
6672
|
+
const active = servers.filter((s) => s.isActive).length;
|
|
6673
|
+
const disabled = servers.length - active;
|
|
6674
|
+
const summary = disabled === 0 ? `${servers.length} server${servers.length === 1 ? "" : "s"}` : `${servers.length} servers (${active} enabled, ${disabled} disabled)`;
|
|
6675
|
+
print(`${summary}${version}`);
|
|
6676
|
+
print("");
|
|
6677
|
+
const rows = servers.map((s) => ({
|
|
6678
|
+
namespace: s.namespace,
|
|
6679
|
+
name: s.name,
|
|
6680
|
+
type: s.type,
|
|
6681
|
+
status: s.isActive ? "enabled" : "disabled",
|
|
6682
|
+
grade: s.complianceGrade ?? "-",
|
|
6683
|
+
tools: s.toolCache ? String(s.toolCache.length) : "?"
|
|
6684
|
+
}));
|
|
6685
|
+
const widths = {
|
|
6686
|
+
namespace: Math.max("NAMESPACE".length, ...rows.map((r) => r.namespace.length)),
|
|
6687
|
+
name: Math.max("NAME".length, ...rows.map((r) => r.name.length)),
|
|
6688
|
+
type: Math.max("TYPE".length, ...rows.map((r) => r.type.length)),
|
|
6689
|
+
status: Math.max("STATUS".length, ...rows.map((r) => r.status.length)),
|
|
6690
|
+
grade: Math.max("GRADE".length, ...rows.map((r) => r.grade.length)),
|
|
6691
|
+
tools: Math.max("TOOLS".length, ...rows.map((r) => r.tools.length))
|
|
6692
|
+
};
|
|
6693
|
+
const header = ` ${"NAMESPACE".padEnd(widths.namespace)} ${"NAME".padEnd(widths.name)} ${"TYPE".padEnd(widths.type)} ${"STATUS".padEnd(widths.status)} ${"GRADE".padEnd(widths.grade)} ${"TOOLS".padStart(widths.tools)}`;
|
|
6694
|
+
print(header);
|
|
6695
|
+
const sorted = [...rows].sort((a, b) => a.namespace.localeCompare(b.namespace));
|
|
6696
|
+
for (const r of sorted) {
|
|
6697
|
+
const line = ` ${r.namespace.padEnd(widths.namespace)} ${r.name.padEnd(widths.name)} ${r.type.padEnd(widths.type)} ${r.status.padEnd(widths.status)} ${r.grade.padEnd(widths.grade)} ${r.tools.padStart(widths.tools)}`;
|
|
6698
|
+
print(line);
|
|
6699
|
+
}
|
|
6700
|
+
}
|
|
6701
|
+
function truncateVersion(v) {
|
|
6702
|
+
return v.length > 8 ? v.slice(0, 8) : v;
|
|
6703
|
+
}
|
|
6704
|
+
|
|
6549
6705
|
// src/index.ts
|
|
6550
|
-
var KNOWN_SUBCOMMANDS = [
|
|
6706
|
+
var KNOWN_SUBCOMMANDS = [
|
|
6707
|
+
"compliance",
|
|
6708
|
+
"install",
|
|
6709
|
+
"doctor",
|
|
6710
|
+
"reset-learning",
|
|
6711
|
+
"servers",
|
|
6712
|
+
"help",
|
|
6713
|
+
"--help",
|
|
6714
|
+
"-h",
|
|
6715
|
+
"--version",
|
|
6716
|
+
"-V"
|
|
6717
|
+
];
|
|
6551
6718
|
var subcommand = process.argv[2];
|
|
6552
6719
|
if (subcommand === "compliance") {
|
|
6553
6720
|
runComplianceCommand(process.argv.slice(3)).then((code) => process.exit(code));
|
|
@@ -6561,6 +6728,16 @@ if (subcommand === "compliance") {
|
|
|
6561
6728
|
runInstall(parsed.options).then((r) => process.exit(r.exitCode));
|
|
6562
6729
|
} else if (subcommand === "doctor") {
|
|
6563
6730
|
runDoctor().then((r) => process.exit(r.exitCode));
|
|
6731
|
+
} else if (subcommand === "reset-learning") {
|
|
6732
|
+
runResetLearning().then((r) => process.exit(r.exitCode));
|
|
6733
|
+
} else if (subcommand === "servers") {
|
|
6734
|
+
const parsed = parseServersArgs(process.argv.slice(3));
|
|
6735
|
+
if (!parsed.ok) {
|
|
6736
|
+
process.stderr.write(`${parsed.error}
|
|
6737
|
+
`);
|
|
6738
|
+
process.exit(2);
|
|
6739
|
+
}
|
|
6740
|
+
runServersCommand(parsed.options).then((r) => process.exit(r.exitCode));
|
|
6564
6741
|
} else if (subcommand === "--help" || subcommand === "-h" || subcommand === "help") {
|
|
6565
6742
|
const installBlock = ` ${INSTALL_USAGE.replace(/^Usage: /, "").replace(/\n/g, "\n ")}`;
|
|
6566
6743
|
process.stdout.write(
|
|
@@ -6571,7 +6748,9 @@ if (subcommand === "compliance") {
|
|
|
6571
6748
|
mcph Run as MCP server (requires a token)
|
|
6572
6749
|
mcph install <client> [flags] Auto-edit an MCP client's config to launch mcph
|
|
6573
6750
|
mcph doctor Print loaded config + detected clients (support diagnostic)
|
|
6751
|
+
mcph servers [--json] List servers configured in your mcp.hosting dashboard
|
|
6574
6752
|
mcph compliance <target> [flags] Run the compliance suite against an MCP server
|
|
6753
|
+
mcph reset-learning Clear cross-session learning history (~/.mcph/state.json)
|
|
6575
6754
|
mcph --version Print version
|
|
6576
6755
|
|
|
6577
6756
|
Install:
|
|
@@ -6592,7 +6771,7 @@ ${installBlock}
|
|
|
6592
6771
|
);
|
|
6593
6772
|
process.exit(0);
|
|
6594
6773
|
} else if (subcommand === "--version" || subcommand === "-V") {
|
|
6595
|
-
process.stdout.write(`mcph ${true ? "0.
|
|
6774
|
+
process.stdout.write(`mcph ${true ? "0.39.0" : "dev"}
|
|
6596
6775
|
`);
|
|
6597
6776
|
process.exit(0);
|
|
6598
6777
|
} else if (subcommand && !subcommand.startsWith("-")) {
|
package/package.json
CHANGED