mobai-mcp 2.3.1 → 2.4.1
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/index.js +80 -10
- package/dist/resources.js +32 -4
- package/package.json +1 -1
- package/server.json +2 -2
package/dist/index.js
CHANGED
|
@@ -223,6 +223,12 @@ const TOOLS = [
|
|
|
223
223
|
properties: {
|
|
224
224
|
device_id: { type: "string", description: "Device ID" },
|
|
225
225
|
path: { type: "string", description: "Local file path to the app (.apk or .ipa)" },
|
|
226
|
+
resign: { type: "boolean", description: "Sign the IPA with iloader before installing (iOS only)" },
|
|
227
|
+
apple_id: { type: "string", description: "Apple ID for signing (optional; used when resign=true)" },
|
|
228
|
+
password: { type: "string", description: "Apple ID password for signing (optional; uses cached credentials if empty)" },
|
|
229
|
+
profile_path: { type: "string", description: "Provisioning profile to reuse for an offline re-sign (with cert_path/key_path); falls back to online signing if invalid" },
|
|
230
|
+
cert_path: { type: "string", description: "Signing certificate PEM to reuse for an offline re-sign" },
|
|
231
|
+
key_path: { type: "string", description: "Signing private key PEM to reuse for an offline re-sign" },
|
|
226
232
|
},
|
|
227
233
|
required: ["device_id", "path"],
|
|
228
234
|
},
|
|
@@ -242,16 +248,65 @@ const TOOLS = [
|
|
|
242
248
|
// DSL execution
|
|
243
249
|
{
|
|
244
250
|
name: "execute_dsl",
|
|
245
|
-
description: `
|
|
251
|
+
description: `The example below is the full command surface. For richer semantics - per-action defaults, platform notes, retry/failure strategies, observe and scroll guidance, web/OCR caveats - read the MCP resource mobai://reference/device-automation. Read it the first time you hit anything the example doesn't make obvious.
|
|
246
252
|
|
|
247
|
-
|
|
253
|
+
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.
|
|
248
254
|
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
+
To find visual/UI bugs on the current screen, run a single {"action":"audit"} step: it returns deterministic findings (touch targets, clipping, alignment, spacing, safe area, accessibility) plus an annotated screenshot whose red boxes are labelled with the element ids in the findings. Findings are evidence, not verdicts - cross-check the annotated screenshot to decide which are real bugs.
|
|
256
|
+
|
|
257
|
+
Input: JSON string with "version": "0.2" and a "steps" array. Optional script-wide "params" (declared defaults, substituted as \${name}) and "on_fail" (strategy: abort|skip|retry|replan|require_user; retry also takes max_retries, retry_delay_ms, fallback_strategy). Any step may carry its own "on_fail".
|
|
258
|
+
|
|
259
|
+
Every action below appears at least once - this is the full command surface (semantics, defaults, and platform notes are in mobai://reference/device-automation):
|
|
260
|
+
{"version":"0.2","params":{"query":"shoes"},"on_fail":{"strategy":"retry","max_retries":2,"retry_delay_ms":1000,"fallback_strategy":{"strategy":"skip"}},"steps":[
|
|
261
|
+
{"action":"open_app","bundle_id":"com.apple.Preferences","fresh":true,"debug":false},
|
|
262
|
+
{"action":"kill_app","bundle_id":"com.apple.mobilesafari"},
|
|
263
|
+
{"action":"open_link","url":"myapp://profile/42"},
|
|
264
|
+
{"action":"tap","predicate":{"text_contains":"Wi-Fi","type":"button"}},
|
|
265
|
+
{"action":"tap","coords":{"x":200,"y":640}},
|
|
266
|
+
{"action":"double_tap","predicate":{"text":"Photo"}},
|
|
267
|
+
{"action":"two_finger_tap","predicate":{"text":"Map"}},
|
|
268
|
+
{"action":"long_press","predicate":{"text":"Item"},"duration_ms":1000},
|
|
269
|
+
{"action":"pinch","predicate":{"text_contains":"Map"},"scale":0.5,"velocity":1.0},
|
|
270
|
+
{"action":"type","text":"\${query}","predicate":{"type":"input"},"clear_first":true,"dismiss_keyboard":true},
|
|
271
|
+
{"action":"type_secret","secret_id":"test-account-password","predicate":{"type":"input","text_contains":"Password"}},
|
|
272
|
+
{"action":"clear","predicate":{"type":"input"}},
|
|
273
|
+
{"action":"toggle","predicate":{"type":"switch","text_contains":"Wi-Fi"},"state":"on"},
|
|
274
|
+
{"action":"press_key","key":"enter"},
|
|
275
|
+
{"action":"swipe","direction":"up","distance":"medium"},
|
|
276
|
+
{"action":"swipe","from_coords":{"x":200,"y":600},"to_coords":{"x":200,"y":200},"duration_ms":300},
|
|
277
|
+
{"action":"scroll","direction":"down","to_element":{"predicate":{"text":"Privacy"}},"predicate":{"type":"scrollview"},"max_scrolls":10,"amount":"page"},
|
|
278
|
+
{"action":"drag","from":{"predicate":{"text":"Item"}},"to_element":{"predicate":{"text":"Trash"}},"press_duration_ms":500,"hold_duration_ms":200,"duration_ms":500},
|
|
279
|
+
{"action":"drag","from_coords":{"x":100,"y":400},"to_coords":{"x":300,"y":400}},
|
|
280
|
+
{"action":"drag_path","points":[{"x":100,"y":400,"duration_ms":200},{"x":150,"y":300,"duration_ms":150},{"x":300,"y":500,"duration_ms":300}]},
|
|
281
|
+
{"action":"navigate","target":"home"},
|
|
282
|
+
{"action":"set_location","lat":40.7128,"lon":-74.0060},
|
|
283
|
+
{"action":"reset_location"},
|
|
284
|
+
{"action":"siri","prompt":"Search YouTube for cat videos"},
|
|
285
|
+
{"action":"if_exists","predicate":{"text":"Allow"},"then":[{"action":"tap","predicate":{"text":"Allow"}}],"else":[{"action":"tap","predicate":{"text":"Don't Allow"}}]},
|
|
286
|
+
{"action":"delay","duration_ms":1000},
|
|
287
|
+
{"action":"wait_for","predicate":{"text":"Welcome"},"timeout_ms":5000,"poll_interval_ms":500},
|
|
288
|
+
{"action":"assert_exists","predicate":{"text":"Success"},"timeout_ms":3000},
|
|
289
|
+
{"action":"assert_not_exists","predicate":{"text":"Error"}},
|
|
290
|
+
{"action":"assert_count","predicate":{"type":"cell"},"count":5},
|
|
291
|
+
{"action":"assert_screen_changed","threshold_percent":15},
|
|
292
|
+
{"action":"metrics_start","types":["system_cpu","fps"],"interval_ms":1000,"label":"login_flow","capture_logs":true,"thresholds":{"cpu_high":80,"fps_low":45}},
|
|
293
|
+
{"action":"metrics_stop","format":"summary"},
|
|
294
|
+
{"action":"record_start","file_path":"/tmp/mobai/rec"},
|
|
295
|
+
{"action":"record_stop"},
|
|
296
|
+
{"action":"select_web_context","url_contains":"google.com"},
|
|
297
|
+
{"action":"navigate","context":"web","url":"https://example.com"},
|
|
298
|
+
{"action":"tap","context":"web","predicate":{"css_selector":"button.submit"}},
|
|
299
|
+
{"action":"type","context":"web","predicate":{"css_selector":"input#email"},"text":"user@example.com","clear_first":true},
|
|
300
|
+
{"action":"execute_js","context":"web","script":"return document.title","async":false},
|
|
301
|
+
{"action":"screenshot","file_path":"/tmp/mobai","name":"final_state"},
|
|
302
|
+
{"action":"wait_for","stable":true,"timeout_ms":3000},
|
|
303
|
+
{"action":"observe","include":["ui_tree"],"only_visible":true,"filter":{"text_regex":"Settings|Wi-Fi"},"store_as":"end_state"},
|
|
304
|
+
{"action":"audit"}
|
|
305
|
+
]}
|
|
306
|
+
|
|
307
|
+
Predicate - object passed in a step's "predicate"/"from"/"to_element". Combine fields (AND); prefer text_contains over exact text. All native fields:
|
|
308
|
+
{"text":"exact","text_contains":"substr","text_starts_with":"prefix","text_regex":"\\d+ items","value":"typed value","value_contains":"typed substr","type":"button|input|switch|text|image|cell|scrollview","accessibility_id":"login_btn","enabled":true,"visible":true,"selected":false,"index":0,"bounds_hint":"top_half|bottom_half|left_half|right_half|center","near":{"text_contains":"Email","direction":"below|above|left|right|any","max_distance":100},"parent_of":{"text":"child label"}}
|
|
309
|
+
Web predicate (context:"web") uses instead: {"css_selector":"button.submit"}.`,
|
|
255
310
|
inputSchema: {
|
|
256
311
|
type: "object",
|
|
257
312
|
properties: {
|
|
@@ -437,8 +492,23 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
437
492
|
}
|
|
438
493
|
case "list_apps":
|
|
439
494
|
return textResult(await doGet(`/devices/${args?.device_id}/apps`));
|
|
440
|
-
case "install_app":
|
|
441
|
-
|
|
495
|
+
case "install_app": {
|
|
496
|
+
const body = { path: args?.path };
|
|
497
|
+
if (args?.resign) {
|
|
498
|
+
body.resign = true;
|
|
499
|
+
if (args?.apple_id)
|
|
500
|
+
body.appleId = args.apple_id;
|
|
501
|
+
if (args?.password)
|
|
502
|
+
body.password = args.password;
|
|
503
|
+
if (args?.profile_path)
|
|
504
|
+
body.profilePath = args.profile_path;
|
|
505
|
+
if (args?.cert_path)
|
|
506
|
+
body.certPath = args.cert_path;
|
|
507
|
+
if (args?.key_path)
|
|
508
|
+
body.keyPath = args.key_path;
|
|
509
|
+
}
|
|
510
|
+
return textResult(await doPost(`/devices/${args?.device_id}/install-app`, body));
|
|
511
|
+
}
|
|
442
512
|
case "uninstall_app":
|
|
443
513
|
return textResult(await doDelete(`/devices/${args?.device_id}/apps/${encodeURIComponent(args?.bundle_id)}`));
|
|
444
514
|
// DSL execution
|
package/dist/resources.js
CHANGED
|
@@ -92,6 +92,7 @@ const DEVICE_AUTOMATION_REF = `<device-automation-reference>
|
|
|
92
92
|
<observe-guidance>
|
|
93
93
|
Prefer UI tree and OCR over screenshots — screenshots consume far more context and are slower. Only use screenshots for visual verification (layout, colors, images).
|
|
94
94
|
only_visible: false — use ONLY for data collection from scrollable views (returns ALL elements including off-screen). NEVER when interacting — off-screen elements cannot be tapped.
|
|
95
|
+
compact: true — token-lean ui_tree (same elements as the full tree, as flat [index] Type "label" #id [state] lines; coordinates and indentation removed, ~2x fewer tokens). Use when you select by id/index/text. Keep the default full tree when you need coordinates or spatial layout (e.g. coordinate taps, near/spatial reasoning).
|
|
95
96
|
</observe-guidance>
|
|
96
97
|
|
|
97
98
|
<ocr-fallback>
|
|
@@ -161,7 +162,6 @@ const DEVICE_AUTOMATION_REF = `<device-automation-reference>
|
|
|
161
162
|
|
|
162
163
|
<predicate context="web">
|
|
163
164
|
<field name="css_selector" type="string">CSS selector</field>
|
|
164
|
-
<field name="xpath" type="string">XPath expression</field>
|
|
165
165
|
</predicate>
|
|
166
166
|
|
|
167
167
|
<failure-strategy>
|
|
@@ -217,6 +217,15 @@ const DEVICE_AUTOMATION_REF = `<device-automation-reference>
|
|
|
217
217
|
<example>{"action": "type", "text": "Hello", "predicate": {"type": "input"}, "clear_first": true}</example>
|
|
218
218
|
</action>
|
|
219
219
|
|
|
220
|
+
<action name="type_secret">
|
|
221
|
+
Type a stored credential without ever exposing its value. secret_id names a credential saved in the app; its value is resolved from the keychain at run time and typed on-device - it never appears in the script, logs, results, or the model context. Use for passwords/tokens in login steps. Same field-targeting as type.
|
|
222
|
+
<field name="secret_id" required="yes">Id of a stored credential (see list_secrets)</field>
|
|
223
|
+
<field name="predicate">Optional target field to focus first</field>
|
|
224
|
+
<field name="clear_first" type="bool"/>
|
|
225
|
+
<field name="dismiss_keyboard" type="bool" default="false"/>
|
|
226
|
+
<example>{"action": "type_secret", "secret_id": "test-account-password", "predicate": {"type": "input", "text_contains": "Password"}}</example>
|
|
227
|
+
</action>
|
|
228
|
+
|
|
220
229
|
<action name="clear">
|
|
221
230
|
Clear a field's text without typing. With a predicate, focuses that field first; without one, clears the currently focused field.
|
|
222
231
|
<field name="predicate">Optional target element</field>
|
|
@@ -235,11 +244,13 @@ const DEVICE_AUTOMATION_REF = `<device-automation-reference>
|
|
|
235
244
|
|
|
236
245
|
<action name="scroll">
|
|
237
246
|
Direction = semantic (where to look), not finger movement.
|
|
238
|
-
<field name="direction" required="yes">down (look below), up (look above)
|
|
247
|
+
<field name="direction" required="yes">down (look below), up (look above), left (look left), right (look right). All four directions are supported - use left/right for horizontal scrollviews (carousels, palettes, paging) instead of falling back to raw-coordinate swipe.</field>
|
|
248
|
+
<field name="predicate" type="Predicate">Targets a specific scrollable container so the gesture lands inside it (e.g. {"type": "scrollview"} or {"parent_of": {"text": "Work"}}). Without it the gesture is applied at screen center - set this when multiple scrollviews share the screen (e.g. a horizontal palette above a vertical list) to avoid scrolling the wrong one.</field>
|
|
239
249
|
<field name="to_element" type="TargetElement"/>
|
|
240
250
|
<field name="max_scrolls" type="int" default="10"/>
|
|
241
251
|
<field name="amount">small, page, full</field>
|
|
242
252
|
<example>{"action": "scroll", "direction": "down", "to_element": {"predicate": {"text": "Privacy"}}, "max_scrolls": 10}</example>
|
|
253
|
+
<example>{"action": "scroll", "direction": "right", "predicate": {"type": "scrollview"}}</example>
|
|
243
254
|
<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>
|
|
244
255
|
</action>
|
|
245
256
|
|
|
@@ -294,11 +305,17 @@ const DEVICE_AUTOMATION_REF = `<device-automation-reference>
|
|
|
294
305
|
<field name="store_as" type="string">Store result in named variable</field>
|
|
295
306
|
<field name="include_keyboard" type="bool" default="false"/>
|
|
296
307
|
<field name="only_visible" type="bool" default="true">MUST be false when collecting data from scrollable lists/search results. MUST be true when interacting with elements — off-screen elements cannot be tapped.</field>
|
|
308
|
+
<field name="compact" type="bool" default="false">Token-lean ui_tree: same elements as the full tree, formatted as flat [index] Type "label" #id [state] lines with coordinates and indentation removed (~2x fewer tokens). Use when selecting by id/index/text; keep false when you need coordinates or spatial layout.</field>
|
|
297
309
|
<field name="filter">text_regex or bounds {"x","y","width","height"} to reduce output</field>
|
|
298
310
|
<note>OCR (iOS only): returns text with tap coordinates. Useful for system dialogs missing from UI tree.</note>
|
|
299
311
|
<example>{"action": "observe", "include": ["ui_tree"], "filter": {"text_regex": "Settings|Wi-Fi"}}</example>
|
|
300
312
|
</action>
|
|
301
313
|
|
|
314
|
+
<action name="audit">
|
|
315
|
+
<note>Runs deterministic UI checks on the current native screen and returns findings plus an annotated screenshot (path under observations.native.screenshot, with red boxes labelled by element id). Findings are EVIDENCE, not conclusions (e.g. margins are 18px/34px, not "misaligned") - cross-check the annotated screenshot to decide which are real bugs. Checks: touch_target, clipping, alignment, spacing, safe_area, accessibility. Dense grids of tiny repeated controls (paint canvas, keyboard) are skipped to avoid noise.</note>
|
|
316
|
+
<example>{"action": "audit"}</example>
|
|
317
|
+
</action>
|
|
318
|
+
|
|
302
319
|
<action name="wait_for">
|
|
303
320
|
<field name="predicate">Required unless stable is true</field>
|
|
304
321
|
<field name="stable" type="bool" default="false">Wait for UI to stop changing</field>
|
|
@@ -337,6 +354,13 @@ const DEVICE_AUTOMATION_REF = `<device-automation-reference>
|
|
|
337
354
|
<example>{"action": "reset_location"}</example>
|
|
338
355
|
</action>
|
|
339
356
|
|
|
357
|
+
<action name="open_link">
|
|
358
|
+
Opens a URL or deep link on the device. The OS routes custom schemes (myapp://path) to the registered app, so this jumps straight to a screen without navigating the UI. Use for deep-link entry points instead of tapping through the app.
|
|
359
|
+
<field name="url" required="yes">Full URL or deep link, e.g. https://example.com or myapp://profile/42</field>
|
|
360
|
+
<example>{"action": "open_link", "url": "myapp://profile/42"}</example>
|
|
361
|
+
<example>{"action": "open_link", "url": "https://example.com"}</example>
|
|
362
|
+
</action>
|
|
363
|
+
|
|
340
364
|
<action name="siri">
|
|
341
365
|
iOS only. Sends a voice command to Siri service on iOS devices. Auto-approves consent dialogs, captures Siri's response text, then dismisses the Siri UI.
|
|
342
366
|
Use for triggering SiriKit intents and App Shortcuts registered by apps (media playback, messaging, banking shortcuts, etc.).
|
|
@@ -360,7 +384,7 @@ const DEVICE_AUTOMATION_REF = `<device-automation-reference>
|
|
|
360
384
|
</action>
|
|
361
385
|
|
|
362
386
|
<action name="tap/type/press_key/wait_for/if_exists">
|
|
363
|
-
Same fields as native but use css_selector
|
|
387
|
+
Same fields as native but use css_selector in predicate.
|
|
364
388
|
<example>{"action": "tap", "context": "web", "predicate": {"css_selector": "button.submit"}}</example>
|
|
365
389
|
<example>{"action": "type", "context": "web", "predicate": {"css_selector": "input#email"}, "text": "user@example.com", "clear_first": true}</example>
|
|
366
390
|
Web press_key keys: enter, tab, delete, escape.
|
|
@@ -462,7 +486,7 @@ const TESTING_REF = `<testing-reference>
|
|
|
462
486
|
|
|
463
487
|
<rules>
|
|
464
488
|
<rule>Never ask the user for information you can get yourself — use observe, list_apps, get_ui_tree.</rule>
|
|
465
|
-
<rule>
|
|
489
|
+
<rule>Add wait_for before an interaction only when the PREVIOUS action may have changed the screen and the target might not be ready yet - after opening an app, navigating, a screen transition, opening a modal/sheet, scrolling, or submitting something that reloads. If the previous action left the same screen up (typing into a field, toggling a switch, tapping a control that stays put) the element is already there - skip wait_for. Never add it before the element you just asserted.</rule>
|
|
466
490
|
<rule>Always use predicates over coordinates — predicates survive layout changes.</rule>
|
|
467
491
|
<rule>Always prefer UI tree and OCR over screenshots for element discovery.</rule>
|
|
468
492
|
<rule>Your first action must be observe — never ask the user first.</rule>
|
|
@@ -542,6 +566,8 @@ const TESTING_REF = `<testing-reference>
|
|
|
542
566
|
type ~"Field" → "text" — partial match field
|
|
543
567
|
type "Field" → "text" clear — clear first
|
|
544
568
|
type "Field" → "text" keep_keyboard — keep keyboard open
|
|
569
|
+
type_secret "Field" → "secret-id" - type a stored credential (value never in script/logs)
|
|
570
|
+
type_secret "secret-id" - type credential into focused field
|
|
545
571
|
swipe up|down|left|right — swipe direction
|
|
546
572
|
swipe up distance:long — short/medium/long
|
|
547
573
|
swipe up duration:500 — custom duration (ms)
|
|
@@ -549,6 +575,7 @@ const TESTING_REF = `<testing-reference>
|
|
|
549
575
|
scroll down — scroll
|
|
550
576
|
scroll down to "Element" — scroll until visible
|
|
551
577
|
scroll down to "Footer" max_scrolls:10 — limit attempts
|
|
578
|
+
scroll right in type:scrollview - target a specific container
|
|
552
579
|
toggle "Wi-Fi" on|off — toggle switch
|
|
553
580
|
toggle type:switch near "Wi-Fi" on — modifier-only
|
|
554
581
|
drag "Item" to "Trash" — drag element
|
|
@@ -568,6 +595,7 @@ const TESTING_REF = `<testing-reference>
|
|
|
568
595
|
paste_text "Field" — paste clipboard into element
|
|
569
596
|
set_location 40.7128,-74.0060 — simulate GPS location (lat,lon)
|
|
570
597
|
reset_location — stop location simulation
|
|
598
|
+
open_link "myapp://profile/42" - open a URL or deep link (routes to the app)
|
|
571
599
|
siri "Search YouTube for cats" — invoke Siri with voice command (iOS only)
|
|
572
600
|
observe — observe screen
|
|
573
601
|
screenshot "path.png" — take screenshot
|
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.4.1",
|
|
10
10
|
"packages": [
|
|
11
11
|
{
|
|
12
12
|
"registryType": "npm",
|
|
13
13
|
"identifier": "mobai-mcp",
|
|
14
|
-
"version": "2.
|
|
14
|
+
"version": "2.4.1",
|
|
15
15
|
"transport": {
|
|
16
16
|
"type": "stdio"
|
|
17
17
|
}
|