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.
- package/README.md +165 -8
- package/build/core/android.js +2 -2
- package/build/core/android.js.map +1 -1
- package/build/core/bundle.d.ts.map +1 -1
- package/build/core/bundle.js +51 -1
- package/build/core/bundle.js.map +1 -1
- package/build/core/connection.d.ts +2 -2
- package/build/core/connection.d.ts.map +1 -1
- package/build/core/connection.js +120 -7
- package/build/core/connection.js.map +1 -1
- package/build/core/connectionState.d.ts +79 -0
- package/build/core/connectionState.d.ts.map +1 -0
- package/build/core/connectionState.js +202 -0
- package/build/core/connectionState.js.map +1 -0
- package/build/core/httpServer.d.ts.map +1 -1
- package/build/core/httpServer.js +60 -1
- package/build/core/httpServer.js.map +1 -1
- package/build/core/httpServerProcess.d.ts +25 -0
- package/build/core/httpServerProcess.d.ts.map +1 -0
- package/build/core/httpServerProcess.js +153 -0
- package/build/core/httpServerProcess.js.map +1 -0
- package/build/core/index.d.ts +6 -2
- package/build/core/index.d.ts.map +1 -1
- package/build/core/index.js +8 -2
- package/build/core/index.js.map +1 -1
- package/build/core/ios.d.ts +10 -0
- package/build/core/ios.d.ts.map +1 -1
- package/build/core/ios.js +101 -67
- package/build/core/ios.js.map +1 -1
- package/build/core/ocr.d.ts +70 -0
- package/build/core/ocr.d.ts.map +1 -0
- package/build/core/ocr.js +322 -0
- package/build/core/ocr.js.map +1 -0
- package/build/core/state.d.ts +3 -0
- package/build/core/state.d.ts.map +1 -1
- package/build/core/state.js +17 -0
- package/build/core/state.js.map +1 -1
- package/build/core/types.d.ts +29 -0
- package/build/core/types.d.ts.map +1 -1
- package/build/httpServerStandalone.d.ts +7 -0
- package/build/httpServerStandalone.d.ts.map +1 -0
- package/build/httpServerStandalone.js +31 -0
- package/build/httpServerStandalone.js.map +1 -0
- package/build/index.js +280 -14
- package/build/index.js.map +1 -1
- 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/
|
|
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.
|
|
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: "
|
|
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/
|
|
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.
|
|
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: "
|
|
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
|
|
1780
|
-
|
|
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);
|