react-native-ai-debugger 1.0.0

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 (47) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +303 -0
  3. package/build/core/android.d.ts +108 -0
  4. package/build/core/android.d.ts.map +1 -0
  5. package/build/core/android.js +686 -0
  6. package/build/core/android.js.map +1 -0
  7. package/build/core/connection.d.ts +12 -0
  8. package/build/core/connection.d.ts.map +1 -0
  9. package/build/core/connection.js +242 -0
  10. package/build/core/connection.js.map +1 -0
  11. package/build/core/executor.d.ts +6 -0
  12. package/build/core/executor.d.ts.map +1 -0
  13. package/build/core/executor.js +112 -0
  14. package/build/core/executor.js.map +1 -0
  15. package/build/core/index.d.ts +10 -0
  16. package/build/core/index.d.ts.map +1 -0
  17. package/build/core/index.js +21 -0
  18. package/build/core/index.js.map +1 -0
  19. package/build/core/ios.d.ts +54 -0
  20. package/build/core/ios.d.ts.map +1 -0
  21. package/build/core/ios.js +393 -0
  22. package/build/core/ios.js.map +1 -0
  23. package/build/core/logs.d.ts +27 -0
  24. package/build/core/logs.d.ts.map +1 -0
  25. package/build/core/logs.js +102 -0
  26. package/build/core/logs.js.map +1 -0
  27. package/build/core/metro.d.ts +8 -0
  28. package/build/core/metro.d.ts.map +1 -0
  29. package/build/core/metro.js +79 -0
  30. package/build/core/metro.js.map +1 -0
  31. package/build/core/network.d.ts +37 -0
  32. package/build/core/network.d.ts.map +1 -0
  33. package/build/core/network.js +210 -0
  34. package/build/core/network.js.map +1 -0
  35. package/build/core/state.d.ts +9 -0
  36. package/build/core/state.d.ts.map +1 -0
  37. package/build/core/state.js +16 -0
  38. package/build/core/state.js.map +1 -0
  39. package/build/core/types.d.ts +68 -0
  40. package/build/core/types.d.ts.map +1 -0
  41. package/build/core/types.js +2 -0
  42. package/build/core/types.js.map +1 -0
  43. package/build/index.d.ts +3 -0
  44. package/build/index.d.ts.map +1 -0
  45. package/build/index.js +951 -0
  46. package/build/index.js.map +1 -0
  47. package/package.json +50 -0
package/build/index.js ADDED
@@ -0,0 +1,951 @@
1
+ #!/usr/bin/env node
2
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
3
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
4
+ import { z } from "zod";
5
+ import { logBuffer, networkBuffer, scanMetroPorts, fetchDevices, selectMainDevice, connectToDevice, getConnectedApps, executeInApp, listDebugGlobals, inspectGlobal, reloadApp, getLogs, searchLogs, getNetworkRequests, searchNetworkRequests, getNetworkStats, formatRequestDetails,
6
+ // Android
7
+ listAndroidDevices, androidScreenshot, androidInstallApp, androidLaunchApp, androidListPackages,
8
+ // Android UI Input (Phase 2)
9
+ ANDROID_KEY_EVENTS, androidTap, androidLongPress, androidSwipe, androidInputText, androidKeyEvent, androidGetScreenSize,
10
+ // iOS
11
+ listIOSSimulators, iosScreenshot, iosInstallApp, iosLaunchApp, iosOpenUrl, iosTerminateApp, iosBootSimulator } from "./core/index.js";
12
+ // Create MCP server
13
+ const server = new McpServer({
14
+ name: "react-native-ai-debugger",
15
+ version: "1.0.0"
16
+ });
17
+ // Tool: Scan for Metro servers
18
+ server.registerTool("scan_metro", {
19
+ description: "Scan for running Metro bundler servers on common ports",
20
+ inputSchema: {
21
+ startPort: z.number().optional().default(8081).describe("Start port for scanning (default: 8081)"),
22
+ endPort: z.number().optional().default(19002).describe("End port for scanning (default: 19002)")
23
+ }
24
+ }, async ({ startPort, endPort }) => {
25
+ const openPorts = await scanMetroPorts(startPort, endPort);
26
+ if (openPorts.length === 0) {
27
+ return {
28
+ content: [
29
+ {
30
+ type: "text",
31
+ text: "No Metro servers found. Make sure Metro bundler is running (npm start or expo start)."
32
+ }
33
+ ]
34
+ };
35
+ }
36
+ // Fetch devices from each port and connect
37
+ const results = [];
38
+ for (const port of openPorts) {
39
+ const devices = await fetchDevices(port);
40
+ if (devices.length === 0) {
41
+ results.push(`Port ${port}: No devices found`);
42
+ continue;
43
+ }
44
+ results.push(`Port ${port}: Found ${devices.length} device(s)`);
45
+ const mainDevice = selectMainDevice(devices);
46
+ if (mainDevice) {
47
+ try {
48
+ const connectionResult = await connectToDevice(mainDevice, port);
49
+ results.push(` - ${connectionResult}`);
50
+ }
51
+ catch (error) {
52
+ results.push(` - Failed: ${error}`);
53
+ }
54
+ }
55
+ }
56
+ return {
57
+ content: [
58
+ {
59
+ type: "text",
60
+ text: `Metro scan results:\n${results.join("\n")}`
61
+ }
62
+ ]
63
+ };
64
+ });
65
+ // Tool: Get connected apps
66
+ server.registerTool("get_apps", {
67
+ description: "List connected React Native apps and Metro server status",
68
+ inputSchema: {}
69
+ }, async () => {
70
+ const connections = getConnectedApps();
71
+ if (connections.length === 0) {
72
+ return {
73
+ content: [
74
+ {
75
+ type: "text",
76
+ text: 'No apps connected. Run "scan_metro" first to discover and connect to running apps.'
77
+ }
78
+ ]
79
+ };
80
+ }
81
+ const status = connections.map(({ app, isConnected }) => {
82
+ const state = isConnected ? "Connected" : "Disconnected";
83
+ return `${app.deviceInfo.title} (${app.deviceInfo.deviceName}): ${state}`;
84
+ });
85
+ return {
86
+ content: [
87
+ {
88
+ type: "text",
89
+ text: `Connected apps:\n${status.join("\n")}\n\nTotal logs in buffer: ${logBuffer.size}`
90
+ }
91
+ ]
92
+ };
93
+ });
94
+ // Tool: Get console logs
95
+ server.registerTool("get_logs", {
96
+ description: "Retrieve console logs from connected React Native app",
97
+ inputSchema: {
98
+ maxLogs: z.number().optional().default(50).describe("Maximum number of logs to return (default: 50)"),
99
+ level: z
100
+ .enum(["all", "log", "warn", "error", "info", "debug"])
101
+ .optional()
102
+ .default("all")
103
+ .describe("Filter by log level (default: all)"),
104
+ startFromText: z.string().optional().describe("Start from the first log line containing this text")
105
+ }
106
+ }, async ({ maxLogs, level, startFromText }) => {
107
+ const { logs, formatted } = getLogs(logBuffer, { maxLogs, level, startFromText });
108
+ const startNote = startFromText ? ` (starting from "${startFromText}")` : "";
109
+ return {
110
+ content: [
111
+ {
112
+ type: "text",
113
+ text: `React Native Console Logs (${logs.length} entries)${startNote}:\n\n${formatted}`
114
+ }
115
+ ]
116
+ };
117
+ });
118
+ // Tool: Search logs
119
+ server.registerTool("search_logs", {
120
+ description: "Search console logs for text (case-insensitive)",
121
+ inputSchema: {
122
+ text: z.string().describe("Text to search for in log messages"),
123
+ maxResults: z.number().optional().default(50).describe("Maximum number of results to return (default: 50)")
124
+ }
125
+ }, async ({ text, maxResults }) => {
126
+ const { logs, formatted } = searchLogs(logBuffer, text, maxResults);
127
+ return {
128
+ content: [
129
+ {
130
+ type: "text",
131
+ text: `Search results for "${text}" (${logs.length} matches):\n\n${formatted}`
132
+ }
133
+ ]
134
+ };
135
+ });
136
+ // Tool: Clear logs
137
+ server.registerTool("clear_logs", {
138
+ description: "Clear the log buffer",
139
+ inputSchema: {}
140
+ }, async () => {
141
+ const count = logBuffer.clear();
142
+ return {
143
+ content: [
144
+ {
145
+ type: "text",
146
+ text: `Cleared ${count} log entries from buffer.`
147
+ }
148
+ ]
149
+ };
150
+ });
151
+ // Tool: Connect to specific Metro port
152
+ server.registerTool("connect_metro", {
153
+ description: "Connect to a specific Metro server port",
154
+ inputSchema: {
155
+ port: z.number().default(8081).describe("Metro server port (default: 8081)")
156
+ }
157
+ }, async ({ port }) => {
158
+ try {
159
+ const devices = await fetchDevices(port);
160
+ if (devices.length === 0) {
161
+ return {
162
+ content: [
163
+ {
164
+ type: "text",
165
+ text: `No devices found on port ${port}. Make sure the app is running.`
166
+ }
167
+ ]
168
+ };
169
+ }
170
+ const results = [`Found ${devices.length} device(s) on port ${port}:`];
171
+ for (const device of devices) {
172
+ try {
173
+ const result = await connectToDevice(device, port);
174
+ results.push(` - ${result}`);
175
+ }
176
+ catch (error) {
177
+ results.push(` - ${device.title}: Failed - ${error}`);
178
+ }
179
+ }
180
+ return {
181
+ content: [
182
+ {
183
+ type: "text",
184
+ text: results.join("\n")
185
+ }
186
+ ]
187
+ };
188
+ }
189
+ catch (error) {
190
+ return {
191
+ content: [
192
+ {
193
+ type: "text",
194
+ text: `Failed to connect: ${error}`
195
+ }
196
+ ]
197
+ };
198
+ }
199
+ });
200
+ // Tool: Execute JavaScript in app
201
+ server.registerTool("execute_in_app", {
202
+ description: "Execute JavaScript code in the connected React Native app and return the result. Use this for REPL-style interactions, inspecting app state, or running diagnostic code.",
203
+ inputSchema: {
204
+ expression: z.string().describe("JavaScript expression to execute in the app"),
205
+ awaitPromise: z.boolean().optional().default(true).describe("Whether to await promises (default: true)")
206
+ }
207
+ }, async ({ expression, awaitPromise }) => {
208
+ const result = await executeInApp(expression, awaitPromise);
209
+ if (!result.success) {
210
+ return {
211
+ content: [
212
+ {
213
+ type: "text",
214
+ text: `Error: ${result.error}`
215
+ }
216
+ ],
217
+ isError: true
218
+ };
219
+ }
220
+ return {
221
+ content: [
222
+ {
223
+ type: "text",
224
+ text: result.result ?? "undefined"
225
+ }
226
+ ]
227
+ };
228
+ });
229
+ // Tool: List debug globals available in the app
230
+ server.registerTool("list_debug_globals", {
231
+ description: "List globally available debugging objects in the connected React Native app (Apollo Client, Redux store, React DevTools, etc.). Use this to discover what state management and debugging tools are available.",
232
+ inputSchema: {}
233
+ }, async () => {
234
+ const result = await listDebugGlobals();
235
+ if (!result.success) {
236
+ return {
237
+ content: [
238
+ {
239
+ type: "text",
240
+ text: `Error: ${result.error}`
241
+ }
242
+ ],
243
+ isError: true
244
+ };
245
+ }
246
+ return {
247
+ content: [
248
+ {
249
+ type: "text",
250
+ text: `Available debug globals in the app:\n\n${result.result}`
251
+ }
252
+ ]
253
+ };
254
+ });
255
+ // Tool: Inspect a global object to see its properties and types
256
+ server.registerTool("inspect_global", {
257
+ description: "Inspect a global object to see its properties, types, and whether they are callable functions. Use this BEFORE calling methods on unfamiliar objects to avoid errors.",
258
+ inputSchema: {
259
+ objectName: z
260
+ .string()
261
+ .describe("Name of the global object to inspect (e.g., '__EXPO_ROUTER__', '__APOLLO_CLIENT__')")
262
+ }
263
+ }, async ({ objectName }) => {
264
+ const result = await inspectGlobal(objectName);
265
+ if (!result.success) {
266
+ return {
267
+ content: [
268
+ {
269
+ type: "text",
270
+ text: `Error: ${result.error}`
271
+ }
272
+ ],
273
+ isError: true
274
+ };
275
+ }
276
+ return {
277
+ content: [
278
+ {
279
+ type: "text",
280
+ text: `Properties of ${objectName}:\n\n${result.result}`
281
+ }
282
+ ]
283
+ };
284
+ });
285
+ // Tool: Get network requests
286
+ server.registerTool("get_network_requests", {
287
+ description: "Retrieve captured network requests from connected React Native app. Shows URL, method, status, and timing.",
288
+ inputSchema: {
289
+ maxRequests: z
290
+ .number()
291
+ .optional()
292
+ .default(50)
293
+ .describe("Maximum number of requests to return (default: 50)"),
294
+ method: z
295
+ .string()
296
+ .optional()
297
+ .describe("Filter by HTTP method (GET, POST, PUT, DELETE, etc.)"),
298
+ urlPattern: z
299
+ .string()
300
+ .optional()
301
+ .describe("Filter by URL pattern (case-insensitive substring match)"),
302
+ status: z
303
+ .number()
304
+ .optional()
305
+ .describe("Filter by HTTP status code (e.g., 200, 401, 500)")
306
+ }
307
+ }, async ({ maxRequests, method, urlPattern, status }) => {
308
+ const { requests, formatted } = getNetworkRequests(networkBuffer, {
309
+ maxRequests,
310
+ method,
311
+ urlPattern,
312
+ status
313
+ });
314
+ return {
315
+ content: [
316
+ {
317
+ type: "text",
318
+ text: `Network Requests (${requests.length} entries):\n\n${formatted}`
319
+ }
320
+ ]
321
+ };
322
+ });
323
+ // Tool: Search network requests
324
+ server.registerTool("search_network", {
325
+ description: "Search network requests by URL pattern (case-insensitive)",
326
+ inputSchema: {
327
+ urlPattern: z.string().describe("URL pattern to search for"),
328
+ maxResults: z
329
+ .number()
330
+ .optional()
331
+ .default(50)
332
+ .describe("Maximum number of results to return (default: 50)")
333
+ }
334
+ }, async ({ urlPattern, maxResults }) => {
335
+ const { requests, formatted } = searchNetworkRequests(networkBuffer, urlPattern, maxResults);
336
+ return {
337
+ content: [
338
+ {
339
+ type: "text",
340
+ text: `Network search results for "${urlPattern}" (${requests.length} matches):\n\n${formatted}`
341
+ }
342
+ ]
343
+ };
344
+ });
345
+ // Tool: Get request details
346
+ server.registerTool("get_request_details", {
347
+ description: "Get full details of a specific network request including headers, body, and timing. Use get_network_requests first to find the request ID.",
348
+ inputSchema: {
349
+ requestId: z.string().describe("The request ID to get details for")
350
+ }
351
+ }, async ({ requestId }) => {
352
+ const request = networkBuffer.get(requestId);
353
+ if (!request) {
354
+ return {
355
+ content: [
356
+ {
357
+ type: "text",
358
+ text: `Request not found: ${requestId}`
359
+ }
360
+ ],
361
+ isError: true
362
+ };
363
+ }
364
+ return {
365
+ content: [
366
+ {
367
+ type: "text",
368
+ text: formatRequestDetails(request)
369
+ }
370
+ ]
371
+ };
372
+ });
373
+ // Tool: Get network stats
374
+ server.registerTool("get_network_stats", {
375
+ description: "Get statistics about captured network requests: counts by method, status code, and domain.",
376
+ inputSchema: {}
377
+ }, async () => {
378
+ const stats = getNetworkStats(networkBuffer);
379
+ return {
380
+ content: [
381
+ {
382
+ type: "text",
383
+ text: `Network Statistics:\n\n${stats}`
384
+ }
385
+ ]
386
+ };
387
+ });
388
+ // Tool: Clear network requests
389
+ server.registerTool("clear_network", {
390
+ description: "Clear the network request buffer",
391
+ inputSchema: {}
392
+ }, async () => {
393
+ const count = networkBuffer.clear();
394
+ return {
395
+ content: [
396
+ {
397
+ type: "text",
398
+ text: `Cleared ${count} network requests from buffer.`
399
+ }
400
+ ]
401
+ };
402
+ });
403
+ // Tool: Reload the app
404
+ server.registerTool("reload_app", {
405
+ description: "Reload the connected React Native app. Triggers a JavaScript bundle reload (like pressing 'r' in Metro or shaking the device).",
406
+ inputSchema: {}
407
+ }, async () => {
408
+ const result = await reloadApp();
409
+ if (!result.success) {
410
+ return {
411
+ content: [
412
+ {
413
+ type: "text",
414
+ text: `Error: ${result.error}`
415
+ }
416
+ ],
417
+ isError: true
418
+ };
419
+ }
420
+ return {
421
+ content: [
422
+ {
423
+ type: "text",
424
+ text: result.result ?? "App reload triggered"
425
+ }
426
+ ]
427
+ };
428
+ });
429
+ // ============================================================================
430
+ // Android Tools
431
+ // ============================================================================
432
+ // Tool: List Android devices
433
+ server.registerTool("list_android_devices", {
434
+ description: "List connected Android devices and emulators via ADB",
435
+ inputSchema: {}
436
+ }, async () => {
437
+ const result = await listAndroidDevices();
438
+ return {
439
+ content: [
440
+ {
441
+ type: "text",
442
+ text: result.success ? result.result : `Error: ${result.error}`
443
+ }
444
+ ],
445
+ isError: !result.success
446
+ };
447
+ });
448
+ // Tool: Android screenshot
449
+ server.registerTool("android_screenshot", {
450
+ description: "Take a screenshot from an Android device/emulator. Returns the image data that can be displayed.",
451
+ inputSchema: {
452
+ outputPath: z
453
+ .string()
454
+ .optional()
455
+ .describe("Optional path to save the screenshot. If not provided, saves to temp directory."),
456
+ deviceId: z
457
+ .string()
458
+ .optional()
459
+ .describe("Optional device ID (from list_android_devices). Uses first available device if not specified.")
460
+ }
461
+ }, async ({ outputPath, deviceId }) => {
462
+ const result = await androidScreenshot(outputPath, deviceId);
463
+ if (!result.success) {
464
+ return {
465
+ content: [
466
+ {
467
+ type: "text",
468
+ text: `Error: ${result.error}`
469
+ }
470
+ ],
471
+ isError: true
472
+ };
473
+ }
474
+ // Include image data if available
475
+ if (result.data) {
476
+ // Build info text with scale factor for coordinate conversion
477
+ let infoText = `Screenshot captured (${result.originalWidth}x${result.originalHeight})`;
478
+ if (result.scaleFactor && result.scaleFactor > 1) {
479
+ infoText += `\n⚠️ Image was scaled down to fit API limits. Scale factor: ${result.scaleFactor.toFixed(3)}`;
480
+ infoText += `\nTo tap/swipe: multiply image coordinates by ${result.scaleFactor.toFixed(3)} to get device coordinates.`;
481
+ }
482
+ return {
483
+ content: [
484
+ {
485
+ type: "text",
486
+ text: infoText
487
+ },
488
+ {
489
+ type: "image",
490
+ data: result.data.toString("base64"),
491
+ mimeType: "image/png"
492
+ }
493
+ ]
494
+ };
495
+ }
496
+ return {
497
+ content: [
498
+ {
499
+ type: "text",
500
+ text: `Screenshot saved to: ${result.result}`
501
+ }
502
+ ]
503
+ };
504
+ });
505
+ // Tool: Android install app
506
+ server.registerTool("android_install_app", {
507
+ description: "Install an APK on an Android device/emulator",
508
+ inputSchema: {
509
+ apkPath: z.string().describe("Path to the APK file to install"),
510
+ deviceId: z
511
+ .string()
512
+ .optional()
513
+ .describe("Optional device ID. Uses first available device if not specified."),
514
+ replace: z
515
+ .boolean()
516
+ .optional()
517
+ .default(true)
518
+ .describe("Replace existing app if already installed (default: true)"),
519
+ grantPermissions: z
520
+ .boolean()
521
+ .optional()
522
+ .default(false)
523
+ .describe("Grant all runtime permissions on install (default: false)")
524
+ }
525
+ }, async ({ apkPath, deviceId, replace, grantPermissions }) => {
526
+ const result = await androidInstallApp(apkPath, deviceId, { replace, grantPermissions });
527
+ return {
528
+ content: [
529
+ {
530
+ type: "text",
531
+ text: result.success ? result.result : `Error: ${result.error}`
532
+ }
533
+ ],
534
+ isError: !result.success
535
+ };
536
+ });
537
+ // Tool: Android launch app
538
+ server.registerTool("android_launch_app", {
539
+ description: "Launch an app on an Android device/emulator by package name",
540
+ inputSchema: {
541
+ packageName: z.string().describe("Package name of the app (e.g., com.example.myapp)"),
542
+ activityName: z
543
+ .string()
544
+ .optional()
545
+ .describe("Optional activity name to launch (e.g., .MainActivity). If not provided, launches the main activity."),
546
+ deviceId: z
547
+ .string()
548
+ .optional()
549
+ .describe("Optional device ID. Uses first available device if not specified.")
550
+ }
551
+ }, async ({ packageName, activityName, deviceId }) => {
552
+ const result = await androidLaunchApp(packageName, activityName, deviceId);
553
+ return {
554
+ content: [
555
+ {
556
+ type: "text",
557
+ text: result.success ? result.result : `Error: ${result.error}`
558
+ }
559
+ ],
560
+ isError: !result.success
561
+ };
562
+ });
563
+ // Tool: Android list packages
564
+ server.registerTool("android_list_packages", {
565
+ description: "List installed packages on an Android device/emulator",
566
+ inputSchema: {
567
+ deviceId: z
568
+ .string()
569
+ .optional()
570
+ .describe("Optional device ID. Uses first available device if not specified."),
571
+ filter: z
572
+ .string()
573
+ .optional()
574
+ .describe("Optional filter to search packages by name (case-insensitive)")
575
+ }
576
+ }, async ({ deviceId, filter }) => {
577
+ const result = await androidListPackages(deviceId, filter);
578
+ return {
579
+ content: [
580
+ {
581
+ type: "text",
582
+ text: result.success ? result.result : `Error: ${result.error}`
583
+ }
584
+ ],
585
+ isError: !result.success
586
+ };
587
+ });
588
+ // ============================================================================
589
+ // Android UI Input Tools (Phase 2)
590
+ // ============================================================================
591
+ // Tool: Android tap
592
+ server.registerTool("android_tap", {
593
+ description: "Tap at specific coordinates on an Android device/emulator screen",
594
+ inputSchema: {
595
+ x: z.number().describe("X coordinate in pixels"),
596
+ y: z.number().describe("Y coordinate in pixels"),
597
+ deviceId: z
598
+ .string()
599
+ .optional()
600
+ .describe("Optional device ID. Uses first available device if not specified.")
601
+ }
602
+ }, async ({ x, y, deviceId }) => {
603
+ const result = await androidTap(x, y, deviceId);
604
+ return {
605
+ content: [
606
+ {
607
+ type: "text",
608
+ text: result.success ? result.result : `Error: ${result.error}`
609
+ }
610
+ ],
611
+ isError: !result.success
612
+ };
613
+ });
614
+ // Tool: Android long press
615
+ server.registerTool("android_long_press", {
616
+ description: "Long press at specific coordinates on an Android device/emulator screen",
617
+ inputSchema: {
618
+ x: z.number().describe("X coordinate in pixels"),
619
+ y: z.number().describe("Y coordinate in pixels"),
620
+ durationMs: z
621
+ .number()
622
+ .optional()
623
+ .default(1000)
624
+ .describe("Press duration in milliseconds (default: 1000)"),
625
+ deviceId: z
626
+ .string()
627
+ .optional()
628
+ .describe("Optional device ID. Uses first available device if not specified.")
629
+ }
630
+ }, async ({ x, y, durationMs, deviceId }) => {
631
+ const result = await androidLongPress(x, y, durationMs, deviceId);
632
+ return {
633
+ content: [
634
+ {
635
+ type: "text",
636
+ text: result.success ? result.result : `Error: ${result.error}`
637
+ }
638
+ ],
639
+ isError: !result.success
640
+ };
641
+ });
642
+ // Tool: Android swipe
643
+ server.registerTool("android_swipe", {
644
+ description: "Swipe from one point to another on an Android device/emulator screen",
645
+ inputSchema: {
646
+ startX: z.number().describe("Starting X coordinate in pixels"),
647
+ startY: z.number().describe("Starting Y coordinate in pixels"),
648
+ endX: z.number().describe("Ending X coordinate in pixels"),
649
+ endY: z.number().describe("Ending Y coordinate in pixels"),
650
+ durationMs: z
651
+ .number()
652
+ .optional()
653
+ .default(300)
654
+ .describe("Swipe duration in milliseconds (default: 300)"),
655
+ deviceId: z
656
+ .string()
657
+ .optional()
658
+ .describe("Optional device ID. Uses first available device if not specified.")
659
+ }
660
+ }, async ({ startX, startY, endX, endY, durationMs, deviceId }) => {
661
+ const result = await androidSwipe(startX, startY, endX, endY, durationMs, deviceId);
662
+ return {
663
+ content: [
664
+ {
665
+ type: "text",
666
+ text: result.success ? result.result : `Error: ${result.error}`
667
+ }
668
+ ],
669
+ isError: !result.success
670
+ };
671
+ });
672
+ // Tool: Android input text
673
+ server.registerTool("android_input_text", {
674
+ description: "Type text on an Android device/emulator. The text will be input at the current focus point (tap an input field first).",
675
+ inputSchema: {
676
+ text: z.string().describe("Text to type"),
677
+ deviceId: z
678
+ .string()
679
+ .optional()
680
+ .describe("Optional device ID. Uses first available device if not specified.")
681
+ }
682
+ }, async ({ text, deviceId }) => {
683
+ const result = await androidInputText(text, deviceId);
684
+ return {
685
+ content: [
686
+ {
687
+ type: "text",
688
+ text: result.success ? result.result : `Error: ${result.error}`
689
+ }
690
+ ],
691
+ isError: !result.success
692
+ };
693
+ });
694
+ // Tool: Android key event
695
+ server.registerTool("android_key_event", {
696
+ description: `Send a key event to an Android device/emulator. Common keys: ${Object.keys(ANDROID_KEY_EVENTS).join(", ")}`,
697
+ inputSchema: {
698
+ key: z
699
+ .string()
700
+ .describe(`Key name (${Object.keys(ANDROID_KEY_EVENTS).join(", ")}) or numeric keycode`),
701
+ deviceId: z
702
+ .string()
703
+ .optional()
704
+ .describe("Optional device ID. Uses first available device if not specified.")
705
+ }
706
+ }, async ({ key, deviceId }) => {
707
+ // Try to parse as number first, otherwise treat as key name
708
+ const keyCode = /^\d+$/.test(key)
709
+ ? parseInt(key, 10)
710
+ : key.toUpperCase();
711
+ const result = await androidKeyEvent(keyCode, deviceId);
712
+ return {
713
+ content: [
714
+ {
715
+ type: "text",
716
+ text: result.success ? result.result : `Error: ${result.error}`
717
+ }
718
+ ],
719
+ isError: !result.success
720
+ };
721
+ });
722
+ // Tool: Android get screen size
723
+ server.registerTool("android_get_screen_size", {
724
+ description: "Get the screen size (resolution) of an Android device/emulator",
725
+ inputSchema: {
726
+ deviceId: z
727
+ .string()
728
+ .optional()
729
+ .describe("Optional device ID. Uses first available device if not specified.")
730
+ }
731
+ }, async ({ deviceId }) => {
732
+ const result = await androidGetScreenSize(deviceId);
733
+ if (!result.success) {
734
+ return {
735
+ content: [
736
+ {
737
+ type: "text",
738
+ text: `Error: ${result.error}`
739
+ }
740
+ ],
741
+ isError: true
742
+ };
743
+ }
744
+ return {
745
+ content: [
746
+ {
747
+ type: "text",
748
+ text: `Screen size: ${result.width}x${result.height} pixels`
749
+ }
750
+ ]
751
+ };
752
+ });
753
+ // ============================================================================
754
+ // iOS Simulator Tools
755
+ // ============================================================================
756
+ // Tool: List iOS simulators
757
+ server.registerTool("list_ios_simulators", {
758
+ description: "List available iOS simulators",
759
+ inputSchema: {
760
+ onlyBooted: z
761
+ .boolean()
762
+ .optional()
763
+ .default(false)
764
+ .describe("Only show currently running simulators (default: false)")
765
+ }
766
+ }, async ({ onlyBooted }) => {
767
+ const result = await listIOSSimulators(onlyBooted);
768
+ return {
769
+ content: [
770
+ {
771
+ type: "text",
772
+ text: result.success ? result.result : `Error: ${result.error}`
773
+ }
774
+ ],
775
+ isError: !result.success
776
+ };
777
+ });
778
+ // Tool: iOS screenshot
779
+ server.registerTool("ios_screenshot", {
780
+ description: "Take a screenshot from an iOS simulator. Returns the image data that can be displayed.",
781
+ inputSchema: {
782
+ outputPath: z
783
+ .string()
784
+ .optional()
785
+ .describe("Optional path to save the screenshot. If not provided, saves to temp directory."),
786
+ udid: z
787
+ .string()
788
+ .optional()
789
+ .describe("Optional simulator UDID (from list_ios_simulators). Uses booted simulator if not specified.")
790
+ }
791
+ }, async ({ outputPath, udid }) => {
792
+ const result = await iosScreenshot(outputPath, udid);
793
+ if (!result.success) {
794
+ return {
795
+ content: [
796
+ {
797
+ type: "text",
798
+ text: `Error: ${result.error}`
799
+ }
800
+ ],
801
+ isError: true
802
+ };
803
+ }
804
+ // Include image data if available
805
+ if (result.data) {
806
+ // Build info text with scale factor for coordinate conversion
807
+ let infoText = `Screenshot captured (${result.originalWidth}x${result.originalHeight})`;
808
+ if (result.scaleFactor && result.scaleFactor > 1) {
809
+ infoText += `\n⚠️ Image was scaled down to fit API limits. Scale factor: ${result.scaleFactor.toFixed(3)}`;
810
+ infoText += `\nTo tap/swipe: multiply image coordinates by ${result.scaleFactor.toFixed(3)} to get device coordinates.`;
811
+ }
812
+ return {
813
+ content: [
814
+ {
815
+ type: "text",
816
+ text: infoText
817
+ },
818
+ {
819
+ type: "image",
820
+ data: result.data.toString("base64"),
821
+ mimeType: "image/png"
822
+ }
823
+ ]
824
+ };
825
+ }
826
+ return {
827
+ content: [
828
+ {
829
+ type: "text",
830
+ text: `Screenshot saved to: ${result.result}`
831
+ }
832
+ ]
833
+ };
834
+ });
835
+ // Tool: iOS install app
836
+ server.registerTool("ios_install_app", {
837
+ description: "Install an app bundle (.app) on an iOS simulator",
838
+ inputSchema: {
839
+ appPath: z.string().describe("Path to the .app bundle to install"),
840
+ udid: z
841
+ .string()
842
+ .optional()
843
+ .describe("Optional simulator UDID. Uses booted simulator if not specified.")
844
+ }
845
+ }, async ({ appPath, udid }) => {
846
+ const result = await iosInstallApp(appPath, udid);
847
+ return {
848
+ content: [
849
+ {
850
+ type: "text",
851
+ text: result.success ? result.result : `Error: ${result.error}`
852
+ }
853
+ ],
854
+ isError: !result.success
855
+ };
856
+ });
857
+ // Tool: iOS launch app
858
+ server.registerTool("ios_launch_app", {
859
+ description: "Launch an app on an iOS simulator by bundle ID",
860
+ inputSchema: {
861
+ bundleId: z.string().describe("Bundle ID of the app (e.g., com.example.myapp)"),
862
+ udid: z
863
+ .string()
864
+ .optional()
865
+ .describe("Optional simulator UDID. Uses booted simulator if not specified.")
866
+ }
867
+ }, async ({ bundleId, udid }) => {
868
+ const result = await iosLaunchApp(bundleId, udid);
869
+ return {
870
+ content: [
871
+ {
872
+ type: "text",
873
+ text: result.success ? result.result : `Error: ${result.error}`
874
+ }
875
+ ],
876
+ isError: !result.success
877
+ };
878
+ });
879
+ // Tool: iOS open URL
880
+ server.registerTool("ios_open_url", {
881
+ description: "Open a URL in the iOS simulator (opens in default handler or Safari)",
882
+ inputSchema: {
883
+ url: z.string().describe("URL to open (e.g., https://example.com or myapp://path)"),
884
+ udid: z
885
+ .string()
886
+ .optional()
887
+ .describe("Optional simulator UDID. Uses booted simulator if not specified.")
888
+ }
889
+ }, async ({ url, udid }) => {
890
+ const result = await iosOpenUrl(url, udid);
891
+ return {
892
+ content: [
893
+ {
894
+ type: "text",
895
+ text: result.success ? result.result : `Error: ${result.error}`
896
+ }
897
+ ],
898
+ isError: !result.success
899
+ };
900
+ });
901
+ // Tool: iOS terminate app
902
+ server.registerTool("ios_terminate_app", {
903
+ description: "Terminate a running app on an iOS simulator",
904
+ inputSchema: {
905
+ bundleId: z.string().describe("Bundle ID of the app to terminate"),
906
+ udid: z
907
+ .string()
908
+ .optional()
909
+ .describe("Optional simulator UDID. Uses booted simulator if not specified.")
910
+ }
911
+ }, async ({ bundleId, udid }) => {
912
+ const result = await iosTerminateApp(bundleId, udid);
913
+ return {
914
+ content: [
915
+ {
916
+ type: "text",
917
+ text: result.success ? result.result : `Error: ${result.error}`
918
+ }
919
+ ],
920
+ isError: !result.success
921
+ };
922
+ });
923
+ // Tool: iOS boot simulator
924
+ server.registerTool("ios_boot_simulator", {
925
+ description: "Boot an iOS simulator by UDID. Use list_ios_simulators to find available simulators.",
926
+ inputSchema: {
927
+ udid: z.string().describe("UDID of the simulator to boot (from list_ios_simulators)")
928
+ }
929
+ }, async ({ udid }) => {
930
+ const result = await iosBootSimulator(udid);
931
+ return {
932
+ content: [
933
+ {
934
+ type: "text",
935
+ text: result.success ? result.result : `Error: ${result.error}`
936
+ }
937
+ ],
938
+ isError: !result.success
939
+ };
940
+ });
941
+ // Main function
942
+ async function main() {
943
+ const transport = new StdioServerTransport();
944
+ await server.connect(transport);
945
+ console.error("[rn-ai-debugger] Server started on stdio");
946
+ }
947
+ main().catch((error) => {
948
+ console.error("[rn-ai-debugger] Fatal error:", error);
949
+ process.exit(1);
950
+ });
951
+ //# sourceMappingURL=index.js.map