@buoy-gg/mcp 7.0.27 → 7.0.29

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/dist/index.js CHANGED
@@ -81,6 +81,33 @@ var DEVICE_RETENTION_MS = 15 * 6e4;
81
81
  var DASHBOARD_NAME = "Dashboard";
82
82
  var ALL_DEVICES = "All";
83
83
 
84
+ // src/sync/actionTimeout.ts
85
+ function isRecoveryAction(toolId, action) {
86
+ return toolId === "app" && (action === "ping" || action === "reloadApp");
87
+ }
88
+ function isUnresponsive(consecutiveTimeouts) {
89
+ return consecutiveTimeouts >= 2;
90
+ }
91
+ function actionTimeoutMs(input) {
92
+ if (isRecoveryAction(input.toolId, input.action)) {
93
+ return { timeoutMs: input.fullMs, usedFullBudget: false };
94
+ }
95
+ if (!isUnresponsive(input.consecutiveTimeouts)) {
96
+ return { timeoutMs: input.fullMs, usedFullBudget: false };
97
+ }
98
+ if (input.now - input.lastFullBudgetAt > input.recoveryProbeMs) {
99
+ return { timeoutMs: input.fullMs, usedFullBudget: true };
100
+ }
101
+ return { timeoutMs: input.shortMs, usedFullBudget: false };
102
+ }
103
+ function diagnoseCrashed(opts) {
104
+ if (opts.consecutiveTimeouts >= 2) return true;
105
+ if (opts.lastOkAt > 0 && opts.now - opts.lastOkAt > opts.unresponsiveAfterMs) {
106
+ return true;
107
+ }
108
+ return false;
109
+ }
110
+
84
111
  // src/sync/SyncClient.ts
85
112
  var SyncClient = class {
86
113
  constructor(opts = {}) {
@@ -349,27 +376,34 @@ var SyncClient = class {
349
376
  noteTimeout(deviceId) {
350
377
  this.healthOf(deviceId).consecutiveTimeouts += 1;
351
378
  }
352
- /** True once a device has missed a call and hasn't answered since. */
379
+ /** True once a device has missed TWO calls and hasn't answered since. */
353
380
  isUnresponsive(deviceId) {
354
- return this.healthOf(deviceId).consecutiveTimeouts > 0;
381
+ return isUnresponsive(this.healthOf(deviceId).consecutiveTimeouts);
355
382
  }
356
383
  /**
357
384
  * Full timeout normally; a short one for a device already not answering.
358
385
  *
359
- * With one escape hatch: every RECOVERY_PROBE_MS a flagged device gets the
360
- * full budget again. Without it the flag is self-sustaining — a device whose
361
- * work legitimately takes longer than the short budget (a big snapshot, a
362
- * slow simulator) can never answer, so it can never clear the flag, and every
363
- * later reply is a confident "most likely CRASHED" about a healthy app.
386
+ * A single missed call does not trigger the short budget — one slow
387
+ * `describeScreen` is not a crash. `app.ping` / `app.reloadApp` always get
388
+ * the full budget (they are the recovery path).
389
+ *
390
+ * Escape hatch: every RECOVERY_PROBE_MS a flagged device gets the full
391
+ * budget again so the short timeout cannot become self-sustaining.
364
392
  */
365
- timeoutFor(deviceId) {
366
- if (!this.isUnresponsive(deviceId)) return this.actionTimeoutMs;
393
+ timeoutFor(deviceId, toolId, action) {
367
394
  const h = this.healthOf(deviceId);
368
- if (Date.now() - h.lastFullBudgetAt > RECOVERY_PROBE_MS) {
369
- h.lastFullBudgetAt = Date.now();
370
- return this.actionTimeoutMs;
371
- }
372
- return UNRESPONSIVE_ACTION_TIMEOUT_MS;
395
+ const { timeoutMs, usedFullBudget } = actionTimeoutMs({
396
+ consecutiveTimeouts: h.consecutiveTimeouts,
397
+ lastFullBudgetAt: h.lastFullBudgetAt,
398
+ now: Date.now(),
399
+ fullMs: this.actionTimeoutMs,
400
+ shortMs: UNRESPONSIVE_ACTION_TIMEOUT_MS,
401
+ recoveryProbeMs: RECOVERY_PROBE_MS,
402
+ toolId,
403
+ action
404
+ });
405
+ if (usedFullBudget) h.lastFullBudgetAt = Date.now();
406
+ return timeoutMs;
373
407
  }
374
408
  /**
375
409
  * Plain-language diagnosis for a device that isn't answering, including the
@@ -382,7 +416,12 @@ var SyncClient = class {
382
416
  unresponsiveNote(deviceId) {
383
417
  const h = this.healthOf(deviceId);
384
418
  const since = h.lastOkAt ? Math.round((Date.now() - h.lastOkAt) / 1e3) : null;
385
- const crashed = h.consecutiveTimeouts >= 2 || h.lastOkAt === 0 || Date.now() - h.lastOkAt > UNRESPONSIVE_AFTER_MS;
419
+ const crashed = diagnoseCrashed({
420
+ consecutiveTimeouts: h.consecutiveTimeouts,
421
+ lastOkAt: h.lastOkAt,
422
+ now: Date.now(),
423
+ unresponsiveAfterMs: UNRESPONSIVE_AFTER_MS
424
+ });
386
425
  const lines = [
387
426
  `The app on "${deviceId}" is not responding (${h.consecutiveTimeouts} call${h.consecutiveTimeouts === 1 ? "" : "s"} unanswered${since != null ? `, last answered ${since}s ago` : ""}).`,
388
427
  crashed ? "It is most likely CRASHED \u2014 a fatal render error unmounts the React tree, and the dev tools live in that tree, so nothing on-device can answer until the app is reloaded." : "It may be busy, mid-reload, or crashing."
@@ -584,7 +623,7 @@ var SyncClient = class {
584
623
  const target = deviceId === ALL_DEVICES ? ALL_DEVICES : this.resolveDevice(deviceId);
585
624
  if (!this.socket) throw new Error("SyncClient is not connected");
586
625
  const requestId = (0, import_node_crypto.randomUUID)();
587
- const timeoutMs = opts.timeoutMs ?? this.timeoutFor(target);
626
+ const timeoutMs = opts.timeoutMs ?? this.timeoutFor(target, toolId, action);
588
627
  const result = await new Promise((resolve, reject) => {
589
628
  const timer = setTimeout(() => {
590
629
  this.pendingActions.delete(requestId);
@@ -647,7 +686,7 @@ var import_node_os = __toESM(require("os"));
647
686
  var import_node_path = __toESM(require("path"));
648
687
 
649
688
  // src/version.ts
650
- var VERSION = "7.0.27" ? "7.0.27" : "0.0.0-dev";
689
+ var VERSION = "7.0.29" ? "7.0.29" : "0.0.0-dev";
651
690
  var PACKAGE_NAME = "@buoy-gg/mcp";
652
691
 
653
692
  // src/update/checkForUpdate.ts
@@ -1673,7 +1712,14 @@ function statusIcon(e) {
1673
1712
  return "\u2705";
1674
1713
  }
1675
1714
  function statusLabel(e) {
1676
- if (e.error) return `ERR ${e.error}`;
1715
+ if (e.status !== void 0 && e.status > 0) return String(e.status);
1716
+ if (e.error) {
1717
+ const cleaned = e.error.replace(/^HTTP\s+\d+:\s*/i, "").trim();
1718
+ if (!cleaned || cleaned === "undefined" || cleaned === "no error") {
1719
+ return "ERR";
1720
+ }
1721
+ return `ERR ${e.error}`;
1722
+ }
1677
1723
  if (e.status === void 0) return "pending";
1678
1724
  return String(e.status);
1679
1725
  }
@@ -2133,10 +2179,20 @@ A rule that never fires is usually one of: the master switch is off, the interce
2133
2179
 
2134
2180
  // src/tools/packs/reactQuery.ts
2135
2181
  var import_zod5 = require("zod");
2136
- var RQ_TOOL_ID = "react-query";
2182
+ var RQ_TOOL_ID = "query";
2183
+ var RQ_TOOL_ID_LEGACY = "react-query";
2137
2184
  var text4 = (s) => ({ content: [{ type: "text", text: s }] });
2138
2185
  var fail5 = (message) => ({ isError: true, content: [{ type: "text", text: message }] });
2139
2186
  var errMsg5 = (e) => e instanceof Error ? e.message : String(e);
2187
+ async function callQueryAction(sync, action, params, target) {
2188
+ try {
2189
+ return await sync.callAction(RQ_TOOL_ID, action, params, target);
2190
+ } catch (e) {
2191
+ const msg = errMsg5(e);
2192
+ if (!/unknown tool/i.test(msg)) throw e;
2193
+ return await sync.callAction(RQ_TOOL_ID_LEGACY, action, params, target);
2194
+ }
2195
+ }
2140
2196
  var STATUS_ICON = {
2141
2197
  fresh: "\u{1F7E2}",
2142
2198
  stale: "\u{1F7E1}",
@@ -2169,8 +2225,8 @@ function registerReactQueryTools(server, sync) {
2169
2225
  async (args) => {
2170
2226
  try {
2171
2227
  const target = sync.requireProDevice(args.deviceId);
2172
- const res = await sync.callAction(
2173
- RQ_TOOL_ID,
2228
+ const res = await callQueryAction(
2229
+ sync,
2174
2230
  "listQueries",
2175
2231
  { includeData: args.includeData ?? false, staleOnly: args.staleOnly ?? false, limit: args.limit ?? 25 },
2176
2232
  target
@@ -2216,13 +2272,13 @@ ${lines.join("\n")}${note}`);
2216
2272
  try {
2217
2273
  const target = sync.requireProDevice(args.deviceId);
2218
2274
  if (args.action === "clear") {
2219
- await sync.callAction(RQ_TOOL_ID, "clearQueryCache", {}, target);
2275
+ await callQueryAction(sync, "clearQueryCache", {}, target);
2220
2276
  return text4("Cleared the entire React Query cache.");
2221
2277
  }
2222
2278
  if (!args.queryHash) {
2223
2279
  return fail5(`'${args.action}' needs a queryHash. Call get_react_query first and use a row's hash (in backticks).`);
2224
2280
  }
2225
- await sync.callAction(RQ_TOOL_ID, args.action, { queryHash: args.queryHash }, target);
2281
+ await callQueryAction(sync, args.action, { queryHash: args.queryHash }, target);
2226
2282
  return text4(`Ran ${args.action} on query \`${args.queryHash}\`.`);
2227
2283
  } catch (e) {
2228
2284
  return fail5(errMsg5(e));
@@ -2649,17 +2705,23 @@ ${shown.map((r) => `- \`${r}\``).join("\n")}`
2649
2705
  server.registerTool(
2650
2706
  "navigate",
2651
2707
  {
2652
- description: "Navigate the app to a route path (e.g. '/pokemon/25' or '/settings'). Use a concrete path \u2014 resolve any dynamic segments (like [id]) to real values first. See get_routes with includeSitemap:true for valid paths.",
2708
+ description: "Navigate the app to a route path (e.g. '/pokemon/25' or '/settings'). Use a concrete path \u2014 resolve any dynamic segments (like [id]) to real values first. See get_routes with includeSitemap:true for valid paths. Pushes by default (back still works); pass replace:true to leave the current screen instead of stacking on top of it (e.g. resetting to '/').",
2653
2709
  inputSchema: {
2654
2710
  path: import_zod10.z.string().describe("The concrete path to navigate to, e.g. '/pokemon/25'."),
2711
+ replace: import_zod10.z.boolean().optional().describe("Replace the current screen instead of pushing (use when resetting, e.g. to '/')."),
2655
2712
  deviceId: import_zod10.z.string().optional().describe("Target device id (auto-selected if one is connected).")
2656
2713
  }
2657
2714
  },
2658
2715
  async (args) => {
2659
2716
  try {
2660
2717
  const target = sync.requireProDevice(args.deviceId);
2661
- await sync.callAction(ROUTES_TOOL_ID, "navigate", { path: args.path }, target);
2662
- return text9(`Navigated to \`${args.path}\`.`);
2718
+ await sync.callAction(
2719
+ ROUTES_TOOL_ID,
2720
+ "navigate",
2721
+ { path: args.path, replace: args.replace === true },
2722
+ target
2723
+ );
2724
+ return text9(`Navigated to \`${args.path}\`${args.replace ? " (replaced current screen)" : ""}.`);
2663
2725
  } catch (e) {
2664
2726
  return fail10(errMsg10(e));
2665
2727
  }
@@ -2959,6 +3021,40 @@ async function cropToBuffer(srcPath, full, region) {
2959
3021
  }
2960
3022
  }
2961
3023
 
3024
+ // src/tools/packs/screenshotLocate.ts
3025
+ function pickDescribeScreenMatch(elements, query, nativeTag) {
3026
+ if (nativeTag != null) {
3027
+ const exact = elements.find((e) => e.nativeTag === nativeTag);
3028
+ if (exact) return exact;
3029
+ }
3030
+ const q = (query ?? "").trim().toLowerCase();
3031
+ if (!q) return void 0;
3032
+ let best;
3033
+ for (const el of elements) {
3034
+ const fields = [el.testID, el.label, el.text, el.name].filter(Boolean).map((s) => s.toLowerCase());
3035
+ let score = 0;
3036
+ for (const f of fields) {
3037
+ if (f === q) score = Math.max(score, 100);
3038
+ else if (f.startsWith(q)) score = Math.max(score, 50);
3039
+ else if (f.includes(q)) score = Math.max(score, 25);
3040
+ }
3041
+ const tap = el.tap;
3042
+ if (tap && tap.x >= 0 && tap.x <= 1 && tap.y >= 0 && tap.y <= 1) score += 20;
3043
+ else if (tap) score -= 40;
3044
+ if (score <= 0) continue;
3045
+ if (!best || score > best.score) best = { el, score };
3046
+ }
3047
+ return best?.el;
3048
+ }
3049
+ function frameToRect(frame, screen) {
3050
+ return {
3051
+ x: frame.x * screen.width,
3052
+ y: frame.y * screen.height,
3053
+ width: frame.width * screen.width,
3054
+ height: frame.height * screen.height
3055
+ };
3056
+ }
3057
+
2962
3058
  // src/tools/packs/screenshot.ts
2963
3059
  var TOOL_ID = "highlight-updates";
2964
3060
  var MARGIN_PT = 12;
@@ -2968,7 +3064,7 @@ var errMsg13 = (e) => e instanceof Error ? e.message : String(e);
2968
3064
  function reasonMessage(res, query) {
2969
3065
  switch (res.reason) {
2970
3066
  case "no-renders":
2971
- return "No components are tracked yet. Navigate to the screen with the target component in the running app (it needs to render at least once), then retry.";
3067
+ return "No components matched via highlight tracking or the live fiber walk. Pass a testID / name from describe_screen, then retry.";
2972
3068
  case "no-match":
2973
3069
  return query ? `Nothing matched "${query}".` : "No query or nativeTag given \u2014 pass one, or a testID/name to search for.";
2974
3070
  case "no-measurement":
@@ -2992,7 +3088,7 @@ function registerScreenshotTools(server, sync) {
2992
3088
  server.registerTool(
2993
3089
  "screenshot_component",
2994
3090
  {
2995
- description: "Take a tight, cropped screenshot of a single React Native component running in the iOS Simulator \u2014 no manual coordinates needed. Give a testID, nativeID, accessibilityLabel, or component name in `query` (or an exact `nativeTag` from a previous call's candidates) and it locates the live component on-device (scrolling it into view), then captures + crops the Simulator framebuffer to just that component. The component must have rendered at least once since the app opened for it to be trackable \u2014 if nothing matches, navigate to the screen that renders it and retry. iOS Simulator only, and the MCP server must be running on the same Mac as the booted Simulator (uses `xcrun simctl` + `sips`). Requires Buoy Pro.",
3091
+ description: "Take a tight, cropped screenshot of a single React Native component running in the iOS Simulator \u2014 no manual coordinates needed. Give a testID, nativeID, accessibilityLabel, or component name in `query` (or an exact `nativeTag` from a previous call's candidates) and it locates the live component on-device (scrolling it into view), then captures + crops the Simulator framebuffer to just that component. If highlight tracking hasn't seen a render yet, it falls back to a live fiber walk (same as describe_screen). iOS Simulator only, and the MCP server must run on the same Mac as the booted Simulator. Requires Buoy Pro.",
2996
3092
  inputSchema: {
2997
3093
  query: import_zod13.z.string().optional().describe('Fuzzy match against testID, nativeID, accessibilityLabel, component name, or view type \u2014 e.g. "submit-button".'),
2998
3094
  nativeTag: import_zod13.z.number().optional().describe("An exact nativeTag (from a previous call's candidates) to skip fuzzy matching."),
@@ -3016,15 +3112,37 @@ function registerScreenshotTools(server, sync) {
3016
3112
  },
3017
3113
  target
3018
3114
  );
3019
- if (!res.matched || !res.rect || !res.screen) {
3020
- return fail13(reasonMessage(res, args.query) + candidatesText(res.candidates));
3115
+ let located = res;
3116
+ if (!located.matched && (located.reason === "no-renders" || located.reason === "no-match")) {
3117
+ const screen = await sync.callAction(TOOL_ID, "describeScreen", {}, target).catch(() => null);
3118
+ const match = screen?.elements ? pickDescribeScreenMatch(screen.elements, args.query, args.nativeTag) : void 0;
3119
+ if (match?.frame && screen?.screen) {
3120
+ located = {
3121
+ matched: true,
3122
+ live: true,
3123
+ rect: frameToRect(match.frame, screen.screen),
3124
+ screen: screen.screen,
3125
+ nativeTag: match.nativeTag ?? void 0,
3126
+ componentName: match.name,
3127
+ testID: match.testID
3128
+ };
3129
+ } else if (located.reason === "no-renders") {
3130
+ located = {
3131
+ matched: false,
3132
+ reason: "no-match",
3133
+ candidates: located.candidates
3134
+ };
3135
+ }
3136
+ }
3137
+ if (!located.matched || !located.rect || !located.screen) {
3138
+ return fail13(reasonMessage(located, args.query) + candidatesText(located.candidates));
3021
3139
  }
3022
3140
  const shot = await captureIosFramebuffer();
3023
3141
  if (!shot.ok || !shot.srcPath || !shot.width || !shot.height) {
3024
3142
  return fail13(shot.error ?? "Screenshot capture failed.");
3025
3143
  }
3026
- const pxScale = shot.width / res.screen.width;
3027
- const r = res.rect;
3144
+ const pxScale = shot.width / located.screen.width;
3145
+ const r = located.rect;
3028
3146
  const pad = CAPTURE_PADDING_PT;
3029
3147
  const crop = await cropToBuffer(
3030
3148
  shot.srcPath,
@@ -3039,9 +3157,9 @@ function registerScreenshotTools(server, sync) {
3039
3157
  if (!crop.ok || !crop.buffer) {
3040
3158
  return fail13(crop.error ?? "Crop failed.");
3041
3159
  }
3042
- const label = res.componentName || "component";
3043
- const idPart = res.testID ? ` (testID: "${res.testID}")` : "";
3044
- const warn = res.inView === false ? "\n\nNote: the component is partly off-screen even after scrolling, so the crop is clamped to the screen edge." : "";
3160
+ const label = located.componentName || "component";
3161
+ const idPart = located.testID ? ` (testID: "${located.testID}")` : "";
3162
+ const warn = located.inView === false ? "\n\nNote: the component is partly off-screen even after scrolling, so the crop is clamped to the screen edge." : "";
3045
3163
  const summary = `Captured "${label}"${idPart} \u2014 ${crop.width}\xD7${crop.height}px.${warn}`;
3046
3164
  return {
3047
3165
  content: [
@@ -3605,6 +3723,19 @@ ${JSON.stringify(slim)}
3605
3723
 
3606
3724
  // src/tools/packs/assets.ts
3607
3725
  var import_zod17 = require("zod");
3726
+
3727
+ // src/tools/packs/assetsGraphNote.ts
3728
+ function graphUnavailableNote(ss) {
3729
+ if (ss.graphSupported && ss.graphCount != null) return null;
3730
+ const err = (ss.graphError ?? "").trim();
3731
+ const looksLikeDev = ss.dev === true || !!err;
3732
+ if (looksLikeDev) {
3733
+ return err ? `\u26A0 Metro bundle graph unavailable (${err}) \u2014 showing runtime-registered assets only; never-loaded detection unavailable.` : "\u26A0 Metro bundle graph unavailable \u2014 showing runtime-registered assets only; never-loaded detection unavailable.";
3734
+ }
3735
+ return "\u26A0 No Metro bundle graph (release build, or graph fetch failed) \u2014 showing runtime-registered assets only; never-loaded detection unavailable.";
3736
+ }
3737
+
3738
+ // src/tools/packs/assets.ts
3608
3739
  var ASSETS_TOOL_ID = "assets";
3609
3740
  var text15 = (s) => ({ content: [{ type: "text", text: s }] });
3610
3741
  var fail17 = (message) => ({ isError: true, content: [{ type: "text", text: message }] });
@@ -3647,11 +3778,8 @@ function formatListMarkdown2(list, limit) {
3647
3778
  if (ss.fontFamilies.length > 0) {
3648
3779
  lines.push(`Loaded font families: ${ss.fontFamilies.join(", ")}`);
3649
3780
  }
3650
- if (!ss.graphSupported || ss.graphCount == null) {
3651
- lines.push(
3652
- "\u26A0 No Metro bundle graph (release build or graph fetch failed) \u2014 showing runtime-registered assets only; never-loaded detection unavailable." + (ss.graphError ? ` (${ss.graphError})` : "")
3653
- );
3654
- }
3781
+ const graphNote = graphUnavailableNote(ss);
3782
+ if (graphNote) lines.push(graphNote);
3655
3783
  if (ss.measuring) lines.push("\u26A0 Size measurement in progress \u2014 byte totals are partial; re-run get_assets shortly.");
3656
3784
  const ins = list.insights;
3657
3785
  const chips = [];
@@ -3940,16 +4068,14 @@ function registerReloadTools(server, sync) {
3940
4068
  const previousRealm = args.wait === false ? null : await ping(sync, target, PROBE_TIMEOUT_MS);
3941
4069
  let ack = null;
3942
4070
  let recovered = false;
3943
- try {
3944
- ack = await sync.callAction(
3945
- APP_TOOL_ID,
3946
- RELOAD_ACTION,
3947
- { strategy: args.strategy ?? "auto" },
3948
- target
3949
- );
3950
- } catch (inAppError) {
4071
+ const tryDevServer = async (inAppError) => {
3951
4072
  const origin = sync.devServerOrigin(target);
3952
- if (!origin) throw inAppError;
4073
+ if (!origin) {
4074
+ if (inAppError) throw inAppError;
4075
+ throw new Error(
4076
+ "The app is not answering and no dev-server origin is known (it was never learned from a successful ping). Reload the app by hand."
4077
+ );
4078
+ }
3953
4079
  const res = await reloadViaDevServer(origin);
3954
4080
  if (!res.ok) {
3955
4081
  throw new Error(
@@ -3957,6 +4083,20 @@ function registerReloadTools(server, sync) {
3957
4083
  );
3958
4084
  }
3959
4085
  recovered = true;
4086
+ };
4087
+ if (sync.isUnresponsive(target) && sync.devServerOrigin(target)) {
4088
+ await tryDevServer();
4089
+ } else {
4090
+ try {
4091
+ ack = await sync.callAction(
4092
+ APP_TOOL_ID,
4093
+ RELOAD_ACTION,
4094
+ { strategy: args.strategy ?? "auto" },
4095
+ target
4096
+ );
4097
+ } catch (inAppError) {
4098
+ await tryDevServer(inAppError);
4099
+ }
3960
4100
  }
3961
4101
  const via = recovered ? ` via the dev server (the app was unresponsive \u2014 its own reload could not be reached)` : ack?.strategy ? ` via ${ack.strategy}` : "";
3962
4102
  if (args.wait === false) {
@@ -4966,9 +5106,8 @@ function runInit(opts = {}) {
4966
5106
  const entry = resolveEntry(opts, log);
4967
5107
  const done = [];
4968
5108
  upsertMcpConfig(import_node_path4.default.join(cwd, ".mcp.json"), "mcpServers", entry, cwd, done);
4969
- if (import_node_fs4.default.existsSync(import_node_path4.default.join(cwd, ".cursor"))) {
4970
- upsertMcpConfig(import_node_path4.default.join(cwd, ".cursor", "mcp.json"), "mcpServers", entry, cwd, done);
4971
- }
5109
+ import_node_fs4.default.mkdirSync(import_node_path4.default.join(cwd, ".cursor"), { recursive: true });
5110
+ upsertMcpConfig(import_node_path4.default.join(cwd, ".cursor", "mcp.json"), "mcpServers", entry, cwd, done);
4972
5111
  if (import_node_fs4.default.existsSync(import_node_path4.default.join(cwd, ".vscode"))) {
4973
5112
  upsertMcpConfig(import_node_path4.default.join(cwd, ".vscode", "mcp.json"), "servers", entry, cwd, done);
4974
5113
  }
package/dist/index.mjs CHANGED
@@ -27,7 +27,7 @@ import {
27
27
  registerStorageTools,
28
28
  registerZustandTools,
29
29
  runInit
30
- } from "./chunk-OFUMTAIL.mjs";
30
+ } from "./chunk-V7PYMDI2.mjs";
31
31
  export {
32
32
  ALL_DEVICES,
33
33
  BROKER_PORT,
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.27",
4
+ "version": "7.0.29",
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",
@@ -32,7 +32,7 @@
32
32
  "socket.io-client": "^4.8.1",
33
33
  "zod": "^3.25.0",
34
34
  "ws": "^8.18.0",
35
- "@buoy-gg/sync-broker": "^7.0.27"
35
+ "@buoy-gg/sync-broker": "^7.0.29"
36
36
  },
37
37
  "devDependencies": {
38
38
  "@types/node": "^22.0.0",