mobile-debug-mcp 0.8.0 → 0.9.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/README.md CHANGED
@@ -1,556 +1,48 @@
1
1
  # Mobile Debug MCP
2
2
 
3
- **Mobile Debug MCP** is a minimal, secure MCP server for AI-assisted mobile development. It allows you to **launch Android or iOS apps**, **read their logs**, and **inspect UI** from an MCP-compatible AI client.
3
+ A minimal, secure MCP server for AI-assisted mobile development. Build, install, and inspect Android/iOS apps from an MCP-compatible client.
4
4
 
5
- This server is designed with security in mind, using strict argument handling to prevent shell injection, and reliability, with robust process management to avoid hanging operations.
5
+ This README was shortened to keep high-level info only. Detailed tool definitions moved to docs/TOOLS.md.
6
6
 
7
- > **Note:** iOS support is currently an untested Work In Progress (WIP). Please use with caution and report any issues.
8
-
9
- ---
10
-
11
- ## Features
12
-
13
- - Launch Android apps via package name.
14
- - Launch iOS apps via bundle ID on a booted simulator.
15
- - Fetch recent logs from Android or iOS apps.
16
- - Terminate and restart apps.
17
- - Clear app data for fresh installs.
18
- - Capture screenshots.
19
- - Cross-platform support (Android + iOS).
20
- - Minimal, focused design for fast debugging loops.
21
-
22
- ---
23
-
24
- ## Requirements
25
-
26
- - Node.js >= 18
27
- - Android SDK (`adb` in PATH) for Android support
28
- - Xcode command-line tools (`xcrun simctl`) for iOS support
29
- - **iOS Device Bridge (`idb`)** for iOS UI tree support
30
- - Booted iOS simulator for iOS testing
31
-
32
- ---
33
-
34
- ## Installation
35
-
36
- You can install and use **Mobile Debug MCP** in one of two ways:
37
-
38
- ### 1. Install Dependencies
39
-
40
- **iOS Prerequisite (`idb`):**
41
- To use the `get_ui_tree` tool on iOS, you must install Facebook's `idb`:
42
-
43
- ```bash
44
- brew tap facebook/fb
45
- brew install idb-companion
46
- pip3 install fb-idb
47
- ```
48
-
49
- ### 2. Clone the repository for local development
7
+ ## Quick start
50
8
 
51
9
  ```bash
52
10
  git clone https://github.com/YOUR_USERNAME/mobile-debug-mcp.git
53
11
  cd mobile-debug-mcp
54
12
  npm install
55
13
  npm run build
14
+ npm start
56
15
  ```
57
16
 
58
- This option is suitable if you want to modify or contribute to the code.
59
-
60
- ### 3. Install via npm for standard use
61
-
62
- ```bash
63
- npm install -g mobile-debug-mcp
64
- ```
65
-
66
- This option installs the package globally for easy use without cloning the repo.
67
-
68
- ---
17
+ ## Requirements
69
18
 
70
- ## MCP Server Configuration
19
+ - Node.js >= 18
20
+ - Android SDK (adb) for Android support
21
+ - Xcode command-line tools for iOS support
22
+ - Optional: idb for enhanced iOS device support
71
23
 
72
- Example WebUI MCP config using `npx --yes` and environment variables:
24
+ ## Configuration example
73
25
 
74
26
  ```json
75
27
  {
76
28
  "mcpServers": {
77
29
  "mobile-debug": {
78
30
  "command": "npx",
79
- "args": [
80
- "--yes",
81
- "mobile-debug-mcp",
82
- "server"
83
- ],
84
- "env": {
85
- "ADB_PATH": "/path/to/adb",
86
- "XCRUN_PATH": "/usr/bin/xcrun"
87
- }
31
+ "args": ["--yes","mobile-debug-mcp","server"],
32
+ "env": { "ADB_PATH": "/path/to/adb", "XCRUN_PATH": "/usr/bin/xcrun" }
88
33
  }
89
34
  }
90
35
  }
91
36
  ```
92
37
 
93
- > Make sure to set `ADB_PATH` (Android) and `XCRUN_PATH` (iOS) if the tools are not in your system PATH.
94
-
95
- ---
96
-
97
- ## Tools
98
-
99
- All tools accept a JSON input payload and return a structured JSON response. **Every response includes a `device` object** (with information about the selected device/simulator used for the operation), plus the tool-specific output.
100
-
101
- ### list_devices
102
- Enumerate connected Android devices and iOS simulators.
103
-
104
- Input (optional):
105
- ```jsonc
106
- { "platform": "android" | "ios" }
107
- ```
108
-
109
- Response:
110
- ```json
111
- { "devices": [ { "id": "emulator-5554", "platform": "android", "osVersion": "11", "model": "sdk_gphone64_arm64", "simulator": true, "appInstalled": false } ] }
112
- ```
113
-
114
- Use `list_devices` when multiple devices are attached to inspect metadata and pick a device explicitly by passing `deviceId` to subsequent tool calls.
115
-
116
- ### start_app
117
- Launch a mobile app.
118
-
119
- **Input:**
120
- ```jsonc
121
- {
122
- "platform": "android" | "ios",
123
- "appId": "com.example.app", // Android package or iOS bundle ID (Required)
124
- "deviceId": "emulator-5554" // Optional: target specific device/simulator
125
- }
126
- ```
127
-
128
- **Response:**
129
- ```json
130
- {
131
- "device": { /* device info */ },
132
- "appStarted": true,
133
- "launchTimeMs": 123
134
- }
135
- ```
136
-
137
- ### get_logs
138
- Fetch recent logs from the app or device.
139
-
140
- **Input:**
141
- ```jsonc
142
- {
143
- "platform": "android" | "ios",
144
- "appId": "com.example.app", // Optional: filter logs by app
145
- "deviceId": "emulator-5554", // Optional: target specific device
146
- "lines": 200 // Optional: number of lines (Android only)
147
- }
148
- ```
149
-
150
- **Response:**
151
- Returns two content blocks:
152
- 1. JSON metadata:
153
- ```json
154
- {
155
- "device": { /* device info */ },
156
- "result": { "lines": 50, "crashLines": [...] }
157
- }
158
- ```
159
- 2. Plain text log output.
160
-
161
- ### capture_screenshot
162
- Capture a screenshot of the current device screen.
163
-
164
- **Input:**
165
- ```jsonc
166
- {
167
- "platform": "android" | "ios",
168
- "deviceId": "emulator-5554" // Optional: target specific device
169
- }
170
- ```
171
-
172
- **Response:**
173
- Returns two content blocks:
174
- 1. JSON metadata:
175
- ```json
176
- {
177
- "device": { /* device info */ },
178
- "result": { "resolution": { "width": 1080, "height": 1920 } }
179
- }
180
- ```
181
- 2. Image content (image/png) containing the raw PNG data.
182
-
183
- ### terminate_app
184
- Terminate a running app.
185
-
186
- **Input:**
187
- ```jsonc
188
- {
189
- "platform": "android" | "ios",
190
- "appId": "com.example.app", // Android package or iOS bundle ID (Required)
191
- "deviceId": "emulator-5554" // Optional
192
- }
193
- ```
194
-
195
- **Response:**
196
- ```json
197
- {
198
- "device": { /* device info */ },
199
- "appTerminated": true
200
- }
201
- ```
202
-
203
- ### restart_app
204
- Restart an app (terminate then launch).
205
-
206
- **Input:**
207
- ```jsonc
208
- {
209
- "platform": "android" | "ios",
210
- "appId": "com.example.app", // Android package or iOS bundle ID (Required)
211
- "deviceId": "emulator-5554" // Optional
212
- }
213
- ```
214
-
215
- **Response:**
216
- ```json
217
- {
218
- "device": { /* device info */ },
219
- "appRestarted": true,
220
- "launchTimeMs": 123
221
- }
222
- ```
223
-
224
- ### reset_app_data
225
- Clear app storage (reset to fresh install state).
226
-
227
- **Input:**
228
- ```jsonc
229
- {
230
- "platform": "android" | "ios",
231
- "appId": "com.example.app", // Android package or iOS bundle ID (Required)
232
- "deviceId": "emulator-5554" // Optional
233
- }
234
- ```
235
-
236
- **Response:**
237
- ```json
238
- {
239
- "device": { /* device info */ },
240
- "dataCleared": true
241
- }
242
- ```
243
-
244
- ### install_app
245
- Install an app onto a connected device or simulator (APK for Android, .app/.ipa for iOS).
246
-
247
- **Input:**
248
- ```jsonc
249
- {
250
- "platform": "android" | "ios",
251
- "appPath": "/path/to/app.apk_or_app.app_or_ipa", // Host path to the app file (Required)
252
- "deviceId": "emulator-5554" // Optional: target specific device/simulator
253
- }
254
- ```
255
-
256
- **Response:**
257
- ```json
258
- {
259
- "device": { /* device info */ },
260
- "installed": true,
261
- "output": "Platform-specific installer output (adb/simctl/idb)",
262
- "error": "Optional error message if installation failed"
263
- }
264
- ```
265
-
266
- Notes:
267
- - Android: uses `adb install -r <apkPath>`. The APK must be accessible from the host running the MCP server.
268
- - iOS: attempts `xcrun simctl install` for simulators and falls back to `idb install` if available for physical devices. Ensure `XCRUN_PATH` and `IDB` are configured if using non-standard locations.
269
- - Installation output and errors are surfaced in the response for debugging.
270
-
271
- ### start_log_stream / read_log_stream / stop_log_stream
272
- Start a live log stream for an Android app and poll the accumulated entries.
273
-
274
- start_log_stream starts a background adb logcat process filtered by the app PID. It returns immediately with success and creates a per-session NDJSON file of parsed log entries.
275
-
276
- read_log_stream retrieves recent parsed entries and includes crash detection metadata.
277
-
278
- Input (start_log_stream):
279
- ```jsonc
280
- {
281
- "packageName": "com.example.app", // Required
282
- "level": "error" | "warn" | "info" | "debug", // Optional, defaults to "error"
283
- "sessionId": "optional-session-id" // Optional - used to track stream per debugging session
284
- }
285
- ```
286
-
287
- Input (read_log_stream):
288
- ```jsonc
289
- {
290
- "sessionId": "optional-session-id",
291
- "limit": 100, // Optional, max number of entries to return (default 100)
292
- "since": "2026-03-13T14:00:00Z" // Optional, ISO timestamp or epoch ms to return only newer entries
293
- }
294
- ```
295
-
296
- Response (read_log_stream):
297
- ```json
298
- {
299
- "entries": [
300
- { "timestamp": "2026-03-13T14:01:04.123Z", "level": "E", "tag": "AndroidRuntime", "message": "FATAL EXCEPTION: main", "crash": true, "exception": "NullPointerException" }
301
- ],
302
- "crash_summary": { "crash_detected": true, "exception": "NullPointerException", "sample": "FATAL EXCEPTION: main" }
303
- }
304
- ```
305
-
306
- Notes:
307
- - The read_log_stream `since` parameter accepts ISO timestamps or epoch milliseconds. Use it to poll incrementally (pass last seen timestamp).
308
- - Crash detection is heuristic-based (looks for 'FATAL EXCEPTION' and Exception names). It helps agents decide to capture traces or stop tests quickly.
309
- - stop_log_stream stops the background adb process for the session.
310
-
311
-
312
- ### get_ui_tree
313
- Get the current UI hierarchy from the device. Returns a structured JSON representation of the screen content.
314
-
315
- **Input:**
316
- ```jsonc
317
- {
318
- "platform": "android" | "ios",
319
- "deviceId": "emulator-5554" // Optional
320
- }
321
- ```
322
-
323
- **Response:**
324
- ```json
325
- {
326
- "device": { /* device info */ },
327
- "screen": "",
328
- "resolution": { "width": 1080, "height": 1920 },
329
- "elements": [
330
- {
331
- "text": "Login",
332
- "contentDescription": null,
333
- "type": "android.widget.Button",
334
- "resourceId": "com.example:id/login_button",
335
- "clickable": true,
336
- "enabled": true,
337
- "visible": true,
338
- "bounds": [120,400,280,450],
339
- "center": [200, 425],
340
- "depth": 1,
341
- "parentId": 0,
342
- "children": []
343
- }
344
- ]
345
- }
346
- ```
347
-
348
- ### get_current_screen
349
- Get the currently visible activity on an Android device.
350
-
351
- **Input:**
352
- ```jsonc
353
- {
354
- "deviceId": "emulator-5554" // Optional: target specific device
355
- }
356
- ```
357
-
358
- **Response:**
359
- ```json
360
- {
361
- "device": { /* device info */ },
362
- "package": "com.example.app",
363
- "activity": "com.example.app.LoginActivity",
364
- "shortActivity": "LoginActivity"
365
- }
366
- ```
367
-
368
- ### wait_for_element
369
- Wait until a UI element with matching text appears on screen or timeout is reached. Useful for handling loading states or transitions.
370
-
371
- **Input:**
372
- ```jsonc
373
- {
374
- "platform": "android" | "ios",
375
- "text": "Home", // Text to wait for
376
- "timeout": 5000, // Max wait time in ms (default 10000)
377
- "deviceId": "emulator-5554" // Optional
378
- }
379
- ```
380
-
381
- **Response:**
382
- ```json
383
- {
384
- "device": { /* device info */ },
385
- "found": true,
386
- "element": { /* UIElement object if found */ }
387
- }
388
- ```
389
-
390
- If the element is not found within the timeout, `found` will be `false`. If a system error occurs (e.g., ADB failure), an `error` field will be present.
391
-
392
- ```json
393
- {
394
- "device": { /* device info */ },
395
- "found": false,
396
- "error": "Optional error message"
397
- }
398
- ```
399
-
400
- ### tap
401
- Simulate a finger tap on the device screen at specific coordinates.
402
-
403
- Platform support and constraints:
404
- - Android: Implemented via `adb shell input tap` and works when `adb` is available in PATH or configured via `ADB_PATH`.
405
- - iOS: Requires Facebook's `idb` tooling. The iOS implementation uses `idb` to deliver UI events and is simulator-oriented (works reliably on a booted simulator). Physical device support depends on `idb` capabilities and a running `idb_companion` on the target device; it may not work in all environments.
406
-
407
- Prerequisites for iOS (if you intend to use tap on iOS):
408
- ```bash
409
- brew tap facebook/fb
410
- brew install idb-companion
411
- pip3 install fb-idb
412
- ```
413
- Ensure `idb` and `idb_companion` are in your PATH. If you use non-standard tool locations, set `XCRUN_PATH` and/or `ADB_PATH` environment variables as appropriate.
414
-
415
- Behavior notes:
416
- - The tool is a primitive input: it only sends a tap at the provided coordinates. It does not inspect or interpret the UI.
417
- - If `idb` is missing or the simulator/device is not available, the tool will return an error explaining the failure.
418
-
419
- **Input:**
420
- ```jsonc
421
- {
422
- "platform": "android" | "ios", // Optional, defaults to "android"
423
- "x": 200, // X coordinate (Required)
424
- "y": 400, // Y coordinate (Required)
425
- "deviceId": "emulator-5554" // Optional
426
- }
427
- ```
428
-
429
- **Response:**
430
- ```json
431
- {
432
- "device": { /* device info */ },
433
- "success": true,
434
- "x": 200,
435
- "y": 400
436
- }
437
- ```
438
-
439
- If the tap fails (e.g., missing `adb`/`idb`, device not found), `success` will be `false` and an `error` field will be present.
440
-
441
- ### swipe
442
- Simulate a swipe gesture on an Android device.
443
-
444
- **Input:**
445
- ```jsonc
446
- {
447
- "platform": "android", // Optional, defaults to "android"
448
- "x1": 500, // Start X (Required)
449
- "y1": 1500, // Start Y (Required)
450
- "x2": 500, // End X (Required)
451
- "y2": 500, // End Y (Required)
452
- "duration": 300, // Duration in ms (Required)
453
- "deviceId": "emulator-5554" // Optional
454
- }
455
- ```
456
-
457
- **Response:**
458
- ```json
459
- {
460
- "device": { /* device info */ },
461
- "success": true,
462
- "start": [500, 1500],
463
- "end": [500, 500],
464
- "duration": 300
465
- }
466
- ```
467
-
468
- If the swipe fails, `success` will be `false` and an `error` field will be present.
469
-
470
- ### type_text
471
- Type text into the currently focused input field on an Android device.
472
-
473
- **Input:**
474
- ```jsonc
475
- {
476
- "platform": "android", // Optional, defaults to "android"
477
- "text": "hello world", // Text to type (Required)
478
- "deviceId": "emulator-5554" // Optional
479
- }
480
- ```
481
-
482
- **Response:**
483
- ```json
484
- {
485
- "device": { /* device info */ },
486
- "success": true,
487
- "text": "hello world"
488
- }
489
- ```
490
-
491
- If the command fails, `success` will be `false` and an `error` field will be present.
492
-
493
- ### press_back
494
- Simulate pressing the Android Back button.
495
-
496
- **Input:**
497
- ```jsonc
498
- {
499
- "platform": "android", // Optional
500
- "deviceId": "emulator-5554" // Optional
501
- }
502
- ```
503
-
504
- **Response:**
505
- ```json
506
- {
507
- "device": { /* device info */ },
508
- "success": true
509
- }
510
- ```
511
-
512
- ---
513
-
514
- ## Recommended Workflow
515
-
516
- 1. Ensure Android device or iOS simulator is running.
517
- 2. Use `start_app` to launch the app.
518
- 3. Use `get_logs` to read the latest logs.
519
- 4. Use `capture_screenshot` to visually inspect the app if needed.
520
- 5. Use `wait_for_element` to ensure the app is in the expected state before proceeding (e.g., after login).
521
- 6. Use `reset_app_data` to clear state if debugging fresh install scenarios.
522
- 7. Use `restart_app` to quickly reboot the app during development cycles.
523
-
524
- ---
525
-
526
- ## Notes
527
-
528
- - Ensure `adb` and `xcrun` are in your PATH or set `ADB_PATH` / `XCRUN_PATH` accordingly.
529
- - For iOS, the simulator must be booted before using tools.
530
- - The `capture_screenshot` tool returns a multi-block response: a JSON text block with metadata, followed by an image block containing the base64-encoded PNG data.
531
-
532
- ---
533
-
534
- ## Testing
535
-
536
- The repository includes a smoke test script to verify end-to-end functionality on real devices or simulators.
537
-
538
- ```bash
539
- # Run smoke test for Android
540
- npx tsx smoke-test.ts android com.example.package
541
-
542
- # Run smoke test for iOS
543
- npx tsx smoke-test.ts ios com.example.bundleid
544
- ```
38
+ > Note: Avoid using `jsonc` fences with inline comments in README code blocks to prevent syntax-highlighting issues on some renderers.
545
39
 
546
- The smoke test performs the following sequence:
547
- 1. Starts the app
548
- 2. Captures a screenshot
549
- 3. Fetches logs
550
- 4. Terminates the app
40
+ ## Docs
551
41
 
552
- ---
42
+ - Tools: docs/TOOLS.md (full input/response examples)
43
+ - Changelog: docs/CHANGELOG.md
44
+ - Tests: test/
553
45
 
554
46
  ## License
555
47
 
556
- MIT License
48
+ MIT
@@ -1,5 +1,9 @@
1
- import { execAdb, getAndroidDeviceMetadata, getDeviceInfo } from "./utils.js";
1
+ import { execAdb, getAndroidDeviceMetadata, getDeviceInfo, detectJavaHome } from "./utils.js";
2
2
  import { AndroidObserve } from "./observe.js";
3
+ import { promises as fs } from "fs";
4
+ import { spawn } from "child_process";
5
+ import path from "path";
6
+ import { existsSync } from "fs";
3
7
  export class AndroidInteract {
4
8
  observe = new AndroidObserve();
5
9
  async waitForElement(text, timeout, deviceId) {
@@ -79,8 +83,88 @@ export class AndroidInteract {
79
83
  async installApp(apkPath, deviceId) {
80
84
  const metadata = await getAndroidDeviceMetadata("", deviceId);
81
85
  const deviceInfo = getDeviceInfo(deviceId || 'default', metadata);
86
+ // Helper to recursively find first APK under a directory
87
+ async function findApk(dir) {
88
+ const entries = await fs.readdir(dir, { withFileTypes: true }).catch(() => []);
89
+ for (const e of entries) {
90
+ const full = path.join(dir, e.name);
91
+ if (e.isDirectory()) {
92
+ const found = await findApk(full);
93
+ if (found)
94
+ return found;
95
+ }
96
+ else if (e.isFile() && full.endsWith('.apk')) {
97
+ return full;
98
+ }
99
+ }
100
+ return undefined;
101
+ }
82
102
  try {
83
- const output = await execAdb(['install', '-r', apkPath], deviceId);
103
+ let apkToInstall = apkPath;
104
+ // If a directory is provided, attempt to build via Gradle
105
+ const stat = await fs.stat(apkPath).catch(() => null);
106
+ if (stat && stat.isDirectory()) {
107
+ const gradlewPath = path.join(apkPath, 'gradlew');
108
+ const gradleCmd = existsSync(gradlewPath) ? './gradlew' : 'gradle';
109
+ await new Promise(async (resolve, reject) => {
110
+ // Auto-detect and set JAVA_HOME (prefer JDK 17) so builds don't require manual environment setup
111
+ const detectedJavaHome = await detectJavaHome().catch(() => undefined);
112
+ const env = Object.assign({}, process.env);
113
+ if (detectedJavaHome) {
114
+ // Override existing JAVA_HOME if detection found a preferably compatible JDK (e.g., JDK 17).
115
+ if (env.JAVA_HOME !== detectedJavaHome) {
116
+ env.JAVA_HOME = detectedJavaHome;
117
+ // Also ensure the JDK bin is on PATH so tools like jlink/javac are resolved from the detected JDK
118
+ env.PATH = `${path.join(detectedJavaHome, 'bin')}${path.delimiter}${env.PATH || ''}`;
119
+ console.debug('[android] Overriding JAVA_HOME with detected path:', detectedJavaHome);
120
+ }
121
+ }
122
+ // Sanitize environment so user shell init scripts are less likely to override our JAVA_HOME.
123
+ try {
124
+ // Remove obvious shell profile hints; avoid touching SDKMAN symlinks or on-disk state.
125
+ delete env.SHELL;
126
+ }
127
+ catch (e) { }
128
+ // If we detected a compatible JDK, instruct Gradle to use it and avoid daemon reuse
129
+ // Prepare gradle invocation
130
+ const gradleArgs = ['assembleDebug'];
131
+ if (detectedJavaHome) {
132
+ gradleArgs.push(`-Dorg.gradle.java.home=${detectedJavaHome}`);
133
+ gradleArgs.push('--no-daemon');
134
+ env.GRADLE_JAVA_HOME = detectedJavaHome;
135
+ }
136
+ // Prefer invoking the wrapper directly without a shell to avoid user profile shims (sdkman) re-setting JAVA_HOME
137
+ const wrapperPath = path.join(apkPath, 'gradlew');
138
+ const useWrapper = existsSync(wrapperPath);
139
+ const execCmd = useWrapper ? wrapperPath : gradleCmd;
140
+ const spawnOpts = { cwd: apkPath, env };
141
+ // When using wrapper, ensure it's executable and invoke directly (no shell)
142
+ if (useWrapper) {
143
+ // Ensure the wrapper is executable; swallow errors from chmod (best-effort).
144
+ await fs.chmod(wrapperPath, 0o755).catch(() => { });
145
+ spawnOpts.shell = false;
146
+ }
147
+ else {
148
+ // if using system 'gradle' allow shell to resolve platform PATH
149
+ spawnOpts.shell = true;
150
+ }
151
+ const proc = spawn(execCmd, gradleArgs, spawnOpts);
152
+ let stderr = '';
153
+ proc.stderr?.on('data', d => stderr += d.toString());
154
+ proc.on('close', code => {
155
+ if (code === 0)
156
+ resolve();
157
+ else
158
+ reject(new Error(stderr || `Gradle build failed with code ${code}`));
159
+ });
160
+ proc.on('error', err => reject(err));
161
+ });
162
+ const built = await findApk(apkPath);
163
+ if (!built)
164
+ throw new Error('Could not locate built APK after running Gradle');
165
+ apkToInstall = built;
166
+ }
167
+ const output = await execAdb(['install', '-r', apkToInstall], deviceId);
84
168
  return { device: deviceInfo, installed: true, output };
85
169
  }
86
170
  catch (e) {