mobai-mcp 2.3.1 → 2.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +77 -10
- package/dist/resources.js +25 -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,62 @@ 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
|
-
Input: JSON string with "version": "0.2" and "steps" array.
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
{"action":"
|
|
254
|
-
|
|
255
|
+
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".
|
|
256
|
+
|
|
257
|
+
Every action below appears at least once - this is the full command surface (semantics, defaults, and platform notes are in mobai://reference/device-automation):
|
|
258
|
+
{"version":"0.2","params":{"query":"shoes"},"on_fail":{"strategy":"retry","max_retries":2,"retry_delay_ms":1000,"fallback_strategy":{"strategy":"skip"}},"steps":[
|
|
259
|
+
{"action":"open_app","bundle_id":"com.apple.Preferences","fresh":true,"debug":false},
|
|
260
|
+
{"action":"kill_app","bundle_id":"com.apple.mobilesafari"},
|
|
261
|
+
{"action":"open_link","url":"myapp://profile/42"},
|
|
262
|
+
{"action":"tap","predicate":{"text_contains":"Wi-Fi","type":"button"}},
|
|
263
|
+
{"action":"tap","coords":{"x":200,"y":640}},
|
|
264
|
+
{"action":"double_tap","predicate":{"text":"Photo"}},
|
|
265
|
+
{"action":"two_finger_tap","predicate":{"text":"Map"}},
|
|
266
|
+
{"action":"long_press","predicate":{"text":"Item"},"duration_ms":1000},
|
|
267
|
+
{"action":"pinch","predicate":{"text_contains":"Map"},"scale":0.5,"velocity":1.0},
|
|
268
|
+
{"action":"type","text":"\${query}","predicate":{"type":"input"},"clear_first":true,"dismiss_keyboard":true},
|
|
269
|
+
{"action":"type_secret","secret_id":"test-account-password","predicate":{"type":"input","text_contains":"Password"}},
|
|
270
|
+
{"action":"clear","predicate":{"type":"input"}},
|
|
271
|
+
{"action":"toggle","predicate":{"type":"switch","text_contains":"Wi-Fi"},"state":"on"},
|
|
272
|
+
{"action":"press_key","key":"enter"},
|
|
273
|
+
{"action":"swipe","direction":"up","distance":"medium"},
|
|
274
|
+
{"action":"swipe","from_coords":{"x":200,"y":600},"to_coords":{"x":200,"y":200},"duration_ms":300},
|
|
275
|
+
{"action":"scroll","direction":"down","to_element":{"predicate":{"text":"Privacy"}},"predicate":{"type":"scrollview"},"max_scrolls":10,"amount":"page"},
|
|
276
|
+
{"action":"drag","from":{"predicate":{"text":"Item"}},"to_element":{"predicate":{"text":"Trash"}},"press_duration_ms":500,"hold_duration_ms":200,"duration_ms":500},
|
|
277
|
+
{"action":"drag","from_coords":{"x":100,"y":400},"to_coords":{"x":300,"y":400}},
|
|
278
|
+
{"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}]},
|
|
279
|
+
{"action":"navigate","target":"home"},
|
|
280
|
+
{"action":"set_location","lat":40.7128,"lon":-74.0060},
|
|
281
|
+
{"action":"reset_location"},
|
|
282
|
+
{"action":"siri","prompt":"Search YouTube for cat videos"},
|
|
283
|
+
{"action":"if_exists","predicate":{"text":"Allow"},"then":[{"action":"tap","predicate":{"text":"Allow"}}],"else":[{"action":"tap","predicate":{"text":"Don't Allow"}}]},
|
|
284
|
+
{"action":"delay","duration_ms":1000},
|
|
285
|
+
{"action":"wait_for","predicate":{"text":"Welcome"},"timeout_ms":5000,"poll_interval_ms":500},
|
|
286
|
+
{"action":"assert_exists","predicate":{"text":"Success"},"timeout_ms":3000},
|
|
287
|
+
{"action":"assert_not_exists","predicate":{"text":"Error"}},
|
|
288
|
+
{"action":"assert_count","predicate":{"type":"cell"},"count":5},
|
|
289
|
+
{"action":"assert_screen_changed","threshold_percent":15},
|
|
290
|
+
{"action":"metrics_start","types":["system_cpu","fps"],"interval_ms":1000,"label":"login_flow","capture_logs":true,"thresholds":{"cpu_high":80,"fps_low":45}},
|
|
291
|
+
{"action":"metrics_stop","format":"summary"},
|
|
292
|
+
{"action":"record_start","file_path":"/tmp/mobai/rec"},
|
|
293
|
+
{"action":"record_stop"},
|
|
294
|
+
{"action":"select_web_context","url_contains":"google.com"},
|
|
295
|
+
{"action":"navigate","context":"web","url":"https://example.com"},
|
|
296
|
+
{"action":"tap","context":"web","predicate":{"css_selector":"button.submit"}},
|
|
297
|
+
{"action":"type","context":"web","predicate":{"css_selector":"input#email"},"text":"user@example.com","clear_first":true},
|
|
298
|
+
{"action":"execute_js","context":"web","script":"return document.title","async":false},
|
|
299
|
+
{"action":"screenshot","file_path":"/tmp/mobai","name":"final_state"},
|
|
300
|
+
{"action":"wait_for","stable":true,"timeout_ms":3000},
|
|
301
|
+
{"action":"observe","include":["ui_tree"],"only_visible":true,"filter":{"text_regex":"Settings|Wi-Fi"},"store_as":"end_state"}
|
|
302
|
+
]}
|
|
303
|
+
|
|
304
|
+
Predicate - object passed in a step's "predicate"/"from"/"to_element". Combine fields (AND); prefer text_contains over exact text. All native fields:
|
|
305
|
+
{"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"}}
|
|
306
|
+
Web predicate (context:"web") uses instead: {"css_selector":"button.submit"}.`,
|
|
255
307
|
inputSchema: {
|
|
256
308
|
type: "object",
|
|
257
309
|
properties: {
|
|
@@ -437,8 +489,23 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
437
489
|
}
|
|
438
490
|
case "list_apps":
|
|
439
491
|
return textResult(await doGet(`/devices/${args?.device_id}/apps`));
|
|
440
|
-
case "install_app":
|
|
441
|
-
|
|
492
|
+
case "install_app": {
|
|
493
|
+
const body = { path: args?.path };
|
|
494
|
+
if (args?.resign) {
|
|
495
|
+
body.resign = true;
|
|
496
|
+
if (args?.apple_id)
|
|
497
|
+
body.appleId = args.apple_id;
|
|
498
|
+
if (args?.password)
|
|
499
|
+
body.password = args.password;
|
|
500
|
+
if (args?.profile_path)
|
|
501
|
+
body.profilePath = args.profile_path;
|
|
502
|
+
if (args?.cert_path)
|
|
503
|
+
body.certPath = args.cert_path;
|
|
504
|
+
if (args?.key_path)
|
|
505
|
+
body.keyPath = args.key_path;
|
|
506
|
+
}
|
|
507
|
+
return textResult(await doPost(`/devices/${args?.device_id}/install-app`, body));
|
|
508
|
+
}
|
|
442
509
|
case "uninstall_app":
|
|
443
510
|
return textResult(await doDelete(`/devices/${args?.device_id}/apps/${encodeURIComponent(args?.bundle_id)}`));
|
|
444
511
|
// DSL execution
|
package/dist/resources.js
CHANGED
|
@@ -161,7 +161,6 @@ const DEVICE_AUTOMATION_REF = `<device-automation-reference>
|
|
|
161
161
|
|
|
162
162
|
<predicate context="web">
|
|
163
163
|
<field name="css_selector" type="string">CSS selector</field>
|
|
164
|
-
<field name="xpath" type="string">XPath expression</field>
|
|
165
164
|
</predicate>
|
|
166
165
|
|
|
167
166
|
<failure-strategy>
|
|
@@ -217,6 +216,15 @@ const DEVICE_AUTOMATION_REF = `<device-automation-reference>
|
|
|
217
216
|
<example>{"action": "type", "text": "Hello", "predicate": {"type": "input"}, "clear_first": true}</example>
|
|
218
217
|
</action>
|
|
219
218
|
|
|
219
|
+
<action name="type_secret">
|
|
220
|
+
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.
|
|
221
|
+
<field name="secret_id" required="yes">Id of a stored credential (see list_secrets)</field>
|
|
222
|
+
<field name="predicate">Optional target field to focus first</field>
|
|
223
|
+
<field name="clear_first" type="bool"/>
|
|
224
|
+
<field name="dismiss_keyboard" type="bool" default="false"/>
|
|
225
|
+
<example>{"action": "type_secret", "secret_id": "test-account-password", "predicate": {"type": "input", "text_contains": "Password"}}</example>
|
|
226
|
+
</action>
|
|
227
|
+
|
|
220
228
|
<action name="clear">
|
|
221
229
|
Clear a field's text without typing. With a predicate, focuses that field first; without one, clears the currently focused field.
|
|
222
230
|
<field name="predicate">Optional target element</field>
|
|
@@ -235,11 +243,13 @@ const DEVICE_AUTOMATION_REF = `<device-automation-reference>
|
|
|
235
243
|
|
|
236
244
|
<action name="scroll">
|
|
237
245
|
Direction = semantic (where to look), not finger movement.
|
|
238
|
-
<field name="direction" required="yes">down (look below), up (look above)
|
|
246
|
+
<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>
|
|
247
|
+
<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
248
|
<field name="to_element" type="TargetElement"/>
|
|
240
249
|
<field name="max_scrolls" type="int" default="10"/>
|
|
241
250
|
<field name="amount">small, page, full</field>
|
|
242
251
|
<example>{"action": "scroll", "direction": "down", "to_element": {"predicate": {"text": "Privacy"}}, "max_scrolls": 10}</example>
|
|
252
|
+
<example>{"action": "scroll", "direction": "right", "predicate": {"type": "scrollview"}}</example>
|
|
243
253
|
<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
254
|
</action>
|
|
245
255
|
|
|
@@ -337,6 +347,13 @@ const DEVICE_AUTOMATION_REF = `<device-automation-reference>
|
|
|
337
347
|
<example>{"action": "reset_location"}</example>
|
|
338
348
|
</action>
|
|
339
349
|
|
|
350
|
+
<action name="open_link">
|
|
351
|
+
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.
|
|
352
|
+
<field name="url" required="yes">Full URL or deep link, e.g. https://example.com or myapp://profile/42</field>
|
|
353
|
+
<example>{"action": "open_link", "url": "myapp://profile/42"}</example>
|
|
354
|
+
<example>{"action": "open_link", "url": "https://example.com"}</example>
|
|
355
|
+
</action>
|
|
356
|
+
|
|
340
357
|
<action name="siri">
|
|
341
358
|
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
359
|
Use for triggering SiriKit intents and App Shortcuts registered by apps (media playback, messaging, banking shortcuts, etc.).
|
|
@@ -360,7 +377,7 @@ const DEVICE_AUTOMATION_REF = `<device-automation-reference>
|
|
|
360
377
|
</action>
|
|
361
378
|
|
|
362
379
|
<action name="tap/type/press_key/wait_for/if_exists">
|
|
363
|
-
Same fields as native but use css_selector
|
|
380
|
+
Same fields as native but use css_selector in predicate.
|
|
364
381
|
<example>{"action": "tap", "context": "web", "predicate": {"css_selector": "button.submit"}}</example>
|
|
365
382
|
<example>{"action": "type", "context": "web", "predicate": {"css_selector": "input#email"}, "text": "user@example.com", "clear_first": true}</example>
|
|
366
383
|
Web press_key keys: enter, tab, delete, escape.
|
|
@@ -462,7 +479,7 @@ const TESTING_REF = `<testing-reference>
|
|
|
462
479
|
|
|
463
480
|
<rules>
|
|
464
481
|
<rule>Never ask the user for information you can get yourself — use observe, list_apps, get_ui_tree.</rule>
|
|
465
|
-
<rule>
|
|
482
|
+
<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
483
|
<rule>Always use predicates over coordinates — predicates survive layout changes.</rule>
|
|
467
484
|
<rule>Always prefer UI tree and OCR over screenshots for element discovery.</rule>
|
|
468
485
|
<rule>Your first action must be observe — never ask the user first.</rule>
|
|
@@ -542,6 +559,8 @@ const TESTING_REF = `<testing-reference>
|
|
|
542
559
|
type ~"Field" → "text" — partial match field
|
|
543
560
|
type "Field" → "text" clear — clear first
|
|
544
561
|
type "Field" → "text" keep_keyboard — keep keyboard open
|
|
562
|
+
type_secret "Field" → "secret-id" - type a stored credential (value never in script/logs)
|
|
563
|
+
type_secret "secret-id" - type credential into focused field
|
|
545
564
|
swipe up|down|left|right — swipe direction
|
|
546
565
|
swipe up distance:long — short/medium/long
|
|
547
566
|
swipe up duration:500 — custom duration (ms)
|
|
@@ -549,6 +568,7 @@ const TESTING_REF = `<testing-reference>
|
|
|
549
568
|
scroll down — scroll
|
|
550
569
|
scroll down to "Element" — scroll until visible
|
|
551
570
|
scroll down to "Footer" max_scrolls:10 — limit attempts
|
|
571
|
+
scroll right in type:scrollview - target a specific container
|
|
552
572
|
toggle "Wi-Fi" on|off — toggle switch
|
|
553
573
|
toggle type:switch near "Wi-Fi" on — modifier-only
|
|
554
574
|
drag "Item" to "Trash" — drag element
|
|
@@ -568,6 +588,7 @@ const TESTING_REF = `<testing-reference>
|
|
|
568
588
|
paste_text "Field" — paste clipboard into element
|
|
569
589
|
set_location 40.7128,-74.0060 — simulate GPS location (lat,lon)
|
|
570
590
|
reset_location — stop location simulation
|
|
591
|
+
open_link "myapp://profile/42" - open a URL or deep link (routes to the app)
|
|
571
592
|
siri "Search YouTube for cats" — invoke Siri with voice command (iOS only)
|
|
572
593
|
observe — observe screen
|
|
573
594
|
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.0",
|
|
10
10
|
"packages": [
|
|
11
11
|
{
|
|
12
12
|
"registryType": "npm",
|
|
13
13
|
"identifier": "mobai-mcp",
|
|
14
|
-
"version": "2.
|
|
14
|
+
"version": "2.4.0",
|
|
15
15
|
"transport": {
|
|
16
16
|
"type": "stdio"
|
|
17
17
|
}
|