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 +18 -526
- package/dist/android/interact.js +86 -2
- package/dist/android/utils.js +95 -10
- package/dist/ios/interact.js +87 -20
- package/dist/server.js +59 -287
- package/dist/tools/app.js +45 -0
- package/dist/tools/devices.js +5 -0
- package/dist/tools/install.js +47 -0
- package/dist/tools/logs.js +62 -0
- package/dist/tools/screenshot.js +17 -0
- package/dist/tools/ui.js +57 -0
- package/docs/CHANGELOG.md +9 -0
- package/docs/TOOLS.md +272 -0
- package/package.json +1 -1
- package/src/android/interact.ts +89 -2
- package/src/android/utils.ts +83 -8
- package/src/ios/interact.ts +90 -24
- package/src/server.ts +76 -375
- package/src/tools/app.ts +46 -0
- package/src/tools/devices.ts +6 -0
- package/src/tools/install.ts +43 -0
- package/src/tools/logs.ts +62 -0
- package/src/tools/screenshot.ts +18 -0
- package/src/tools/ui.ts +62 -0
- package/test/integration/install.integration.ts +64 -0
- package/test/integration/run-install-android.ts +21 -0
- package/test/integration/run-install-ios.ts +21 -0
- package/test/integration/test-dist.ts +41 -0
- package/test/unit/index.ts +1 -0
- package/test/unit/install.test.ts +82 -0
package/README.md
CHANGED
|
@@ -1,556 +1,48 @@
|
|
|
1
1
|
# Mobile Debug MCP
|
|
2
2
|
|
|
3
|
-
|
|
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
|
|
5
|
+
This README was shortened to keep high-level info only. Detailed tool definitions moved to docs/TOOLS.md.
|
|
6
6
|
|
|
7
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
24
|
+
## Configuration example
|
|
73
25
|
|
|
74
26
|
```json
|
|
75
27
|
{
|
|
76
28
|
"mcpServers": {
|
|
77
29
|
"mobile-debug": {
|
|
78
30
|
"command": "npx",
|
|
79
|
-
"args": [
|
|
80
|
-
|
|
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
|
-
>
|
|
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
|
-
|
|
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
|
|
48
|
+
MIT
|
package/dist/android/interact.js
CHANGED
|
@@ -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
|
-
|
|
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) {
|