@yawlabs/mcph 0.41.0 → 0.42.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 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.42.0 — 2026-04-18
6
+
7
+ - **`mcph completion <shell>` — shell completion scripts** — Prints a completion script for `bash`, `zsh`, `fish`, or `powershell` to stdout so users can one-line it into their completions directory. Each script covers every known subcommand (install, doctor, servers, bundles, compliance, reset-learning, completion) with positional choices (install clients, bundles actions, completion shells) and per-subcommand flags (`--json`, `--scope`, `--token`, `--force`, etc.). Every template derives from a single `SUBCOMMAND_SPEC` table so adding a new subcommand elsewhere updates all four shells at once — no drift between what the CLI accepts and what it completes. Install hints are inlined as comments at the top of each generated script: the bash file drops into `~/.local/share/bash-completion/completions/mcph`, zsh into any `$fpath` dir as `_mcph`, fish into `~/.config/fish/completions/mcph.fish`, pwsh appended to `$PROFILE`.
8
+
5
9
  ## 0.41.0 — 2026-04-18
6
10
 
7
11
  - **`mcph doctor --json` — machine-readable diagnostic output** — Doctor already tracks a lot of state (config files, token source, env overrides, persisted learning, installed clients, shell-history shadow hits, upgrade availability, diagnosis summary) and the text output optimises for pasting into a support ticket. `--json` emits the same data as a single structured blob so dashboards, CI scripts, and support tooling can pick fields with `jq` instead of parsing the text layout. Token is fingerprinted the same way in both modes (never raw). Section data is 1:1 with the text renderer: config (token/apiBase/loadedFiles/warnings), env overrides (null when unset), state (path/savedAt/entries; `disabled: true` when `MCPH_DISABLE_PERSISTENCE` is set), reliability (same `selectFlakyNamespaces` rollup that `mcp_connect_health` and the text RELIABILITY section use), clients probe results, shell shadow hits, upgrade info, and the exit-code diagnosis. Completes the `--json` pattern across `servers`, `bundles`, and now `doctor` — every CLI that reads state has a pipeline mode.
package/dist/index.js CHANGED
@@ -648,6 +648,222 @@ function renderMatch(match, installed, print) {
648
648
  }
649
649
  }
650
650
 
651
+ // src/completion-cmd.ts
652
+ var COMPLETION_USAGE = `Usage: mcph completion <bash|zsh|fish|powershell>
653
+
654
+ Print a shell completion script to stdout. Redirect it to the right
655
+ location for your shell:
656
+
657
+ bash mcph completion bash > ~/.local/share/bash-completion/completions/mcph
658
+ zsh mcph completion zsh > "\${fpath[1]}/_mcph" (must be on $fpath)
659
+ fish mcph completion fish > ~/.config/fish/completions/mcph.fish
660
+ powershell mcph completion powershell >> $PROFILE`;
661
+ var INSTALL_CLIENTS = ["claude-code", "claude-desktop", "cursor", "vscode"];
662
+ var SUBCOMMAND_SPEC = [
663
+ {
664
+ name: "install",
665
+ positional: [...INSTALL_CLIENTS],
666
+ flags: ["--scope", "--token", "--project-dir", "--os", "--force", "--skip", "--dry-run", "--no-mcph-config"]
667
+ },
668
+ { name: "doctor", flags: ["--json", "--help"] },
669
+ { name: "servers", flags: ["--json", "--help"] },
670
+ { name: "bundles", positional: ["list", "match"], flags: ["--json", "--help"] },
671
+ { name: "compliance", flags: ["--publish", "--help"] },
672
+ { name: "reset-learning", flags: ["--help"] },
673
+ { name: "completion", positional: ["bash", "zsh", "fish", "powershell"], flags: ["--help"] },
674
+ { name: "help", flags: [] }
675
+ ];
676
+ function parseCompletionArgs(argv) {
677
+ if (argv.includes("--help") || argv.includes("-h")) {
678
+ return { ok: false, error: COMPLETION_USAGE };
679
+ }
680
+ const positional = argv.filter((a) => !a.startsWith("-"));
681
+ if (positional.length === 0) {
682
+ return { ok: false, error: `mcph completion: missing shell argument
683
+
684
+ ${COMPLETION_USAGE}` };
685
+ }
686
+ if (positional.length > 1) {
687
+ return { ok: false, error: `mcph completion: too many arguments
688
+
689
+ ${COMPLETION_USAGE}` };
690
+ }
691
+ const shell = positional[0];
692
+ if (shell !== "bash" && shell !== "zsh" && shell !== "fish" && shell !== "powershell") {
693
+ return { ok: false, error: `mcph completion: unknown shell "${shell}"
694
+
695
+ ${COMPLETION_USAGE}` };
696
+ }
697
+ return { ok: true, options: { shell } };
698
+ }
699
+ async function runCompletion(opts = {}) {
700
+ const write = opts.out ?? ((s) => process.stdout.write(s));
701
+ const writeErr = opts.err ?? ((s) => process.stderr.write(s));
702
+ const lines = [];
703
+ const print = (s) => {
704
+ lines.push(s);
705
+ write(`${s}
706
+ `);
707
+ };
708
+ if (!opts.shell) {
709
+ writeErr(`mcph completion: missing shell argument
710
+ ${COMPLETION_USAGE}
711
+ `);
712
+ return { exitCode: 2, lines };
713
+ }
714
+ const script = renderScript(opts.shell);
715
+ print(script);
716
+ return { exitCode: 0, lines };
717
+ }
718
+ function renderScript(shell) {
719
+ switch (shell) {
720
+ case "bash":
721
+ return renderBash();
722
+ case "zsh":
723
+ return renderZsh();
724
+ case "fish":
725
+ return renderFish();
726
+ case "powershell":
727
+ return renderPowershell();
728
+ }
729
+ }
730
+ function renderBash() {
731
+ const subcommandList = SUBCOMMAND_SPEC.map((s) => s.name).join(" ");
732
+ const topLevelFlags = "--help -h --version -V";
733
+ const cases = SUBCOMMAND_SPEC.map((spec) => {
734
+ const posClause = spec.positional ? ` if [[ $cword -eq 2 ]]; then
735
+ COMPREPLY=( $(compgen -W "${spec.positional.join(" ")} ${spec.flags.join(" ")}" -- "$cur") )
736
+ return 0
737
+ fi` : "";
738
+ return ` ${spec.name})
739
+ ${posClause}
740
+ COMPREPLY=( $(compgen -W "${spec.flags.join(" ")}" -- "$cur") )
741
+ return 0
742
+ ;;`;
743
+ }).join("\n");
744
+ return `# bash completion for mcph \u2014 generated by \`mcph completion bash\`
745
+ # Install: save this to ~/.local/share/bash-completion/completions/mcph
746
+ # or source it from your .bashrc.
747
+ _mcph() {
748
+ local cur prev words cword
749
+ cur="\${COMP_WORDS[COMP_CWORD]}"
750
+ cword=$COMP_CWORD
751
+
752
+ if [[ $cword -eq 1 ]]; then
753
+ COMPREPLY=( $(compgen -W "${subcommandList} ${topLevelFlags}" -- "$cur") )
754
+ return 0
755
+ fi
756
+
757
+ case "\${COMP_WORDS[1]}" in
758
+ ${cases}
759
+ esac
760
+ }
761
+ complete -F _mcph mcph
762
+ `;
763
+ }
764
+ function renderZsh() {
765
+ const subcommandDescriptions = {
766
+ install: "Auto-edit an MCP client's config",
767
+ doctor: "Print diagnostic of mcph setup",
768
+ servers: "List servers in your mcp.hosting dashboard",
769
+ bundles: "Browse curated multi-server bundles",
770
+ compliance: "Run the compliance suite against a server",
771
+ "reset-learning": "Clear cross-session learning history",
772
+ completion: "Print a shell completion script",
773
+ help: "Show usage"
774
+ };
775
+ const subcommandList = SUBCOMMAND_SPEC.map((s) => ` '${s.name}:${subcommandDescriptions[s.name] ?? ""}'`).join(
776
+ "\n"
777
+ );
778
+ const argsCases = SUBCOMMAND_SPEC.map((spec) => {
779
+ const lines = [` ${spec.name})`];
780
+ if (spec.positional) {
781
+ lines.push(` _arguments '1: :(${spec.positional.join(" ")})' '*: :(${spec.flags.join(" ")})'`);
782
+ } else {
783
+ lines.push(` _arguments '*: :(${spec.flags.join(" ")})'`);
784
+ }
785
+ lines.push(" ;;");
786
+ return lines.join("\n");
787
+ }).join("\n");
788
+ return `#compdef mcph
789
+ # zsh completion for mcph \u2014 generated by \`mcph completion zsh\`
790
+ # Install: save this to a file on your $fpath named _mcph
791
+ # (e.g., ~/.zsh/completions/_mcph), then rebuild completions:
792
+ # autoload -U compinit && compinit
793
+ _mcph() {
794
+ local context state line
795
+ _arguments -C \\
796
+ '1: :->cmd' \\
797
+ '*::arg:->args'
798
+
799
+ case $state in
800
+ cmd)
801
+ _values 'mcph subcommand' \\
802
+ ${subcommandList}
803
+ ;;
804
+ args)
805
+ case $line[1] in
806
+ ${argsCases}
807
+ esac
808
+ ;;
809
+ esac
810
+ }
811
+ _mcph "$@"
812
+ `;
813
+ }
814
+ function renderFish() {
815
+ const header = `# fish completion for mcph \u2014 generated by \`mcph completion fish\`
816
+ # Install: save this to ~/.config/fish/completions/mcph.fish
817
+ complete -c mcph -f`;
818
+ const subcommandLines = SUBCOMMAND_SPEC.map((spec) => {
819
+ return `complete -c mcph -n __fish_use_subcommand -a ${spec.name}`;
820
+ });
821
+ const positionalLines = [];
822
+ const flagLines = [];
823
+ for (const spec of SUBCOMMAND_SPEC) {
824
+ if (spec.positional) {
825
+ for (const p of spec.positional) {
826
+ positionalLines.push(`complete -c mcph -n "__fish_seen_subcommand_from ${spec.name}" -a ${p}`);
827
+ }
828
+ }
829
+ for (const f of spec.flags) {
830
+ const long = f.replace(/^--/, "");
831
+ flagLines.push(`complete -c mcph -n "__fish_seen_subcommand_from ${spec.name}" -l ${long}`);
832
+ }
833
+ }
834
+ return [header, "", ...subcommandLines, "", ...positionalLines, "", ...flagLines, ""].join("\n");
835
+ }
836
+ function renderPowershell() {
837
+ const subcommandNames = SUBCOMMAND_SPEC.map((s) => `'${s.name}'`).join(", ");
838
+ const caseBranches = SUBCOMMAND_SPEC.map((spec) => {
839
+ const positional = spec.positional ? spec.positional.map((p) => `'${p}'`).join(", ") : "";
840
+ const flags = spec.flags.map((f) => `'${f}'`).join(", ");
841
+ const positionalLine = positional ? ` $completions += @(${positional})
842
+ ` : "";
843
+ return ` '${spec.name}' {
844
+ ${positionalLine} $completions += @(${flags})
845
+ }`;
846
+ }).join("\n");
847
+ return `# PowerShell completion for mcph \u2014 generated by \`mcph completion powershell\`
848
+ # Install: append this script to your profile ($PROFILE) and reload.
849
+ Register-ArgumentCompleter -CommandName mcph -ScriptBlock {
850
+ param($wordToComplete, $commandAst, $cursorPosition)
851
+ $tokens = $commandAst.CommandElements | ForEach-Object { $_.ToString() }
852
+ $completions = @()
853
+ if ($tokens.Count -le 2) {
854
+ $completions = @(${subcommandNames}, '--help', '-h', '--version', '-V')
855
+ } else {
856
+ switch ($tokens[1]) {
857
+ ${caseBranches}
858
+ }
859
+ }
860
+ $completions | Where-Object { $_ -like "$wordToComplete*" } | ForEach-Object {
861
+ [System.Management.Automation.CompletionResult]::new($_, $_, 'ParameterValue', $_)
862
+ }
863
+ }
864
+ `;
865
+ }
866
+
651
867
  // src/compliance-cmd.ts
652
868
  import { spawn } from "child_process";
653
869
  import { request as request2 } from "undici";
@@ -1219,7 +1435,7 @@ function selectFlakyNamespaces(entries, limit) {
1219
1435
  }
1220
1436
 
1221
1437
  // src/doctor-cmd.ts
1222
- var VERSION = true ? "0.41.0" : "dev";
1438
+ var VERSION = true ? "0.42.0" : "dev";
1223
1439
  async function runDoctor(opts = {}) {
1224
1440
  if (opts.json) return runDoctorJson(opts);
1225
1441
  const lines = [];
@@ -4303,7 +4519,7 @@ function categorizeSpawnError(err) {
4303
4519
  }
4304
4520
  async function connectToUpstream(config, onDisconnect, onListChanged) {
4305
4521
  const client = new Client(
4306
- { name: "mcph", version: true ? "0.41.0" : "dev" },
4522
+ { name: "mcph", version: true ? "0.42.0" : "dev" },
4307
4523
  { capabilities: {} }
4308
4524
  );
4309
4525
  let transport;
@@ -4784,7 +5000,7 @@ var ConnectServer = class _ConnectServer {
4784
5000
  this.apiUrl = apiUrl6;
4785
5001
  this.token = token6;
4786
5002
  this.server = new Server(
4787
- { name: "mcph", version: true ? "0.41.0" : "dev" },
5003
+ { name: "mcph", version: true ? "0.42.0" : "dev" },
4788
5004
  {
4789
5005
  capabilities: {
4790
5006
  tools: { listChanged: true },
@@ -6948,6 +7164,7 @@ var KNOWN_SUBCOMMANDS = [
6948
7164
  "reset-learning",
6949
7165
  "servers",
6950
7166
  "bundles",
7167
+ "completion",
6951
7168
  "help",
6952
7169
  "--help",
6953
7170
  "-h",
@@ -6999,6 +7216,14 @@ if (subcommand === "compliance") {
6999
7216
  process.exit(2);
7000
7217
  }
7001
7218
  runBundlesCommand(parsed.options).then((r) => process.exit(r.exitCode));
7219
+ } else if (subcommand === "completion") {
7220
+ const parsed = parseCompletionArgs(process.argv.slice(3));
7221
+ if (!parsed.ok) {
7222
+ process.stderr.write(`${parsed.error}
7223
+ `);
7224
+ process.exit(2);
7225
+ }
7226
+ runCompletion(parsed.options).then((r) => process.exit(r.exitCode));
7002
7227
  } else if (subcommand === "--help" || subcommand === "-h" || subcommand === "help") {
7003
7228
  const installBlock = ` ${INSTALL_USAGE.replace(/^Usage: /, "").replace(/\n/g, "\n ")}`;
7004
7229
  process.stdout.write(
@@ -7013,6 +7238,7 @@ if (subcommand === "compliance") {
7013
7238
  mcph bundles [list|match] Browse curated multi-server bundles
7014
7239
  mcph compliance <target> [flags] Run the compliance suite against an MCP server
7015
7240
  mcph reset-learning Clear cross-session learning history (~/.mcph/state.json)
7241
+ mcph completion <shell> Print a shell completion script (bash|zsh|fish|powershell)
7016
7242
  mcph --version Print version
7017
7243
 
7018
7244
  Install:
@@ -7033,7 +7259,7 @@ ${installBlock}
7033
7259
  );
7034
7260
  process.exit(0);
7035
7261
  } else if (subcommand === "--version" || subcommand === "-V") {
7036
- process.stdout.write(`mcph ${true ? "0.41.0" : "dev"}
7262
+ process.stdout.write(`mcph ${true ? "0.42.0" : "dev"}
7037
7263
  `);
7038
7264
  process.exit(0);
7039
7265
  } else if (subcommand && !subcommand.startsWith("-")) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yawlabs/mcph",
3
- "version": "0.41.0",
3
+ "version": "0.42.0",
4
4
  "description": "mcp.hosting — one install, all your MCP servers, managed from the cloud",
5
5
  "license": "UNLICENSED",
6
6
  "author": "Yaw Labs <contact@yaw.sh> (https://yaw.sh)",