mobile-debug-mcp 0.6.0 → 0.8.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.
package/dist/server.js CHANGED
@@ -6,13 +6,16 @@ import { AndroidObserve } from "./android/observe.js";
6
6
  import { AndroidInteract } from "./android/interact.js";
7
7
  import { iOSObserve } from "./ios/observe.js";
8
8
  import { iOSInteract } from "./ios/interact.js";
9
+ import { resolveTargetDevice, listDevices } from "./resolve-device.js";
10
+ import { startAndroidLogStream, readLogStreamLines, stopAndroidLogStream } from "./android/utils.js";
11
+ import { startIOSLogStream, readIOSLogStreamLines, stopIOSLogStream } from "./ios/utils.js";
9
12
  const androidObserve = new AndroidObserve();
10
13
  const androidInteract = new AndroidInteract();
11
14
  const iosObserve = new iOSObserve();
12
15
  const iosInteract = new iOSInteract();
13
16
  const server = new Server({
14
17
  name: "mobile-debug-mcp",
15
- version: "0.6.0"
18
+ version: "0.7.0"
16
19
  }, {
17
20
  capabilities: {
18
21
  tools: {}
@@ -116,6 +119,19 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
116
119
  required: ["platform", "appId"]
117
120
  }
118
121
  },
122
+ {
123
+ name: "install_app",
124
+ description: "Install an app on Android (apk) or iOS simulator/device (ipa/.app).",
125
+ inputSchema: {
126
+ type: "object",
127
+ properties: {
128
+ platform: { type: "string", enum: ["android", "ios"] },
129
+ appPath: { type: "string", description: "Path to APK (Android) or .app/.ipa (iOS) on the host machine" },
130
+ deviceId: { type: "string", description: "Device UDID (iOS) or Serial (Android). Defaults to booted/connected." }
131
+ },
132
+ required: ["platform", "appPath"]
133
+ }
134
+ },
119
135
  {
120
136
  name: "get_logs",
121
137
  description: "Get recent logs from Android or iOS simulator. Returns device metadata and the log output.",
@@ -142,6 +158,16 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
142
158
  required: ["platform"]
143
159
  }
144
160
  },
161
+ {
162
+ name: "list_devices",
163
+ description: "List connected devices and their metadata (android + ios).",
164
+ inputSchema: {
165
+ type: "object",
166
+ properties: {
167
+ platform: { type: "string", enum: ["android", "ios"] }
168
+ }
169
+ }
170
+ },
145
171
  {
146
172
  name: "capture_screenshot",
147
173
  description: "Capture a screenshot from an Android device or iOS simulator. Returns device metadata and the screenshot image.",
@@ -160,6 +186,41 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
160
186
  required: ["platform"]
161
187
  }
162
188
  },
189
+ {
190
+ name: "start_log_stream",
191
+ description: "Start streaming logs for a target application on Android or iOS. For Android this uses adb logcat --pid=<pid>; for iOS it streams `xcrun simctl spawn <device> log stream` with a predicate.",
192
+ inputSchema: {
193
+ type: "object",
194
+ properties: {
195
+ platform: { type: "string", enum: ["android", "ios"], default: "android" },
196
+ packageName: { type: "string", description: "Android package name or iOS bundle id" },
197
+ level: { type: "string", enum: ["error", "warn", "info", "debug"], default: "error" },
198
+ deviceId: { type: "string", description: "Device Serial (Android) or UDID (iOS). Defaults to connected/booted device." },
199
+ sessionId: { type: "string", description: "Session identifier for the log stream" }
200
+ },
201
+ required: ["packageName"]
202
+ }
203
+ },
204
+ {
205
+ name: "read_log_stream",
206
+ description: "Read accumulated log stream entries for the active session.",
207
+ inputSchema: {
208
+ type: "object",
209
+ properties: {
210
+ sessionId: { type: "string" }
211
+ }
212
+ }
213
+ },
214
+ {
215
+ name: "stop_log_stream",
216
+ description: "Stop an active log stream for the session.",
217
+ inputSchema: {
218
+ type: "object",
219
+ properties: {
220
+ sessionId: { type: "string" }
221
+ }
222
+ }
223
+ },
163
224
  {
164
225
  name: "get_ui_tree",
165
226
  description: "Get the current UI hierarchy from an Android device or iOS simulator. Returns a structured JSON representation of the screen content.",
@@ -191,6 +252,126 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
191
252
  }
192
253
  }
193
254
  }
255
+ },
256
+ {
257
+ name: "wait_for_element",
258
+ description: "Wait until a UI element with matching text appears on screen or timeout is reached.",
259
+ inputSchema: {
260
+ type: "object",
261
+ properties: {
262
+ platform: {
263
+ type: "string",
264
+ enum: ["android", "ios"],
265
+ description: "Platform to check"
266
+ },
267
+ text: {
268
+ type: "string",
269
+ description: "Text content of the element to wait for"
270
+ },
271
+ timeout: {
272
+ type: "number",
273
+ description: "Max wait time in ms (default 10000)",
274
+ default: 10000
275
+ },
276
+ deviceId: {
277
+ type: "string",
278
+ description: "Device Serial/UDID. Defaults to connected/booted device."
279
+ }
280
+ },
281
+ required: ["platform", "text"]
282
+ }
283
+ },
284
+ {
285
+ name: "tap",
286
+ description: "Simulate a finger tap on the device screen at specific coordinates.",
287
+ inputSchema: {
288
+ type: "object",
289
+ properties: {
290
+ platform: {
291
+ type: "string",
292
+ enum: ["android", "ios"],
293
+ description: "Platform to tap on"
294
+ },
295
+ x: {
296
+ type: "number",
297
+ description: "X coordinate"
298
+ },
299
+ y: {
300
+ type: "number",
301
+ description: "Y coordinate"
302
+ },
303
+ deviceId: {
304
+ type: "string",
305
+ description: "Device Serial/UDID. Defaults to connected/booted device."
306
+ }
307
+ },
308
+ required: ["x", "y"]
309
+ }
310
+ },
311
+ {
312
+ name: "swipe",
313
+ description: "Simulate a swipe gesture on an Android device.",
314
+ inputSchema: {
315
+ type: "object",
316
+ properties: {
317
+ platform: {
318
+ type: "string",
319
+ enum: ["android"],
320
+ description: "Platform to swipe on (currently only android supported)"
321
+ },
322
+ x1: { type: "number", description: "Start X coordinate" },
323
+ y1: { type: "number", description: "Start Y coordinate" },
324
+ x2: { type: "number", description: "End X coordinate" },
325
+ y2: { type: "number", description: "End Y coordinate" },
326
+ duration: { type: "number", description: "Duration in ms" },
327
+ deviceId: {
328
+ type: "string",
329
+ description: "Device Serial/UDID. Defaults to connected/booted device."
330
+ }
331
+ },
332
+ required: ["x1", "y1", "x2", "y2", "duration"]
333
+ }
334
+ },
335
+ {
336
+ name: "type_text",
337
+ description: "Type text into the currently focused input field on an Android device.",
338
+ inputSchema: {
339
+ type: "object",
340
+ properties: {
341
+ platform: {
342
+ type: "string",
343
+ enum: ["android"],
344
+ description: "Platform to type on (currently only android supported)"
345
+ },
346
+ text: {
347
+ type: "string",
348
+ description: "The text to type"
349
+ },
350
+ deviceId: {
351
+ type: "string",
352
+ description: "Device Serial/UDID. Defaults to connected/booted device."
353
+ }
354
+ },
355
+ required: ["text"]
356
+ }
357
+ },
358
+ {
359
+ name: "press_back",
360
+ description: "Simulate pressing the Android Back button.",
361
+ inputSchema: {
362
+ type: "object",
363
+ properties: {
364
+ platform: {
365
+ type: "string",
366
+ enum: ["android"],
367
+ description: "Platform (currently only android supported)"
368
+ },
369
+ deviceId: {
370
+ type: "string",
371
+ description: "Device Serial/UDID. Defaults to connected/booted device."
372
+ }
373
+ }
374
+ }
194
375
  }
195
376
  ]
196
377
  }));
@@ -203,13 +384,15 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
203
384
  let launchTimeMs;
204
385
  let deviceInfo;
205
386
  if (platform === "android") {
206
- const result = await androidInteract.startApp(appId, deviceId);
387
+ const resolved = await resolveTargetDevice({ platform: 'android', appId, deviceId });
388
+ const result = await androidInteract.startApp(appId, resolved.id);
207
389
  appStarted = result.appStarted;
208
390
  launchTimeMs = result.launchTimeMs;
209
391
  deviceInfo = result.device;
210
392
  }
211
393
  else {
212
- const result = await iosInteract.startApp(appId, deviceId);
394
+ const resolved = await resolveTargetDevice({ platform: 'ios', appId, deviceId });
395
+ const result = await iosInteract.startApp(appId, resolved.id);
213
396
  appStarted = result.appStarted;
214
397
  launchTimeMs = result.launchTimeMs;
215
398
  deviceInfo = result.device;
@@ -226,12 +409,14 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
226
409
  let appTerminated;
227
410
  let deviceInfo;
228
411
  if (platform === "android") {
229
- const result = await androidInteract.terminateApp(appId, deviceId);
412
+ const resolved = await resolveTargetDevice({ platform: 'android', appId, deviceId });
413
+ const result = await androidInteract.terminateApp(appId, resolved.id);
230
414
  appTerminated = result.appTerminated;
231
415
  deviceInfo = result.device;
232
416
  }
233
417
  else {
234
- const result = await iosInteract.terminateApp(appId, deviceId);
418
+ const resolved = await resolveTargetDevice({ platform: 'ios', appId, deviceId });
419
+ const result = await iosInteract.terminateApp(appId, resolved.id);
235
420
  appTerminated = result.appTerminated;
236
421
  deviceInfo = result.device;
237
422
  }
@@ -247,13 +432,15 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
247
432
  let launchTimeMs;
248
433
  let deviceInfo;
249
434
  if (platform === "android") {
250
- const result = await androidInteract.restartApp(appId, deviceId);
435
+ const resolved = await resolveTargetDevice({ platform: 'android', appId, deviceId });
436
+ const result = await androidInteract.restartApp(appId, resolved.id);
251
437
  appRestarted = result.appRestarted;
252
438
  launchTimeMs = result.launchTimeMs;
253
439
  deviceInfo = result.device;
254
440
  }
255
441
  else {
256
- const result = await iosInteract.restartApp(appId, deviceId);
442
+ const resolved = await resolveTargetDevice({ platform: 'ios', appId, deviceId });
443
+ const result = await iosInteract.restartApp(appId, resolved.id);
257
444
  appRestarted = result.appRestarted;
258
445
  launchTimeMs = result.launchTimeMs;
259
446
  deviceInfo = result.device;
@@ -270,12 +457,14 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
270
457
  let dataCleared;
271
458
  let deviceInfo;
272
459
  if (platform === "android") {
273
- const result = await androidInteract.resetAppData(appId, deviceId);
460
+ const resolved = await resolveTargetDevice({ platform: 'android', appId, deviceId });
461
+ const result = await androidInteract.resetAppData(appId, resolved.id);
274
462
  dataCleared = result.dataCleared;
275
463
  deviceInfo = result.device;
276
464
  }
277
465
  else {
278
- const result = await iosInteract.resetAppData(appId, deviceId);
466
+ const resolved = await resolveTargetDevice({ platform: 'ios', appId, deviceId });
467
+ const result = await iosInteract.resetAppData(appId, resolved.id);
279
468
  dataCleared = result.dataCleared;
280
469
  deviceInfo = result.device;
281
470
  }
@@ -285,18 +474,51 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
285
474
  };
286
475
  return wrapResponse(response);
287
476
  }
477
+ if (name === "install_app") {
478
+ const { platform, appPath, deviceId } = args;
479
+ let installed;
480
+ let output;
481
+ let deviceInfo;
482
+ let errorMsg;
483
+ if (platform === "android") {
484
+ const resolved = await resolveTargetDevice({ platform: 'android', deviceId });
485
+ const result = await androidInteract.installApp(appPath, resolved.id);
486
+ installed = result.installed;
487
+ output = result.output;
488
+ deviceInfo = result.device;
489
+ errorMsg = result.error;
490
+ }
491
+ else {
492
+ const resolved = await resolveTargetDevice({ platform: 'ios', deviceId });
493
+ const result = await iosInteract.installApp(appPath, resolved.id);
494
+ installed = result.installed;
495
+ output = result.output;
496
+ deviceInfo = result.device;
497
+ errorMsg = result.error;
498
+ }
499
+ const response = {
500
+ device: deviceInfo,
501
+ installed,
502
+ output,
503
+ error: errorMsg
504
+ };
505
+ return wrapResponse(response);
506
+ }
288
507
  if (name === "get_logs") {
289
508
  const { platform, appId, deviceId, lines } = args;
290
509
  let logs;
291
510
  let deviceInfo;
292
511
  if (platform === "android") {
293
- deviceInfo = await androidObserve.getDeviceMetadata(appId || "", deviceId);
294
- const response = await androidObserve.getLogs(appId, lines ?? 200, deviceId);
512
+ // Resolve an explicit target device when multiple are attached
513
+ const resolved = await resolveTargetDevice({ platform: 'android', appId, deviceId });
514
+ deviceInfo = resolved;
515
+ const response = await androidObserve.getLogs(appId, lines ?? 200, resolved.id);
295
516
  logs = Array.isArray(response.logs) ? response.logs : [];
296
517
  }
297
518
  else {
298
- deviceInfo = await iosObserve.getDeviceMetadata(deviceId);
299
- const response = await iosObserve.getLogs(appId, deviceId);
519
+ const resolved = await resolveTargetDevice({ platform: 'ios', appId, deviceId });
520
+ deviceInfo = resolved;
521
+ const response = await iosObserve.getLogs(appId, resolved.id);
300
522
  logs = Array.isArray(response.logs) ? response.logs : [];
301
523
  }
302
524
  // Filter crash lines (e.g. lines containing 'FATAL EXCEPTION') for internal or AI use
@@ -321,20 +543,27 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
321
543
  ]
322
544
  };
323
545
  }
546
+ if (name === "list_devices") {
547
+ const { platform, appId } = (args || {});
548
+ const devices = await listDevices(platform, appId);
549
+ return wrapResponse({ devices });
550
+ }
324
551
  if (name === "capture_screenshot") {
325
552
  const { platform, deviceId } = args;
326
553
  let screenshot;
327
554
  let resolution;
328
555
  let deviceInfo;
329
556
  if (platform === "android") {
330
- deviceInfo = await androidObserve.getDeviceMetadata("", deviceId);
331
- const result = await androidObserve.captureScreen(deviceId);
557
+ const resolved = await resolveTargetDevice({ platform: 'android', deviceId });
558
+ deviceInfo = resolved;
559
+ const result = await androidObserve.captureScreen(resolved.id);
332
560
  screenshot = result.screenshot;
333
561
  resolution = result.resolution;
334
562
  }
335
563
  else {
336
- deviceInfo = await iosObserve.getDeviceMetadata(deviceId);
337
- const result = await iosObserve.captureScreenshot(deviceId);
564
+ const resolved = await resolveTargetDevice({ platform: 'ios', deviceId });
565
+ deviceInfo = resolved;
566
+ const result = await iosObserve.captureScreenshot(resolved.id);
338
567
  screenshot = result.screenshot;
339
568
  resolution = result.resolution;
340
569
  }
@@ -361,10 +590,12 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
361
590
  const { platform, deviceId } = args;
362
591
  let result;
363
592
  if (platform === "android") {
364
- result = await androidObserve.getUITree(deviceId);
593
+ const resolved = await resolveTargetDevice({ platform: 'android', deviceId });
594
+ result = await androidObserve.getUITree(resolved.id);
365
595
  }
366
596
  else if (platform === "ios") {
367
- result = await iosObserve.getUITree(deviceId);
597
+ const resolved = await resolveTargetDevice({ platform: 'ios', deviceId });
598
+ result = await iosObserve.getUITree(resolved.id);
368
599
  }
369
600
  else {
370
601
  throw new Error(`Platform ${platform} not supported for get_ui_tree`);
@@ -373,9 +604,125 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
373
604
  }
374
605
  if (name === "get_current_screen") {
375
606
  const { deviceId } = (args || {});
376
- const result = await androidObserve.getCurrentScreen(deviceId);
607
+ const resolved = await resolveTargetDevice({ platform: 'android', deviceId });
608
+ const result = await androidObserve.getCurrentScreen(resolved.id);
609
+ return wrapResponse(result);
610
+ }
611
+ if (name === "wait_for_element") {
612
+ const { platform, text, timeout, deviceId } = (args || {});
613
+ const effectiveTimeout = timeout ?? 10000;
614
+ let result;
615
+ if (platform === "android") {
616
+ const resolved = await resolveTargetDevice({ platform: 'android', deviceId });
617
+ result = await androidInteract.waitForElement(text, effectiveTimeout, resolved.id);
618
+ }
619
+ else {
620
+ const resolved = await resolveTargetDevice({ platform: 'ios', deviceId });
621
+ result = await iosInteract.waitForElement(text, effectiveTimeout, resolved.id);
622
+ }
623
+ return wrapResponse(result);
624
+ }
625
+ if (name === "tap") {
626
+ const { platform, x, y, deviceId } = (args || {});
627
+ const effectivePlatform = platform || "android";
628
+ // Basic validation
629
+ if (typeof x !== 'number' || typeof y !== 'number') {
630
+ throw new Error("x and y coordinates are required and must be numbers");
631
+ }
632
+ let result;
633
+ if (effectivePlatform === "android") {
634
+ const resolved = await resolveTargetDevice({ platform: 'android', deviceId });
635
+ result = await androidInteract.tap(x, y, resolved.id);
636
+ }
637
+ else {
638
+ const resolved = await resolveTargetDevice({ platform: 'ios', deviceId });
639
+ result = await iosInteract.tap(x, y, resolved.id);
640
+ }
641
+ return wrapResponse(result);
642
+ }
643
+ if (name === "swipe") {
644
+ const { platform, x1, y1, x2, y2, duration, deviceId } = (args || {});
645
+ const effectivePlatform = platform || "android";
646
+ if (typeof x1 !== 'number' || typeof y1 !== 'number' || typeof x2 !== 'number' || typeof y2 !== 'number' || typeof duration !== 'number') {
647
+ throw new Error("x1, y1, x2, y2, and duration are required and must be numbers");
648
+ }
649
+ let result;
650
+ if (effectivePlatform === "android") {
651
+ const resolved = await resolveTargetDevice({ platform: 'android', deviceId });
652
+ result = await androidInteract.swipe(x1, y1, x2, y2, duration, resolved.id);
653
+ }
654
+ else {
655
+ throw new Error(`Platform ${effectivePlatform} not supported for swipe`);
656
+ }
657
+ return wrapResponse(result);
658
+ }
659
+ if (name === "type_text") {
660
+ const { platform, text, deviceId } = (args || {});
661
+ const effectivePlatform = platform || "android";
662
+ if (typeof text !== 'string') {
663
+ throw new Error("text is required and must be a string");
664
+ }
665
+ let result;
666
+ if (effectivePlatform === "android") {
667
+ const resolved = await resolveTargetDevice({ platform: 'android', deviceId });
668
+ result = await androidInteract.typeText(text, resolved.id);
669
+ }
670
+ else {
671
+ throw new Error(`Platform ${effectivePlatform} not supported for type_text`);
672
+ }
377
673
  return wrapResponse(result);
378
674
  }
675
+ if (name === "press_back") {
676
+ const { platform, deviceId } = (args || {});
677
+ const effectivePlatform = platform || "android";
678
+ if (effectivePlatform !== "android") {
679
+ throw new Error(`Platform ${effectivePlatform} not supported for press_back`);
680
+ }
681
+ const resolved = await resolveTargetDevice({ platform: 'android', deviceId });
682
+ const result = await androidInteract.pressBack(resolved.id);
683
+ return wrapResponse(result);
684
+ }
685
+ if (name === 'start_log_stream') {
686
+ const { platform, packageName, level, sessionId: argSession, deviceId } = args;
687
+ const sessionId = argSession || 'default';
688
+ const effectivePlatform = platform || 'android';
689
+ if (effectivePlatform === 'android') {
690
+ const resolved = await resolveTargetDevice({ platform: 'android', appId: packageName, deviceId });
691
+ const res = await startAndroidLogStream(packageName, level || 'error', resolved.id, sessionId);
692
+ return wrapResponse(res);
693
+ }
694
+ else {
695
+ const resolved = await resolveTargetDevice({ platform: 'ios', appId: packageName, deviceId });
696
+ const res = await startIOSLogStream(packageName, level || 'error', resolved.id, sessionId);
697
+ return wrapResponse(res);
698
+ }
699
+ }
700
+ if (name === 'read_log_stream') {
701
+ const { platform, sessionId: argSession, limit, since } = (args || {});
702
+ const sid = argSession || 'default';
703
+ const effectivePlatform = platform || 'android';
704
+ if (effectivePlatform === 'android') {
705
+ const { entries, crash_summary } = await readLogStreamLines(sid, limit ?? 100, since);
706
+ return wrapResponse({ entries, crash_summary });
707
+ }
708
+ else {
709
+ const { entries, crash_summary } = await readIOSLogStreamLines(sid, limit ?? 100, since);
710
+ return wrapResponse({ entries, crash_summary });
711
+ }
712
+ }
713
+ if (name === 'stop_log_stream') {
714
+ const { platform, sessionId: argSession } = (args || {});
715
+ const sid = argSession || 'default';
716
+ const effectivePlatform = platform || 'android';
717
+ if (effectivePlatform === 'android') {
718
+ const res = await stopAndroidLogStream(sid);
719
+ return wrapResponse(res);
720
+ }
721
+ else {
722
+ const res = await stopIOSLogStream(sid);
723
+ return wrapResponse(res);
724
+ }
725
+ }
379
726
  }
380
727
  catch (error) {
381
728
  return {
package/docs/CHANGELOG.md CHANGED
@@ -2,12 +2,27 @@
2
2
 
3
3
  All notable changes to the **Mobile Debug MCP** project will be documented in this file.
4
4
 
5
- ## [0.6.0] - 2026-03-11
5
+ ## [0.8.0]
6
6
 
7
7
  ### Added
8
+ - **`list_devices` tool**: enumerate connected Android devices and iOS simulators. Returns device metadata (id, platform, osVersion, model, simulator, appInstalled).
9
+ - **`install_app` tool**: install an APK (.apk) on Android or an app bundle (.app/.ipa) on iOS simulators/devices. Uses `adb install -r` for Android and `simctl`/`idb` for iOS.
10
+ - **`start_log_stream`, `read_log_stream`, `stop_log_stream` tools**: stream Android logcat filtered by application PID, poll parsed entries, support incremental reads (limit/since) and basic crash detection metadata (crash_detected, exception, sample).
11
+
12
+ ### Changed
13
+ - Device-selection: server handlers now use a central resolver to pick a sensible default device when `deviceId` is omitted. This reduces duplication and makes behavior deterministic when multiple devices are attached.
14
+
15
+ ## [0.7.0]
16
+
17
+ ### Added
18
+ - **`wait_for_element` tool**: Added ability to wait for a specific UI element to appear on screen. Polls `get_ui_tree` until timeout. Useful for waiting on app transitions or loading states.
8
19
  - **`get_current_screen` tool**: Added ability to determine the currently visible activity on an Android device using `dumpsys activity activities`. Includes robust regex parsing to handle various Android versions.
20
+ - **`tap` tool**: Added ability to tap at specific screen coordinates on Android and iOS devices.
21
+ - **`swipe` tool**: Added ability to simulate swipe gestures (scroll, drag) on Android devices.
22
+ - **`type_text` tool**: Added ability to type text into focused input fields on Android devices.
23
+ - **`press_back` tool**: Added ability to simulate the Android Back button.
9
24
 
10
- ## [0.4.0] - 2026-03-09
25
+ ## [0.4.0]
11
26
 
12
27
  ### Added
13
28
  - **`terminate_app` tool**: Added ability to terminate apps on Android and iOS.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mobile-debug-mcp",
3
- "version": "0.6.0",
3
+ "version": "0.8.0",
4
4
  "description": "MCP server for mobile app debugging (Android + iOS), with focus on security and reliability",
5
5
  "type": "module",
6
6
  "bin": {
@@ -9,7 +9,10 @@
9
9
  "scripts": {
10
10
  "build": "tsc",
11
11
  "start": "node ./dist/server.js",
12
- "prepare": "npm run build"
12
+ "prepare": "npm run build",
13
+ "test:unit": "tsx test/unit/index.ts",
14
+ "test:integration": "tsx test/integration/index.ts",
15
+ "test": "npm run test:unit && npm run test:integration"
13
16
  },
14
17
  "engines": {
15
18
  "node": ">=18"
@@ -21,6 +24,7 @@
21
24
  },
22
25
  "devDependencies": {
23
26
  "@types/node": "^25.4.0",
27
+ "tsx": "^4.21.0",
24
28
  "typescript": "^5.9.3"
25
29
  }
26
30
  }