react-native-ai-debugger 1.0.8 → 1.0.10

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/build/index.js CHANGED
@@ -8,7 +8,7 @@ connectMetroBuildEvents, getBundleErrors, getBundleStatusWithErrors,
8
8
  // Android
9
9
  listAndroidDevices, androidScreenshot, androidInstallApp, androidLaunchApp, androidListPackages,
10
10
  // Android UI Input (Phase 2)
11
- ANDROID_KEY_EVENTS, androidTap, androidLongPress, androidSwipe, androidInputText, androidKeyEvent, androidGetScreenSize,
11
+ ANDROID_KEY_EVENTS, androidTap, androidLongPress, androidSwipe, androidInputText, androidKeyEvent, androidGetScreenSize, androidGetDensity, androidGetStatusBarHeight,
12
12
  // Android Accessibility (UI Hierarchy)
13
13
  androidDescribeAll, androidDescribePoint, androidTapElement,
14
14
  // Android Element Finding (no screenshots)
@@ -20,14 +20,43 @@ iosTap, iosTapElement, iosSwipe, iosInputText, iosButton, iosKeyEvent, iosKeySeq
20
20
  // iOS Element Finding (no screenshots)
21
21
  iosFindElement, iosWaitForElement,
22
22
  // Debug HTTP Server
23
- startDebugHttpServer, getDebugServerPort } from "./core/index.js";
23
+ startDebugHttpServer, getDebugServerPort,
24
+ // Telemetry
25
+ initTelemetry, trackToolInvocation } from "./core/index.js";
24
26
  // Create MCP server
25
27
  const server = new McpServer({
26
28
  name: "react-native-ai-debugger",
27
29
  version: "1.0.0"
28
30
  });
31
+ // ============================================================================
32
+ // Telemetry Wrapper
33
+ // ============================================================================
34
+ /* eslint-disable @typescript-eslint/no-explicit-any */
35
+ function registerToolWithTelemetry(toolName, config, handler) {
36
+ server.registerTool(toolName, config, async (args) => {
37
+ const startTime = Date.now();
38
+ let success = true;
39
+ try {
40
+ const result = await handler(args);
41
+ // Check if result indicates an error
42
+ if (result?.isError) {
43
+ success = false;
44
+ }
45
+ return result;
46
+ }
47
+ catch (error) {
48
+ success = false;
49
+ throw error;
50
+ }
51
+ finally {
52
+ const duration = Date.now() - startTime;
53
+ trackToolInvocation(toolName, success, duration);
54
+ }
55
+ });
56
+ }
57
+ /* eslint-enable @typescript-eslint/no-explicit-any */
29
58
  // Tool: Scan for Metro servers
30
- server.registerTool("scan_metro", {
59
+ registerToolWithTelemetry("scan_metro", {
31
60
  description: "Scan for running Metro bundler servers on common ports",
32
61
  inputSchema: {
33
62
  startPort: z.number().optional().default(8081).describe("Start port for scanning (default: 8081)"),
@@ -83,7 +112,7 @@ server.registerTool("scan_metro", {
83
112
  };
84
113
  });
85
114
  // Tool: Get connected apps
86
- server.registerTool("get_apps", {
115
+ registerToolWithTelemetry("get_apps", {
87
116
  description: "List connected React Native apps and Metro server status",
88
117
  inputSchema: {}
89
118
  }, async () => {
@@ -112,7 +141,7 @@ server.registerTool("get_apps", {
112
141
  };
113
142
  });
114
143
  // Tool: Get console logs
115
- server.registerTool("get_logs", {
144
+ registerToolWithTelemetry("get_logs", {
116
145
  description: "Retrieve console logs from connected React Native app",
117
146
  inputSchema: {
118
147
  maxLogs: z.number().optional().default(50).describe("Maximum number of logs to return (default: 50)"),
@@ -136,7 +165,7 @@ server.registerTool("get_logs", {
136
165
  };
137
166
  });
138
167
  // Tool: Search logs
139
- server.registerTool("search_logs", {
168
+ registerToolWithTelemetry("search_logs", {
140
169
  description: "Search console logs for text (case-insensitive)",
141
170
  inputSchema: {
142
171
  text: z.string().describe("Text to search for in log messages"),
@@ -154,7 +183,7 @@ server.registerTool("search_logs", {
154
183
  };
155
184
  });
156
185
  // Tool: Clear logs
157
- server.registerTool("clear_logs", {
186
+ registerToolWithTelemetry("clear_logs", {
158
187
  description: "Clear the log buffer",
159
188
  inputSchema: {}
160
189
  }, async () => {
@@ -169,7 +198,7 @@ server.registerTool("clear_logs", {
169
198
  };
170
199
  });
171
200
  // Tool: Connect to specific Metro port
172
- server.registerTool("connect_metro", {
201
+ registerToolWithTelemetry("connect_metro", {
173
202
  description: "Connect to a specific Metro server port",
174
203
  inputSchema: {
175
204
  port: z.number().default(8081).describe("Metro server port (default: 8081)")
@@ -226,8 +255,8 @@ server.registerTool("connect_metro", {
226
255
  }
227
256
  });
228
257
  // Tool: Execute JavaScript in app
229
- server.registerTool("execute_in_app", {
230
- 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.",
258
+ registerToolWithTelemetry("execute_in_app", {
259
+ 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. Hermes compatible: 'global' is automatically polyfilled to 'globalThis', so both global.__REDUX_STORE__ and globalThis.__REDUX_STORE__ work.",
231
260
  inputSchema: {
232
261
  expression: z.string().describe("JavaScript expression to execute in the app"),
233
262
  awaitPromise: z.boolean().optional().default(true).describe("Whether to await promises (default: true)")
@@ -255,7 +284,7 @@ server.registerTool("execute_in_app", {
255
284
  };
256
285
  });
257
286
  // Tool: List debug globals available in the app
258
- server.registerTool("list_debug_globals", {
287
+ registerToolWithTelemetry("list_debug_globals", {
259
288
  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.",
260
289
  inputSchema: {}
261
290
  }, async () => {
@@ -281,7 +310,7 @@ server.registerTool("list_debug_globals", {
281
310
  };
282
311
  });
283
312
  // Tool: Inspect a global object to see its properties and types
284
- server.registerTool("inspect_global", {
313
+ registerToolWithTelemetry("inspect_global", {
285
314
  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.",
286
315
  inputSchema: {
287
316
  objectName: z
@@ -311,7 +340,7 @@ server.registerTool("inspect_global", {
311
340
  };
312
341
  });
313
342
  // Tool: Get network requests
314
- server.registerTool("get_network_requests", {
343
+ registerToolWithTelemetry("get_network_requests", {
315
344
  description: "Retrieve captured network requests from connected React Native app. Shows URL, method, status, and timing.",
316
345
  inputSchema: {
317
346
  maxRequests: z
@@ -349,7 +378,7 @@ server.registerTool("get_network_requests", {
349
378
  };
350
379
  });
351
380
  // Tool: Search network requests
352
- server.registerTool("search_network", {
381
+ registerToolWithTelemetry("search_network", {
353
382
  description: "Search network requests by URL pattern (case-insensitive)",
354
383
  inputSchema: {
355
384
  urlPattern: z.string().describe("URL pattern to search for"),
@@ -371,7 +400,7 @@ server.registerTool("search_network", {
371
400
  };
372
401
  });
373
402
  // Tool: Get request details
374
- server.registerTool("get_request_details", {
403
+ registerToolWithTelemetry("get_request_details", {
375
404
  description: "Get full details of a specific network request including headers, body, and timing. Use get_network_requests first to find the request ID.",
376
405
  inputSchema: {
377
406
  requestId: z.string().describe("The request ID to get details for")
@@ -399,7 +428,7 @@ server.registerTool("get_request_details", {
399
428
  };
400
429
  });
401
430
  // Tool: Get network stats
402
- server.registerTool("get_network_stats", {
431
+ registerToolWithTelemetry("get_network_stats", {
403
432
  description: "Get statistics about captured network requests: counts by method, status code, and domain.",
404
433
  inputSchema: {}
405
434
  }, async () => {
@@ -414,7 +443,7 @@ server.registerTool("get_network_stats", {
414
443
  };
415
444
  });
416
445
  // Tool: Clear network requests
417
- server.registerTool("clear_network", {
446
+ registerToolWithTelemetry("clear_network", {
418
447
  description: "Clear the network request buffer",
419
448
  inputSchema: {}
420
449
  }, async () => {
@@ -429,7 +458,7 @@ server.registerTool("clear_network", {
429
458
  };
430
459
  });
431
460
  // Tool: Reload the app
432
- server.registerTool("reload_app", {
461
+ registerToolWithTelemetry("reload_app", {
433
462
  description: "Reload the connected React Native app. Triggers a JavaScript bundle reload (like pressing 'r' in Metro or shaking the device).",
434
463
  inputSchema: {}
435
464
  }, async () => {
@@ -458,7 +487,7 @@ server.registerTool("reload_app", {
458
487
  // Bundle/Build Error Tools
459
488
  // ============================================================================
460
489
  // Tool: Get bundle status
461
- server.registerTool("get_bundle_status", {
490
+ registerToolWithTelemetry("get_bundle_status", {
462
491
  description: "Get the current Metro bundler status including build state and any recent bundling errors. Use this to check if there are compilation/bundling errors that prevent the app from loading.",
463
492
  inputSchema: {}
464
493
  }, async () => {
@@ -473,7 +502,7 @@ server.registerTool("get_bundle_status", {
473
502
  };
474
503
  });
475
504
  // Tool: Get bundle errors
476
- server.registerTool("get_bundle_errors", {
505
+ registerToolWithTelemetry("get_bundle_errors", {
477
506
  description: "Retrieve captured Metro bundling/compilation errors. These are errors that occur during the bundle build process (import resolution, syntax errors, transform errors) that prevent the app from loading.",
478
507
  inputSchema: {
479
508
  maxErrors: z
@@ -494,7 +523,7 @@ server.registerTool("get_bundle_errors", {
494
523
  };
495
524
  });
496
525
  // Tool: Clear bundle errors
497
- server.registerTool("clear_bundle_errors", {
526
+ registerToolWithTelemetry("clear_bundle_errors", {
498
527
  description: "Clear the bundle error buffer",
499
528
  inputSchema: {}
500
529
  }, async () => {
@@ -512,7 +541,7 @@ server.registerTool("clear_bundle_errors", {
512
541
  // Android Tools
513
542
  // ============================================================================
514
543
  // Tool: List Android devices
515
- server.registerTool("list_android_devices", {
544
+ registerToolWithTelemetry("list_android_devices", {
516
545
  description: "List connected Android devices and emulators via ADB",
517
546
  inputSchema: {}
518
547
  }, async () => {
@@ -528,7 +557,7 @@ server.registerTool("list_android_devices", {
528
557
  };
529
558
  });
530
559
  // Tool: Android screenshot
531
- server.registerTool("android_screenshot", {
560
+ registerToolWithTelemetry("android_screenshot", {
532
561
  description: "Take a screenshot from an Android device/emulator. Returns the image data that can be displayed.",
533
562
  inputSchema: {
534
563
  outputPath: z
@@ -555,12 +584,38 @@ server.registerTool("android_screenshot", {
555
584
  }
556
585
  // Include image data if available
557
586
  if (result.data) {
558
- // Build info text with scale factor for coordinate conversion
559
- let infoText = `Screenshot captured (${result.originalWidth}x${result.originalHeight})`;
587
+ // Build info text with coordinate conversion guidance
588
+ const pixelWidth = result.originalWidth || 0;
589
+ const pixelHeight = result.originalHeight || 0;
590
+ let infoText = `Screenshot captured (${pixelWidth}x${pixelHeight} pixels)`;
591
+ // Get status bar height for coordinate guidance
592
+ let statusBarPixels = 63; // Default fallback
593
+ let statusBarDp = 24;
594
+ let densityDpi = 440; // Common default
595
+ try {
596
+ const statusBarResult = await androidGetStatusBarHeight(deviceId);
597
+ if (statusBarResult.success && statusBarResult.heightPixels) {
598
+ statusBarPixels = statusBarResult.heightPixels;
599
+ statusBarDp = statusBarResult.heightDp || 24;
600
+ }
601
+ const densityResult = await androidGetDensity(deviceId);
602
+ if (densityResult.success && densityResult.density) {
603
+ densityDpi = densityResult.density;
604
+ }
605
+ }
606
+ catch {
607
+ // Use defaults
608
+ }
609
+ infoText += `\n📱 Android uses PIXELS for tap coordinates (same as screenshot)`;
560
610
  if (result.scaleFactor && result.scaleFactor > 1) {
561
611
  infoText += `\n⚠️ Image was scaled down to fit API limits. Scale factor: ${result.scaleFactor.toFixed(3)}`;
562
- infoText += `\nTo tap/swipe: multiply image coordinates by ${result.scaleFactor.toFixed(3)} to get device coordinates.`;
612
+ infoText += `\n📐 To convert image coords to tap coords: multiply by ${result.scaleFactor.toFixed(3)}`;
613
+ }
614
+ else {
615
+ infoText += `\n📐 Screenshot coords = tap coords (no conversion needed)`;
563
616
  }
617
+ infoText += `\n⚠️ Status bar: ${statusBarPixels}px (${statusBarDp}dp) from top - app content starts below this`;
618
+ infoText += `\n📊 Display density: ${densityDpi}dpi`;
564
619
  return {
565
620
  content: [
566
621
  {
@@ -585,7 +640,7 @@ server.registerTool("android_screenshot", {
585
640
  };
586
641
  });
587
642
  // Tool: Android install app
588
- server.registerTool("android_install_app", {
643
+ registerToolWithTelemetry("android_install_app", {
589
644
  description: "Install an APK on an Android device/emulator",
590
645
  inputSchema: {
591
646
  apkPath: z.string().describe("Path to the APK file to install"),
@@ -617,7 +672,7 @@ server.registerTool("android_install_app", {
617
672
  };
618
673
  });
619
674
  // Tool: Android launch app
620
- server.registerTool("android_launch_app", {
675
+ registerToolWithTelemetry("android_launch_app", {
621
676
  description: "Launch an app on an Android device/emulator by package name",
622
677
  inputSchema: {
623
678
  packageName: z.string().describe("Package name of the app (e.g., com.example.myapp)"),
@@ -643,7 +698,7 @@ server.registerTool("android_launch_app", {
643
698
  };
644
699
  });
645
700
  // Tool: Android list packages
646
- server.registerTool("android_list_packages", {
701
+ registerToolWithTelemetry("android_list_packages", {
647
702
  description: "List installed packages on an Android device/emulator",
648
703
  inputSchema: {
649
704
  deviceId: z
@@ -671,7 +726,7 @@ server.registerTool("android_list_packages", {
671
726
  // Android UI Input Tools (Phase 2)
672
727
  // ============================================================================
673
728
  // Tool: Android tap
674
- server.registerTool("android_tap", {
729
+ registerToolWithTelemetry("android_tap", {
675
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.",
676
731
  inputSchema: {
677
732
  x: z.number().describe("X coordinate in pixels"),
@@ -694,7 +749,7 @@ server.registerTool("android_tap", {
694
749
  };
695
750
  });
696
751
  // Tool: Android long press
697
- server.registerTool("android_long_press", {
752
+ registerToolWithTelemetry("android_long_press", {
698
753
  description: "Long press at specific coordinates on an Android device/emulator screen",
699
754
  inputSchema: {
700
755
  x: z.number().describe("X coordinate in pixels"),
@@ -722,7 +777,7 @@ server.registerTool("android_long_press", {
722
777
  };
723
778
  });
724
779
  // Tool: Android swipe
725
- server.registerTool("android_swipe", {
780
+ registerToolWithTelemetry("android_swipe", {
726
781
  description: "Swipe from one point to another on an Android device/emulator screen",
727
782
  inputSchema: {
728
783
  startX: z.number().describe("Starting X coordinate in pixels"),
@@ -752,7 +807,7 @@ server.registerTool("android_swipe", {
752
807
  };
753
808
  });
754
809
  // Tool: Android input text
755
- server.registerTool("android_input_text", {
810
+ registerToolWithTelemetry("android_input_text", {
756
811
  description: "Type text on an Android device/emulator. The text will be input at the current focus point (tap an input field first).",
757
812
  inputSchema: {
758
813
  text: z.string().describe("Text to type"),
@@ -774,7 +829,7 @@ server.registerTool("android_input_text", {
774
829
  };
775
830
  });
776
831
  // Tool: Android key event
777
- server.registerTool("android_key_event", {
832
+ registerToolWithTelemetry("android_key_event", {
778
833
  description: `Send a key event to an Android device/emulator. Common keys: ${Object.keys(ANDROID_KEY_EVENTS).join(", ")}`,
779
834
  inputSchema: {
780
835
  key: z
@@ -802,7 +857,7 @@ server.registerTool("android_key_event", {
802
857
  };
803
858
  });
804
859
  // Tool: Android get screen size
805
- server.registerTool("android_get_screen_size", {
860
+ registerToolWithTelemetry("android_get_screen_size", {
806
861
  description: "Get the screen size (resolution) of an Android device/emulator",
807
862
  inputSchema: {
808
863
  deviceId: z
@@ -1075,7 +1130,7 @@ server.registerTool("android_wait_for_element", {
1075
1130
  // iOS Simulator Tools
1076
1131
  // ============================================================================
1077
1132
  // Tool: List iOS simulators
1078
- server.registerTool("list_ios_simulators", {
1133
+ registerToolWithTelemetry("list_ios_simulators", {
1079
1134
  description: "List available iOS simulators",
1080
1135
  inputSchema: {
1081
1136
  onlyBooted: z
@@ -1097,7 +1152,7 @@ server.registerTool("list_ios_simulators", {
1097
1152
  };
1098
1153
  });
1099
1154
  // Tool: iOS screenshot
1100
- server.registerTool("ios_screenshot", {
1155
+ registerToolWithTelemetry("ios_screenshot", {
1101
1156
  description: "Take a screenshot from an iOS simulator. Returns the image data that can be displayed.",
1102
1157
  inputSchema: {
1103
1158
  outputPath: z
@@ -1125,17 +1180,62 @@ server.registerTool("ios_screenshot", {
1125
1180
  // Include image data if available
1126
1181
  if (result.data) {
1127
1182
  // Build info text with coordinate guidance for iOS
1128
- // iOS simulators use points, not pixels. Retina displays are typically 2x or 3x.
1129
1183
  const pixelWidth = result.originalWidth || 0;
1130
1184
  const pixelHeight = result.originalHeight || 0;
1131
- // Assume 2x Retina scale for most iOS simulators
1132
- const pointWidth = Math.round(pixelWidth / 2);
1133
- const pointHeight = Math.round(pixelHeight / 2);
1185
+ // Try to get actual screen dimensions and safe area from accessibility tree
1186
+ let pointWidth = 0;
1187
+ let pointHeight = 0;
1188
+ let scaleFactor = 3; // Default to 3x for modern iPhones
1189
+ let safeAreaTop = 59; // Default safe area offset
1190
+ try {
1191
+ const describeResult = await iosDescribeAll(udid);
1192
+ if (describeResult.success && describeResult.elements && describeResult.elements.length > 0) {
1193
+ // First element is typically the Application with full screen frame
1194
+ const rootElement = describeResult.elements[0];
1195
+ // Try parsed frame first, then parse AXFrame string
1196
+ if (rootElement.frame) {
1197
+ pointWidth = Math.round(rootElement.frame.width);
1198
+ pointHeight = Math.round(rootElement.frame.height);
1199
+ // The frame.y of the root element indicates where content starts (after status bar)
1200
+ if (rootElement.frame.y > 0) {
1201
+ safeAreaTop = Math.round(rootElement.frame.y);
1202
+ }
1203
+ }
1204
+ else if (rootElement.AXFrame) {
1205
+ // Parse format: "{{x, y}, {width, height}}"
1206
+ const match = rootElement.AXFrame.match(/\{\{([\d.]+),\s*([\d.]+)\},\s*\{([\d.]+),\s*([\d.]+)\}\}/);
1207
+ if (match) {
1208
+ const frameY = parseFloat(match[2]);
1209
+ pointWidth = Math.round(parseFloat(match[3]));
1210
+ pointHeight = Math.round(parseFloat(match[4]));
1211
+ if (frameY > 0) {
1212
+ safeAreaTop = Math.round(frameY);
1213
+ }
1214
+ }
1215
+ }
1216
+ // Calculate actual scale factor
1217
+ if (pointWidth > 0) {
1218
+ scaleFactor = Math.round(pixelWidth / pointWidth);
1219
+ }
1220
+ }
1221
+ }
1222
+ catch {
1223
+ // Fallback: use 3x scale for modern devices
1224
+ }
1225
+ // Fallback if we couldn't get dimensions
1226
+ if (pointWidth === 0) {
1227
+ pointWidth = Math.round(pixelWidth / scaleFactor);
1228
+ pointHeight = Math.round(pixelHeight / scaleFactor);
1229
+ }
1230
+ const safeAreaOffsetPixels = safeAreaTop * scaleFactor;
1134
1231
  let infoText = `Screenshot captured (${pixelWidth}x${pixelHeight} pixels)`;
1135
- infoText += `\n📱 iOS IDB coordinates use POINTS: ${pointWidth}x${pointHeight}`;
1136
- infoText += `\nTo convert image coords to IDB points: divide pixel coordinates by 2`;
1232
+ infoText += `\n📱 iOS tap coordinates use POINTS: ${pointWidth}x${pointHeight}`;
1233
+ infoText += `\n📐 To convert screenshot coords to tap points:`;
1234
+ infoText += `\n tap_x = pixel_x / ${scaleFactor}`;
1235
+ infoText += `\n tap_y = pixel_y / ${scaleFactor}`;
1236
+ infoText += `\n⚠️ Status bar + safe area: ${safeAreaTop} points (${safeAreaOffsetPixels} pixels) from top`;
1137
1237
  if (result.scaleFactor && result.scaleFactor > 1) {
1138
- infoText += `\n⚠️ Image was scaled down to fit API limits (scale: ${result.scaleFactor.toFixed(3)})`;
1238
+ infoText += `\n🖼️ Image was scaled down to fit API limits (scale: ${result.scaleFactor.toFixed(3)})`;
1139
1239
  }
1140
1240
  infoText += `\n💡 Use ios_describe_all to get exact element coordinates`;
1141
1241
  return {
@@ -1162,7 +1262,7 @@ server.registerTool("ios_screenshot", {
1162
1262
  };
1163
1263
  });
1164
1264
  // Tool: iOS install app
1165
- server.registerTool("ios_install_app", {
1265
+ registerToolWithTelemetry("ios_install_app", {
1166
1266
  description: "Install an app bundle (.app) on an iOS simulator",
1167
1267
  inputSchema: {
1168
1268
  appPath: z.string().describe("Path to the .app bundle to install"),
@@ -1184,7 +1284,7 @@ server.registerTool("ios_install_app", {
1184
1284
  };
1185
1285
  });
1186
1286
  // Tool: iOS launch app
1187
- server.registerTool("ios_launch_app", {
1287
+ registerToolWithTelemetry("ios_launch_app", {
1188
1288
  description: "Launch an app on an iOS simulator by bundle ID",
1189
1289
  inputSchema: {
1190
1290
  bundleId: z.string().describe("Bundle ID of the app (e.g., com.example.myapp)"),
@@ -1206,7 +1306,7 @@ server.registerTool("ios_launch_app", {
1206
1306
  };
1207
1307
  });
1208
1308
  // Tool: iOS open URL
1209
- server.registerTool("ios_open_url", {
1309
+ registerToolWithTelemetry("ios_open_url", {
1210
1310
  description: "Open a URL in the iOS simulator (opens in default handler or Safari)",
1211
1311
  inputSchema: {
1212
1312
  url: z.string().describe("URL to open (e.g., https://example.com or myapp://path)"),
@@ -1228,7 +1328,7 @@ server.registerTool("ios_open_url", {
1228
1328
  };
1229
1329
  });
1230
1330
  // Tool: iOS terminate app
1231
- server.registerTool("ios_terminate_app", {
1331
+ registerToolWithTelemetry("ios_terminate_app", {
1232
1332
  description: "Terminate a running app on an iOS simulator",
1233
1333
  inputSchema: {
1234
1334
  bundleId: z.string().describe("Bundle ID of the app to terminate"),
@@ -1250,7 +1350,7 @@ server.registerTool("ios_terminate_app", {
1250
1350
  };
1251
1351
  });
1252
1352
  // Tool: iOS boot simulator
1253
- server.registerTool("ios_boot_simulator", {
1353
+ registerToolWithTelemetry("ios_boot_simulator", {
1254
1354
  description: "Boot an iOS simulator by UDID. Use list_ios_simulators to find available simulators.",
1255
1355
  inputSchema: {
1256
1356
  udid: z.string().describe("UDID of the simulator to boot (from list_ios_simulators)")
@@ -1637,7 +1737,7 @@ server.registerTool("ios_wait_for_element", {
1637
1737
  };
1638
1738
  });
1639
1739
  // Tool: Get debug server info
1640
- server.registerTool("get_debug_server", {
1740
+ registerToolWithTelemetry("get_debug_server", {
1641
1741
  description: "Get the debug HTTP server URL. Use this to find where you can access logs, network requests, and other debug data via HTTP.",
1642
1742
  inputSchema: {}
1643
1743
  }, async () => {
@@ -1674,6 +1774,8 @@ server.registerTool("get_debug_server", {
1674
1774
  });
1675
1775
  // Main function
1676
1776
  async function main() {
1777
+ // Initialize telemetry (checks opt-out env var, loads/creates installation ID)
1778
+ initTelemetry();
1677
1779
  // Start debug HTTP server for buffer inspection (finds available port automatically)
1678
1780
  await startDebugHttpServer();
1679
1781
  const transport = new StdioServerTransport();