mobai-mcp 2.0.0 → 2.1.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 +70 -106
- package/dist/index.js +35 -193
- package/dist/resources.js +39 -27
- package/package.json +1 -1
- package/server.json +2 -2
package/README.md
CHANGED
|
@@ -3,24 +3,27 @@
|
|
|
3
3
|
[](https://www.npmjs.com/package/mobai-mcp)
|
|
4
4
|
[](https://opensource.org/licenses/Apache-2.0)
|
|
5
5
|
|
|
6
|
-
MCP (Model Context Protocol) server for [MobAI](https://mobai.run)
|
|
6
|
+
MCP (Model Context Protocol) server for [MobAI](https://mobai.run) — AI-powered mobile device automation. Lets AI assistants (Claude Code, Cursor, Windsurf, Cline, and other MCP-compatible tools) control Android and iOS devices, emulators, and simulators via a single DSL-first interface.
|
|
7
7
|
|
|
8
|
-
##
|
|
8
|
+
## How it works
|
|
9
9
|
|
|
10
|
-
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
- **DSL Batch Execution**: Execute multiple automation steps efficiently
|
|
14
|
-
- **AI Agent**: Run autonomous agents to complete complex tasks
|
|
15
|
-
- **Screenshot Capture**: Capture and save device screenshots
|
|
10
|
+
All device interaction is batched through one primary tool: **`execute_dsl`**. Instead of exposing dozens of fine-grained tools (tap, swipe, type…), the server accepts a JSON script describing a sequence of actions with predicates, assertions, waits, and conditional branches. This keeps round-trips low and encodes retry/failure strategies server-side.
|
|
11
|
+
|
|
12
|
+
A small set of companion tools handles device discovery, screenshots, app management, and running `.mob` test files.
|
|
16
13
|
|
|
17
14
|
## Prerequisites
|
|
18
15
|
|
|
19
16
|
- Node.js 18+
|
|
20
|
-
- [MobAI desktop app](https://mobai.run) running locally (
|
|
21
|
-
-
|
|
17
|
+
- [MobAI desktop app](https://mobai.run) running locally (HTTP API on `127.0.0.1:8686`)
|
|
18
|
+
- A connected Android or iOS device, emulator, or simulator
|
|
19
|
+
|
|
20
|
+
## Installation
|
|
21
|
+
|
|
22
|
+
### Claude Code
|
|
22
23
|
|
|
23
|
-
|
|
24
|
+
```bash
|
|
25
|
+
claude mcp add mobai -- npx -y mobai-mcp
|
|
26
|
+
```
|
|
24
27
|
|
|
25
28
|
### Cursor
|
|
26
29
|
|
|
@@ -39,7 +42,7 @@ Add to `.cursor/mcp.json`:
|
|
|
39
42
|
|
|
40
43
|
### Claude Desktop
|
|
41
44
|
|
|
42
|
-
Add to
|
|
45
|
+
Add to `~/Library/Application Support/Claude/claude_desktop_config.json` (macOS):
|
|
43
46
|
|
|
44
47
|
```json
|
|
45
48
|
{
|
|
@@ -52,146 +55,107 @@ Add to Claude Desktop config (`~/Library/Application Support/Claude/claude_deskt
|
|
|
52
55
|
}
|
|
53
56
|
```
|
|
54
57
|
|
|
55
|
-
### Windsurf
|
|
58
|
+
### Windsurf / Cline / other MCP clients
|
|
56
59
|
|
|
57
|
-
|
|
60
|
+
The server speaks stdio — use your client's generic MCP configuration:
|
|
58
61
|
|
|
59
62
|
```json
|
|
60
63
|
{
|
|
61
|
-
"
|
|
62
|
-
|
|
63
|
-
"command": "npx",
|
|
64
|
-
"args": ["-y", "mobai-mcp"]
|
|
65
|
-
}
|
|
66
|
-
}
|
|
64
|
+
"command": "npx",
|
|
65
|
+
"args": ["-y", "mobai-mcp"]
|
|
67
66
|
}
|
|
68
67
|
```
|
|
69
68
|
|
|
70
|
-
|
|
69
|
+
## Tools
|
|
71
70
|
|
|
72
|
-
|
|
71
|
+
### Device management
|
|
73
72
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
73
|
+
| Tool | Description |
|
|
74
|
+
|---|---|
|
|
75
|
+
| `list_devices` | List all connected Android and iOS devices |
|
|
76
|
+
| `get_device` | Get details about a specific device |
|
|
77
|
+
| `start_bridge` | Start the automation bridge on a device (required before interaction) |
|
|
78
|
+
| `stop_bridge` | Stop the automation bridge |
|
|
80
79
|
|
|
81
|
-
|
|
80
|
+
### Screenshots
|
|
82
81
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
- `stop_bridge` - Stop automation bridge
|
|
82
|
+
| Tool | Description |
|
|
83
|
+
|---|---|
|
|
84
|
+
| `get_screenshot` | Fast, low-quality screenshot for LLM visual analysis (may be downscaled; response includes scale factor) |
|
|
85
|
+
| `save_screenshot` | Full-quality PNG to disk for reporting, debugging, or sharing |
|
|
88
86
|
|
|
89
|
-
###
|
|
90
|
-
- `get_screenshot` - Capture device screenshot
|
|
91
|
-
- `get_ui_tree` - Get accessibility tree (supports text_regex and bounds filtering)
|
|
92
|
-
- `tap` - Tap element by index or coordinates
|
|
93
|
-
- `type_text` - Type text
|
|
94
|
-
- `swipe` - Perform swipe gesture
|
|
95
|
-
- `go_home` - Navigate to home screen
|
|
96
|
-
- `launch_app` - Launch app by bundle ID
|
|
97
|
-
- `list_apps` - List installed apps
|
|
87
|
+
### Apps
|
|
98
88
|
|
|
99
|
-
|
|
100
|
-
|
|
89
|
+
| Tool | Description |
|
|
90
|
+
|---|---|
|
|
91
|
+
| `list_apps` | List installed apps on the device |
|
|
92
|
+
| `install_app` | Install an `.apk` or `.ipa` from a local file path |
|
|
93
|
+
| `uninstall_app` | Uninstall an app by bundle ID / package name |
|
|
94
|
+
| `debug_app` | Launch an app in debug mode and write stdout/stderr to a log file |
|
|
101
95
|
|
|
102
|
-
###
|
|
103
|
-
- `run_agent` - Run autonomous agent for complex tasks
|
|
96
|
+
### Automation
|
|
104
97
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
- `web_get_dom` - Get DOM tree
|
|
109
|
-
- `web_click` - Click element by CSS selector
|
|
110
|
-
- `web_type` - Type into element by CSS selector
|
|
111
|
-
- `web_execute_js` - Execute JavaScript
|
|
98
|
+
| Tool | Description |
|
|
99
|
+
|---|---|
|
|
100
|
+
| `execute_dsl` | **Primary tool.** Execute a batch of DSL steps: tap, type, swipe, observe, assertions, web automation, metrics, screen recording, and more. |
|
|
112
101
|
|
|
113
|
-
###
|
|
114
|
-
- `http_request` - Make raw HTTP request to MobAI API
|
|
102
|
+
### Test management
|
|
115
103
|
|
|
116
|
-
|
|
104
|
+
Tests are `.mob` files on disk inside project directories. You read, write, and edit them directly using your assistant's filesystem tools — MobAI watches for changes and updates the UI live. MCP is only needed to discover projects and run tests.
|
|
117
105
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
106
|
+
| Tool | Description |
|
|
107
|
+
|---|---|
|
|
108
|
+
| `test_get_active` | Get the active test project directory and its `.mob` cases |
|
|
109
|
+
| `test_list_projects` | List all known test project directories with their `.mob` cases |
|
|
110
|
+
| `test_run` | Run a `.mob` test case on a device (`project_dir` + `case_path` + `device_id`) |
|
|
122
111
|
|
|
123
|
-
##
|
|
112
|
+
## Resources
|
|
124
113
|
|
|
125
|
-
|
|
114
|
+
Read these **before** attempting any device interaction — they describe the DSL schema, action set, predicates, failure strategies, and `.mob` syntax.
|
|
126
115
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
116
|
+
| URI | Purpose |
|
|
117
|
+
|---|---|
|
|
118
|
+
| `mobai://reference/device-automation` | How to control devices — guide, all DSL actions, predicates, and failure strategies |
|
|
119
|
+
| `mobai://reference/testing` | Testing workflow, rules, error fixes, and `.mob` script syntax |
|
|
131
120
|
|
|
132
|
-
|
|
121
|
+
## Example
|
|
133
122
|
|
|
134
|
-
|
|
135
|
-
|
|
123
|
+
Open the iOS Settings app, navigate to Wi-Fi, and verify the toggle exists:
|
|
124
|
+
|
|
125
|
+
```json
|
|
136
126
|
{
|
|
137
127
|
"version": "0.2",
|
|
138
128
|
"steps": [
|
|
139
129
|
{"action": "open_app", "bundle_id": "com.apple.Preferences"},
|
|
140
|
-
{"action": "
|
|
141
|
-
{"action": "
|
|
142
|
-
{"action": "
|
|
130
|
+
{"action": "wait_for", "predicate": {"text": "Settings"}, "timeout_ms": 3000},
|
|
131
|
+
{"action": "tap", "predicate": {"text_contains": "Wi-Fi"}},
|
|
132
|
+
{"action": "wait_for", "predicate": {"type": "switch"}, "timeout_ms": 3000},
|
|
133
|
+
{"action": "assert_exists", "predicate": {"type": "switch"}},
|
|
134
|
+
{"action": "observe", "include": ["ui_tree"]}
|
|
143
135
|
]
|
|
144
136
|
}
|
|
145
137
|
```
|
|
146
138
|
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
```
|
|
150
|
-
Use run_agent with device_id and task: "Open Settings and enable WiFi"
|
|
151
|
-
```
|
|
152
|
-
|
|
153
|
-
## Comparison with Claude Code Plugin
|
|
154
|
-
|
|
155
|
-
| Feature | Claude Code Plugin | MCP Server |
|
|
156
|
-
|---------|-------------------|------------|
|
|
157
|
-
| Platform | Claude Code only | Any MCP client |
|
|
158
|
-
| Tools | http_request (generic) | Named tools + http_request |
|
|
159
|
-
| Resources | Skills (markdown) | MCP resources |
|
|
160
|
-
| Setup | Plugin install | npx |
|
|
161
|
-
|
|
162
|
-
The MCP server provides the same functionality as the Claude Code plugin but works with any MCP-compatible AI tool.
|
|
139
|
+
Pass this as the `commands` argument (a JSON string) to `execute_dsl` along with a `device_id` from `list_devices`.
|
|
163
140
|
|
|
164
141
|
## Troubleshooting
|
|
165
142
|
|
|
166
|
-
|
|
167
|
-
- Ensure MobAI desktop app is running
|
|
168
|
-
- Check that API is available at http://127.0.0.1:8686
|
|
143
|
+
**"Connection refused"** — Make sure the MobAI desktop app is running and the API is reachable at `http://127.0.0.1:8686`.
|
|
169
144
|
|
|
170
|
-
|
|
171
|
-
- Use `start_bridge` tool first before automation
|
|
172
|
-
- iOS bridge may take up to 60 seconds to start
|
|
145
|
+
**"Bridge not running"** — Call `start_bridge` first. The iOS bridge can take up to a minute to come up.
|
|
173
146
|
|
|
174
|
-
|
|
175
|
-
- Screenshots are saved to `/tmp/mobai/screenshots/`
|
|
176
|
-
- Use your AI tool's file reading capability to view them
|
|
147
|
+
**Screenshots not visible** — `get_screenshot` saves to `/tmp/mobai/screenshots/` by default and returns the file path. Use your assistant's file-reading capability to view them. DSL `observe` screenshots are extracted from the response and saved to the same directory.
|
|
177
148
|
|
|
178
149
|
## Development
|
|
179
150
|
|
|
180
151
|
```bash
|
|
181
|
-
# Clone the repository
|
|
182
152
|
git clone https://github.com/MobAI-App/mobai-mcp.git
|
|
183
153
|
cd mobai-mcp
|
|
184
|
-
|
|
185
|
-
# Install dependencies
|
|
186
154
|
npm install
|
|
187
|
-
|
|
188
|
-
# Build
|
|
189
155
|
npm run build
|
|
190
|
-
|
|
191
|
-
# Run locally
|
|
192
156
|
node dist/index.js
|
|
193
157
|
```
|
|
194
158
|
|
|
195
159
|
## License
|
|
196
160
|
|
|
197
|
-
Apache 2.0
|
|
161
|
+
Apache 2.0 — see [LICENSE](LICENSE).
|
package/dist/index.js
CHANGED
|
@@ -107,8 +107,6 @@ async function doRequest(method, urlPath, payload, timeoutMs = DEFAULT_TIMEOUT_M
|
|
|
107
107
|
const doGet = (p) => doRequest("GET", p);
|
|
108
108
|
const doPost = (p, body) => doRequest("POST", p, body);
|
|
109
109
|
const doDelete = (p) => doRequest("DELETE", p);
|
|
110
|
-
const doPut = (p, body) => doRequest("PUT", p, body);
|
|
111
|
-
const doPatch = (p, body) => doRequest("PATCH", p, body);
|
|
112
110
|
function textResult(data) {
|
|
113
111
|
return {
|
|
114
112
|
content: [{ type: "text", text: typeof data === "string" ? data : JSON.stringify(data, null, 2) }],
|
|
@@ -127,8 +125,8 @@ function errResult(err) {
|
|
|
127
125
|
const server = new Server({ name: "mobai", version: "1.0.0" }, {
|
|
128
126
|
capabilities: { tools: {}, resources: {} },
|
|
129
127
|
instructions: `MobAI controls Android and iOS devices. Before starting any device task, read the relevant MCP resources:
|
|
130
|
-
- mobai://reference/device-automation — how to control devices
|
|
131
|
-
- mobai://reference/testing —
|
|
128
|
+
- mobai://reference/device-automation — how to control devices (read before ANY device interaction)
|
|
129
|
+
- mobai://reference/testing — .mob script syntax (read ONLY when user asks to create or fix test scripts)
|
|
132
130
|
Check available skills in current work directory and load any relevant to the user's request.`,
|
|
133
131
|
});
|
|
134
132
|
// ---------------------------------------------------------------------------
|
|
@@ -171,7 +169,7 @@ const TOOLS = [
|
|
|
171
169
|
// Screenshot
|
|
172
170
|
{
|
|
173
171
|
name: "get_screenshot",
|
|
174
|
-
description: "Capture a fast, low-quality screenshot for LLM visual analysis. Returns the file path to the saved image. Use this for AI/LLM processing only — for full-quality screenshots use save_screenshot instead.",
|
|
172
|
+
description: "Capture a fast, low-quality screenshot for LLM visual analysis. Returns the file path to the saved image. The image may be downscaled by an integer factor so its long edge stays ≤ 2000px; when that happens the response includes a scale factor — multiply any coordinates you read off the image by that factor before using them in device actions (tap, swipe, drag, long-press, etc.). UI tree coordinates are already in device pixels, do not scale those. Use this for AI/LLM processing only — for full-quality screenshots use save_screenshot instead.",
|
|
175
173
|
inputSchema: {
|
|
176
174
|
type: "object",
|
|
177
175
|
properties: { device_id: { type: "string", description: "Device ID" } },
|
|
@@ -191,6 +189,20 @@ const TOOLS = [
|
|
|
191
189
|
required: ["device_id"],
|
|
192
190
|
},
|
|
193
191
|
},
|
|
192
|
+
// Debug launch
|
|
193
|
+
{
|
|
194
|
+
name: "debug_app",
|
|
195
|
+
description: "Launch an app in debug mode and write logs to a file. Returns the log file path — use Read/Grep to inspect logs. Use kill_app to stop.",
|
|
196
|
+
inputSchema: {
|
|
197
|
+
type: "object",
|
|
198
|
+
properties: {
|
|
199
|
+
device_id: { type: "string", description: "Device ID" },
|
|
200
|
+
bundle_id: { type: "string", description: "Bundle ID of the app to debug" },
|
|
201
|
+
log_path: { type: "string", description: "Directory for log file (supports ~/). Defaults to OS temp directory." },
|
|
202
|
+
},
|
|
203
|
+
required: ["device_id", "bundle_id"],
|
|
204
|
+
},
|
|
205
|
+
},
|
|
194
206
|
// App management
|
|
195
207
|
{
|
|
196
208
|
name: "list_apps",
|
|
@@ -230,7 +242,7 @@ const TOOLS = [
|
|
|
230
242
|
name: "execute_dsl",
|
|
231
243
|
description: `Execute a batch of DSL commands on a device. This is the primary tool for all device interaction — tap, type, swipe, observe, launch apps, assertions, web automation, and more.
|
|
232
244
|
|
|
233
|
-
|
|
245
|
+
You MUST read the MCP resource mobai://reference/device-automation to learn how to control devices before using this tool.
|
|
234
246
|
|
|
235
247
|
Input: JSON string with "version": "0.2" and "steps" array. Example:
|
|
236
248
|
{"version":"0.2","steps":[
|
|
@@ -250,150 +262,25 @@ Input: JSON string with "version": "0.2" and "steps" array. Example:
|
|
|
250
262
|
// Test management
|
|
251
263
|
{
|
|
252
264
|
name: "test_get_active",
|
|
253
|
-
description: "Get the currently active test project and its cases. Use this to discover
|
|
265
|
+
description: "Get the currently active test project directory and its .mob test cases. Use this to discover the project path and available tests. The agent can then read/write/create/delete .mob files directly in the returned directory.",
|
|
254
266
|
inputSchema: { type: "object", properties: {}, required: [] },
|
|
255
267
|
},
|
|
256
268
|
{
|
|
257
269
|
name: "test_list_projects",
|
|
258
|
-
description: "List all test
|
|
270
|
+
description: "List all known test project directories with their .mob test cases. Each project is a directory containing .mob script files.",
|
|
259
271
|
inputSchema: { type: "object", properties: {}, required: [] },
|
|
260
272
|
},
|
|
261
|
-
{
|
|
262
|
-
name: "test_create_project",
|
|
263
|
-
description: "Create a new test project",
|
|
264
|
-
inputSchema: {
|
|
265
|
-
type: "object",
|
|
266
|
-
properties: { name: { type: "string", description: "Project name" } },
|
|
267
|
-
required: ["name"],
|
|
268
|
-
},
|
|
269
|
-
},
|
|
270
|
-
{
|
|
271
|
-
name: "test_rename_project",
|
|
272
|
-
description: "Rename an existing test project",
|
|
273
|
-
inputSchema: {
|
|
274
|
-
type: "object",
|
|
275
|
-
properties: {
|
|
276
|
-
project_id: { type: "string", description: "Project ID" },
|
|
277
|
-
name: { type: "string", description: "New project name" },
|
|
278
|
-
},
|
|
279
|
-
required: ["project_id", "name"],
|
|
280
|
-
},
|
|
281
|
-
},
|
|
282
|
-
{
|
|
283
|
-
name: "test_create_case",
|
|
284
|
-
description: "Create a new test case in a project",
|
|
285
|
-
inputSchema: {
|
|
286
|
-
type: "object",
|
|
287
|
-
properties: {
|
|
288
|
-
project_id: { type: "string", description: "Project ID" },
|
|
289
|
-
name: { type: "string", description: "Test case name" },
|
|
290
|
-
folder: { type: "string", description: "Optional folder path within the project" },
|
|
291
|
-
},
|
|
292
|
-
required: ["project_id", "name"],
|
|
293
|
-
},
|
|
294
|
-
},
|
|
295
|
-
{
|
|
296
|
-
name: "test_rename_case",
|
|
297
|
-
description: "Rename an existing test case",
|
|
298
|
-
inputSchema: {
|
|
299
|
-
type: "object",
|
|
300
|
-
properties: {
|
|
301
|
-
project_id: { type: "string", description: "Project ID" },
|
|
302
|
-
case_id: { type: "string", description: "Test case ID" },
|
|
303
|
-
name: { type: "string", description: "New test case name" },
|
|
304
|
-
},
|
|
305
|
-
required: ["project_id", "case_id", "name"],
|
|
306
|
-
},
|
|
307
|
-
},
|
|
308
|
-
{
|
|
309
|
-
name: "test_delete_case",
|
|
310
|
-
description: "Delete a test case from a project",
|
|
311
|
-
inputSchema: {
|
|
312
|
-
type: "object",
|
|
313
|
-
properties: {
|
|
314
|
-
project_id: { type: "string", description: "Project ID" },
|
|
315
|
-
case_id: { type: "string", description: "Test case ID" },
|
|
316
|
-
},
|
|
317
|
-
required: ["project_id", "case_id"],
|
|
318
|
-
},
|
|
319
|
-
},
|
|
320
|
-
{
|
|
321
|
-
name: "test_get_script",
|
|
322
|
-
description: "Get the .mob script content for a test case (with 1-based line numbers)",
|
|
323
|
-
inputSchema: {
|
|
324
|
-
type: "object",
|
|
325
|
-
properties: {
|
|
326
|
-
project_id: { type: "string", description: "Project ID" },
|
|
327
|
-
case_id: { type: "string", description: "Test case ID" },
|
|
328
|
-
},
|
|
329
|
-
required: ["project_id", "case_id"],
|
|
330
|
-
},
|
|
331
|
-
},
|
|
332
|
-
{
|
|
333
|
-
name: "test_replace_script",
|
|
334
|
-
description: "Replace the entire .mob script for a test case",
|
|
335
|
-
inputSchema: {
|
|
336
|
-
type: "object",
|
|
337
|
-
properties: {
|
|
338
|
-
project_id: { type: "string", description: "Project ID" },
|
|
339
|
-
case_id: { type: "string", description: "Test case ID" },
|
|
340
|
-
script: { type: "string", description: "New script content (without line numbers)" },
|
|
341
|
-
},
|
|
342
|
-
required: ["project_id", "case_id", "script"],
|
|
343
|
-
},
|
|
344
|
-
},
|
|
345
|
-
{
|
|
346
|
-
name: "test_update_line",
|
|
347
|
-
description: "Update a single line in the .mob script",
|
|
348
|
-
inputSchema: {
|
|
349
|
-
type: "object",
|
|
350
|
-
properties: {
|
|
351
|
-
project_id: { type: "string", description: "Project ID" },
|
|
352
|
-
case_id: { type: "string", description: "Test case ID" },
|
|
353
|
-
line_number: { type: "number", description: "1-based line number to update" },
|
|
354
|
-
content: { type: "string", description: "New line content" },
|
|
355
|
-
},
|
|
356
|
-
required: ["project_id", "case_id", "line_number", "content"],
|
|
357
|
-
},
|
|
358
|
-
},
|
|
359
|
-
{
|
|
360
|
-
name: "test_insert_after",
|
|
361
|
-
description: "Insert a new line after the specified line number in the .mob script",
|
|
362
|
-
inputSchema: {
|
|
363
|
-
type: "object",
|
|
364
|
-
properties: {
|
|
365
|
-
project_id: { type: "string", description: "Project ID" },
|
|
366
|
-
case_id: { type: "string", description: "Test case ID" },
|
|
367
|
-
line_number: { type: "number", description: "1-based line number to insert after (0 = insert at beginning)" },
|
|
368
|
-
content: { type: "string", description: "Line content to insert" },
|
|
369
|
-
},
|
|
370
|
-
required: ["project_id", "case_id", "line_number", "content"],
|
|
371
|
-
},
|
|
372
|
-
},
|
|
373
|
-
{
|
|
374
|
-
name: "test_delete_line",
|
|
375
|
-
description: "Delete a line from the .mob script",
|
|
376
|
-
inputSchema: {
|
|
377
|
-
type: "object",
|
|
378
|
-
properties: {
|
|
379
|
-
project_id: { type: "string", description: "Project ID" },
|
|
380
|
-
case_id: { type: "string", description: "Test case ID" },
|
|
381
|
-
line_number: { type: "number", description: "1-based line number to delete" },
|
|
382
|
-
},
|
|
383
|
-
required: ["project_id", "case_id", "line_number"],
|
|
384
|
-
},
|
|
385
|
-
},
|
|
386
273
|
{
|
|
387
274
|
name: "test_run",
|
|
388
|
-
description: "Run a test case on a device",
|
|
275
|
+
description: "Run a .mob test case on a device. The case_path is relative to the project directory.",
|
|
389
276
|
inputSchema: {
|
|
390
277
|
type: "object",
|
|
391
278
|
properties: {
|
|
392
|
-
|
|
393
|
-
|
|
279
|
+
project_dir: { type: "string", description: "Absolute path to the project directory" },
|
|
280
|
+
case_path: { type: "string", description: "Relative path to the .mob file within the project, e.g. auth/login.mob" },
|
|
394
281
|
device_id: { type: "string", description: "Device ID to run the test on" },
|
|
395
282
|
},
|
|
396
|
-
required: ["
|
|
283
|
+
required: ["project_dir", "case_path", "device_id"],
|
|
397
284
|
},
|
|
398
285
|
},
|
|
399
286
|
];
|
|
@@ -406,13 +293,6 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
406
293
|
// ---------------------------------------------------------------------------
|
|
407
294
|
// Tool call handler
|
|
408
295
|
// ---------------------------------------------------------------------------
|
|
409
|
-
function testCasePath(args) {
|
|
410
|
-
const projectId = args?.project_id;
|
|
411
|
-
const caseId = args?.case_id;
|
|
412
|
-
if (!projectId || !caseId)
|
|
413
|
-
throw new Error("project_id and case_id are required");
|
|
414
|
-
return `/tests/projects/${projectId}/cases/${caseId}`;
|
|
415
|
-
}
|
|
416
296
|
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
417
297
|
const { name, arguments: args } = request.params;
|
|
418
298
|
try {
|
|
@@ -442,6 +322,12 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
442
322
|
return textResult(screenshotToFile(body));
|
|
443
323
|
}
|
|
444
324
|
// App management
|
|
325
|
+
case "debug_app": {
|
|
326
|
+
const body = { bundleId: args?.bundle_id };
|
|
327
|
+
if (args?.log_path)
|
|
328
|
+
body.logPath = args.log_path;
|
|
329
|
+
return textResult(await doPost(`/devices/${args?.device_id}/debug/launch`, body));
|
|
330
|
+
}
|
|
445
331
|
case "list_apps":
|
|
446
332
|
return textResult(await doGet(`/devices/${args?.device_id}/apps`));
|
|
447
333
|
case "install_app":
|
|
@@ -468,56 +354,12 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
468
354
|
return textResult(await doGet("/tests/active"));
|
|
469
355
|
case "test_list_projects":
|
|
470
356
|
return textResult(await doGet("/tests/projects"));
|
|
471
|
-
case "
|
|
472
|
-
return textResult(await doPost("/tests/
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
const body = { name: args?.name };
|
|
477
|
-
if (args?.folder)
|
|
478
|
-
body.folder = args.folder;
|
|
479
|
-
return textResult(await doPost(`/tests/projects/${args?.project_id}/cases`, body));
|
|
480
|
-
}
|
|
481
|
-
case "test_rename_case": {
|
|
482
|
-
const p = testCasePath(args);
|
|
483
|
-
return textResult(await doPatch(p, { name: args?.name }));
|
|
484
|
-
}
|
|
485
|
-
case "test_delete_case": {
|
|
486
|
-
const p = testCasePath(args);
|
|
487
|
-
return textResult(await doDelete(p));
|
|
488
|
-
}
|
|
489
|
-
case "test_get_script": {
|
|
490
|
-
const p = testCasePath(args);
|
|
491
|
-
return textResult(await doGet(`${p}/script`));
|
|
492
|
-
}
|
|
493
|
-
case "test_replace_script": {
|
|
494
|
-
const p = testCasePath(args);
|
|
495
|
-
return textResult(await doPut(`${p}/script`, { script: args?.script }));
|
|
496
|
-
}
|
|
497
|
-
case "test_update_line": {
|
|
498
|
-
const p = testCasePath(args);
|
|
499
|
-
return textResult(await doPost(`${p}/script/update-line`, {
|
|
500
|
-
line_number: args?.line_number,
|
|
501
|
-
content: args?.content,
|
|
357
|
+
case "test_run":
|
|
358
|
+
return textResult(await doPost("/tests/cases/run", {
|
|
359
|
+
project_dir: args?.project_dir,
|
|
360
|
+
case_path: args?.case_path,
|
|
361
|
+
device_id: args?.device_id,
|
|
502
362
|
}));
|
|
503
|
-
}
|
|
504
|
-
case "test_insert_after": {
|
|
505
|
-
const p = testCasePath(args);
|
|
506
|
-
return textResult(await doPost(`${p}/script/insert-after`, {
|
|
507
|
-
line_number: args?.line_number,
|
|
508
|
-
content: args?.content,
|
|
509
|
-
}));
|
|
510
|
-
}
|
|
511
|
-
case "test_delete_line": {
|
|
512
|
-
const p = testCasePath(args);
|
|
513
|
-
return textResult(await doPost(`${p}/script/delete-line`, {
|
|
514
|
-
line_number: args?.line_number,
|
|
515
|
-
}));
|
|
516
|
-
}
|
|
517
|
-
case "test_run": {
|
|
518
|
-
const p = testCasePath(args);
|
|
519
|
-
return textResult(await doPost(`${p}/run`, { device_id: args?.device_id }));
|
|
520
|
-
}
|
|
521
363
|
default:
|
|
522
364
|
return { content: [{ type: "text", text: `Unknown tool: ${name}` }], isError: true };
|
|
523
365
|
}
|
package/dist/resources.js
CHANGED
|
@@ -22,9 +22,6 @@ export function getResourceContent(uri) {
|
|
|
22
22
|
return null;
|
|
23
23
|
}
|
|
24
24
|
}
|
|
25
|
-
// ---------------------------------------------------------------------------
|
|
26
|
-
// Resource content — copied verbatim from Go resources.go
|
|
27
|
-
// ---------------------------------------------------------------------------
|
|
28
25
|
const DEVICE_AUTOMATION_REF = `<device-automation-reference>
|
|
29
26
|
|
|
30
27
|
<guide>
|
|
@@ -54,8 +51,8 @@ const DEVICE_AUTOMATION_REF = `<device-automation-reference>
|
|
|
54
51
|
</ocr-fallback>
|
|
55
52
|
|
|
56
53
|
<execution-modes>
|
|
57
|
-
Default (explore mode):
|
|
58
|
-
|
|
54
|
+
Default (explore mode): only the last observe in a script runs — earlier observes are skipped. This is the right mode for the typical pattern: actions first, then one observe at the end to see the result. If a step fails, the error includes a debug UI tree so you don't need a separate observe.
|
|
55
|
+
Deterministic mode: every observe runs. Use only when you need to capture screen state between actions within a single script (rare — prefer separate execute_dsl calls so you can reason between steps).
|
|
59
56
|
</execution-modes>
|
|
60
57
|
|
|
61
58
|
<workflow>Observe screen → plan → act via execute_dsl → verify (end script with wait_for stable + observe) → repeat until done.</workflow>
|
|
@@ -63,6 +60,7 @@ const DEVICE_AUTOMATION_REF = `<device-automation-reference>
|
|
|
63
60
|
<screenshot-tools>
|
|
64
61
|
get_screenshot — fast low-quality image for LLM visual analysis.
|
|
65
62
|
save_screenshot — full-quality PNG for reporting, debugging, or sharing.
|
|
63
|
+
To verify animations and UI transitions, use record_start/record_stop.
|
|
66
64
|
</screenshot-tools>
|
|
67
65
|
|
|
68
66
|
<infinite-scrolling>To collect data from infinite-scrolling views (feeds, search results), scroll to load a batch first, then observe with only_visible:false to get all loaded items in one go.</infinite-scrolling>
|
|
@@ -79,14 +77,13 @@ const DEVICE_AUTOMATION_REF = `<device-automation-reference>
|
|
|
79
77
|
<target-element>{"predicate": Predicate}</target-element>
|
|
80
78
|
|
|
81
79
|
<predicate context="native">
|
|
82
|
-
<note>Prefer text_contains or text_regex over text (exact match) — UI text often changes with state, locale, or dynamic content. Exact match breaks easily
|
|
80
|
+
<note>Prefer text_contains or text_regex over text (exact match) — UI text often changes with state, locale, or dynamic content. Exact match breaks easily.</note>
|
|
83
81
|
<field name="text" type="string">Exact match — use only when the full text is short, static, and unique</field>
|
|
84
82
|
<field name="text_contains" type="string">Substring, case-insensitive — preferred for most matching</field>
|
|
85
83
|
<field name="text_starts_with" type="string">Prefix match</field>
|
|
86
84
|
<field name="text_regex" type="string">Regex pattern — use for dynamic text (numbers, dates, counts)</field>
|
|
87
85
|
<field name="type" type="string">button, input, switch, text, image, cell, scrollview</field>
|
|
88
|
-
<field name="
|
|
89
|
-
<field name="label_contains" type="string">Accessibility label (partial) — use only when text fields are empty</field>
|
|
86
|
+
<field name="accessibility_id" type="string">Exact match on the #id shown in UI tree (without the # prefix)</field>
|
|
90
87
|
<field name="enabled" type="bool">Enabled state</field>
|
|
91
88
|
<field name="visible" type="bool">Visible state</field>
|
|
92
89
|
<field name="selected" type="bool">Selected state</field>
|
|
@@ -165,9 +162,10 @@ const DEVICE_AUTOMATION_REF = `<device-automation-reference>
|
|
|
165
162
|
Direction = semantic (where to look), not finger movement.
|
|
166
163
|
<field name="direction" required="yes">down (look below), up (look above)</field>
|
|
167
164
|
<field name="to_element" type="TargetElement"/>
|
|
168
|
-
<field name="max_scrolls" type="int"/>
|
|
165
|
+
<field name="max_scrolls" type="int" default="10"/>
|
|
169
166
|
<field name="amount">small, page, full</field>
|
|
170
167
|
<example>{"action": "scroll", "direction": "down", "to_element": {"predicate": {"text": "Privacy"}}, "max_scrolls": 10}</example>
|
|
168
|
+
<note>scroll with to_element returns "reached end of scrollable content" if the list ends before the element is found. If it returns "element not found after scrolling" instead, the list has more content — increase max_scrolls or call scroll again to continue searching.</note>
|
|
171
169
|
</action>
|
|
172
170
|
|
|
173
171
|
<action name="drag">
|
|
@@ -190,7 +188,7 @@ const DEVICE_AUTOMATION_REF = `<device-automation-reference>
|
|
|
190
188
|
|
|
191
189
|
<action name="toggle">
|
|
192
190
|
<field name="predicate" required="yes"/>
|
|
193
|
-
<field name="state" required="
|
|
191
|
+
<field name="state" required="no">Desired state: "on" or "off". If omitted, always toggles. If set, skips when already correct.</field>
|
|
194
192
|
<example>{"action": "toggle", "predicate": {"type": "switch", "text_contains": "Wi-Fi"}, "state": "on"}</example>
|
|
195
193
|
</action>
|
|
196
194
|
|
|
@@ -342,6 +340,7 @@ const DEVICE_AUTOMATION_REF = `<device-automation-reference>
|
|
|
342
340
|
<action name="record_stop">
|
|
343
341
|
<field name="file_path">Override output directory</field>
|
|
344
342
|
<returns>recording_path, frame_count, transition_hints (anomalies: jump/flash/stutter/incoherent_motion with from_frame, to_frame, type, delta_percent, region, message)</returns>
|
|
343
|
+
<note>transition_hints contains anomalous frame pairs (from_frame, to_frame). If transition_hints is empty, do not read any frames. If not empty, read only the flagged frame pairs. Read additional frames only if strictly necessary to investigate a flagged anomaly.</note>
|
|
345
344
|
</action>
|
|
346
345
|
</screen-recording>
|
|
347
346
|
|
|
@@ -351,8 +350,15 @@ const TESTING_REF = `<testing-reference>
|
|
|
351
350
|
|
|
352
351
|
<important>Read mobai://reference/device-automation to learn how to control devices before interacting with them.</important>
|
|
353
352
|
|
|
353
|
+
<file-model>
|
|
354
|
+
Tests are .mob files on disk inside project directories. You work with them directly:
|
|
355
|
+
- Use test_list_projects to discover project directories and their .mob files
|
|
356
|
+
- Read .mob files directly from the project directory using filesystem tools
|
|
357
|
+
- Create, edit, rename, and delete .mob files directly — MobAI watches for changes and updates the UI live
|
|
358
|
+
- Use test_run to execute a test on a device — this is the only operation that requires MCP
|
|
359
|
+
</file-model>
|
|
360
|
+
|
|
354
361
|
<rules>
|
|
355
|
-
<rule>Test scripts are ONLY accessible via MCP test_* tools. There are NO .mob files on disk. Do NOT use grep, find, cat, or any filesystem commands to look for scripts.</rule>
|
|
356
362
|
<rule>Never ask the user for information you can get yourself — use observe, list_apps, get_ui_tree.</rule>
|
|
357
363
|
<rule>Always add wait_for before every element interaction (tap, type, toggle, long_press, double_tap, drag). Exception: the element was asserted on the immediately preceding line.</rule>
|
|
358
364
|
<rule>Always use predicates over coordinates — predicates survive layout changes.</rule>
|
|
@@ -361,22 +367,21 @@ const TESTING_REF = `<testing-reference>
|
|
|
361
367
|
</rules>
|
|
362
368
|
|
|
363
369
|
<workflow-create>
|
|
364
|
-
1.
|
|
365
|
-
2.
|
|
366
|
-
3.
|
|
367
|
-
4.
|
|
368
|
-
5.
|
|
369
|
-
6.
|
|
370
|
-
7.
|
|
371
|
-
8. Re-run to verify fixes (max 3 retry cycles)
|
|
370
|
+
1. Call test_list_projects to find the project directory and existing tests
|
|
371
|
+
2. Observe the current screen on the device
|
|
372
|
+
3. Plan the test steps from the user's description
|
|
373
|
+
4. Write the .mob file directly to the project directory
|
|
374
|
+
5. Run the test with test_run
|
|
375
|
+
6. Fix — if steps fail, read the error, observe the screen, edit the .mob file
|
|
376
|
+
7. Re-run to verify fixes (max 3 retry cycles)
|
|
372
377
|
</workflow-create>
|
|
373
378
|
|
|
374
379
|
<workflow-fix>
|
|
375
|
-
1. Read the
|
|
380
|
+
1. Read the .mob file from disk
|
|
376
381
|
2. Analyze the error messages — they reference exact line numbers
|
|
377
|
-
3. Reproduce — run
|
|
378
|
-
4.
|
|
379
|
-
5.
|
|
382
|
+
3. Reproduce — run a failing action via DSL to observe device state
|
|
383
|
+
4. Edit the .mob file directly
|
|
384
|
+
5. Re-run with test_run
|
|
380
385
|
</workflow-fix>
|
|
381
386
|
|
|
382
387
|
<error-fixes>
|
|
@@ -405,10 +410,9 @@ const TESTING_REF = `<testing-reference>
|
|
|
405
410
|
|
|
406
411
|
<verification>
|
|
407
412
|
Check before every response:
|
|
408
|
-
1.
|
|
409
|
-
2.
|
|
410
|
-
3.
|
|
411
|
-
4. Did you observe the screen before acting?
|
|
413
|
+
1. Does every element interaction have a wait_for on the preceding line?
|
|
414
|
+
2. Are predicates used instead of coordinates wherever possible?
|
|
415
|
+
3. Did you observe the screen before acting?
|
|
412
416
|
</verification>
|
|
413
417
|
|
|
414
418
|
<mob-script-syntax>
|
|
@@ -452,6 +456,14 @@ const TESTING_REF = `<testing-reference>
|
|
|
452
456
|
delay 1000 — wait N ms
|
|
453
457
|
press_key home|back|enter — hardware key
|
|
454
458
|
navigate back|home — navigation shortcut
|
|
459
|
+
two_finger_tap "Map" — two-finger tap
|
|
460
|
+
pinch "Map" scale:0.5 — pinch (scale <1 = zoom out, >1 = zoom in)
|
|
461
|
+
pinch "Photo" scale:2.0 — pinch to zoom in
|
|
462
|
+
hide_keyboard — dismiss keyboard
|
|
463
|
+
copy_text "Field" — copy text from element
|
|
464
|
+
paste_text "Field" — paste clipboard into element
|
|
465
|
+
set_location 40.7128,-74.0060 — simulate GPS location (lat,lon)
|
|
466
|
+
reset_location — stop location simulation
|
|
455
467
|
observe — observe screen
|
|
456
468
|
screenshot "path.png" — take screenshot
|
|
457
469
|
</actions>
|
package/package.json
CHANGED
package/server.json
CHANGED
|
@@ -6,12 +6,12 @@
|
|
|
6
6
|
"url": "https://github.com/MobAI-App/mobai-mcp",
|
|
7
7
|
"source": "github"
|
|
8
8
|
},
|
|
9
|
-
"version": "2.
|
|
9
|
+
"version": "2.1.0",
|
|
10
10
|
"packages": [
|
|
11
11
|
{
|
|
12
12
|
"registryType": "npm",
|
|
13
13
|
"identifier": "mobai-mcp",
|
|
14
|
-
"version": "2.
|
|
14
|
+
"version": "2.1.0",
|
|
15
15
|
"transport": {
|
|
16
16
|
"type": "stdio"
|
|
17
17
|
}
|