@yawlabs/mcph 0.47.7 → 0.48.6

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.
Files changed (2) hide show
  1. package/dist/index.js +340 -215
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -1006,6 +1006,8 @@ var flushTimer = null;
1006
1006
  var apiUrl = "";
1007
1007
  var token = "";
1008
1008
  var lastFailure = null;
1009
+ var lastLoggedConnectStatus = null;
1010
+ var lastLoggedDispatchStatus = null;
1009
1011
  function getLastAnalyticsFailure() {
1010
1012
  return lastFailure;
1011
1013
  }
@@ -1041,12 +1043,19 @@ async function flush() {
1041
1043
  bodyTimeout: 1e4
1042
1044
  });
1043
1045
  if (res.statusCode >= 400) {
1044
- const room = MAX_BUFFER - buffer.length;
1045
- if (room > 0) buffer.push(...events.slice(0, room));
1046
- log("warn", "Analytics flush failed", { status: res.statusCode });
1046
+ const retryable = res.statusCode >= 500 || res.statusCode === 408 || res.statusCode === 429;
1047
+ if (retryable) {
1048
+ const room = MAX_BUFFER - buffer.length;
1049
+ if (room > 0) buffer.push(...events.slice(0, room));
1050
+ }
1051
+ if (lastLoggedConnectStatus !== res.statusCode) {
1052
+ log("warn", "Analytics flush failed", { status: res.statusCode, retried: retryable });
1053
+ lastLoggedConnectStatus = res.statusCode;
1054
+ }
1047
1055
  lastFailure = { statusCode: res.statusCode, url, at: Date.now() };
1048
1056
  } else {
1049
1057
  lastFailure = null;
1058
+ lastLoggedConnectStatus = null;
1050
1059
  }
1051
1060
  await res.body.text().catch(() => {
1052
1061
  });
@@ -1072,12 +1081,19 @@ async function flushDispatch() {
1072
1081
  bodyTimeout: 1e4
1073
1082
  });
1074
1083
  if (res.statusCode >= 400 && res.statusCode !== 204) {
1075
- const room = MAX_BUFFER - dispatchBuffer.length;
1076
- if (room > 0) dispatchBuffer.push(...events.slice(0, room));
1077
- log("warn", "Dispatch-events flush failed", { status: res.statusCode });
1084
+ const retryable = res.statusCode >= 500 || res.statusCode === 408 || res.statusCode === 429;
1085
+ if (retryable) {
1086
+ const room = MAX_BUFFER - dispatchBuffer.length;
1087
+ if (room > 0) dispatchBuffer.push(...events.slice(0, room));
1088
+ }
1089
+ if (lastLoggedDispatchStatus !== res.statusCode) {
1090
+ log("warn", "Dispatch-events flush failed", { status: res.statusCode, retried: retryable });
1091
+ lastLoggedDispatchStatus = res.statusCode;
1092
+ }
1078
1093
  lastFailure = { statusCode: res.statusCode, url, at: Date.now() };
1079
1094
  } else if (res.statusCode < 400) {
1080
1095
  lastFailure = null;
1096
+ lastLoggedDispatchStatus = null;
1081
1097
  }
1082
1098
  await res.body.text().catch(() => {
1083
1099
  });
@@ -1090,6 +1106,8 @@ async function flushDispatch() {
1090
1106
  function initAnalytics(url, tok) {
1091
1107
  apiUrl = url;
1092
1108
  token = tok;
1109
+ lastLoggedConnectStatus = null;
1110
+ lastLoggedDispatchStatus = null;
1093
1111
  flushTimer = setInterval(() => {
1094
1112
  flush().catch(() => {
1095
1113
  });
@@ -1435,7 +1453,7 @@ function pathFor(client, scope, os, base) {
1435
1453
  throw new Error(`Unhandled client: ${client}`);
1436
1454
  }
1437
1455
  function buildLaunchEntry(opts) {
1438
- const pkg = opts.pkg ?? "@yawlabs/mcph";
1456
+ const pkg = opts.pkg ?? "@yawlabs/mcph@latest";
1439
1457
  const entry = opts.os === "windows" ? { command: "cmd", args: ["/c", "npx", "-y", pkg] } : { command: "npx", args: ["-y", pkg] };
1440
1458
  if (opts.token) entry.env = { MCPH_TOKEN: opts.token };
1441
1459
  return entry;
@@ -1650,7 +1668,7 @@ function selectFlakyNamespaces(entries, limit) {
1650
1668
  }
1651
1669
 
1652
1670
  // src/doctor-cmd.ts
1653
- var VERSION = true ? "0.47.7" : "dev";
1671
+ var VERSION = true ? "0.48.6" : "dev";
1654
1672
  async function runDoctor(opts = {}) {
1655
1673
  if (opts.json) return runDoctorJson(opts);
1656
1674
  const lines = [];
@@ -2905,6 +2923,259 @@ import {
2905
2923
  } from "@modelcontextprotocol/sdk/types.js";
2906
2924
  import { request as request9 } from "undici";
2907
2925
 
2926
+ // src/auto-upgrade.ts
2927
+ import { spawn as spawn3 } from "child_process";
2928
+
2929
+ // src/upgrade-cmd.ts
2930
+ import { spawn as spawn2 } from "child_process";
2931
+ var UPGRADE_USAGE = `Usage: mcph upgrade [--run] [--json]
2932
+
2933
+ Show (or execute) the command to upgrade @yawlabs/mcph to the latest version.
2934
+
2935
+ --run If this install is global (npm install -g), spawn the upgrade
2936
+ command. No-op for npx installs \u2014 they always fetch the latest.
2937
+ --json Emit a machine-readable snapshot ({ current, latest, stale,
2938
+ method, command }) instead of prose.`;
2939
+ function parseUpgradeArgs(argv) {
2940
+ const opts = {};
2941
+ for (const a of argv) {
2942
+ if (a === "--run") opts.run = true;
2943
+ else if (a === "--json") opts.json = true;
2944
+ else if (a === "--help" || a === "-h") return { ok: false, error: UPGRADE_USAGE };
2945
+ else return { ok: false, error: `mcph upgrade: unknown argument "${a}"
2946
+
2947
+ ${UPGRADE_USAGE}` };
2948
+ }
2949
+ return { ok: true, options: opts };
2950
+ }
2951
+ function detectInstallMethod(argvPath) {
2952
+ if (!argvPath) return "unknown";
2953
+ const normalized = argvPath.replace(/\\/g, "/");
2954
+ if (/\/_npx\//.test(normalized)) return "npx";
2955
+ if (/\/npm\/node_modules\/@yawlabs\/mcph\//.test(normalized)) return "global-npm";
2956
+ if (/\/lib\/node_modules\/@yawlabs\/mcph\//.test(normalized)) return "global-npm";
2957
+ if (/\/AppData\/Roaming\/npm\/node_modules\/@yawlabs\/mcph\//.test(normalized)) return "global-npm";
2958
+ if (/\/node_modules\/@yawlabs\/mcph\//.test(normalized)) return "local-node-modules";
2959
+ if (/\/mcph\/(dist|src)\//.test(normalized)) return "dev-checkout";
2960
+ return "unknown";
2961
+ }
2962
+ function buildUpgradePlan(input) {
2963
+ const { current, latest, method } = input;
2964
+ const stale = latest !== null && current !== "dev" && compareSemverLocal(current, latest) < 0;
2965
+ let command;
2966
+ switch (method) {
2967
+ case "global-npm":
2968
+ command = "npm install -g @yawlabs/mcph@latest";
2969
+ break;
2970
+ case "npx":
2971
+ command = null;
2972
+ break;
2973
+ case "local-node-modules":
2974
+ command = "npm install @yawlabs/mcph@latest";
2975
+ break;
2976
+ case "dev-checkout":
2977
+ command = "git pull && npm run build";
2978
+ break;
2979
+ default:
2980
+ command = "npm install -g @yawlabs/mcph@latest";
2981
+ break;
2982
+ }
2983
+ return { current, latest, stale, method, command };
2984
+ }
2985
+ function compareSemverLocal(a, b) {
2986
+ const parse = (s) => {
2987
+ const m = /^v?(\d+)\.(\d+)\.(\d+)/.exec(s);
2988
+ if (!m) return null;
2989
+ return [Number(m[1]), Number(m[2]), Number(m[3])];
2990
+ };
2991
+ const pa = parse(a);
2992
+ const pb = parse(b);
2993
+ if (!pa || !pb) return 0;
2994
+ for (let i = 0; i < 3; i++) {
2995
+ if (pa[i] < pb[i]) return -1;
2996
+ if (pa[i] > pb[i]) return 1;
2997
+ }
2998
+ return 0;
2999
+ }
3000
+ async function defaultFetchLatest() {
3001
+ const ac = new AbortController();
3002
+ const timer = setTimeout(() => ac.abort(), 3e3);
3003
+ try {
3004
+ const res = await fetch("https://registry.npmjs.org/@yawlabs/mcph/latest", {
3005
+ signal: ac.signal,
3006
+ headers: { accept: "application/json" }
3007
+ });
3008
+ if (!res.ok) return null;
3009
+ const body = await res.json();
3010
+ return typeof body.version === "string" ? body.version : null;
3011
+ } catch {
3012
+ return null;
3013
+ } finally {
3014
+ clearTimeout(timer);
3015
+ }
3016
+ }
3017
+ async function defaultSpawn(cmd, args) {
3018
+ return new Promise((resolve4) => {
3019
+ const child = spawn2(cmd, args, { stdio: "inherit", shell: process.platform === "win32" });
3020
+ child.on("close", (code) => resolve4(typeof code === "number" ? code : 1));
3021
+ child.on("error", () => resolve4(1));
3022
+ });
3023
+ }
3024
+ async function runUpgrade(opts = {}) {
3025
+ const write = opts.out ?? ((s) => process.stdout.write(s));
3026
+ const writeErr = opts.err ?? ((s) => process.stderr.write(s));
3027
+ const lines = [];
3028
+ const print = (s = "") => {
3029
+ lines.push(s);
3030
+ write(`${s}
3031
+ `);
3032
+ };
3033
+ const printErr = (s) => {
3034
+ lines.push(s);
3035
+ writeErr(`${s}
3036
+ `);
3037
+ };
3038
+ const fetcher = opts.fetchLatest ?? defaultFetchLatest;
3039
+ const current = opts.currentVersion ?? readCurrentVersion();
3040
+ const argvPath = opts.argvPath ?? process.argv[1];
3041
+ const method = detectInstallMethod(argvPath);
3042
+ let latest;
3043
+ try {
3044
+ latest = await fetcher();
3045
+ } catch {
3046
+ latest = null;
3047
+ }
3048
+ const plan = buildUpgradePlan({ current, latest, method });
3049
+ if (opts.json) {
3050
+ print(JSON.stringify(plan, null, 2));
3051
+ return { exitCode: plan.stale && !opts.run ? 1 : 0, lines };
3052
+ }
3053
+ if (latest === null) {
3054
+ print("mcph upgrade: couldn't reach the npm registry (offline? firewall?).");
3055
+ if (plan.command) {
3056
+ print(`When you're back online, run:
3057
+ ${plan.command}`);
3058
+ } else {
3059
+ print("Your install uses `npx -y` \u2014 just restart the MCP client when you're back online.");
3060
+ }
3061
+ return { exitCode: 0, lines };
3062
+ }
3063
+ print(`Current: ${current}`);
3064
+ print(`Latest: ${latest}`);
3065
+ print(`Install: ${method}`);
3066
+ if (!plan.stale) {
3067
+ print("");
3068
+ print("\u2713 You're on the latest version \u2014 nothing to do.");
3069
+ return { exitCode: 0, lines };
3070
+ }
3071
+ print("");
3072
+ if (method === "npx") {
3073
+ print("Your install uses `npx -y` \u2014 restart the MCP client and it will fetch the new version.");
3074
+ return { exitCode: 0, lines };
3075
+ }
3076
+ if (!plan.command) {
3077
+ print("No upgrade command available for this install method.");
3078
+ return { exitCode: 0, lines };
3079
+ }
3080
+ const autoRunnable = method === "global-npm";
3081
+ if (!opts.run) {
3082
+ if (autoRunnable) {
3083
+ print(`Run:
3084
+ ${plan.command}
3085
+
3086
+ Or re-run with --run to upgrade in place.`);
3087
+ } else {
3088
+ print(`Suggested command (run it yourself; --run only works for global-npm installs):
3089
+ ${plan.command}`);
3090
+ }
3091
+ return { exitCode: 1, lines };
3092
+ }
3093
+ if (!autoRunnable) {
3094
+ printErr(
3095
+ `mcph upgrade --run: install method "${method}" can't be upgraded automatically. Run manually:
3096
+ ${plan.command}`
3097
+ );
3098
+ return { exitCode: 2, lines };
3099
+ }
3100
+ const runner = opts.spawnImpl ?? defaultSpawn;
3101
+ print(`Running: ${plan.command}`);
3102
+ const code = await runner("npm", ["install", "-g", "@yawlabs/mcph@latest"]);
3103
+ if (code === 0) {
3104
+ print("");
3105
+ print(`\u2713 Upgraded @yawlabs/mcph to ${latest}.`);
3106
+ return { exitCode: 0, lines };
3107
+ }
3108
+ printErr(`mcph upgrade: npm exited ${code}. Try running the command manually.`);
3109
+ return { exitCode: 3, lines };
3110
+ }
3111
+ function readCurrentVersion() {
3112
+ return true ? "0.48.6" : "dev";
3113
+ }
3114
+
3115
+ // src/auto-upgrade.ts
3116
+ async function fetchLatestVersion2() {
3117
+ const ac = new AbortController();
3118
+ const timer = setTimeout(() => ac.abort(), 3e3);
3119
+ try {
3120
+ const res = await fetch("https://registry.npmjs.org/@yawlabs/mcph/latest", {
3121
+ signal: ac.signal,
3122
+ headers: { accept: "application/json" }
3123
+ });
3124
+ if (!res.ok) return null;
3125
+ const body = await res.json();
3126
+ return typeof body.version === "string" ? body.version : null;
3127
+ } catch {
3128
+ return null;
3129
+ } finally {
3130
+ clearTimeout(timer);
3131
+ }
3132
+ }
3133
+ function defaultSpawn2(cmd, args) {
3134
+ const child = spawn3(cmd, args, {
3135
+ stdio: "ignore",
3136
+ // Stay a child of this process (not detached) so it dies with mcph
3137
+ // if mcph exits mid-install -- a half-finished `npm i -g` is fine
3138
+ // (npm is atomic per package) and a re-run next startup completes it.
3139
+ detached: false,
3140
+ shell: process.platform === "win32"
3141
+ });
3142
+ child.on("close", (code) => {
3143
+ if (code === 0) {
3144
+ log("info", "mcph self-upgrade complete; the next client restart will run the new version");
3145
+ } else {
3146
+ log(
3147
+ "warn",
3148
+ "mcph self-upgrade: npm exited non-zero (often EACCES on a sudo-installed global). Run `npm install -g @yawlabs/mcph@latest` with the right permissions, or set MCPH_AUTO_UPGRADE=0 to silence this.",
3149
+ { code }
3150
+ );
3151
+ }
3152
+ });
3153
+ child.on("error", (err) => {
3154
+ log("warn", "mcph self-upgrade: npm spawn failed", { error: err?.message });
3155
+ });
3156
+ }
3157
+ async function maybeAutoUpgrade(deps = {}) {
3158
+ const optOut = process.env.MCPH_AUTO_UPGRADE;
3159
+ if (optOut === "0" || optOut?.toLowerCase() === "false") return;
3160
+ const current = deps.currentVersion ?? (true ? "0.48.6" : "dev");
3161
+ if (current === "dev") return;
3162
+ const method = detectInstallMethod(deps.argvPath ?? process.argv[1]);
3163
+ const latest = await (deps.fetchLatestImpl ?? fetchLatestVersion2)();
3164
+ if (latest === null) return;
3165
+ const plan = buildUpgradePlan({ current, latest, method });
3166
+ if (!plan.stale) return;
3167
+ if (method === "global-npm") {
3168
+ log("info", "mcph is out of date; upgrading the global install in the background", { current, latest });
3169
+ (deps.spawnImpl ?? defaultSpawn2)("npm", ["install", "-g", "@yawlabs/mcph@latest"]);
3170
+ return;
3171
+ }
3172
+ log("info", "mcph is out of date; restart your MCP client to pick up the latest version", {
3173
+ current,
3174
+ latest,
3175
+ method
3176
+ });
3177
+ }
3178
+
2908
3179
  // src/compliance.ts
2909
3180
  var GRADE_ORDER = {
2910
3181
  A: 4,
@@ -3341,9 +3612,13 @@ import { request as request5 } from "undici";
3341
3612
  var HEARTBEAT_PATH = "/api/connect/heartbeat";
3342
3613
  var apiUrl3 = "";
3343
3614
  var token3 = "";
3615
+ var lastLoggedFailureStatus = null;
3616
+ var lastLoggedErrorMessage = null;
3344
3617
  function initHeartbeat(url, tok) {
3345
3618
  apiUrl3 = url;
3346
3619
  token3 = tok;
3620
+ lastLoggedFailureStatus = null;
3621
+ lastLoggedErrorMessage = null;
3347
3622
  }
3348
3623
  async function reportHeartbeat(clientName, clientVersion, isRefresh = false) {
3349
3624
  if (!apiUrl3 || !token3) return;
@@ -3368,15 +3643,26 @@ async function reportHeartbeat(clientName, clientVersion, isRefresh = false) {
3368
3643
  await res.body.text().catch(() => {
3369
3644
  });
3370
3645
  if (res.statusCode >= 400 && res.statusCode !== 404) {
3371
- log("warn", "Heartbeat failed", { status: res.statusCode, isRefresh });
3372
- } else if (!isRefresh) {
3373
- log("info", "Reported AI client connect to mcp.hosting", {
3374
- clientName: clientName ?? null,
3375
- clientVersion: clientVersion ?? null
3376
- });
3646
+ if (lastLoggedFailureStatus !== res.statusCode) {
3647
+ log("warn", "Heartbeat failed", { status: res.statusCode, isRefresh });
3648
+ lastLoggedFailureStatus = res.statusCode;
3649
+ }
3650
+ } else {
3651
+ lastLoggedFailureStatus = null;
3652
+ lastLoggedErrorMessage = null;
3653
+ if (!isRefresh) {
3654
+ log("info", "Reported AI client connect to mcp.hosting", {
3655
+ clientName: clientName ?? null,
3656
+ clientVersion: clientVersion ?? null
3657
+ });
3658
+ }
3377
3659
  }
3378
3660
  } catch (err) {
3379
- log("warn", "Heartbeat error", { error: err?.message, isRefresh });
3661
+ const errMsg = err?.message ?? "unknown error";
3662
+ if (lastLoggedErrorMessage !== errMsg) {
3663
+ log("warn", "Heartbeat error", { error: errMsg, isRefresh });
3664
+ lastLoggedErrorMessage = errMsg;
3665
+ }
3380
3666
  }
3381
3667
  }
3382
3668
 
@@ -4500,7 +4786,7 @@ async function rerank(intent, candidateIds, limit) {
4500
4786
  }
4501
4787
 
4502
4788
  // src/runtime-detect.ts
4503
- import { spawn as spawn2 } from "child_process";
4789
+ import { spawn as spawn4 } from "child_process";
4504
4790
  import { request as request7 } from "undici";
4505
4791
  var PROBE_TIMEOUT_MS = 3e3;
4506
4792
  var RUNTIME_REPORT_PATH = "/api/connect/runtimes";
@@ -4558,7 +4844,7 @@ async function probe(name, p) {
4558
4844
  let stderr = "";
4559
4845
  let child;
4560
4846
  try {
4561
- child = spawn2(p.bin, p.args, {
4847
+ child = spawn4(p.bin, p.args, {
4562
4848
  stdio: ["ignore", "pipe", "pipe"],
4563
4849
  // Windows needs a shell for PATH lookup of .cmd/.bat shims —
4564
4850
  // node/npx/uvx arrive as `npx.cmd` in PATH, and native spawn
@@ -4783,7 +5069,7 @@ import {
4783
5069
  } from "@modelcontextprotocol/sdk/types.js";
4784
5070
 
4785
5071
  // src/uv-bootstrap.ts
4786
- import { spawn as spawn3 } from "child_process";
5072
+ import { spawn as spawn5 } from "child_process";
4787
5073
  import { createHash } from "crypto";
4788
5074
  import { createWriteStream } from "fs";
4789
5075
  import fs from "fs/promises";
@@ -4836,7 +5122,7 @@ async function onPath(cmd) {
4836
5122
  };
4837
5123
  let child;
4838
5124
  try {
4839
- child = spawn3(cmd, ["--version"], {
5125
+ child = spawn5(cmd, ["--version"], {
4840
5126
  stdio: "ignore",
4841
5127
  shell: false,
4842
5128
  windowsHide: process.platform === "win32"
@@ -4897,7 +5183,7 @@ async function extractArchive(archivePath, destDir) {
4897
5183
  }
4898
5184
  function runCommand(cmd, args) {
4899
5185
  return new Promise((resolve4, reject) => {
4900
- const child = spawn3(cmd, args, {
5186
+ const child = spawn5(cmd, args, {
4901
5187
  stdio: ["ignore", "pipe", "pipe"],
4902
5188
  shell: false,
4903
5189
  windowsHide: process.platform === "win32"
@@ -5015,7 +5301,7 @@ function categorizeSpawnError(err) {
5015
5301
  }
5016
5302
  async function connectToUpstream(config, onDisconnect, onListChanged) {
5017
5303
  const client = new Client(
5018
- { name: "mcph", version: true ? "0.47.7" : "dev" },
5304
+ { name: "mcph", version: true ? "0.48.6" : "dev" },
5019
5305
  { capabilities: {} }
5020
5306
  );
5021
5307
  let transport;
@@ -5323,7 +5609,7 @@ var ConnectServer = class _ConnectServer {
5323
5609
  this.apiUrl = apiUrl6;
5324
5610
  this.token = token6;
5325
5611
  this.server = new Server(
5326
- { name: "mcph", version: true ? "0.47.7" : "dev" },
5612
+ { name: "mcph", version: true ? "0.48.6" : "dev" },
5327
5613
  {
5328
5614
  capabilities: {
5329
5615
  tools: { listChanged: true },
@@ -5609,6 +5895,7 @@ var ConnectServer = class _ConnectServer {
5609
5895
  await this.server.connect(transport);
5610
5896
  this.startPolling();
5611
5897
  this.prewarmDormantServers().catch((err) => log("warn", "Pre-warm failed", { error: err?.message }));
5898
+ maybeAutoUpgrade().catch((err) => log("warn", "Auto-upgrade check failed", { error: err?.message }));
5612
5899
  if (isAutoLoadEnabled() && this.persistenceReady) {
5613
5900
  this.autoLoadRecurringPack().catch((err) => log("warn", "Auto-load failed", { error: err?.message }));
5614
5901
  }
@@ -5634,15 +5921,33 @@ var ConnectServer = class _ConnectServer {
5634
5921
  });
5635
5922
  if (candidates.length === 0) return;
5636
5923
  const top = candidates[0];
5924
+ const loaded = [];
5925
+ const refused = [];
5637
5926
  for (const namespace of top.namespaces) {
5638
- await this.activateOne(namespace).catch(
5639
- (err) => log("warn", "Auto-load activateOne failed", { namespace, error: err?.message })
5640
- );
5927
+ try {
5928
+ const result = await this.activateOne(namespace);
5929
+ if (result.ok) {
5930
+ loaded.push(namespace);
5931
+ } else {
5932
+ refused.push({ namespace, message: result.message });
5933
+ }
5934
+ } catch (err) {
5935
+ refused.push({ namespace, message: err?.message ?? "unknown error" });
5936
+ }
5641
5937
  }
5642
5938
  log("info", "Auto-loaded recurring pack", {
5643
- namespaces: top.namespaces,
5939
+ loaded,
5940
+ refusedCount: refused.length,
5644
5941
  frequency: top.frequency
5645
5942
  });
5943
+ if (refused.length > 0) {
5944
+ const message = loaded.length === 0 ? "Auto-load could not activate any namespace in the pack" : "Auto-load could not activate every namespace in the pack";
5945
+ log("warn", message, {
5946
+ serverCap: this.serverCap,
5947
+ loadedCount: loaded.length,
5948
+ refused
5949
+ });
5950
+ }
5646
5951
  }
5647
5952
  // Populate toolCache for any isActive-but-never-activated server so
5648
5953
  // Claude's tools/list shows the full toggled set on first run.
@@ -7522,192 +7827,6 @@ function truncateVersion(v) {
7522
7827
  return v.length > 8 ? v.slice(0, 8) : v;
7523
7828
  }
7524
7829
 
7525
- // src/upgrade-cmd.ts
7526
- import { spawn as spawn4 } from "child_process";
7527
- var UPGRADE_USAGE = `Usage: mcph upgrade [--run] [--json]
7528
-
7529
- Show (or execute) the command to upgrade @yawlabs/mcph to the latest version.
7530
-
7531
- --run If this install is global (npm install -g), spawn the upgrade
7532
- command. No-op for npx installs \u2014 they always fetch the latest.
7533
- --json Emit a machine-readable snapshot ({ current, latest, stale,
7534
- method, command }) instead of prose.`;
7535
- function parseUpgradeArgs(argv) {
7536
- const opts = {};
7537
- for (const a of argv) {
7538
- if (a === "--run") opts.run = true;
7539
- else if (a === "--json") opts.json = true;
7540
- else if (a === "--help" || a === "-h") return { ok: false, error: UPGRADE_USAGE };
7541
- else return { ok: false, error: `mcph upgrade: unknown argument "${a}"
7542
-
7543
- ${UPGRADE_USAGE}` };
7544
- }
7545
- return { ok: true, options: opts };
7546
- }
7547
- function detectInstallMethod(argvPath) {
7548
- if (!argvPath) return "unknown";
7549
- const normalized = argvPath.replace(/\\/g, "/");
7550
- if (/\/_npx\//.test(normalized)) return "npx";
7551
- if (/\/npm\/node_modules\/@yawlabs\/mcph\//.test(normalized)) return "global-npm";
7552
- if (/\/lib\/node_modules\/@yawlabs\/mcph\//.test(normalized)) return "global-npm";
7553
- if (/\/AppData\/Roaming\/npm\/node_modules\/@yawlabs\/mcph\//.test(normalized)) return "global-npm";
7554
- if (/\/node_modules\/@yawlabs\/mcph\//.test(normalized)) return "local-node-modules";
7555
- if (/\/mcph\/(dist|src)\//.test(normalized)) return "dev-checkout";
7556
- return "unknown";
7557
- }
7558
- function buildUpgradePlan(input) {
7559
- const { current, latest, method } = input;
7560
- const stale = latest !== null && current !== "dev" && compareSemverLocal(current, latest) < 0;
7561
- let command;
7562
- switch (method) {
7563
- case "global-npm":
7564
- command = "npm install -g @yawlabs/mcph@latest";
7565
- break;
7566
- case "npx":
7567
- command = null;
7568
- break;
7569
- case "local-node-modules":
7570
- command = "npm install @yawlabs/mcph@latest";
7571
- break;
7572
- case "dev-checkout":
7573
- command = "git pull && npm run build";
7574
- break;
7575
- default:
7576
- command = "npm install -g @yawlabs/mcph@latest";
7577
- break;
7578
- }
7579
- return { current, latest, stale, method, command };
7580
- }
7581
- function compareSemverLocal(a, b) {
7582
- const parse = (s) => {
7583
- const m = /^v?(\d+)\.(\d+)\.(\d+)/.exec(s);
7584
- if (!m) return null;
7585
- return [Number(m[1]), Number(m[2]), Number(m[3])];
7586
- };
7587
- const pa = parse(a);
7588
- const pb = parse(b);
7589
- if (!pa || !pb) return 0;
7590
- for (let i = 0; i < 3; i++) {
7591
- if (pa[i] < pb[i]) return -1;
7592
- if (pa[i] > pb[i]) return 1;
7593
- }
7594
- return 0;
7595
- }
7596
- async function defaultFetchLatest() {
7597
- const ac = new AbortController();
7598
- const timer = setTimeout(() => ac.abort(), 3e3);
7599
- try {
7600
- const res = await fetch("https://registry.npmjs.org/@yawlabs/mcph/latest", {
7601
- signal: ac.signal,
7602
- headers: { accept: "application/json" }
7603
- });
7604
- if (!res.ok) return null;
7605
- const body = await res.json();
7606
- return typeof body.version === "string" ? body.version : null;
7607
- } catch {
7608
- return null;
7609
- } finally {
7610
- clearTimeout(timer);
7611
- }
7612
- }
7613
- async function defaultSpawn(cmd, args) {
7614
- return new Promise((resolve4) => {
7615
- const child = spawn4(cmd, args, { stdio: "inherit", shell: process.platform === "win32" });
7616
- child.on("close", (code) => resolve4(typeof code === "number" ? code : 1));
7617
- child.on("error", () => resolve4(1));
7618
- });
7619
- }
7620
- async function runUpgrade(opts = {}) {
7621
- const write = opts.out ?? ((s) => process.stdout.write(s));
7622
- const writeErr = opts.err ?? ((s) => process.stderr.write(s));
7623
- const lines = [];
7624
- const print = (s = "") => {
7625
- lines.push(s);
7626
- write(`${s}
7627
- `);
7628
- };
7629
- const printErr = (s) => {
7630
- lines.push(s);
7631
- writeErr(`${s}
7632
- `);
7633
- };
7634
- const fetcher = opts.fetchLatest ?? defaultFetchLatest;
7635
- const current = opts.currentVersion ?? readCurrentVersion();
7636
- const argvPath = opts.argvPath ?? process.argv[1];
7637
- const method = detectInstallMethod(argvPath);
7638
- let latest;
7639
- try {
7640
- latest = await fetcher();
7641
- } catch {
7642
- latest = null;
7643
- }
7644
- const plan = buildUpgradePlan({ current, latest, method });
7645
- if (opts.json) {
7646
- print(JSON.stringify(plan, null, 2));
7647
- return { exitCode: plan.stale && !opts.run ? 1 : 0, lines };
7648
- }
7649
- if (latest === null) {
7650
- print("mcph upgrade: couldn't reach the npm registry (offline? firewall?).");
7651
- if (plan.command) {
7652
- print(`When you're back online, run:
7653
- ${plan.command}`);
7654
- } else {
7655
- print("Your install uses `npx -y` \u2014 just restart the MCP client when you're back online.");
7656
- }
7657
- return { exitCode: 0, lines };
7658
- }
7659
- print(`Current: ${current}`);
7660
- print(`Latest: ${latest}`);
7661
- print(`Install: ${method}`);
7662
- if (!plan.stale) {
7663
- print("");
7664
- print("\u2713 You're on the latest version \u2014 nothing to do.");
7665
- return { exitCode: 0, lines };
7666
- }
7667
- print("");
7668
- if (method === "npx") {
7669
- print("Your install uses `npx -y` \u2014 restart the MCP client and it will fetch the new version.");
7670
- return { exitCode: 0, lines };
7671
- }
7672
- if (!plan.command) {
7673
- print("No upgrade command available for this install method.");
7674
- return { exitCode: 0, lines };
7675
- }
7676
- const autoRunnable = method === "global-npm";
7677
- if (!opts.run) {
7678
- if (autoRunnable) {
7679
- print(`Run:
7680
- ${plan.command}
7681
-
7682
- Or re-run with --run to upgrade in place.`);
7683
- } else {
7684
- print(`Suggested command (run it yourself; --run only works for global-npm installs):
7685
- ${plan.command}`);
7686
- }
7687
- return { exitCode: 1, lines };
7688
- }
7689
- if (!autoRunnable) {
7690
- printErr(
7691
- `mcph upgrade --run: install method "${method}" can't be upgraded automatically. Run manually:
7692
- ${plan.command}`
7693
- );
7694
- return { exitCode: 2, lines };
7695
- }
7696
- const runner = opts.spawnImpl ?? defaultSpawn;
7697
- print(`Running: ${plan.command}`);
7698
- const code = await runner("npm", ["install", "-g", "@yawlabs/mcph@latest"]);
7699
- if (code === 0) {
7700
- print("");
7701
- print(`\u2713 Upgraded @yawlabs/mcph to ${latest}.`);
7702
- return { exitCode: 0, lines };
7703
- }
7704
- printErr(`mcph upgrade: npm exited ${code}. Try running the command manually.`);
7705
- return { exitCode: 3, lines };
7706
- }
7707
- function readCurrentVersion() {
7708
- return true ? "0.47.7" : "dev";
7709
- }
7710
-
7711
7830
  // src/index.ts
7712
7831
  var KNOWN_SUBCOMMANDS = [
7713
7832
  "compliance",
@@ -7850,10 +7969,16 @@ if (subcommand === "compliance") {
7850
7969
  MCPH_POLL_INTERVAL Dashboard polling interval, seconds (default 60).
7851
7970
  MCPH_SERVER_CAP Max concurrently active servers (default 6).
7852
7971
  MCPH_MIN_COMPLIANCE Minimum grade to auto-activate (A|B|C|D|F).
7853
- MCPH_AUTO_LOAD Load all servers at startup, ignoring SERVER_CAP.
7972
+ MCPH_AUTO_LOAD Auto-activate the namespaces of the highest-
7973
+ ranked recurring pack at startup, subject to
7974
+ SERVER_CAP (default: off).
7854
7975
  MCPH_AUTO_ACTIVATE Set to \`0\` to disable discover's auto-activate
7855
7976
  gate (default: a clearly-winning server is
7856
7977
  activated in the same call).
7978
+ MCPH_AUTO_UPGRADE Set to \`0\` to disable the background
7979
+ self-upgrade check at \`mcph serve\` startup
7980
+ (default: stale global-npm installs are
7981
+ upgraded in the background).
7857
7982
  MCPH_PRUNE_RESPONSES Set to \`0\` to disable response pruning.
7858
7983
  MCPH_DISABLE_PERSISTENCE Disable cross-session learning state.
7859
7984
 
@@ -7873,7 +7998,7 @@ if (subcommand === "compliance") {
7873
7998
  `);
7874
7999
  process.exit(0);
7875
8000
  } else if (subcommand === "--version" || subcommand === "-V") {
7876
- process.stdout.write(`mcph ${true ? "0.47.7" : "dev"}
8001
+ process.stdout.write(`mcph ${true ? "0.48.6" : "dev"}
7877
8002
  `);
7878
8003
  process.exit(0);
7879
8004
  } else if (subcommand && !subcommand.startsWith("-")) {
@@ -7890,7 +8015,7 @@ async function runServer() {
7890
8015
  const config = await loadMcphConfig();
7891
8016
  if (!config.token) {
7892
8017
  process.stderr.write(
7893
- '\n mcph: no token resolved.\n\n Quick start (recommended):\n mcph install <claude-code|claude-desktop|cursor|vscode> --token mcp_pat_\u2026\n Creates ~/.mcph/config.json so every MCP client picks up the token automatically.\n\n Or set MCPH_TOKEN in your MCP client config:\n\n {\n "mcpServers": {\n "mcp.hosting": {\n "command": "npx",\n "args": ["-y", "@yawlabs/mcph"],\n "env": {\n "MCPH_TOKEN": "mcp_pat_your_token_here"\n }\n }\n }\n }\n\n Get a token at https://mcp.hosting \u2192 Settings \u2192 API Tokens, or run\n `mcph doctor` to see exactly where mcph looked.\n\n'
8018
+ '\n mcph: no token resolved.\n\n Quick start (recommended):\n mcph install <claude-code|claude-desktop|cursor|vscode> --token mcp_pat_\u2026\n Creates ~/.mcph/config.json so every MCP client picks up the token automatically.\n\n Or set MCPH_TOKEN in your MCP client config:\n\n {\n "mcpServers": {\n "mcp.hosting": {\n "command": "npx",\n "args": ["-y", "@yawlabs/mcph@latest"],\n "env": {\n "MCPH_TOKEN": "mcp_pat_your_token_here"\n }\n }\n }\n }\n\n Get a token at https://mcp.hosting \u2192 Settings \u2192 API Tokens, or run\n `mcph doctor` to see exactly where mcph looked.\n\n'
7894
8019
  );
7895
8020
  process.exit(1);
7896
8021
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yawlabs/mcph",
3
- "version": "0.47.7",
3
+ "version": "0.48.6",
4
4
  "description": "mcp.hosting — one install, all your MCP servers, managed from the cloud",
5
5
  "license": "UNLICENSED",
6
6
  "author": "Yaw Labs <support@mcp.hosting> (https://mcp.hosting)",