react-native-ai-debugger 1.0.10 → 1.0.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. package/README.md +165 -8
  2. package/build/core/android.js +2 -2
  3. package/build/core/android.js.map +1 -1
  4. package/build/core/bundle.d.ts.map +1 -1
  5. package/build/core/bundle.js +51 -1
  6. package/build/core/bundle.js.map +1 -1
  7. package/build/core/connection.d.ts +2 -2
  8. package/build/core/connection.d.ts.map +1 -1
  9. package/build/core/connection.js +120 -7
  10. package/build/core/connection.js.map +1 -1
  11. package/build/core/connectionState.d.ts +79 -0
  12. package/build/core/connectionState.d.ts.map +1 -0
  13. package/build/core/connectionState.js +202 -0
  14. package/build/core/connectionState.js.map +1 -0
  15. package/build/core/httpServer.d.ts.map +1 -1
  16. package/build/core/httpServer.js +60 -1
  17. package/build/core/httpServer.js.map +1 -1
  18. package/build/core/httpServerProcess.d.ts +25 -0
  19. package/build/core/httpServerProcess.d.ts.map +1 -0
  20. package/build/core/httpServerProcess.js +153 -0
  21. package/build/core/httpServerProcess.js.map +1 -0
  22. package/build/core/index.d.ts +6 -2
  23. package/build/core/index.d.ts.map +1 -1
  24. package/build/core/index.js +8 -2
  25. package/build/core/index.js.map +1 -1
  26. package/build/core/ios.d.ts +10 -0
  27. package/build/core/ios.d.ts.map +1 -1
  28. package/build/core/ios.js +101 -67
  29. package/build/core/ios.js.map +1 -1
  30. package/build/core/ocr.d.ts +70 -0
  31. package/build/core/ocr.d.ts.map +1 -0
  32. package/build/core/ocr.js +322 -0
  33. package/build/core/ocr.js.map +1 -0
  34. package/build/core/state.d.ts +3 -0
  35. package/build/core/state.d.ts.map +1 -1
  36. package/build/core/state.js +17 -0
  37. package/build/core/state.js.map +1 -1
  38. package/build/core/types.d.ts +29 -0
  39. package/build/core/types.d.ts.map +1 -1
  40. package/build/httpServerStandalone.d.ts +7 -0
  41. package/build/httpServerStandalone.d.ts.map +1 -0
  42. package/build/httpServerStandalone.js +31 -0
  43. package/build/httpServerStandalone.js.map +1 -0
  44. package/build/index.js +280 -14
  45. package/build/index.js.map +1 -1
  46. package/package.json +8 -3
package/build/index.js CHANGED
@@ -2,7 +2,9 @@
2
2
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
3
3
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
4
4
  import { z } from "zod";
5
- import { logBuffer, networkBuffer, bundleErrorBuffer, scanMetroPorts, fetchDevices, selectMainDevice, connectToDevice, getConnectedApps, executeInApp, listDebugGlobals, inspectGlobal, reloadApp, getLogs, searchLogs, getNetworkRequests, searchNetworkRequests, getNetworkStats, formatRequestDetails,
5
+ import { logBuffer, networkBuffer, bundleErrorBuffer, getActiveSimulatorUdid, scanMetroPorts, fetchDevices, selectMainDevice, connectToDevice, getConnectedApps, executeInApp, listDebugGlobals, inspectGlobal, reloadApp, getLogs, searchLogs, getNetworkRequests, searchNetworkRequests, getNetworkStats, formatRequestDetails,
6
+ // Connection state
7
+ getAllConnectionStates, getAllConnectionMetadata, getRecentGaps, formatDuration,
6
8
  // Bundle (Metro build errors)
7
9
  connectMetroBuildEvents, getBundleErrors, getBundleStatusWithErrors,
8
10
  // Android
@@ -19,8 +21,10 @@ listIOSSimulators, iosScreenshot, iosInstallApp, iosLaunchApp, iosOpenUrl, iosTe
19
21
  iosTap, iosTapElement, iosSwipe, iosInputText, iosButton, iosKeyEvent, iosKeySequence, iosDescribeAll, iosDescribePoint, IOS_BUTTON_TYPES,
20
22
  // iOS Element Finding (no screenshots)
21
23
  iosFindElement, iosWaitForElement,
22
- // Debug HTTP Server
24
+ // Debug HTTP Server (in-process, for fallback)
23
25
  startDebugHttpServer, getDebugServerPort,
26
+ // HTTP Server Process (child process, for hot-reload)
27
+ startHttpServerProcess, restartHttpServerProcess, getHttpServerProcessPort,
24
28
  // Telemetry
25
29
  initTelemetry, trackToolInvocation } from "./core/index.js";
26
30
  // Create MCP server
@@ -131,15 +135,83 @@ registerToolWithTelemetry("get_apps", {
131
135
  const state = isConnected ? "Connected" : "Disconnected";
132
136
  return `${app.deviceInfo.title} (${app.deviceInfo.deviceName}): ${state}`;
133
137
  });
138
+ // Include active iOS simulator info if available
139
+ const activeSimulatorUdid = getActiveSimulatorUdid();
140
+ const simulatorInfo = activeSimulatorUdid
141
+ ? `\nActive iOS Simulator (auto-scoped): ${activeSimulatorUdid}`
142
+ : "\nNo iOS simulator linked (iOS tools will use first booted simulator)";
134
143
  return {
135
144
  content: [
136
145
  {
137
146
  type: "text",
138
- text: `Connected apps:\n${status.join("\n")}\n\nTotal logs in buffer: ${logBuffer.size}`
147
+ text: `Connected apps:\n${status.join("\n")}${simulatorInfo}\n\nTotal logs in buffer: ${logBuffer.size}`
139
148
  }
140
149
  ]
141
150
  };
142
151
  });
152
+ // Tool: Get connection status (detailed health and gap tracking)
153
+ registerToolWithTelemetry("get_connection_status", {
154
+ description: "Get detailed connection health status including uptime, recent disconnects/reconnects, and connection gaps that may indicate missing data.",
155
+ inputSchema: {}
156
+ }, async () => {
157
+ const connections = getConnectedApps();
158
+ const states = getAllConnectionStates();
159
+ const metadata = getAllConnectionMetadata();
160
+ const lines = [];
161
+ lines.push("=== Connection Status ===\n");
162
+ if (connections.length === 0 && states.size === 0) {
163
+ lines.push("No connections established. Run scan_metro to connect.");
164
+ return {
165
+ content: [{ type: "text", text: lines.join("\n") }]
166
+ };
167
+ }
168
+ // Show active connections
169
+ for (const { key, app, isConnected } of connections) {
170
+ const state = states.get(key);
171
+ lines.push(`--- ${app.deviceInfo.title} (Port ${app.port}) ---`);
172
+ lines.push(` Status: ${isConnected ? "CONNECTED" : "DISCONNECTED"}`);
173
+ if (state) {
174
+ if (state.lastConnectedTime) {
175
+ const uptime = Date.now() - state.lastConnectedTime.getTime();
176
+ lines.push(` Connected since: ${state.lastConnectedTime.toLocaleTimeString()}`);
177
+ lines.push(` Uptime: ${formatDuration(uptime)}`);
178
+ }
179
+ if (state.status === "reconnecting") {
180
+ lines.push(` Reconnecting: Attempt ${state.reconnectionAttempts}`);
181
+ }
182
+ // Show recent gaps (last 5 minutes)
183
+ if (state.connectionGaps.length > 0) {
184
+ const recentGaps = state.connectionGaps.filter((g) => Date.now() - g.disconnectedAt.getTime() < 300000);
185
+ if (recentGaps.length > 0) {
186
+ lines.push(` Recent gaps: ${recentGaps.length}`);
187
+ for (const gap of recentGaps.slice(-3)) {
188
+ const duration = gap.durationMs ? formatDuration(gap.durationMs) : "ongoing";
189
+ lines.push(` - ${gap.disconnectedAt.toLocaleTimeString()} (${duration}): ${gap.reason}`);
190
+ }
191
+ }
192
+ }
193
+ }
194
+ lines.push("");
195
+ }
196
+ // Show disconnected/reconnecting states without active connections
197
+ for (const [key, state] of states.entries()) {
198
+ if (!connections.find((c) => c.key === key)) {
199
+ const meta = metadata.get(key);
200
+ lines.push(`--- ${meta?.deviceInfo.title || key} (Disconnected) ---`);
201
+ lines.push(` Status: ${state.status.toUpperCase()}`);
202
+ if (state.lastDisconnectTime) {
203
+ lines.push(` Disconnected at: ${state.lastDisconnectTime.toLocaleTimeString()}`);
204
+ }
205
+ if (state.reconnectionAttempts > 0) {
206
+ lines.push(` Reconnection attempts: ${state.reconnectionAttempts}`);
207
+ }
208
+ lines.push("");
209
+ }
210
+ }
211
+ return {
212
+ content: [{ type: "text", text: lines.join("\n") }]
213
+ };
214
+ });
143
215
  // Tool: Get console logs
144
216
  registerToolWithTelemetry("get_logs", {
145
217
  description: "Retrieve console logs from connected React Native app",
@@ -154,12 +226,27 @@ registerToolWithTelemetry("get_logs", {
154
226
  }
155
227
  }, async ({ maxLogs, level, startFromText }) => {
156
228
  const { logs, formatted } = getLogs(logBuffer, { maxLogs, level, startFromText });
229
+ // Check for recent connection gaps
230
+ const warningThresholdMs = 30000; // 30 seconds
231
+ const recentGaps = getRecentGaps(warningThresholdMs);
232
+ let warning = "";
233
+ if (recentGaps.length > 0) {
234
+ const latestGap = recentGaps[recentGaps.length - 1];
235
+ const gapDuration = latestGap.durationMs || (Date.now() - latestGap.disconnectedAt.getTime());
236
+ if (latestGap.reconnectedAt) {
237
+ const secAgo = Math.round((Date.now() - latestGap.reconnectedAt.getTime()) / 1000);
238
+ warning = `\n\n[WARNING] Connection was restored ${secAgo}s ago. Some logs may have been missed during the ${formatDuration(gapDuration)} gap.`;
239
+ }
240
+ else {
241
+ warning = `\n\n[WARNING] Connection is currently disconnected. Logs may be incomplete.`;
242
+ }
243
+ }
157
244
  const startNote = startFromText ? ` (starting from "${startFromText}")` : "";
158
245
  return {
159
246
  content: [
160
247
  {
161
248
  type: "text",
162
- text: `React Native Console Logs (${logs.length} entries)${startNote}:\n\n${formatted}`
249
+ text: `React Native Console Logs (${logs.length} entries)${startNote}:\n\n${formatted}${warning}`
163
250
  }
164
251
  ]
165
252
  };
@@ -368,11 +455,26 @@ registerToolWithTelemetry("get_network_requests", {
368
455
  urlPattern,
369
456
  status
370
457
  });
458
+ // Check for recent connection gaps
459
+ const warningThresholdMs = 30000; // 30 seconds
460
+ const recentGaps = getRecentGaps(warningThresholdMs);
461
+ let warning = "";
462
+ if (recentGaps.length > 0) {
463
+ const latestGap = recentGaps[recentGaps.length - 1];
464
+ const gapDuration = latestGap.durationMs || (Date.now() - latestGap.disconnectedAt.getTime());
465
+ if (latestGap.reconnectedAt) {
466
+ const secAgo = Math.round((Date.now() - latestGap.reconnectedAt.getTime()) / 1000);
467
+ warning = `\n\n[WARNING] Connection was restored ${secAgo}s ago. Some requests may have been missed during the ${formatDuration(gapDuration)} gap.`;
468
+ }
469
+ else {
470
+ warning = `\n\n[WARNING] Connection is currently disconnected. Network data may be incomplete.`;
471
+ }
472
+ }
371
473
  return {
372
474
  content: [
373
475
  {
374
476
  type: "text",
375
- text: `Network Requests (${requests.length} entries):\n\n${formatted}`
477
+ text: `Network Requests (${requests.length} entries):\n\n${formatted}${warning}`
376
478
  }
377
479
  ]
378
480
  };
@@ -625,7 +727,7 @@ registerToolWithTelemetry("android_screenshot", {
625
727
  {
626
728
  type: "image",
627
729
  data: result.data.toString("base64"),
628
- mimeType: "image/png"
730
+ mimeType: "image/jpeg"
629
731
  }
630
732
  ]
631
733
  };
@@ -727,7 +829,7 @@ registerToolWithTelemetry("android_list_packages", {
727
829
  // ============================================================================
728
830
  // Tool: Android tap
729
831
  registerToolWithTelemetry("android_tap", {
730
- description: "Tap at specific coordinates on an Android device/emulator screen. NOTE: Prefer using android_tap_element instead, which finds elements by text/content-desc and is more reliable.",
832
+ description: "Tap at specific coordinates on an Android device/emulator screen. WORKFLOW: Use ocr_screenshot first to get tap coordinates, then use this tool with the returned tapX/tapY values.",
731
833
  inputSchema: {
732
834
  x: z.number().describe("X coordinate in pixels"),
733
835
  y: z.number().describe("Y coordinate in pixels"),
@@ -936,7 +1038,7 @@ server.registerTool("android_describe_point", {
936
1038
  });
937
1039
  // Tool: Android tap element
938
1040
  server.registerTool("android_tap_element", {
939
- description: "PREFERRED: Tap an element by its text, content-description, or resource-id. More reliable than coordinate-based tapping. Automatically finds the element using uiautomator and taps its center.",
1041
+ description: "Tap an element by its text, content-description, or resource-id using uiautomator. TIP: Consider using ocr_screenshot first - it returns ready-to-use tap coordinates for all visible text and works more reliably across different apps.",
940
1042
  inputSchema: {
941
1043
  text: z
942
1044
  .string()
@@ -1247,7 +1349,7 @@ registerToolWithTelemetry("ios_screenshot", {
1247
1349
  {
1248
1350
  type: "image",
1249
1351
  data: result.data.toString("base64"),
1250
- mimeType: "image/png"
1352
+ mimeType: "image/jpeg"
1251
1353
  }
1252
1354
  ]
1253
1355
  };
@@ -1261,6 +1363,90 @@ registerToolWithTelemetry("ios_screenshot", {
1261
1363
  ]
1262
1364
  };
1263
1365
  });
1366
+ // Tool: OCR Screenshot - Extract text with coordinates from screenshot
1367
+ registerToolWithTelemetry("ocr_screenshot", {
1368
+ description: "RECOMMENDED: Use this tool FIRST when you need to find and tap UI elements. Takes a screenshot and extracts all visible text with tap-ready coordinates using OCR. " +
1369
+ "ADVANTAGES over accessibility trees: (1) Works on ANY visible text regardless of accessibility labels, (2) Returns ready-to-use tapX/tapY coordinates - no conversion needed, (3) Faster than parsing accessibility hierarchies, (4) Works consistently across iOS and Android. " +
1370
+ "USE THIS FOR: Finding buttons, labels, menu items, tab bars, or any text you need to tap. Simply find the text in the results and use its tapX/tapY with the tap command.",
1371
+ inputSchema: {
1372
+ platform: z.enum(["ios", "android"]).describe("Platform to capture screenshot from"),
1373
+ deviceId: z
1374
+ .string()
1375
+ .optional()
1376
+ .describe("Optional device ID (Android) or UDID (iOS). Uses first available device if not specified.")
1377
+ }
1378
+ }, async ({ platform, deviceId }) => {
1379
+ // Call the HTTP endpoint for OCR (allows hot-reload without session restart)
1380
+ // Prefer child process port, fall back to in-process port
1381
+ const port = getHttpServerProcessPort() || getDebugServerPort();
1382
+ if (!port) {
1383
+ return {
1384
+ content: [
1385
+ {
1386
+ type: "text",
1387
+ text: "Debug HTTP server not running"
1388
+ }
1389
+ ],
1390
+ isError: true
1391
+ };
1392
+ }
1393
+ try {
1394
+ const params = new URLSearchParams({ platform, engine: "auto" });
1395
+ if (deviceId)
1396
+ params.set("deviceId", deviceId);
1397
+ const response = await fetch(`http://localhost:${port}/api/ocr?${params}`);
1398
+ const ocrResult = await response.json();
1399
+ if (!ocrResult.success) {
1400
+ return {
1401
+ content: [
1402
+ {
1403
+ type: "text",
1404
+ text: `OCR failed: ${ocrResult.error || "Unknown error"}`
1405
+ }
1406
+ ],
1407
+ isError: true
1408
+ };
1409
+ }
1410
+ // Format results for MCP tool output
1411
+ const elements = ocrResult.words
1412
+ .filter((w) => w.confidence > 50 && w.text.trim().length > 0)
1413
+ .map((w) => ({
1414
+ text: w.text,
1415
+ confidence: Math.round(w.confidence),
1416
+ tapX: w.tapCenter.x,
1417
+ tapY: w.tapCenter.y
1418
+ }));
1419
+ const result = {
1420
+ platform,
1421
+ engine: ocrResult.engine || "unknown",
1422
+ processingTimeMs: ocrResult.processingTimeMs,
1423
+ fullText: ocrResult.fullText?.trim() || "",
1424
+ confidence: Math.round(ocrResult.confidence || 0),
1425
+ elementCount: elements.length,
1426
+ elements,
1427
+ note: "tapX/tapY are ready to use with tap commands (already converted for platform)"
1428
+ };
1429
+ return {
1430
+ content: [
1431
+ {
1432
+ type: "text",
1433
+ text: JSON.stringify(result, null, 2)
1434
+ }
1435
+ ]
1436
+ };
1437
+ }
1438
+ catch (error) {
1439
+ return {
1440
+ content: [
1441
+ {
1442
+ type: "text",
1443
+ text: `OCR request failed: ${error instanceof Error ? error.message : String(error)}`
1444
+ }
1445
+ ],
1446
+ isError: true
1447
+ };
1448
+ }
1449
+ });
1264
1450
  // Tool: iOS install app
1265
1451
  registerToolWithTelemetry("ios_install_app", {
1266
1452
  description: "Install an app bundle (.app) on an iOS simulator",
@@ -1373,7 +1559,7 @@ registerToolWithTelemetry("ios_boot_simulator", {
1373
1559
  // ============================================================================
1374
1560
  // Tool: iOS tap
1375
1561
  server.registerTool("ios_tap", {
1376
- description: "Tap at specific coordinates on an iOS simulator screen. NOTE: Prefer using ios_tap_element instead, which finds elements by accessibility label and is more reliable. Requires IDB (brew install idb-companion).",
1562
+ description: "Tap at specific coordinates on an iOS simulator screen. WORKFLOW: Use ocr_screenshot first to get tap coordinates, then use this tool with the returned tapX/tapY values. Requires IDB (brew install idb-companion).",
1377
1563
  inputSchema: {
1378
1564
  x: z.number().describe("X coordinate in pixels"),
1379
1565
  y: z.number().describe("Y coordinate in pixels"),
@@ -1400,7 +1586,7 @@ server.registerTool("ios_tap", {
1400
1586
  });
1401
1587
  // Tool: iOS tap element by label
1402
1588
  server.registerTool("ios_tap_element", {
1403
- description: "PREFERRED: Tap an element by its accessibility label. More reliable than coordinate-based tapping. Automatically finds the element and taps its center. Requires IDB (brew install idb-companion).",
1589
+ description: "Tap an element by its accessibility label. Requires IDB (brew install idb-companion). TIP: Consider using ocr_screenshot first - it returns ready-to-use tap coordinates for all visible text and works without requiring accessibility labels.",
1404
1590
  inputSchema: {
1405
1591
  label: z
1406
1592
  .string()
@@ -1741,7 +1927,7 @@ registerToolWithTelemetry("get_debug_server", {
1741
1927
  description: "Get the debug HTTP server URL. Use this to find where you can access logs, network requests, and other debug data via HTTP.",
1742
1928
  inputSchema: {}
1743
1929
  }, async () => {
1744
- const port = getDebugServerPort();
1930
+ const port = getHttpServerProcessPort() || getDebugServerPort();
1745
1931
  if (!port) {
1746
1932
  return {
1747
1933
  content: [
@@ -1772,15 +1958,95 @@ registerToolWithTelemetry("get_debug_server", {
1772
1958
  ]
1773
1959
  };
1774
1960
  });
1961
+ // Tool: Restart HTTP server (hot-reload)
1962
+ registerToolWithTelemetry("restart_http_server", {
1963
+ description: "Restart the debug HTTP server to apply code changes (hot-reload). Use this after running 'npm run build' to load new server code without restarting the MCP session.",
1964
+ inputSchema: {}
1965
+ }, async () => {
1966
+ try {
1967
+ const newPort = await restartHttpServerProcess();
1968
+ return {
1969
+ content: [
1970
+ {
1971
+ type: "text",
1972
+ text: `HTTP server restarted successfully on port ${newPort}. New code is now active.`
1973
+ }
1974
+ ]
1975
+ };
1976
+ }
1977
+ catch (err) {
1978
+ return {
1979
+ content: [
1980
+ {
1981
+ type: "text",
1982
+ text: `Failed to restart HTTP server: ${err instanceof Error ? err.message : String(err)}`
1983
+ }
1984
+ ],
1985
+ isError: true
1986
+ };
1987
+ }
1988
+ });
1989
+ /**
1990
+ * Auto-connect to Metro bundler on startup
1991
+ * Scans common ports and connects to any running Metro servers
1992
+ */
1993
+ async function autoConnectToMetro() {
1994
+ console.error("[rn-ai-debugger] Auto-scanning for Metro servers...");
1995
+ try {
1996
+ const openPorts = await scanMetroPorts();
1997
+ if (openPorts.length === 0) {
1998
+ console.error("[rn-ai-debugger] No Metro servers found on startup. Use scan_metro to connect later.");
1999
+ return;
2000
+ }
2001
+ for (const port of openPorts) {
2002
+ try {
2003
+ const devices = await fetchDevices(port);
2004
+ const mainDevice = selectMainDevice(devices);
2005
+ if (mainDevice) {
2006
+ await connectToDevice(mainDevice, port);
2007
+ console.error(`[rn-ai-debugger] Auto-connected to ${mainDevice.title} on port ${port}`);
2008
+ // Also connect to Metro build events
2009
+ try {
2010
+ await connectMetroBuildEvents(port);
2011
+ }
2012
+ catch {
2013
+ // Build events connection is optional
2014
+ }
2015
+ }
2016
+ }
2017
+ catch (error) {
2018
+ console.error(`[rn-ai-debugger] Failed to auto-connect on port ${port}: ${error}`);
2019
+ }
2020
+ }
2021
+ }
2022
+ catch (error) {
2023
+ console.error(`[rn-ai-debugger] Auto-connect error: ${error}`);
2024
+ }
2025
+ }
1775
2026
  // Main function
1776
2027
  async function main() {
1777
2028
  // Initialize telemetry (checks opt-out env var, loads/creates installation ID)
1778
2029
  initTelemetry();
1779
- // Start debug HTTP server for buffer inspection (finds available port automatically)
1780
- await startDebugHttpServer();
2030
+ // Start debug HTTP server as child process (enables hot-reload)
2031
+ // Falls back to in-process if child process fails
2032
+ try {
2033
+ await startHttpServerProcess();
2034
+ console.error("[rn-ai-debugger] HTTP server started as child process (hot-reload enabled)");
2035
+ }
2036
+ catch (err) {
2037
+ console.error("[rn-ai-debugger] Child process failed, falling back to in-process server:", err);
2038
+ await startDebugHttpServer();
2039
+ }
1781
2040
  const transport = new StdioServerTransport();
1782
2041
  await server.connect(transport);
1783
2042
  console.error("[rn-ai-debugger] Server started on stdio");
2043
+ // Auto-connect to Metro in background (non-blocking)
2044
+ // Use setImmediate to ensure MCP server is fully ready first
2045
+ setImmediate(() => {
2046
+ autoConnectToMetro().catch((err) => {
2047
+ console.error("[rn-ai-debugger] Auto-connect failed:", err);
2048
+ });
2049
+ });
1784
2050
  }
1785
2051
  main().catch((error) => {
1786
2052
  console.error("[rn-ai-debugger] Fatal error:", error);