metro-mcp 0.5.0 → 0.5.2

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.
@@ -1,5 +1,4 @@
1
- #!/usr/bin/env bun
2
- // @bun
1
+ #!/usr/bin/env node
3
2
 
4
3
  // src/utils/logger.ts
5
4
  function createLogger(name) {
@@ -99,6 +98,8 @@ function mergeConfig(target, source) {
99
98
  }
100
99
 
101
100
  // src/server.ts
101
+ import { exec } from "child_process";
102
+ import { promisify } from "util";
102
103
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
103
104
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
104
105
  import { z as z20 } from "zod";
@@ -128,7 +129,7 @@ class CDPClient {
128
129
  suppressReconnect = false;
129
130
  _isConnected = false;
130
131
  target = null;
131
- pongReceived = false;
132
+ lastPingAt = 0;
132
133
  requestTimeout = 1e4;
133
134
  keepAliveInterval = 1e4;
134
135
  async connect(target) {
@@ -164,7 +165,7 @@ class CDPClient {
164
165
  const socketForThisConnection = this.ws;
165
166
  this.ws.on("open", () => {
166
167
  this._isConnected = true;
167
- this.pongReceived = true;
168
+ this.lastPingAt = Date.now();
168
169
  this.startKeepAlive();
169
170
  logger2.info(`Connected to ${this.target?.title || "unknown"}`);
170
171
  resolve();
@@ -190,12 +191,9 @@ class CDPClient {
190
191
  }
191
192
  });
192
193
  this.ws.on("ping", () => {
194
+ this.lastPingAt = Date.now();
193
195
  logger2.debug("Received ping from Metro");
194
196
  });
195
- this.ws.on("pong", () => {
196
- this.pongReceived = true;
197
- logger2.debug("Received pong from Metro");
198
- });
199
197
  } catch (err) {
200
198
  reject(err);
201
199
  }
@@ -245,24 +243,16 @@ class CDPClient {
245
243
  }
246
244
  startKeepAlive() {
247
245
  this.stopKeepAlive();
248
- let missedKeepAlives = 0;
249
246
  this.keepAliveTimer = setInterval(() => {
250
247
  if (!this._isConnected || !this.ws)
251
248
  return;
252
- if (!this.pongReceived) {
253
- missedKeepAlives++;
254
- if (missedKeepAlives >= 3) {
255
- logger2.warn(`${missedKeepAlives} consecutive pongs missed \u2014 closing connection`);
256
- try {
257
- this.ws.close();
258
- } catch {}
259
- return;
260
- }
261
- } else {
262
- missedKeepAlives = 0;
249
+ const elapsed = Date.now() - this.lastPingAt;
250
+ if (elapsed > 20000) {
251
+ logger2.warn(`No ping received from Metro in ${elapsed}ms — closing connection`);
252
+ try {
253
+ this.ws.close();
254
+ } catch {}
263
255
  }
264
- this.pongReceived = false;
265
- this.ws.ping();
266
256
  }, this.keepAliveInterval);
267
257
  }
268
258
  stopKeepAlive() {
@@ -524,12 +514,12 @@ function extractCDPExceptionMessage(details, fallback = "Evaluation failed") {
524
514
  // package.json
525
515
  var package_default = {
526
516
  name: "metro-mcp",
527
- version: "0.5.0",
517
+ version: "0.5.2",
528
518
  description: "Plugin-based MCP server for React Native/Expo runtime debugging, inspection, and automation via Metro/CDP",
529
519
  homepage: "https://github.com/steve228uk/metro-mcp",
530
520
  repository: {
531
521
  type: "git",
532
- url: "https://github.com/steve228uk/metro-mcp.git"
522
+ url: "git+https://github.com/steve228uk/metro-mcp.git"
533
523
  },
534
524
  bugs: {
535
525
  url: "https://github.com/steve228uk/metro-mcp/issues"
@@ -565,7 +555,7 @@ var package_default = {
565
555
  clean: "rm -rf dist",
566
556
  build: "bun run clean && bun run build:js && bun run build:bin && bun run build:types",
567
557
  "build:js": "bun build src/index.ts src/plugin.ts --outdir dist --target node --external @modelcontextprotocol/sdk --external zod --external ws && bun build src/client/index.ts --outfile dist/client/index.js --target browser --external @modelcontextprotocol/sdk --external zod && bun build src/client/index.ts --outfile dist/client/index.cjs --target browser --format cjs --external @modelcontextprotocol/sdk --external zod && bun build src/plugin.ts --outfile dist/plugin.cjs --target node --format cjs --external @modelcontextprotocol/sdk --external zod",
568
- "build:bin": "bun build bin/metro-mcp.ts --outfile dist/bin/metro-mcp.js --target bun --external @modelcontextprotocol/sdk --external zod --external ws && chmod +x dist/bin/metro-mcp.js",
558
+ "build:bin": "bun build bin/metro-mcp.ts --outfile dist/bin/metro-mcp.js --target node --external @modelcontextprotocol/sdk --external zod --external ws && chmod +x dist/bin/metro-mcp.js",
569
559
  "build:types": "bunx tsc -p tsconfig.build.json",
570
560
  typecheck: "tsc --noEmit"
571
561
  },
@@ -698,7 +688,7 @@ var consolePlugin = definePlugin({
698
688
  buffer.push({
699
689
  timestamp: Date.now(),
700
690
  level: "info",
701
- message: "\u2500\u2500 Metro rebuilding \u2500\u2500 (file change detected)"
691
+ message: "── Metro rebuilding ── (file change detected)"
702
692
  });
703
693
  }
704
694
  });
@@ -706,7 +696,7 @@ var consolePlugin = definePlugin({
706
696
  buffer.push({
707
697
  timestamp: Date.now(),
708
698
  level: "info",
709
- message: "\u2500\u2500 CDP reconnected \u2500\u2500 (logs during the disconnection gap may be missing)"
699
+ message: "── CDP reconnected ── (logs during the disconnection gap may be missing)"
710
700
  });
711
701
  });
712
702
  ctx.registerTool("get_console_logs", {
@@ -864,7 +854,7 @@ var networkPlugin = definePlugin({
864
854
  return result.map((r) => {
865
855
  const duration = r.endTime ? `${r.endTime - r.startTime}ms` : "pending";
866
856
  const status = r.error ? `ERR: ${r.error}` : `${r.status || "???"}`;
867
- return `${r.method} ${r.url} \u2192 ${status} (${duration})`;
857
+ return `${r.method} ${r.url} ${status} (${duration})`;
868
858
  }).join(`
869
859
  `);
870
860
  }
@@ -1627,7 +1617,7 @@ var componentsPlugin = definePlugin({
1627
1617
  }
1628
1618
  });
1629
1619
  ctx.registerTool("get_testable_elements", {
1630
- description: "Get all elements with testID or accessibilityLabel \u2014 useful for test generation.",
1620
+ description: "Get all elements with testID or accessibilityLabel useful for test generation.",
1631
1621
  parameters: z8.object({}),
1632
1622
  handler: async () => {
1633
1623
  const options = JSON.stringify({
@@ -1749,6 +1739,8 @@ var storagePlugin = definePlugin({
1749
1739
  });
1750
1740
 
1751
1741
  // src/plugins/simulator.ts
1742
+ import { readFile } from "fs/promises";
1743
+ import { existsSync } from "fs";
1752
1744
  import { z as z10 } from "zod";
1753
1745
  var simulatorPlugin = definePlugin({
1754
1746
  name: "simulator",
@@ -1780,10 +1772,9 @@ var simulatorPlugin = definePlugin({
1780
1772
  } else {
1781
1773
  await ctx.exec(`adb exec-out screencap -p > "${tmpFile}"`);
1782
1774
  }
1783
- const file = Bun.file(tmpFile);
1784
- if (await file.exists()) {
1785
- const buffer = await file.arrayBuffer();
1786
- const base64 = Buffer.from(buffer).toString("base64");
1775
+ if (existsSync(tmpFile)) {
1776
+ const buffer = await readFile(tmpFile);
1777
+ const base64 = buffer.toString("base64");
1787
1778
  await ctx.exec(`rm -f "${tmpFile}"`);
1788
1779
  return {
1789
1780
  type: "image",
@@ -2027,6 +2018,7 @@ var deeplinkPlugin = definePlugin({
2027
2018
  });
2028
2019
 
2029
2020
  // src/plugins/ui-interact.ts
2021
+ import { readFile as readFile2 } from "fs/promises";
2030
2022
  import { z as z12 } from "zod";
2031
2023
 
2032
2024
  // src/utils/fiber.ts
@@ -2146,7 +2138,7 @@ var uiInteractPlugin = definePlugin({
2146
2138
  }
2147
2139
  const IDB_INSTALL = "Install IDB with: brew install idb-companion";
2148
2140
  ctx.registerTool("list_elements", {
2149
- description: "Get interactive elements from the current screen via the React component tree. Returns labels, testIDs, and roles \u2014 use label or testID with tap_element.",
2141
+ description: "Get interactive elements from the current screen via the React component tree. Returns labels, testIDs, and roles use label or testID with tap_element.",
2150
2142
  parameters: z12.object({
2151
2143
  interactiveOnly: z12.boolean().default(false).describe("Return only elements with onPress handlers")
2152
2144
  }),
@@ -2273,7 +2265,7 @@ var uiInteractPlugin = definePlugin({
2273
2265
  let content = "";
2274
2266
  try {
2275
2267
  await ctx.exec(`adb shell uiautomator dump /sdcard/uidump.xml && adb pull /sdcard/uidump.xml ${tmpFile} 2>/dev/null`);
2276
- content = await Bun.file(tmpFile).text();
2268
+ content = await readFile2(tmpFile, "utf8");
2277
2269
  } finally {
2278
2270
  await ctx.exec(`rm -f ${tmpFile}`).catch(() => {});
2279
2271
  }
@@ -3100,9 +3092,9 @@ var commandsPlugin = definePlugin({
3100
3092
 
3101
3093
  // src/plugins/test-recorder.ts
3102
3094
  import { z as z16 } from "zod";
3103
- import { readdir, readFile, writeFile, mkdir } from "fs/promises";
3104
- import { homedir } from "os";
3105
- import { join } from "path";
3095
+ import { readdir, readFile as readFile3, writeFile, mkdir } from "node:fs/promises";
3096
+ import { homedir } from "node:os";
3097
+ import { join } from "node:path";
3106
3098
  var CURRENT_ROUTE_JS = `
3107
3099
  (function() {
3108
3100
  try {
@@ -3125,7 +3117,7 @@ var START_RECORDING_JS = `
3125
3117
 
3126
3118
  ${GET_ROUTE_FUNC_JS}
3127
3119
 
3128
- // \u2500\u2500 Intercept Object.freeze: wrap event handlers before React freezes props \u2500\u2500
3120
+ // ── Intercept Object.freeze: wrap event handlers before React freezes props ──
3129
3121
  var origFreeze = Object.freeze;
3130
3122
  Object.freeze = function(obj) {
3131
3123
  if (globalThis.__METRO_MCP_REC_ACTIVE__ && obj && typeof obj === 'object' && !Array.isArray(obj) && !obj.__mcpRec) {
@@ -3223,7 +3215,7 @@ var START_RECORDING_JS = `
3223
3215
  return origFreeze.call(this, obj);
3224
3216
  };
3225
3217
 
3226
- // \u2500\u2500 Force re-render of already-mounted scroll containers \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
3218
+ // ── Force re-render of already-mounted scroll containers ────────────────────
3227
3219
  // Object.freeze only fires on future renders. For scroll views mounted before
3228
3220
  // recording started, we trigger a one-time re-render so our freeze interceptor
3229
3221
  // can wrap their handlers.
@@ -3235,7 +3227,7 @@ var START_RECORDING_JS = `
3235
3227
  var cn = typeof fiber.type === 'string'
3236
3228
  ? fiber.type
3237
3229
  : (fiber.type && (fiber.type.displayName || fiber.type.name)) || '';
3238
- // String checks before regex \u2014 faster for the common case
3230
+ // String checks before regex faster for the common case
3239
3231
  if (cn === 'ScrollView' || cn === 'FlatList' || cn === 'SectionList' ||
3240
3232
  cn === 'VirtualizedList' || cn === 'FlashList' || cn === 'BigList' ||
3241
3233
  cn === 'RecyclerListView' || cn === 'MasonryFlashList') return true;
@@ -3275,7 +3267,7 @@ var START_RECORDING_JS = `
3275
3267
  }
3276
3268
  })();
3277
3269
 
3278
- // \u2500\u2500 Track navigation events on every React commit \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
3270
+ // ── Track navigation events on every React commit ───────────────────────────
3279
3271
  var origCommit = hook.onCommitFiberRoot;
3280
3272
  hook.onCommitFiberRoot = function(id, root) {
3281
3273
  if (globalThis.__METRO_MCP_REC_ACTIVE__) {
@@ -3360,7 +3352,7 @@ var testRecorderPlugin = definePlugin({
3360
3352
  description: "Unified mobile test recorder: captures taps, text entry, swipes and navigation via fiber patching; generates Appium, Maestro, and Detox tests",
3361
3353
  async setup(ctx) {
3362
3354
  ctx.registerTool("start_test_recording", {
3363
- description: "Inject interaction interceptors into the running app via the React fiber tree. " + "Captures taps, text entry, long presses, keyboard submits, and scroll/swipe gestures \u2014 " + "with no changes to your app code. Works with ScrollView, FlatList, SectionList, " + "FlashList, and other scroll containers. " + "Call stop_test_recording when done, then generate_test_from_recording to get the test.",
3355
+ description: "Inject interaction interceptors into the running app via the React fiber tree. " + "Captures taps, text entry, long presses, keyboard submits, and scroll/swipe gestures " + "with no changes to your app code. Works with ScrollView, FlatList, SectionList, " + "FlashList, and other scroll containers. " + "Call stop_test_recording when done, then generate_test_from_recording to get the test.",
3364
3356
  parameters: z16.object({}),
3365
3357
  handler: async () => {
3366
3358
  storedEvents = null;
@@ -3373,7 +3365,7 @@ var testRecorderPlugin = definePlugin({
3373
3365
  injected = false;
3374
3366
  }
3375
3367
  if (!injected) {
3376
- return `Could not inject recording hooks \u2014 ${injectError}`;
3368
+ return `Could not inject recording hooks ${injectError}`;
3377
3369
  }
3378
3370
  const route = await ctx.evalInApp(CURRENT_ROUTE_JS, { timeout: 3000 }).catch(() => null);
3379
3371
  const routeInfo = route ? ` on screen "${route}"` : "";
@@ -3562,7 +3554,7 @@ var testRecorderPlugin = definePlugin({
3562
3554
  const filePath = join(RECORDINGS_DIR, `${safe}.json`);
3563
3555
  let raw;
3564
3556
  try {
3565
- raw = await readFile(filePath, "utf8");
3557
+ raw = await readFile3(filePath, "utf8");
3566
3558
  } catch {
3567
3559
  return `Recording not found: ${filePath}. Call list_test_recordings to see available files.`;
3568
3560
  }
@@ -3883,13 +3875,13 @@ var MAX_DEPTH = 8;
3883
3875
  var MIN_PERCENT = 0.5;
3884
3876
  function bar(value, max) {
3885
3877
  const filled = max > 0 ? Math.max(1, Math.round(value / max * BAR_WIDTH)) : 0;
3886
- return "\u2588".repeat(filled).padEnd(BAR_WIDTH);
3878
+ return "".repeat(filled).padEnd(BAR_WIDTH);
3887
3879
  }
3888
3880
  function barPct(pct) {
3889
- return "\u2588".repeat(Math.max(1, Math.round(pct / 100 * BAR_WIDTH))).padEnd(BAR_WIDTH);
3881
+ return "".repeat(Math.max(1, Math.round(pct / 100 * BAR_WIDTH))).padEnd(BAR_WIDTH);
3890
3882
  }
3891
3883
  function trunc(s, max) {
3892
- return s.length > max ? s.slice(0, max - 1) + "\u2026" : s;
3884
+ return s.length > max ? s.slice(0, max - 1) + "" : s;
3893
3885
  }
3894
3886
  function memoSavings(r) {
3895
3887
  return r.baseDuration > 0 ? parseFloat(((r.baseDuration - r.actualDuration) / r.baseDuration * 100).toFixed(1)) : null;
@@ -3924,7 +3916,7 @@ function buildCpuFlamegraph(profile, analysis) {
3924
3916
  const label = trunc(fnName, 30).padEnd(30);
3925
3917
  const ms = parseFloat(((hasChildren ? total : self) / (sampleCount || 1) * durationMs).toFixed(1));
3926
3918
  const pct = hasChildren ? totalPct : selfPct;
3927
- lines.push(`${indent}${hasChildren ? "\u25BC" : "\u25A0"} ${label} ${pct.toFixed(1).padStart(5)}% ${barPct(pct)} ${ms}ms ${hasChildren ? "total" : "self"}`);
3919
+ lines.push(`${indent}${hasChildren ? "" : ""} ${label} ${pct.toFixed(1).padStart(5)}% ${barPct(pct)} ${ms}ms ${hasChildren ? "total" : "self"}`);
3928
3920
  for (const c of node.children ?? [])
3929
3921
  renderNode(c, depth + 1);
3930
3922
  }
@@ -3975,7 +3967,7 @@ function buildRenderChart(renders) {
3975
3967
  const lines = [];
3976
3968
  const sorted = [...renders].sort((a, b) => b.actualDuration - a.actualDuration);
3977
3969
  const maxActual = sorted[0]?.actualDuration ?? 1;
3978
- lines.push("=== React Renders \u2014 Ranked by Actual Duration ===");
3970
+ lines.push("=== React Renders Ranked by Actual Duration ===");
3979
3971
  const hdr = ` # ${"Component".padEnd(26)} ${"Phase".padEnd(14)} ${"Actual".padStart(8)} ${"Base".padStart(8)} Savings Chart`;
3980
3972
  lines.push(hdr);
3981
3973
  lines.push("-".repeat(hdr.length + BAR_WIDTH));
@@ -4015,7 +4007,7 @@ var DEVTOOLS_START_EXPR = `(function() {
4015
4007
  }
4016
4008
  if (count > 0) return { ok: true, method: 'startProfiling', count: count };
4017
4009
 
4018
- // Path 2: patch onCommitFiberRoot \u2014 works without DevTools backend.
4010
+ // Path 2: patch onCommitFiberRoot works without DevTools backend.
4019
4011
  // React calls this on every commit; fiber.actualDuration is tracked in dev builds.
4020
4012
  if (typeof hook.onCommitFiberRoot === 'undefined') return { error: 'no-hook-method' };
4021
4013
  var orig = hook.onCommitFiberRoot;
@@ -4136,7 +4128,7 @@ var profilerPlugin = definePlugin({
4136
4128
  } else if (lastCpuProfile && lastCpuAnalysis) {
4137
4129
  sections.push(buildCpuFlamegraph(lastCpuProfile, lastCpuAnalysis));
4138
4130
  } else {
4139
- sections.push("(no profile \u2014 call start_profiling, interact, then stop_profiling)");
4131
+ sections.push("(no profile call start_profiling, interact, then stop_profiling)");
4140
4132
  }
4141
4133
  sections.push("");
4142
4134
  try {
@@ -4151,7 +4143,7 @@ ${NOT_SETUP_MSG}`);
4151
4143
  `);
4152
4144
  }
4153
4145
  ctx.registerTool("start_profiling", {
4154
- description: "Start profiling the running React Native app. " + "Primary path: injects into the React DevTools hook (__REACT_DEVTOOLS_GLOBAL_HOOK__) via evalInApp \u2014 " + "captures all component render durations without requiring <Profiler> wrappers, works on all architectures. " + "Fallback (legacy arch only): CDP Profiler domain for JS CPU call-graph sampling. " + "Perform the interaction you want to measure, then call stop_profiling.",
4146
+ description: "Start profiling the running React Native app. " + "Primary path: injects into the React DevTools hook (__REACT_DEVTOOLS_GLOBAL_HOOK__) via evalInApp " + "captures all component render durations without requiring <Profiler> wrappers, works on all architectures. " + "Fallback (legacy arch only): CDP Profiler domain for JS CPU call-graph sampling. " + "Perform the interaction you want to measure, then call stop_profiling.",
4155
4147
  parameters: z17.object({
4156
4148
  samplingInterval: z17.number().int().min(100).max(1e5).default(1000).describe("CDP fallback only: sampling interval in microseconds (default 1000).")
4157
4149
  }),
@@ -4183,7 +4175,7 @@ ${NOT_SETUP_MSG}`);
4183
4175
  await ctx.cdp.send("Profiler.setSamplingInterval", { interval: samplingInterval });
4184
4176
  await ctx.cdp.send("Profiler.start");
4185
4177
  profilingMode = "cdp";
4186
- return `Profiling started via CDP (sampling every ${samplingInterval} \xB5s). Perform the interaction you want to measure, then call stop_profiling.`;
4178
+ return `Profiling started via CDP (sampling every ${samplingInterval} µs). Perform the interaction you want to measure, then call stop_profiling.`;
4187
4179
  } catch (cdpErr) {
4188
4180
  const msg = cdpErr instanceof Error ? cdpErr.message : String(cdpErr);
4189
4181
  if (!msg.includes("Unsupported method") && !msg.includes("not supported")) {
@@ -4195,7 +4187,7 @@ ${NOT_SETUP_MSG}`);
4195
4187
  profilingMode = "console";
4196
4188
  return "Profiling started via console.profile(). Perform the interaction you want to measure, then call stop_profiling.";
4197
4189
  } catch (consoleErr) {
4198
- return `Failed to start profiling: all paths exhausted \u2014 ${consoleErr instanceof Error ? consoleErr.message : String(consoleErr)}`;
4190
+ return `Failed to start profiling: all paths exhausted ${consoleErr instanceof Error ? consoleErr.message : String(consoleErr)}`;
4199
4191
  }
4200
4192
  }
4201
4193
  });
@@ -4213,7 +4205,7 @@ ${NOT_SETUP_MSG}`);
4213
4205
  const raw = await ctx.evalInApp(DEVTOOLS_STOP_EXPR);
4214
4206
  profilingMode = null;
4215
4207
  if (!raw || raw.length === 0) {
4216
- return { mode: "devtools-hook", commitCount: 0, message: "No commits recorded \u2014 profiling window may be too short." };
4208
+ return { mode: "devtools-hook", commitCount: 0, message: "No commits recorded profiling window may be too short." };
4217
4209
  }
4218
4210
  lastDevToolsProfile = raw;
4219
4211
  const byName = new Map;
@@ -4389,7 +4381,7 @@ ${NOT_SETUP_MSG}`);
4389
4381
  const raw = await ctx.evalInApp(DEVTOOLS_STOP_EXPR);
4390
4382
  profilingMode = null;
4391
4383
  if (!raw || raw.length === 0) {
4392
- return { mode: "devtools-hook", commitCount: 0, message: "No commits recorded \u2014 expression may have been too fast." };
4384
+ return { mode: "devtools-hook", commitCount: 0, message: "No commits recorded expression may have been too fast." };
4393
4385
  }
4394
4386
  lastDevToolsProfile = raw;
4395
4387
  const byName = new Map;
@@ -4566,11 +4558,11 @@ var promptsPlugin = definePlugin({
4566
4558
  a. Stop CPU profiling and get the analysis (stop_profiling)
4567
4559
  b. Read React render timings (get_react_renders)
4568
4560
  c. Read the flamegraph resource (metro://profiler/flamegraph) for a combined visual breakdown
4569
- d. Check for slow network requests (search_network \u2014 look for responses > 1s)
4561
+ d. Check for slow network requests (search_network look for responses > 1s)
4570
4562
  e. Check console logs for perf warnings (get_console_logs with search="slow" or "perf")
4571
4563
  6. Summarize:
4572
- - Top JS CPU hotspots by self time \u2014 which function and file is burning the most CPU
4573
- - Slowest React component renders and whether memoization (memo/useMemo) is helping \u2014 compare actualDuration vs baseDuration
4564
+ - Top JS CPU hotspots by self time which function and file is burning the most CPU
4565
+ - Slowest React component renders and whether memoization (memo/useMemo) is helping compare actualDuration vs baseDuration
4574
4566
  - Components that re-render frequently (high count in the summary)
4575
4567
  - Any slow network requests contributing to perceived slowness
4576
4568
  - Concrete, prioritised recommendations`
@@ -4628,7 +4620,7 @@ var promptsPlugin = definePlugin({
4628
4620
  handler: async (args) => {
4629
4621
  const format = args.format || "appium";
4630
4622
  const platform = args.platform || "ios";
4631
- const bundleIdNote = args.bundleId ? `bundleId: "${args.bundleId}"` : "bundleId: not provided \u2014 you will need to add it manually to the generated test or pass it as a parameter";
4623
+ const bundleIdNote = args.bundleId ? `bundleId: "${args.bundleId}"` : "bundleId: not provided you will need to add it manually to the generated test or pass it as a parameter";
4632
4624
  return [
4633
4625
  {
4634
4626
  role: "user",
@@ -4713,7 +4705,7 @@ Please follow this systematic process:
4713
4705
  6. **Capture allocations**: Call stop_heap_sampling to see which functions allocated the most memory
4714
4706
  7. **Final memory**: Call get_memory_info to measure total heap growth
4715
4707
  8. **Analysis**:
4716
- - Compare baseline vs final heap usage \u2014 how much did it grow?
4708
+ - Compare baseline vs final heap usage how much did it grow?
4717
4709
  - Which functions in the heap sampling report are allocating unexpectedly (look for non-GC-friendly objects)?
4718
4710
  - Check if any components from the scenario appear in the top allocations
4719
4711
  - Look for retained closures, large arrays, or repeated DOM/fiber allocations
@@ -4736,7 +4728,7 @@ Please follow these steps:
4736
4728
 
4737
4729
  1. **Collect errors**: Call get_errors to retrieve all recent exceptions with stack traces
4738
4730
  2. **Symbolicate**: For each error, call symbolicate with the stack trace to get readable file/line references
4739
- 3. **Check logs**: Call get_console_logs \u2014 look for warnings or errors immediately before the crash timestamp
4731
+ 3. **Check logs**: Call get_console_logs look for warnings or errors immediately before the crash timestamp
4740
4732
  4. **Current state**:
4741
4733
  - Call get_current_route to see what screen the app is on
4742
4734
  - Call take_screenshot to capture the current visual state
@@ -4786,7 +4778,7 @@ var automationPlugin = definePlugin({
4786
4778
  description: "Wait and polling tools for reliable E2E automation with async state changes",
4787
4779
  async setup(ctx) {
4788
4780
  ctx.registerTool("wait_for_element", {
4789
- description: "Poll the component tree until an element matching the given testID or accessibilityLabel appears. " + "Returns element info on success. Use after tap_element, navigate(), or any action that triggers " + "async screen transitions or data loading \u2014 instead of immediately calling the next tool.",
4781
+ description: "Poll the component tree until an element matching the given testID or accessibilityLabel appears. " + "Returns element info on success. Use after tap_element, navigate(), or any action that triggers " + "async screen transitions or data loading instead of immediately calling the next tool.",
4790
4782
  parameters: z18.object({
4791
4783
  selector: z18.string().describe("testID or accessibilityLabel to wait for"),
4792
4784
  timeout: z18.number().int().min(100).max(60000).default(1e4).describe("Maximum wait time in milliseconds (default 10000)"),
@@ -4853,9 +4845,9 @@ var automationPlugin = definePlugin({
4853
4845
  });
4854
4846
 
4855
4847
  // src/plugins/statusline.ts
4856
- import { writeFileSync, mkdirSync } from "fs";
4857
- import { homedir as homedir2 } from "os";
4858
- import { join as join2 } from "path";
4848
+ import { writeFileSync, mkdirSync } from "node:fs";
4849
+ import { homedir as homedir2 } from "node:os";
4850
+ import { join as join2 } from "node:path";
4859
4851
  import { z as z19 } from "zod";
4860
4852
  var STATUS_FILE = "/tmp/metro-mcp-status.json";
4861
4853
  var CLAUDE_DIR = join2(homedir2(), ".claude");
@@ -4863,12 +4855,12 @@ var SCRIPT_PATH = join2(CLAUDE_DIR, "metro-mcp-statusline.sh");
4863
4855
  var SCRIPT_CONTENT = `#!/bin/bash
4864
4856
  # Metro MCP status line for Claude Code
4865
4857
  # Shows CDP connection status of the Metro bundler.
4866
- # Auto-generated by metro-mcp \u2014 re-run setup_statusline to regenerate.
4858
+ # Auto-generated by metro-mcp re-run setup_statusline to regenerate.
4867
4859
 
4868
4860
  STATUS_FILE="/tmp/metro-mcp-status.json"
4869
4861
 
4870
4862
  if [ ! -f "$STATUS_FILE" ]; then
4871
- printf "\\033[2mMetro \u25CB\\033[0m\\n"
4863
+ printf "\\033[2mMetro ○\\033[0m\\n"
4872
4864
  exit 0
4873
4865
  fi
4874
4866
 
@@ -4877,9 +4869,9 @@ HOST=$(jq -r '.host' "$STATUS_FILE" 2>/dev/null)
4877
4869
  PORT=$(jq -r '.port' "$STATUS_FILE" 2>/dev/null)
4878
4870
 
4879
4871
  if [ "$CONNECTED" = "true" ]; then
4880
- printf "\\033[32mMetro \u25CF %s:%s\\033[0m\\n" "$HOST" "$PORT"
4872
+ printf "\\033[32mMetro %s:%s\\033[0m\\n" "$HOST" "$PORT"
4881
4873
  else
4882
- printf "\\033[31mMetro \u25CF\\033[0m\\n"
4874
+ printf "\\033[31mMetro ●\\033[0m\\n"
4883
4875
  fi
4884
4876
  `;
4885
4877
  function writeStatus(data) {
@@ -4891,7 +4883,12 @@ var statuslinePlugin = definePlugin({
4891
4883
  name: "statusline",
4892
4884
  description: "Writes CDP connection state to a file for Claude Code status bar integration",
4893
4885
  async setup(ctx) {
4894
- function write(connected) {
4886
+ let lastConnected = null;
4887
+ function write() {
4888
+ const connected = ctx.cdp.isConnected();
4889
+ if (connected === lastConnected)
4890
+ return;
4891
+ lastConnected = connected;
4895
4892
  const target = ctx.cdp.getTarget();
4896
4893
  writeStatus({
4897
4894
  connected,
@@ -4901,11 +4898,12 @@ var statuslinePlugin = definePlugin({
4901
4898
  updatedAt: Date.now()
4902
4899
  });
4903
4900
  }
4904
- ctx.cdp.on("reconnected", () => write(true));
4905
- ctx.cdp.on("disconnected", () => write(false));
4906
- write(ctx.cdp.isConnected());
4901
+ ctx.cdp.on("reconnected", write);
4902
+ ctx.cdp.on("disconnected", write);
4903
+ setInterval(write, 5000);
4904
+ write();
4907
4905
  ctx.registerTool("setup_statusline", {
4908
- description: "Writes the Metro CDP connection status script to ~/.claude/metro-mcp-statusline.sh. " + "Does not modify settings.json \u2014 tell the user to add it to their status line themselves " + '(e.g. ask Claude: "/statusline add the script at ~/.claude/metro-mcp-statusline.sh"). ' + "Only works with Claude Code.",
4906
+ description: "Writes the Metro CDP connection status script to ~/.claude/metro-mcp-statusline.sh. " + "Does not modify settings.json tell the user to add it to their status line themselves " + '(e.g. ask Claude: "/statusline add the script at ~/.claude/metro-mcp-statusline.sh"). ' + "Only works with Claude Code.",
4909
4907
  parameters: z19.object({}),
4910
4908
  handler: async () => {
4911
4909
  mkdirSync(CLAUDE_DIR, { recursive: true });
@@ -4923,6 +4921,7 @@ var statuslinePlugin = definePlugin({
4923
4921
  });
4924
4922
 
4925
4923
  // src/server.ts
4924
+ var execAsync = promisify(exec);
4926
4925
  var logger6 = createLogger("server");
4927
4926
  var BUILT_IN_PLUGINS = [
4928
4927
  consolePlugin,
@@ -5094,16 +5093,7 @@ async function startServer(config) {
5094
5093
  }
5095
5094
  },
5096
5095
  exec: async (command) => {
5097
- const proc = Bun.spawn(["sh", "-c", command], {
5098
- stdout: "pipe",
5099
- stderr: "pipe"
5100
- });
5101
- const stdout = await new Response(proc.stdout).text();
5102
- const stderr = await new Response(proc.stderr).text();
5103
- await proc.exited;
5104
- if (proc.exitCode !== 0) {
5105
- throw new Error(stderr || `Command failed with exit code ${proc.exitCode}`);
5106
- }
5096
+ const { stdout } = await execAsync(command);
5107
5097
  return stdout;
5108
5098
  },
5109
5099
  format: formatUtils
@@ -5198,7 +5188,7 @@ async function main() {
5198
5188
  const args = process.argv.slice(2);
5199
5189
  if (args.includes("--help") || args.includes("-h")) {
5200
5190
  console.error(`
5201
- metro-mcp \u2014 React Native MCP Server
5191
+ metro-mcp React Native MCP Server
5202
5192
 
5203
5193
  Usage:
5204
5194
  metro-mcp [options]
package/dist/index.d.ts CHANGED
@@ -1,3 +1,3 @@
1
- #!/usr/bin/env bun
1
+ #!/usr/bin/env node
2
2
  export {};
3
3
  //# sourceMappingURL=index.d.ts.map
package/dist/index.js CHANGED
@@ -1,5 +1,4 @@
1
- #!/usr/bin/env bun
2
- // @bun
1
+ #!/usr/bin/env node
3
2
 
4
3
  // src/plugin.ts
5
4
  function definePlugin(plugin) {
@@ -107,6 +106,8 @@ function mergeConfig(target, source) {
107
106
  }
108
107
 
109
108
  // src/server.ts
109
+ import { exec } from "child_process";
110
+ import { promisify } from "util";
110
111
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
111
112
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
112
113
  import { z as z20 } from "zod";
@@ -136,7 +137,7 @@ class CDPClient {
136
137
  suppressReconnect = false;
137
138
  _isConnected = false;
138
139
  target = null;
139
- pongReceived = false;
140
+ lastPingAt = 0;
140
141
  requestTimeout = 1e4;
141
142
  keepAliveInterval = 1e4;
142
143
  async connect(target) {
@@ -172,7 +173,7 @@ class CDPClient {
172
173
  const socketForThisConnection = this.ws;
173
174
  this.ws.on("open", () => {
174
175
  this._isConnected = true;
175
- this.pongReceived = true;
176
+ this.lastPingAt = Date.now();
176
177
  this.startKeepAlive();
177
178
  logger2.info(`Connected to ${this.target?.title || "unknown"}`);
178
179
  resolve();
@@ -198,12 +199,9 @@ class CDPClient {
198
199
  }
199
200
  });
200
201
  this.ws.on("ping", () => {
202
+ this.lastPingAt = Date.now();
201
203
  logger2.debug("Received ping from Metro");
202
204
  });
203
- this.ws.on("pong", () => {
204
- this.pongReceived = true;
205
- logger2.debug("Received pong from Metro");
206
- });
207
205
  } catch (err) {
208
206
  reject(err);
209
207
  }
@@ -253,24 +251,16 @@ class CDPClient {
253
251
  }
254
252
  startKeepAlive() {
255
253
  this.stopKeepAlive();
256
- let missedKeepAlives = 0;
257
254
  this.keepAliveTimer = setInterval(() => {
258
255
  if (!this._isConnected || !this.ws)
259
256
  return;
260
- if (!this.pongReceived) {
261
- missedKeepAlives++;
262
- if (missedKeepAlives >= 3) {
263
- logger2.warn(`${missedKeepAlives} consecutive pongs missed — closing connection`);
264
- try {
265
- this.ws.close();
266
- } catch {}
267
- return;
268
- }
269
- } else {
270
- missedKeepAlives = 0;
257
+ const elapsed = Date.now() - this.lastPingAt;
258
+ if (elapsed > 20000) {
259
+ logger2.warn(`No ping received from Metro in ${elapsed}ms — closing connection`);
260
+ try {
261
+ this.ws.close();
262
+ } catch {}
271
263
  }
272
- this.pongReceived = false;
273
- this.ws.ping();
274
264
  }, this.keepAliveInterval);
275
265
  }
276
266
  stopKeepAlive() {
@@ -532,12 +522,12 @@ function extractCDPExceptionMessage(details, fallback = "Evaluation failed") {
532
522
  // package.json
533
523
  var package_default = {
534
524
  name: "metro-mcp",
535
- version: "0.5.0",
525
+ version: "0.5.2",
536
526
  description: "Plugin-based MCP server for React Native/Expo runtime debugging, inspection, and automation via Metro/CDP",
537
527
  homepage: "https://github.com/steve228uk/metro-mcp",
538
528
  repository: {
539
529
  type: "git",
540
- url: "https://github.com/steve228uk/metro-mcp.git"
530
+ url: "git+https://github.com/steve228uk/metro-mcp.git"
541
531
  },
542
532
  bugs: {
543
533
  url: "https://github.com/steve228uk/metro-mcp/issues"
@@ -573,7 +563,7 @@ var package_default = {
573
563
  clean: "rm -rf dist",
574
564
  build: "bun run clean && bun run build:js && bun run build:bin && bun run build:types",
575
565
  "build:js": "bun build src/index.ts src/plugin.ts --outdir dist --target node --external @modelcontextprotocol/sdk --external zod --external ws && bun build src/client/index.ts --outfile dist/client/index.js --target browser --external @modelcontextprotocol/sdk --external zod && bun build src/client/index.ts --outfile dist/client/index.cjs --target browser --format cjs --external @modelcontextprotocol/sdk --external zod && bun build src/plugin.ts --outfile dist/plugin.cjs --target node --format cjs --external @modelcontextprotocol/sdk --external zod",
576
- "build:bin": "bun build bin/metro-mcp.ts --outfile dist/bin/metro-mcp.js --target bun --external @modelcontextprotocol/sdk --external zod --external ws && chmod +x dist/bin/metro-mcp.js",
566
+ "build:bin": "bun build bin/metro-mcp.ts --outfile dist/bin/metro-mcp.js --target node --external @modelcontextprotocol/sdk --external zod --external ws && chmod +x dist/bin/metro-mcp.js",
577
567
  "build:types": "bunx tsc -p tsconfig.build.json",
578
568
  typecheck: "tsc --noEmit"
579
569
  },
@@ -1752,6 +1742,8 @@ var storagePlugin = definePlugin({
1752
1742
  });
1753
1743
 
1754
1744
  // src/plugins/simulator.ts
1745
+ import { readFile } from "fs/promises";
1746
+ import { existsSync } from "fs";
1755
1747
  import { z as z10 } from "zod";
1756
1748
  var simulatorPlugin = definePlugin({
1757
1749
  name: "simulator",
@@ -1783,10 +1775,9 @@ var simulatorPlugin = definePlugin({
1783
1775
  } else {
1784
1776
  await ctx.exec(`adb exec-out screencap -p > "${tmpFile}"`);
1785
1777
  }
1786
- const file = Bun.file(tmpFile);
1787
- if (await file.exists()) {
1788
- const buffer = await file.arrayBuffer();
1789
- const base64 = Buffer.from(buffer).toString("base64");
1778
+ if (existsSync(tmpFile)) {
1779
+ const buffer = await readFile(tmpFile);
1780
+ const base64 = buffer.toString("base64");
1790
1781
  await ctx.exec(`rm -f "${tmpFile}"`);
1791
1782
  return {
1792
1783
  type: "image",
@@ -2030,6 +2021,7 @@ var deeplinkPlugin = definePlugin({
2030
2021
  });
2031
2022
 
2032
2023
  // src/plugins/ui-interact.ts
2024
+ import { readFile as readFile2 } from "fs/promises";
2033
2025
  import { z as z12 } from "zod";
2034
2026
 
2035
2027
  // src/utils/fiber.ts
@@ -2276,7 +2268,7 @@ var uiInteractPlugin = definePlugin({
2276
2268
  let content = "";
2277
2269
  try {
2278
2270
  await ctx.exec(`adb shell uiautomator dump /sdcard/uidump.xml && adb pull /sdcard/uidump.xml ${tmpFile} 2>/dev/null`);
2279
- content = await Bun.file(tmpFile).text();
2271
+ content = await readFile2(tmpFile, "utf8");
2280
2272
  } finally {
2281
2273
  await ctx.exec(`rm -f ${tmpFile}`).catch(() => {});
2282
2274
  }
@@ -3103,7 +3095,7 @@ var commandsPlugin = definePlugin({
3103
3095
 
3104
3096
  // src/plugins/test-recorder.ts
3105
3097
  import { z as z16 } from "zod";
3106
- import { readdir, readFile, writeFile, mkdir } from "node:fs/promises";
3098
+ import { readdir, readFile as readFile3, writeFile, mkdir } from "node:fs/promises";
3107
3099
  import { homedir } from "node:os";
3108
3100
  import { join } from "node:path";
3109
3101
  var CURRENT_ROUTE_JS = `
@@ -3565,7 +3557,7 @@ var testRecorderPlugin = definePlugin({
3565
3557
  const filePath = join(RECORDINGS_DIR, `${safe}.json`);
3566
3558
  let raw;
3567
3559
  try {
3568
- raw = await readFile(filePath, "utf8");
3560
+ raw = await readFile3(filePath, "utf8");
3569
3561
  } catch {
3570
3562
  return `Recording not found: ${filePath}. Call list_test_recordings to see available files.`;
3571
3563
  }
@@ -4894,7 +4886,12 @@ var statuslinePlugin = definePlugin({
4894
4886
  name: "statusline",
4895
4887
  description: "Writes CDP connection state to a file for Claude Code status bar integration",
4896
4888
  async setup(ctx) {
4897
- function write(connected) {
4889
+ let lastConnected = null;
4890
+ function write() {
4891
+ const connected = ctx.cdp.isConnected();
4892
+ if (connected === lastConnected)
4893
+ return;
4894
+ lastConnected = connected;
4898
4895
  const target = ctx.cdp.getTarget();
4899
4896
  writeStatus({
4900
4897
  connected,
@@ -4904,9 +4901,10 @@ var statuslinePlugin = definePlugin({
4904
4901
  updatedAt: Date.now()
4905
4902
  });
4906
4903
  }
4907
- ctx.cdp.on("reconnected", () => write(true));
4908
- ctx.cdp.on("disconnected", () => write(false));
4909
- write(ctx.cdp.isConnected());
4904
+ ctx.cdp.on("reconnected", write);
4905
+ ctx.cdp.on("disconnected", write);
4906
+ setInterval(write, 5000);
4907
+ write();
4910
4908
  ctx.registerTool("setup_statusline", {
4911
4909
  description: "Writes the Metro CDP connection status script to ~/.claude/metro-mcp-statusline.sh. " + "Does not modify settings.json — tell the user to add it to their status line themselves " + '(e.g. ask Claude: "/statusline add the script at ~/.claude/metro-mcp-statusline.sh"). ' + "Only works with Claude Code.",
4912
4910
  parameters: z19.object({}),
@@ -4926,6 +4924,7 @@ var statuslinePlugin = definePlugin({
4926
4924
  });
4927
4925
 
4928
4926
  // src/server.ts
4927
+ var execAsync = promisify(exec);
4929
4928
  var logger6 = createLogger("server");
4930
4929
  var BUILT_IN_PLUGINS = [
4931
4930
  consolePlugin,
@@ -5097,16 +5096,7 @@ async function startServer(config) {
5097
5096
  }
5098
5097
  },
5099
5098
  exec: async (command) => {
5100
- const proc = Bun.spawn(["sh", "-c", command], {
5101
- stdout: "pipe",
5102
- stderr: "pipe"
5103
- });
5104
- const stdout = await new Response(proc.stdout).text();
5105
- const stderr = await new Response(proc.stderr).text();
5106
- await proc.exited;
5107
- if (proc.exitCode !== 0) {
5108
- throw new Error(stderr || `Command failed with exit code ${proc.exitCode}`);
5109
- }
5099
+ const { stdout } = await execAsync(command);
5110
5100
  return stdout;
5111
5101
  },
5112
5102
  format: formatUtils
@@ -5195,28 +5185,13 @@ async function startServer(config) {
5195
5185
  connectToMetro();
5196
5186
  }
5197
5187
 
5198
- // src/utils/logger.ts
5199
- function createLogger2(name) {
5200
- const prefix = `[metro-mcp:${name}]`;
5201
- return {
5202
- info: (msg, ...args) => console.error(prefix, msg, ...args),
5203
- warn: (msg, ...args) => console.error(prefix, "WARN:", msg, ...args),
5204
- error: (msg, ...args) => console.error(prefix, "ERROR:", msg, ...args),
5205
- debug: (msg, ...args) => {
5206
- if (process.env.DEBUG) {
5207
- console.error(prefix, "DEBUG:", msg, ...args);
5208
- }
5209
- }
5210
- };
5211
- }
5212
-
5213
5188
  // src/index.ts
5214
- var logger7 = createLogger2("main");
5189
+ var logger7 = createLogger("main");
5215
5190
  async function main() {
5216
5191
  const args = process.argv.slice(2);
5217
5192
  if (args.includes("--help") || args.includes("-h")) {
5218
5193
  console.error(`
5219
- metro-mcp \u2014 React Native MCP Server
5194
+ metro-mcp React Native MCP Server
5220
5195
 
5221
5196
  Usage:
5222
5197
  metro-mcp [options]
@@ -19,7 +19,7 @@ export declare class CDPClient implements CDPConnection {
19
19
  private suppressReconnect;
20
20
  private _isConnected;
21
21
  private target;
22
- private pongReceived;
22
+ private lastPingAt;
23
23
  private readonly requestTimeout;
24
24
  private readonly keepAliveInterval;
25
25
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"connection.d.ts","sourceRoot":"","sources":["../../src/metro/connection.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAClD,OAAO,KAAK,EAA2B,WAAW,EAAE,MAAM,YAAY,CAAC;AAMvE,KAAK,eAAe,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,IAAI,CAAC;AAQjE;;;;;;;GAOG;AACH,qBAAa,SAAU,YAAW,aAAa;IAC7C,OAAO,CAAC,EAAE,CAA0B;IACpC,OAAO,CAAC,SAAS,CAAK;IACtB,OAAO,CAAC,eAAe,CAAqC;IAC5D,OAAO,CAAC,aAAa,CAA2C;IAChE,OAAO,CAAC,cAAc,CAA+C;IACrE,OAAO,CAAC,iBAAiB,CAA8B;IACvD,OAAO,CAAC,iBAAiB,CAAS;IAClC,OAAO,CAAC,YAAY,CAAS;IAC7B,OAAO,CAAC,MAAM,CAA4B;IAC1C,OAAO,CAAC,YAAY,CAAS;IAE7B,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAS;IACxC,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CAAS;IAE3C;;OAEG;IACG,OAAO,CAAC,MAAM,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC;IAU3C,iBAAiB,IAAI,OAAO,CAAC,OAAO,CAAC;IAQ3C,OAAO,CAAC,SAAS;IA6DjB;;OAEG;IACG,IAAI,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO,CAAC,OAAO,CAAC;IAoB9E;;OAEG;IACH,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,eAAe,GAAG,IAAI;IAOjD;;OAEG;IACH,GAAG,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,eAAe,GAAG,IAAI;IAIlD;;OAEG;IACH,WAAW,IAAI,OAAO;IAItB;;OAEG;IACH,SAAS,IAAI,WAAW,GAAG,IAAI;IAI/B;;OAEG;IACH,UAAU,IAAI,IAAI;IAWlB,OAAO,CAAC,cAAc;IAsBtB,OAAO,CAAC,aAAa;IAOrB,OAAO,CAAC,aAAa;IAuCrB,OAAO,CAAC,gBAAgB;IAQxB,OAAO,CAAC,IAAI;CAYb"}
1
+ {"version":3,"file":"connection.d.ts","sourceRoot":"","sources":["../../src/metro/connection.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAClD,OAAO,KAAK,EAA2B,WAAW,EAAE,MAAM,YAAY,CAAC;AAMvE,KAAK,eAAe,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,IAAI,CAAC;AAQjE;;;;;;;GAOG;AACH,qBAAa,SAAU,YAAW,aAAa;IAC7C,OAAO,CAAC,EAAE,CAA0B;IACpC,OAAO,CAAC,SAAS,CAAK;IACtB,OAAO,CAAC,eAAe,CAAqC;IAC5D,OAAO,CAAC,aAAa,CAA2C;IAChE,OAAO,CAAC,cAAc,CAA+C;IACrE,OAAO,CAAC,iBAAiB,CAA8B;IACvD,OAAO,CAAC,iBAAiB,CAAS;IAClC,OAAO,CAAC,YAAY,CAAS;IAC7B,OAAO,CAAC,MAAM,CAA4B;IAC1C,OAAO,CAAC,UAAU,CAAK;IAEvB,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAS;IACxC,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CAAS;IAE3C;;OAEG;IACG,OAAO,CAAC,MAAM,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC;IAU3C,iBAAiB,IAAI,OAAO,CAAC,OAAO,CAAC;IAQ3C,OAAO,CAAC,SAAS;IAwDjB;;OAEG;IACG,IAAI,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO,CAAC,OAAO,CAAC;IAoB9E;;OAEG;IACH,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,eAAe,GAAG,IAAI;IAOjD;;OAEG;IACH,GAAG,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,eAAe,GAAG,IAAI;IAIlD;;OAEG;IACH,WAAW,IAAI,OAAO;IAItB;;OAEG;IACH,SAAS,IAAI,WAAW,GAAG,IAAI;IAI/B;;OAEG;IACH,UAAU,IAAI,IAAI;IAWlB,OAAO,CAAC,cAAc;IActB,OAAO,CAAC,aAAa;IAOrB,OAAO,CAAC,aAAa;IAuCrB,OAAO,CAAC,gBAAgB;IAQxB,OAAO,CAAC,IAAI;CAYb"}
@@ -1 +1 @@
1
- {"version":3,"file":"simulator.d.ts","sourceRoot":"","sources":["../../src/plugins/simulator.ts"],"names":[],"mappings":"AAGA,eAAO,MAAM,eAAe,yCA2N1B,CAAC"}
1
+ {"version":3,"file":"simulator.d.ts","sourceRoot":"","sources":["../../src/plugins/simulator.ts"],"names":[],"mappings":"AAKA,eAAO,MAAM,eAAe,yCA0N1B,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"statusline.d.ts","sourceRoot":"","sources":["../../src/plugins/statusline.ts"],"names":[],"mappings":"AAMA,eAAO,MAAM,WAAW,+BAA+B,CAAC;AA4CxD,eAAO,MAAM,gBAAgB,yCAyC3B,CAAC"}
1
+ {"version":3,"file":"statusline.d.ts","sourceRoot":"","sources":["../../src/plugins/statusline.ts"],"names":[],"mappings":"AAMA,eAAO,MAAM,WAAW,+BAA+B,CAAC;AA4CxD,eAAO,MAAM,gBAAgB,yCAiD3B,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"ui-interact.d.ts","sourceRoot":"","sources":["../../src/plugins/ui-interact.ts"],"names":[],"mappings":"AASA,eAAO,MAAM,gBAAgB,yCAuiB3B,CAAC"}
1
+ {"version":3,"file":"ui-interact.d.ts","sourceRoot":"","sources":["../../src/plugins/ui-interact.ts"],"names":[],"mappings":"AAUA,eAAO,MAAM,gBAAgB,yCAuiB3B,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EACV,cAAc,EAOf,MAAM,aAAa,CAAC;AAwDrB,wBAAsB,WAAW,CAAC,MAAM,EAAE,QAAQ,CAAC,cAAc,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAqSjF"}
1
+ {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EACV,cAAc,EAOf,MAAM,aAAa,CAAC;AAwDrB,wBAAsB,WAAW,CAAC,MAAM,EAAE,QAAQ,CAAC,cAAc,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CA4RjF"}
package/package.json CHANGED
@@ -1,11 +1,11 @@
1
1
  {
2
2
  "name": "metro-mcp",
3
- "version": "0.5.0",
3
+ "version": "0.5.2",
4
4
  "description": "Plugin-based MCP server for React Native/Expo runtime debugging, inspection, and automation via Metro/CDP",
5
5
  "homepage": "https://github.com/steve228uk/metro-mcp",
6
6
  "repository": {
7
7
  "type": "git",
8
- "url": "https://github.com/steve228uk/metro-mcp.git"
8
+ "url": "git+https://github.com/steve228uk/metro-mcp.git"
9
9
  },
10
10
  "bugs": {
11
11
  "url": "https://github.com/steve228uk/metro-mcp/issues"
@@ -41,7 +41,7 @@
41
41
  "clean": "rm -rf dist",
42
42
  "build": "bun run clean && bun run build:js && bun run build:bin && bun run build:types",
43
43
  "build:js": "bun build src/index.ts src/plugin.ts --outdir dist --target node --external @modelcontextprotocol/sdk --external zod --external ws && bun build src/client/index.ts --outfile dist/client/index.js --target browser --external @modelcontextprotocol/sdk --external zod && bun build src/client/index.ts --outfile dist/client/index.cjs --target browser --format cjs --external @modelcontextprotocol/sdk --external zod && bun build src/plugin.ts --outfile dist/plugin.cjs --target node --format cjs --external @modelcontextprotocol/sdk --external zod",
44
- "build:bin": "bun build bin/metro-mcp.ts --outfile dist/bin/metro-mcp.js --target bun --external @modelcontextprotocol/sdk --external zod --external ws && chmod +x dist/bin/metro-mcp.js",
44
+ "build:bin": "bun build bin/metro-mcp.ts --outfile dist/bin/metro-mcp.js --target node --external @modelcontextprotocol/sdk --external zod --external ws && chmod +x dist/bin/metro-mcp.js",
45
45
  "build:types": "bunx tsc -p tsconfig.build.json",
46
46
  "typecheck": "tsc --noEmit"
47
47
  },