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 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: `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.
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
- You MUST read the MCP resource mobai://reference/device-automation to learn how to control devices before using this tool.
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. Example:
250
- {"version":"0.2","steps":[
251
- {"action":"open_app","bundle_id":"com.apple.Preferences"},
252
- {"action":"tap","predicate":{"text_contains":"Wi-Fi"}},
253
- {"action":"wait_for","predicate":{"type":"switch"},"timeout_ms":3000}
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
- return textResult(await doPost(`/devices/${args?.device_id}/install-app`, { path: args?.path }));
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)</field>
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 or xpath in predicate.
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>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>
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mobai-mcp",
3
- "version": "2.3.1",
3
+ "version": "2.4.0",
4
4
  "mcpName": "io.github.MobAI-App/mobai-mcp",
5
5
  "description": "MCP server for MobAI - AI-powered mobile device automation",
6
6
  "type": "module",
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.3.1",
9
+ "version": "2.4.0",
10
10
  "packages": [
11
11
  {
12
12
  "registryType": "npm",
13
13
  "identifier": "mobai-mcp",
14
- "version": "2.3.1",
14
+ "version": "2.4.0",
15
15
  "transport": {
16
16
  "type": "stdio"
17
17
  }