@buoy-gg/mcp 7.0.3 → 7.0.4
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 +14 -0
- package/dist/{chunk-MGZN6MJE.mjs → chunk-EC6YNLYA.mjs} +120 -3
- package/dist/cli.js +127 -4
- package/dist/cli.mjs +8 -2
- package/dist/index.d.mts +45 -4
- package/dist/index.d.ts +45 -4
- package/dist/index.js +120 -3
- package/dist/index.mjs +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -18,6 +18,20 @@ One command, non-destructive:
|
|
|
18
18
|
|
|
19
19
|
The config it writes launches the server via `npx -y @buoy-gg/mcp@latest`, so every editor restart re-resolves the newest published version — you never get pinned to a stale copy. Restart your editor, open your app with Buoy running, and start with `list_devices`.
|
|
20
20
|
|
|
21
|
+
### Corporate / private npm registries
|
|
22
|
+
|
|
23
|
+
If your machine's `.npmrc` points npm at a private registry that isn't reachable (a common setup on work laptops, e.g. off-VPN), `npx @latest` would hang trying to download the package on every editor launch — so `init` probes that registry first and, when it's unreachable, automatically installs a **pinned local copy** (from public npm) and writes a `node <path>` config instead. That takes the network off the startup path entirely.
|
|
24
|
+
|
|
25
|
+
You can also force it:
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
npx -y @buoy-gg/mcp@latest init --local # always install locally, no network on startup
|
|
29
|
+
npx -y @buoy-gg/mcp@latest init --npx # always use the auto-updating npx entry
|
|
30
|
+
npx -y @buoy-gg/mcp@latest init --registry <url> # registry the local install pulls from
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
To update a local install later, just re-run `init`.
|
|
34
|
+
|
|
21
35
|
## What your agent can do
|
|
22
36
|
|
|
23
37
|
**Read the runtime**
|
|
@@ -296,7 +296,7 @@ async function waitForBroker(url, attempts = 20) {
|
|
|
296
296
|
var delay = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
297
297
|
|
|
298
298
|
// src/version.ts
|
|
299
|
-
var VERSION = "7.0.
|
|
299
|
+
var VERSION = "7.0.4" ? "7.0.4" : "0.0.0-dev";
|
|
300
300
|
var PACKAGE_NAME = "@buoy-gg/mcp";
|
|
301
301
|
|
|
302
302
|
// src/update/checkForUpdate.ts
|
|
@@ -2615,13 +2615,17 @@ var deviceIdField = {
|
|
|
2615
2615
|
};
|
|
2616
2616
|
|
|
2617
2617
|
// src/init.ts
|
|
2618
|
+
import { spawnSync } from "child_process";
|
|
2618
2619
|
import fs3 from "fs";
|
|
2620
|
+
import os3 from "os";
|
|
2619
2621
|
import path3 from "path";
|
|
2622
|
+
var PUBLIC_REGISTRY = "https://registry.npmjs.org";
|
|
2623
|
+
var PROBE_TIMEOUT_MS = 3e3;
|
|
2620
2624
|
function runInit(opts = {}) {
|
|
2621
2625
|
const cwd = opts.cwd ?? process.cwd();
|
|
2622
2626
|
const skillSrcDir = opts.skillSrcDir ?? path3.resolve(__dirname, "../skill");
|
|
2623
2627
|
const log = opts.log ?? ((m) => process.stdout.write(m + "\n"));
|
|
2624
|
-
const entry = opts
|
|
2628
|
+
const entry = resolveEntry(opts, log);
|
|
2625
2629
|
const done = [];
|
|
2626
2630
|
upsertMcpConfig(path3.join(cwd, ".mcp.json"), "mcpServers", entry, cwd, done);
|
|
2627
2631
|
if (fs3.existsSync(path3.join(cwd, ".cursor"))) {
|
|
@@ -2638,12 +2642,125 @@ function runInit(opts = {}) {
|
|
|
2638
2642
|
log(" 1. Restart your editor (or reconnect the MCP server) to load `buoy`.");
|
|
2639
2643
|
log(" 2. Open your app with Buoy devtools running on a device or simulator.");
|
|
2640
2644
|
log(' 3. Say "buoy optimize" to start a guided performance pass.');
|
|
2641
|
-
if (
|
|
2645
|
+
if (entry.command === "npx") {
|
|
2642
2646
|
log("");
|
|
2643
2647
|
log(`This launches the server via \`npx -y ${PACKAGE_NAME}@latest\`, so it stays`);
|
|
2644
2648
|
log(`up to date automatically. To force-refresh, re-run \`npx -y ${PACKAGE_NAME}@latest init\`.`);
|
|
2649
|
+
} else if (!opts.cliPath) {
|
|
2650
|
+
log("");
|
|
2651
|
+
log(`This uses a pinned local install (no network on startup). To update it later,`);
|
|
2652
|
+
log(`re-run init (\`node ${entry.args[0]?.replace(/dist[/\\]cli\.js$/, "") ?? ""}...\` \u2014 or reinstall).`);
|
|
2653
|
+
}
|
|
2654
|
+
}
|
|
2655
|
+
function resolveEntry(opts, log) {
|
|
2656
|
+
if (opts.cliPath) {
|
|
2657
|
+
return { type: "stdio", command: "node", args: [opts.cliPath] };
|
|
2658
|
+
}
|
|
2659
|
+
const mode = opts.mode ?? "auto";
|
|
2660
|
+
if (mode === "npx") {
|
|
2661
|
+
return npxEntry();
|
|
2662
|
+
}
|
|
2663
|
+
if (mode === "local") {
|
|
2664
|
+
return localEntry(opts, log);
|
|
2665
|
+
}
|
|
2666
|
+
const probe = probeRegistry();
|
|
2667
|
+
if (probe.reachable) {
|
|
2668
|
+
return npxEntry();
|
|
2669
|
+
}
|
|
2670
|
+
log(
|
|
2671
|
+
`\u26A0 npm registry ${probe.registry ? `(${probe.registry}) ` : ""}looks unreachable \u2014 \`npx @latest\` would hang your editor at startup. Falling back to a pinned local install.`
|
|
2672
|
+
);
|
|
2673
|
+
return localEntry(opts, log);
|
|
2674
|
+
}
|
|
2675
|
+
function npxEntry() {
|
|
2676
|
+
return { type: "stdio", command: "npx", args: ["-y", `${PACKAGE_NAME}@latest`] };
|
|
2677
|
+
}
|
|
2678
|
+
function localEntry(opts, log) {
|
|
2679
|
+
const installDir = opts.installDir ?? process.env.BUOY_MCP_HOME ?? path3.join(os3.homedir(), ".buoy-mcp");
|
|
2680
|
+
const registry = opts.registry ?? PUBLIC_REGISTRY;
|
|
2681
|
+
const cliPath = installLocalCopy(installDir, registry, log);
|
|
2682
|
+
if (cliPath) {
|
|
2683
|
+
return { type: "stdio", command: "node", args: [cliPath] };
|
|
2645
2684
|
}
|
|
2685
|
+
log("\u26A0 local install failed \u2014 writing the npx entry instead (see the error above).");
|
|
2686
|
+
return npxEntry();
|
|
2687
|
+
}
|
|
2688
|
+
function installLocalCopy(installDir, registry, log) {
|
|
2689
|
+
const spec = `${PACKAGE_NAME}@${VERSION === "0.0.0-dev" ? "latest" : VERSION}`;
|
|
2690
|
+
fs3.mkdirSync(installDir, { recursive: true });
|
|
2691
|
+
const npm = process.platform === "win32" ? "npm.cmd" : "npm";
|
|
2692
|
+
log(`Installing ${spec} into ${installDir} from ${registry} \u2026`);
|
|
2693
|
+
const res = spawnSync(
|
|
2694
|
+
npm,
|
|
2695
|
+
[
|
|
2696
|
+
"install",
|
|
2697
|
+
spec,
|
|
2698
|
+
"--prefix",
|
|
2699
|
+
installDir,
|
|
2700
|
+
"--registry",
|
|
2701
|
+
registry,
|
|
2702
|
+
`--@buoy-gg:registry=${registry}`,
|
|
2703
|
+
"--no-audit",
|
|
2704
|
+
"--no-fund",
|
|
2705
|
+
"--loglevel=error"
|
|
2706
|
+
],
|
|
2707
|
+
{ stdio: ["ignore", "ignore", "inherit"], encoding: "utf8" }
|
|
2708
|
+
);
|
|
2709
|
+
if (res.status !== 0) {
|
|
2710
|
+
log(`\u26A0 npm install exited with code ${res.status ?? "?"}${res.error ? ` (${res.error.message})` : ""}.`);
|
|
2711
|
+
return null;
|
|
2712
|
+
}
|
|
2713
|
+
const cliPath = path3.join(installDir, "node_modules", PACKAGE_NAME, "dist", "cli.js");
|
|
2714
|
+
if (!fs3.existsSync(cliPath)) {
|
|
2715
|
+
log(`\u26A0 install finished but ${cliPath} is missing.`);
|
|
2716
|
+
return null;
|
|
2717
|
+
}
|
|
2718
|
+
return cliPath;
|
|
2646
2719
|
}
|
|
2720
|
+
function probeRegistry() {
|
|
2721
|
+
const registry = effectiveRegistry();
|
|
2722
|
+
if (!registry) return { reachable: false, registry: null };
|
|
2723
|
+
if (/^https?:\/\/registry\.npmjs\.org\/?$/i.test(registry)) {
|
|
2724
|
+
return { reachable: true, registry };
|
|
2725
|
+
}
|
|
2726
|
+
return { reachable: hostReachable(registry), registry };
|
|
2727
|
+
}
|
|
2728
|
+
function effectiveRegistry() {
|
|
2729
|
+
const scoped = npmConfigGet("@buoy-gg:registry");
|
|
2730
|
+
if (scoped) return scoped;
|
|
2731
|
+
const global = npmConfigGet("registry");
|
|
2732
|
+
return global ?? PUBLIC_REGISTRY;
|
|
2733
|
+
}
|
|
2734
|
+
function npmConfigGet(key) {
|
|
2735
|
+
const npm = process.platform === "win32" ? "npm.cmd" : "npm";
|
|
2736
|
+
const res = spawnSync(npm, ["config", "get", key], { encoding: "utf8", timeout: PROBE_TIMEOUT_MS });
|
|
2737
|
+
if (res.status !== 0 || typeof res.stdout !== "string") return null;
|
|
2738
|
+
const val = res.stdout.trim();
|
|
2739
|
+
if (!val || val === "undefined" || val === "null") return null;
|
|
2740
|
+
return val;
|
|
2741
|
+
}
|
|
2742
|
+
function hostReachable(registry) {
|
|
2743
|
+
const done = spawnSync(
|
|
2744
|
+
process.execPath,
|
|
2745
|
+
["-e", REACHABILITY_PROBE, registry, String(PROBE_TIMEOUT_MS)],
|
|
2746
|
+
{ encoding: "utf8", timeout: PROBE_TIMEOUT_MS + 2e3 }
|
|
2747
|
+
);
|
|
2748
|
+
return done.status === 0;
|
|
2749
|
+
}
|
|
2750
|
+
var REACHABILITY_PROBE = `
|
|
2751
|
+
const url = process.argv[1];
|
|
2752
|
+
const ms = Number(process.argv[2]) || 3000;
|
|
2753
|
+
const c = new AbortController();
|
|
2754
|
+
const t = setTimeout(() => c.abort(), ms);
|
|
2755
|
+
fetch(url, { method: 'HEAD', signal: c.signal })
|
|
2756
|
+
.then(() => { clearTimeout(t); process.exit(0); })
|
|
2757
|
+
.catch(() => {
|
|
2758
|
+
// A HEAD may be rejected while the host is up; a bare TCP-level failure is
|
|
2759
|
+
// what we care about. fetch throwing a TypeError = network unreachable.
|
|
2760
|
+
clearTimeout(t);
|
|
2761
|
+
process.exit(1);
|
|
2762
|
+
});
|
|
2763
|
+
`;
|
|
2647
2764
|
function upsertMcpConfig(file, key, entry, cwd, done) {
|
|
2648
2765
|
let json3 = {};
|
|
2649
2766
|
if (fs3.existsSync(file)) {
|
package/dist/cli.js
CHANGED
|
@@ -336,7 +336,7 @@ var import_node_os = __toESM(require("os"));
|
|
|
336
336
|
var import_node_path = __toESM(require("path"));
|
|
337
337
|
|
|
338
338
|
// src/version.ts
|
|
339
|
-
var VERSION = "7.0.
|
|
339
|
+
var VERSION = "7.0.4" ? "7.0.4" : "0.0.0-dev";
|
|
340
340
|
var PACKAGE_NAME = "@buoy-gg/mcp";
|
|
341
341
|
|
|
342
342
|
// src/update/checkForUpdate.ts
|
|
@@ -2647,13 +2647,17 @@ var deviceIdField = {
|
|
|
2647
2647
|
};
|
|
2648
2648
|
|
|
2649
2649
|
// src/init.ts
|
|
2650
|
+
var import_node_child_process2 = require("child_process");
|
|
2650
2651
|
var import_node_fs3 = __toESM(require("fs"));
|
|
2652
|
+
var import_node_os3 = __toESM(require("os"));
|
|
2651
2653
|
var import_node_path3 = __toESM(require("path"));
|
|
2654
|
+
var PUBLIC_REGISTRY = "https://registry.npmjs.org";
|
|
2655
|
+
var PROBE_TIMEOUT_MS = 3e3;
|
|
2652
2656
|
function runInit(opts = {}) {
|
|
2653
2657
|
const cwd = opts.cwd ?? process.cwd();
|
|
2654
2658
|
const skillSrcDir = opts.skillSrcDir ?? import_node_path3.default.resolve(__dirname, "../skill");
|
|
2655
2659
|
const log = opts.log ?? ((m) => process.stdout.write(m + "\n"));
|
|
2656
|
-
const entry = opts
|
|
2660
|
+
const entry = resolveEntry(opts, log);
|
|
2657
2661
|
const done = [];
|
|
2658
2662
|
upsertMcpConfig(import_node_path3.default.join(cwd, ".mcp.json"), "mcpServers", entry, cwd, done);
|
|
2659
2663
|
if (import_node_fs3.default.existsSync(import_node_path3.default.join(cwd, ".cursor"))) {
|
|
@@ -2670,12 +2674,125 @@ function runInit(opts = {}) {
|
|
|
2670
2674
|
log(" 1. Restart your editor (or reconnect the MCP server) to load `buoy`.");
|
|
2671
2675
|
log(" 2. Open your app with Buoy devtools running on a device or simulator.");
|
|
2672
2676
|
log(' 3. Say "buoy optimize" to start a guided performance pass.');
|
|
2673
|
-
if (
|
|
2677
|
+
if (entry.command === "npx") {
|
|
2674
2678
|
log("");
|
|
2675
2679
|
log(`This launches the server via \`npx -y ${PACKAGE_NAME}@latest\`, so it stays`);
|
|
2676
2680
|
log(`up to date automatically. To force-refresh, re-run \`npx -y ${PACKAGE_NAME}@latest init\`.`);
|
|
2681
|
+
} else if (!opts.cliPath) {
|
|
2682
|
+
log("");
|
|
2683
|
+
log(`This uses a pinned local install (no network on startup). To update it later,`);
|
|
2684
|
+
log(`re-run init (\`node ${entry.args[0]?.replace(/dist[/\\]cli\.js$/, "") ?? ""}...\` \u2014 or reinstall).`);
|
|
2685
|
+
}
|
|
2686
|
+
}
|
|
2687
|
+
function resolveEntry(opts, log) {
|
|
2688
|
+
if (opts.cliPath) {
|
|
2689
|
+
return { type: "stdio", command: "node", args: [opts.cliPath] };
|
|
2690
|
+
}
|
|
2691
|
+
const mode = opts.mode ?? "auto";
|
|
2692
|
+
if (mode === "npx") {
|
|
2693
|
+
return npxEntry();
|
|
2694
|
+
}
|
|
2695
|
+
if (mode === "local") {
|
|
2696
|
+
return localEntry(opts, log);
|
|
2677
2697
|
}
|
|
2698
|
+
const probe = probeRegistry();
|
|
2699
|
+
if (probe.reachable) {
|
|
2700
|
+
return npxEntry();
|
|
2701
|
+
}
|
|
2702
|
+
log(
|
|
2703
|
+
`\u26A0 npm registry ${probe.registry ? `(${probe.registry}) ` : ""}looks unreachable \u2014 \`npx @latest\` would hang your editor at startup. Falling back to a pinned local install.`
|
|
2704
|
+
);
|
|
2705
|
+
return localEntry(opts, log);
|
|
2706
|
+
}
|
|
2707
|
+
function npxEntry() {
|
|
2708
|
+
return { type: "stdio", command: "npx", args: ["-y", `${PACKAGE_NAME}@latest`] };
|
|
2678
2709
|
}
|
|
2710
|
+
function localEntry(opts, log) {
|
|
2711
|
+
const installDir = opts.installDir ?? process.env.BUOY_MCP_HOME ?? import_node_path3.default.join(import_node_os3.default.homedir(), ".buoy-mcp");
|
|
2712
|
+
const registry = opts.registry ?? PUBLIC_REGISTRY;
|
|
2713
|
+
const cliPath = installLocalCopy(installDir, registry, log);
|
|
2714
|
+
if (cliPath) {
|
|
2715
|
+
return { type: "stdio", command: "node", args: [cliPath] };
|
|
2716
|
+
}
|
|
2717
|
+
log("\u26A0 local install failed \u2014 writing the npx entry instead (see the error above).");
|
|
2718
|
+
return npxEntry();
|
|
2719
|
+
}
|
|
2720
|
+
function installLocalCopy(installDir, registry, log) {
|
|
2721
|
+
const spec = `${PACKAGE_NAME}@${VERSION === "0.0.0-dev" ? "latest" : VERSION}`;
|
|
2722
|
+
import_node_fs3.default.mkdirSync(installDir, { recursive: true });
|
|
2723
|
+
const npm = process.platform === "win32" ? "npm.cmd" : "npm";
|
|
2724
|
+
log(`Installing ${spec} into ${installDir} from ${registry} \u2026`);
|
|
2725
|
+
const res = (0, import_node_child_process2.spawnSync)(
|
|
2726
|
+
npm,
|
|
2727
|
+
[
|
|
2728
|
+
"install",
|
|
2729
|
+
spec,
|
|
2730
|
+
"--prefix",
|
|
2731
|
+
installDir,
|
|
2732
|
+
"--registry",
|
|
2733
|
+
registry,
|
|
2734
|
+
`--@buoy-gg:registry=${registry}`,
|
|
2735
|
+
"--no-audit",
|
|
2736
|
+
"--no-fund",
|
|
2737
|
+
"--loglevel=error"
|
|
2738
|
+
],
|
|
2739
|
+
{ stdio: ["ignore", "ignore", "inherit"], encoding: "utf8" }
|
|
2740
|
+
);
|
|
2741
|
+
if (res.status !== 0) {
|
|
2742
|
+
log(`\u26A0 npm install exited with code ${res.status ?? "?"}${res.error ? ` (${res.error.message})` : ""}.`);
|
|
2743
|
+
return null;
|
|
2744
|
+
}
|
|
2745
|
+
const cliPath = import_node_path3.default.join(installDir, "node_modules", PACKAGE_NAME, "dist", "cli.js");
|
|
2746
|
+
if (!import_node_fs3.default.existsSync(cliPath)) {
|
|
2747
|
+
log(`\u26A0 install finished but ${cliPath} is missing.`);
|
|
2748
|
+
return null;
|
|
2749
|
+
}
|
|
2750
|
+
return cliPath;
|
|
2751
|
+
}
|
|
2752
|
+
function probeRegistry() {
|
|
2753
|
+
const registry = effectiveRegistry();
|
|
2754
|
+
if (!registry) return { reachable: false, registry: null };
|
|
2755
|
+
if (/^https?:\/\/registry\.npmjs\.org\/?$/i.test(registry)) {
|
|
2756
|
+
return { reachable: true, registry };
|
|
2757
|
+
}
|
|
2758
|
+
return { reachable: hostReachable(registry), registry };
|
|
2759
|
+
}
|
|
2760
|
+
function effectiveRegistry() {
|
|
2761
|
+
const scoped = npmConfigGet("@buoy-gg:registry");
|
|
2762
|
+
if (scoped) return scoped;
|
|
2763
|
+
const global = npmConfigGet("registry");
|
|
2764
|
+
return global ?? PUBLIC_REGISTRY;
|
|
2765
|
+
}
|
|
2766
|
+
function npmConfigGet(key) {
|
|
2767
|
+
const npm = process.platform === "win32" ? "npm.cmd" : "npm";
|
|
2768
|
+
const res = (0, import_node_child_process2.spawnSync)(npm, ["config", "get", key], { encoding: "utf8", timeout: PROBE_TIMEOUT_MS });
|
|
2769
|
+
if (res.status !== 0 || typeof res.stdout !== "string") return null;
|
|
2770
|
+
const val = res.stdout.trim();
|
|
2771
|
+
if (!val || val === "undefined" || val === "null") return null;
|
|
2772
|
+
return val;
|
|
2773
|
+
}
|
|
2774
|
+
function hostReachable(registry) {
|
|
2775
|
+
const done = (0, import_node_child_process2.spawnSync)(
|
|
2776
|
+
process.execPath,
|
|
2777
|
+
["-e", REACHABILITY_PROBE, registry, String(PROBE_TIMEOUT_MS)],
|
|
2778
|
+
{ encoding: "utf8", timeout: PROBE_TIMEOUT_MS + 2e3 }
|
|
2779
|
+
);
|
|
2780
|
+
return done.status === 0;
|
|
2781
|
+
}
|
|
2782
|
+
var REACHABILITY_PROBE = `
|
|
2783
|
+
const url = process.argv[1];
|
|
2784
|
+
const ms = Number(process.argv[2]) || 3000;
|
|
2785
|
+
const c = new AbortController();
|
|
2786
|
+
const t = setTimeout(() => c.abort(), ms);
|
|
2787
|
+
fetch(url, { method: 'HEAD', signal: c.signal })
|
|
2788
|
+
.then(() => { clearTimeout(t); process.exit(0); })
|
|
2789
|
+
.catch(() => {
|
|
2790
|
+
// A HEAD may be rejected while the host is up; a bare TCP-level failure is
|
|
2791
|
+
// what we care about. fetch throwing a TypeError = network unreachable.
|
|
2792
|
+
clearTimeout(t);
|
|
2793
|
+
process.exit(1);
|
|
2794
|
+
});
|
|
2795
|
+
`;
|
|
2679
2796
|
function upsertMcpConfig(file, key, entry, cwd, done) {
|
|
2680
2797
|
let json3 = {};
|
|
2681
2798
|
if (import_node_fs3.default.existsSync(file)) {
|
|
@@ -2762,7 +2879,13 @@ async function main() {
|
|
|
2762
2879
|
}
|
|
2763
2880
|
if (process.argv[2] === "init") {
|
|
2764
2881
|
try {
|
|
2765
|
-
|
|
2882
|
+
const args = process.argv.slice(3);
|
|
2883
|
+
let mode = "auto";
|
|
2884
|
+
if (args.includes("--local")) mode = "local";
|
|
2885
|
+
else if (args.includes("--npx")) mode = "npx";
|
|
2886
|
+
const ri = args.indexOf("--registry");
|
|
2887
|
+
const registry = ri !== -1 ? args[ri + 1] : void 0;
|
|
2888
|
+
runInit({ mode, registry });
|
|
2766
2889
|
process.exit(0);
|
|
2767
2890
|
} catch (err) {
|
|
2768
2891
|
process.stderr.write(`[buoy-mcp] init failed: ${err?.message ?? err}
|
package/dist/cli.mjs
CHANGED
|
@@ -3,7 +3,7 @@ import {
|
|
|
3
3
|
SyncClient,
|
|
4
4
|
createMcpServer,
|
|
5
5
|
runInit
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-EC6YNLYA.mjs";
|
|
7
7
|
|
|
8
8
|
// src/cli.ts
|
|
9
9
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
@@ -41,7 +41,13 @@ async function main() {
|
|
|
41
41
|
}
|
|
42
42
|
if (process.argv[2] === "init") {
|
|
43
43
|
try {
|
|
44
|
-
|
|
44
|
+
const args = process.argv.slice(3);
|
|
45
|
+
let mode = "auto";
|
|
46
|
+
if (args.includes("--local")) mode = "local";
|
|
47
|
+
else if (args.includes("--npx")) mode = "npx";
|
|
48
|
+
const ri = args.indexOf("--registry");
|
|
49
|
+
const registry = ri !== -1 ? args[ri + 1] : void 0;
|
|
50
|
+
runInit({ mode, registry });
|
|
45
51
|
process.exit(0);
|
|
46
52
|
} catch (err) {
|
|
47
53
|
process.stderr.write(`[buoy-mcp] init failed: ${err?.message ?? err}
|
package/dist/index.d.mts
CHANGED
|
@@ -132,20 +132,61 @@ declare function createMcpServer(sync: SyncClient): McpServer;
|
|
|
132
132
|
* Idempotent + non-destructive: merges into existing configs (never clobbers
|
|
133
133
|
* other servers), and re-running just refreshes the buoy entry + skill.
|
|
134
134
|
*
|
|
135
|
+
* ## How the server gets launched (and why corporate registries broke it)
|
|
136
|
+
*
|
|
135
137
|
* By default the written config launches the server via
|
|
136
138
|
* `npx -y @buoy-gg/mcp@latest`, so every editor restart re-resolves the newest
|
|
137
|
-
* published version — users can't get pinned to a stale local copy.
|
|
138
|
-
*
|
|
139
|
-
*
|
|
139
|
+
* published version — users can't get pinned to a stale local copy.
|
|
140
|
+
*
|
|
141
|
+
* That default fails hard on machines whose `.npmrc` routes npm at an
|
|
142
|
+
* unreachable private registry (e.g. a corporate `@buoy-gg:registry=` or a
|
|
143
|
+
* global `registry=` pointing at an internal host that doesn't resolve off-VPN).
|
|
144
|
+
* There, `npx @latest` blocks trying to download the package from a host that
|
|
145
|
+
* never answers, the editor's MCP startup times out (~30s), and the server
|
|
146
|
+
* silently never comes up — invisible to the user.
|
|
147
|
+
*
|
|
148
|
+
* So `init` now:
|
|
149
|
+
* 1. Probes the npm registry that WOULD serve `@buoy-gg/mcp`. If it's
|
|
150
|
+
* unreachable, it falls back to a **local install** automatically.
|
|
151
|
+
* 2. Local mode installs a pinned copy into a stable dir (`~/.buoy-mcp`) from
|
|
152
|
+
* the public npm registry (bypassing the broken `.npmrc`) and writes a
|
|
153
|
+
* `node <path>` config — taking the network off the editor-startup path
|
|
154
|
+
* entirely.
|
|
155
|
+
* 3. `--local` / `--npx` force a mode; `--registry <url>` overrides which
|
|
156
|
+
* registry the local install pulls from.
|
|
157
|
+
* 4. A `cliPath` (local-dev escape hatch) still writes a `node <path>` entry
|
|
158
|
+
* pointed at a working build, no publish needed.
|
|
140
159
|
*/
|
|
160
|
+
type InitMode = "auto" | "npx" | "local";
|
|
141
161
|
interface InitOptions {
|
|
142
162
|
/** Project root to install into. @default process.cwd() */
|
|
143
163
|
cwd?: string;
|
|
144
164
|
/**
|
|
145
165
|
* Absolute path to a built cli.js. When set, the config launches that file
|
|
146
|
-
* via `node` instead of `npx @latest` — for local development only.
|
|
166
|
+
* via `node` instead of `npx @latest` — for local development only. Takes
|
|
167
|
+
* precedence over `mode`.
|
|
147
168
|
*/
|
|
148
169
|
cliPath?: string;
|
|
170
|
+
/**
|
|
171
|
+
* Launch strategy for the written config:
|
|
172
|
+
* - `"auto"` (default): npx `@latest`, but fall back to a local install if
|
|
173
|
+
* the npm registry for `@buoy-gg/mcp` is unreachable.
|
|
174
|
+
* - `"npx"`: always write the `npx -y @latest` entry (skip the probe).
|
|
175
|
+
* - `"local"`: always install a pinned copy to a stable dir and point the
|
|
176
|
+
* config at it (no network on editor startup).
|
|
177
|
+
* @default "auto"
|
|
178
|
+
*/
|
|
179
|
+
mode?: InitMode;
|
|
180
|
+
/**
|
|
181
|
+
* Registry to pull the local install from. @default the public npm registry.
|
|
182
|
+
* Only used for `local` (or auto-fallback-to-local) installs.
|
|
183
|
+
*/
|
|
184
|
+
registry?: string;
|
|
185
|
+
/**
|
|
186
|
+
* Stable dir the local install lives in.
|
|
187
|
+
* @default `$BUOY_MCP_HOME` or `~/.buoy-mcp`.
|
|
188
|
+
*/
|
|
189
|
+
installDir?: string;
|
|
149
190
|
/** Where the skill source lives. @default resolved from __dirname (../skill). */
|
|
150
191
|
skillSrcDir?: string;
|
|
151
192
|
log?: (msg: string) => void;
|
package/dist/index.d.ts
CHANGED
|
@@ -132,20 +132,61 @@ declare function createMcpServer(sync: SyncClient): McpServer;
|
|
|
132
132
|
* Idempotent + non-destructive: merges into existing configs (never clobbers
|
|
133
133
|
* other servers), and re-running just refreshes the buoy entry + skill.
|
|
134
134
|
*
|
|
135
|
+
* ## How the server gets launched (and why corporate registries broke it)
|
|
136
|
+
*
|
|
135
137
|
* By default the written config launches the server via
|
|
136
138
|
* `npx -y @buoy-gg/mcp@latest`, so every editor restart re-resolves the newest
|
|
137
|
-
* published version — users can't get pinned to a stale local copy.
|
|
138
|
-
*
|
|
139
|
-
*
|
|
139
|
+
* published version — users can't get pinned to a stale local copy.
|
|
140
|
+
*
|
|
141
|
+
* That default fails hard on machines whose `.npmrc` routes npm at an
|
|
142
|
+
* unreachable private registry (e.g. a corporate `@buoy-gg:registry=` or a
|
|
143
|
+
* global `registry=` pointing at an internal host that doesn't resolve off-VPN).
|
|
144
|
+
* There, `npx @latest` blocks trying to download the package from a host that
|
|
145
|
+
* never answers, the editor's MCP startup times out (~30s), and the server
|
|
146
|
+
* silently never comes up — invisible to the user.
|
|
147
|
+
*
|
|
148
|
+
* So `init` now:
|
|
149
|
+
* 1. Probes the npm registry that WOULD serve `@buoy-gg/mcp`. If it's
|
|
150
|
+
* unreachable, it falls back to a **local install** automatically.
|
|
151
|
+
* 2. Local mode installs a pinned copy into a stable dir (`~/.buoy-mcp`) from
|
|
152
|
+
* the public npm registry (bypassing the broken `.npmrc`) and writes a
|
|
153
|
+
* `node <path>` config — taking the network off the editor-startup path
|
|
154
|
+
* entirely.
|
|
155
|
+
* 3. `--local` / `--npx` force a mode; `--registry <url>` overrides which
|
|
156
|
+
* registry the local install pulls from.
|
|
157
|
+
* 4. A `cliPath` (local-dev escape hatch) still writes a `node <path>` entry
|
|
158
|
+
* pointed at a working build, no publish needed.
|
|
140
159
|
*/
|
|
160
|
+
type InitMode = "auto" | "npx" | "local";
|
|
141
161
|
interface InitOptions {
|
|
142
162
|
/** Project root to install into. @default process.cwd() */
|
|
143
163
|
cwd?: string;
|
|
144
164
|
/**
|
|
145
165
|
* Absolute path to a built cli.js. When set, the config launches that file
|
|
146
|
-
* via `node` instead of `npx @latest` — for local development only.
|
|
166
|
+
* via `node` instead of `npx @latest` — for local development only. Takes
|
|
167
|
+
* precedence over `mode`.
|
|
147
168
|
*/
|
|
148
169
|
cliPath?: string;
|
|
170
|
+
/**
|
|
171
|
+
* Launch strategy for the written config:
|
|
172
|
+
* - `"auto"` (default): npx `@latest`, but fall back to a local install if
|
|
173
|
+
* the npm registry for `@buoy-gg/mcp` is unreachable.
|
|
174
|
+
* - `"npx"`: always write the `npx -y @latest` entry (skip the probe).
|
|
175
|
+
* - `"local"`: always install a pinned copy to a stable dir and point the
|
|
176
|
+
* config at it (no network on editor startup).
|
|
177
|
+
* @default "auto"
|
|
178
|
+
*/
|
|
179
|
+
mode?: InitMode;
|
|
180
|
+
/**
|
|
181
|
+
* Registry to pull the local install from. @default the public npm registry.
|
|
182
|
+
* Only used for `local` (or auto-fallback-to-local) installs.
|
|
183
|
+
*/
|
|
184
|
+
registry?: string;
|
|
185
|
+
/**
|
|
186
|
+
* Stable dir the local install lives in.
|
|
187
|
+
* @default `$BUOY_MCP_HOME` or `~/.buoy-mcp`.
|
|
188
|
+
*/
|
|
189
|
+
installDir?: string;
|
|
149
190
|
/** Where the skill source lives. @default resolved from __dirname (../skill). */
|
|
150
191
|
skillSrcDir?: string;
|
|
151
192
|
log?: (msg: string) => void;
|
package/dist/index.js
CHANGED
|
@@ -371,7 +371,7 @@ var import_node_os = __toESM(require("os"));
|
|
|
371
371
|
var import_node_path = __toESM(require("path"));
|
|
372
372
|
|
|
373
373
|
// src/version.ts
|
|
374
|
-
var VERSION = "7.0.
|
|
374
|
+
var VERSION = "7.0.4" ? "7.0.4" : "0.0.0-dev";
|
|
375
375
|
var PACKAGE_NAME = "@buoy-gg/mcp";
|
|
376
376
|
|
|
377
377
|
// src/update/checkForUpdate.ts
|
|
@@ -2682,13 +2682,17 @@ var deviceIdField = {
|
|
|
2682
2682
|
};
|
|
2683
2683
|
|
|
2684
2684
|
// src/init.ts
|
|
2685
|
+
var import_node_child_process2 = require("child_process");
|
|
2685
2686
|
var import_node_fs3 = __toESM(require("fs"));
|
|
2687
|
+
var import_node_os3 = __toESM(require("os"));
|
|
2686
2688
|
var import_node_path3 = __toESM(require("path"));
|
|
2689
|
+
var PUBLIC_REGISTRY = "https://registry.npmjs.org";
|
|
2690
|
+
var PROBE_TIMEOUT_MS = 3e3;
|
|
2687
2691
|
function runInit(opts = {}) {
|
|
2688
2692
|
const cwd = opts.cwd ?? process.cwd();
|
|
2689
2693
|
const skillSrcDir = opts.skillSrcDir ?? import_node_path3.default.resolve(__dirname, "../skill");
|
|
2690
2694
|
const log = opts.log ?? ((m) => process.stdout.write(m + "\n"));
|
|
2691
|
-
const entry = opts
|
|
2695
|
+
const entry = resolveEntry(opts, log);
|
|
2692
2696
|
const done = [];
|
|
2693
2697
|
upsertMcpConfig(import_node_path3.default.join(cwd, ".mcp.json"), "mcpServers", entry, cwd, done);
|
|
2694
2698
|
if (import_node_fs3.default.existsSync(import_node_path3.default.join(cwd, ".cursor"))) {
|
|
@@ -2705,12 +2709,125 @@ function runInit(opts = {}) {
|
|
|
2705
2709
|
log(" 1. Restart your editor (or reconnect the MCP server) to load `buoy`.");
|
|
2706
2710
|
log(" 2. Open your app with Buoy devtools running on a device or simulator.");
|
|
2707
2711
|
log(' 3. Say "buoy optimize" to start a guided performance pass.');
|
|
2708
|
-
if (
|
|
2712
|
+
if (entry.command === "npx") {
|
|
2709
2713
|
log("");
|
|
2710
2714
|
log(`This launches the server via \`npx -y ${PACKAGE_NAME}@latest\`, so it stays`);
|
|
2711
2715
|
log(`up to date automatically. To force-refresh, re-run \`npx -y ${PACKAGE_NAME}@latest init\`.`);
|
|
2716
|
+
} else if (!opts.cliPath) {
|
|
2717
|
+
log("");
|
|
2718
|
+
log(`This uses a pinned local install (no network on startup). To update it later,`);
|
|
2719
|
+
log(`re-run init (\`node ${entry.args[0]?.replace(/dist[/\\]cli\.js$/, "") ?? ""}...\` \u2014 or reinstall).`);
|
|
2720
|
+
}
|
|
2721
|
+
}
|
|
2722
|
+
function resolveEntry(opts, log) {
|
|
2723
|
+
if (opts.cliPath) {
|
|
2724
|
+
return { type: "stdio", command: "node", args: [opts.cliPath] };
|
|
2725
|
+
}
|
|
2726
|
+
const mode = opts.mode ?? "auto";
|
|
2727
|
+
if (mode === "npx") {
|
|
2728
|
+
return npxEntry();
|
|
2729
|
+
}
|
|
2730
|
+
if (mode === "local") {
|
|
2731
|
+
return localEntry(opts, log);
|
|
2732
|
+
}
|
|
2733
|
+
const probe = probeRegistry();
|
|
2734
|
+
if (probe.reachable) {
|
|
2735
|
+
return npxEntry();
|
|
2736
|
+
}
|
|
2737
|
+
log(
|
|
2738
|
+
`\u26A0 npm registry ${probe.registry ? `(${probe.registry}) ` : ""}looks unreachable \u2014 \`npx @latest\` would hang your editor at startup. Falling back to a pinned local install.`
|
|
2739
|
+
);
|
|
2740
|
+
return localEntry(opts, log);
|
|
2741
|
+
}
|
|
2742
|
+
function npxEntry() {
|
|
2743
|
+
return { type: "stdio", command: "npx", args: ["-y", `${PACKAGE_NAME}@latest`] };
|
|
2744
|
+
}
|
|
2745
|
+
function localEntry(opts, log) {
|
|
2746
|
+
const installDir = opts.installDir ?? process.env.BUOY_MCP_HOME ?? import_node_path3.default.join(import_node_os3.default.homedir(), ".buoy-mcp");
|
|
2747
|
+
const registry = opts.registry ?? PUBLIC_REGISTRY;
|
|
2748
|
+
const cliPath = installLocalCopy(installDir, registry, log);
|
|
2749
|
+
if (cliPath) {
|
|
2750
|
+
return { type: "stdio", command: "node", args: [cliPath] };
|
|
2712
2751
|
}
|
|
2752
|
+
log("\u26A0 local install failed \u2014 writing the npx entry instead (see the error above).");
|
|
2753
|
+
return npxEntry();
|
|
2754
|
+
}
|
|
2755
|
+
function installLocalCopy(installDir, registry, log) {
|
|
2756
|
+
const spec = `${PACKAGE_NAME}@${VERSION === "0.0.0-dev" ? "latest" : VERSION}`;
|
|
2757
|
+
import_node_fs3.default.mkdirSync(installDir, { recursive: true });
|
|
2758
|
+
const npm = process.platform === "win32" ? "npm.cmd" : "npm";
|
|
2759
|
+
log(`Installing ${spec} into ${installDir} from ${registry} \u2026`);
|
|
2760
|
+
const res = (0, import_node_child_process2.spawnSync)(
|
|
2761
|
+
npm,
|
|
2762
|
+
[
|
|
2763
|
+
"install",
|
|
2764
|
+
spec,
|
|
2765
|
+
"--prefix",
|
|
2766
|
+
installDir,
|
|
2767
|
+
"--registry",
|
|
2768
|
+
registry,
|
|
2769
|
+
`--@buoy-gg:registry=${registry}`,
|
|
2770
|
+
"--no-audit",
|
|
2771
|
+
"--no-fund",
|
|
2772
|
+
"--loglevel=error"
|
|
2773
|
+
],
|
|
2774
|
+
{ stdio: ["ignore", "ignore", "inherit"], encoding: "utf8" }
|
|
2775
|
+
);
|
|
2776
|
+
if (res.status !== 0) {
|
|
2777
|
+
log(`\u26A0 npm install exited with code ${res.status ?? "?"}${res.error ? ` (${res.error.message})` : ""}.`);
|
|
2778
|
+
return null;
|
|
2779
|
+
}
|
|
2780
|
+
const cliPath = import_node_path3.default.join(installDir, "node_modules", PACKAGE_NAME, "dist", "cli.js");
|
|
2781
|
+
if (!import_node_fs3.default.existsSync(cliPath)) {
|
|
2782
|
+
log(`\u26A0 install finished but ${cliPath} is missing.`);
|
|
2783
|
+
return null;
|
|
2784
|
+
}
|
|
2785
|
+
return cliPath;
|
|
2713
2786
|
}
|
|
2787
|
+
function probeRegistry() {
|
|
2788
|
+
const registry = effectiveRegistry();
|
|
2789
|
+
if (!registry) return { reachable: false, registry: null };
|
|
2790
|
+
if (/^https?:\/\/registry\.npmjs\.org\/?$/i.test(registry)) {
|
|
2791
|
+
return { reachable: true, registry };
|
|
2792
|
+
}
|
|
2793
|
+
return { reachable: hostReachable(registry), registry };
|
|
2794
|
+
}
|
|
2795
|
+
function effectiveRegistry() {
|
|
2796
|
+
const scoped = npmConfigGet("@buoy-gg:registry");
|
|
2797
|
+
if (scoped) return scoped;
|
|
2798
|
+
const global = npmConfigGet("registry");
|
|
2799
|
+
return global ?? PUBLIC_REGISTRY;
|
|
2800
|
+
}
|
|
2801
|
+
function npmConfigGet(key) {
|
|
2802
|
+
const npm = process.platform === "win32" ? "npm.cmd" : "npm";
|
|
2803
|
+
const res = (0, import_node_child_process2.spawnSync)(npm, ["config", "get", key], { encoding: "utf8", timeout: PROBE_TIMEOUT_MS });
|
|
2804
|
+
if (res.status !== 0 || typeof res.stdout !== "string") return null;
|
|
2805
|
+
const val = res.stdout.trim();
|
|
2806
|
+
if (!val || val === "undefined" || val === "null") return null;
|
|
2807
|
+
return val;
|
|
2808
|
+
}
|
|
2809
|
+
function hostReachable(registry) {
|
|
2810
|
+
const done = (0, import_node_child_process2.spawnSync)(
|
|
2811
|
+
process.execPath,
|
|
2812
|
+
["-e", REACHABILITY_PROBE, registry, String(PROBE_TIMEOUT_MS)],
|
|
2813
|
+
{ encoding: "utf8", timeout: PROBE_TIMEOUT_MS + 2e3 }
|
|
2814
|
+
);
|
|
2815
|
+
return done.status === 0;
|
|
2816
|
+
}
|
|
2817
|
+
var REACHABILITY_PROBE = `
|
|
2818
|
+
const url = process.argv[1];
|
|
2819
|
+
const ms = Number(process.argv[2]) || 3000;
|
|
2820
|
+
const c = new AbortController();
|
|
2821
|
+
const t = setTimeout(() => c.abort(), ms);
|
|
2822
|
+
fetch(url, { method: 'HEAD', signal: c.signal })
|
|
2823
|
+
.then(() => { clearTimeout(t); process.exit(0); })
|
|
2824
|
+
.catch(() => {
|
|
2825
|
+
// A HEAD may be rejected while the host is up; a bare TCP-level failure is
|
|
2826
|
+
// what we care about. fetch throwing a TypeError = network unreachable.
|
|
2827
|
+
clearTimeout(t);
|
|
2828
|
+
process.exit(1);
|
|
2829
|
+
});
|
|
2830
|
+
`;
|
|
2714
2831
|
function upsertMcpConfig(file, key, entry, cwd, done) {
|
|
2715
2832
|
let json3 = {};
|
|
2716
2833
|
if (import_node_fs3.default.existsSync(file)) {
|
package/dist/index.mjs
CHANGED
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@buoy-gg/mcp",
|
|
3
3
|
"mcpName": "io.github.buoy-gg/mcp",
|
|
4
|
-
"version": "7.0.
|
|
4
|
+
"version": "7.0.4",
|
|
5
5
|
"description": "MCP server for Buoy devtools — lets any LLM copilot drive Buoy tools (perf-monitor benchmarks, etc.) over the devtool sync protocol",
|
|
6
6
|
"main": "dist/index.js",
|
|
7
7
|
"module": "dist/index.mjs",
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
32
32
|
"socket.io-client": "^4.8.1",
|
|
33
33
|
"zod": "^3.25.0",
|
|
34
|
-
"@buoy-gg/sync-broker": "7.0.
|
|
34
|
+
"@buoy-gg/sync-broker": "7.0.4"
|
|
35
35
|
},
|
|
36
36
|
"devDependencies": {
|
|
37
37
|
"@types/node": "^22.0.0",
|