mobai-mcp 1.5.0 → 2.0.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.
@@ -0,0 +1,7 @@
1
+ export declare const RESOURCES: {
2
+ uri: string;
3
+ name: string;
4
+ description: string;
5
+ mimeType: string;
6
+ }[];
7
+ export declare function getResourceContent(uri: string): string | null;
@@ -0,0 +1,497 @@
1
+ export const RESOURCES = [
2
+ {
3
+ uri: "mobai://reference/device-automation",
4
+ name: "Device Automation Reference",
5
+ description: "How to control Android and iOS devices — guide, all actions, predicates, and failure strategies",
6
+ mimeType: "text/plain",
7
+ },
8
+ {
9
+ uri: "mobai://reference/testing",
10
+ name: "Testing Reference",
11
+ description: "Testing workflow, rules, error fixes, and .mob script syntax for test generation",
12
+ mimeType: "text/plain",
13
+ },
14
+ ];
15
+ export function getResourceContent(uri) {
16
+ switch (uri) {
17
+ case "mobai://reference/device-automation":
18
+ return DEVICE_AUTOMATION_REF;
19
+ case "mobai://reference/testing":
20
+ return TESTING_REF;
21
+ default:
22
+ return null;
23
+ }
24
+ }
25
+ // ---------------------------------------------------------------------------
26
+ // Resource content — copied verbatim from Go resources.go
27
+ // ---------------------------------------------------------------------------
28
+ const DEVICE_AUTOMATION_REF = `<device-automation-reference>
29
+
30
+ <guide>
31
+ All device interaction goes through DSL scripts via MCP execute_dsl tool or POST /devices/{id}/dsl/execute.
32
+
33
+ <script-format>
34
+ {"version": "0.2", "steps": [...actions...], "on_fail": {"strategy": "retry", "max_retries": 2}}
35
+ Every script must include "version": "0.2" and a "steps" array.
36
+ </script-format>
37
+
38
+ <important>
39
+ Always end scripts with these two steps to receive latest UI state:
40
+ {"action": "wait_for", "stable": true, "timeout_ms": 3000}
41
+ {"action": "observe", "include": ["ui_tree"]}
42
+ Adjust timeout_ms (higher after app launches or network requests). Adjust observe "include" to request only what you need: ui_tree, screenshot, ocr, activity, installed_apps.
43
+ </important>
44
+
45
+ <scroll-vs-swipe>scroll is semantic (direction = where to look); swipe is physical (direction = finger movement). Prefer scroll with to_element to find off-screen elements.</scroll-vs-swipe>
46
+
47
+ <observe-guidance>
48
+ Prefer UI tree and OCR over screenshots — screenshots consume far more context and are slower. Only use screenshots for visual verification (layout, colors, images).
49
+ 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.
50
+ </observe-guidance>
51
+
52
+ <ocr-fallback>
53
+ iOS only. When UI tree has suspiciously few elements but screen has more content (React Native, Flutter, custom-rendered apps, system dialogs like Sign in with Apple), use observe with "ocr". Returns text with tap coordinates for elements missing from UI tree.
54
+ </ocr-fallback>
55
+
56
+ <execution-modes>
57
+ Default (explore mode): non-last observe actions are skipped — only final observe executes. Use "mode": "deterministic" when you need every observe to execute (observe → act → observe → act → observe).
58
+ Example: {"version": "0.2", "mode": "deterministic", "steps": [{"action": "observe", "include": ["ui_tree"]}, {"action": "tap", "predicate": {"text": "Next"}}, {"action": "observe", "include": ["ui_tree"]}]}
59
+ </execution-modes>
60
+
61
+ <workflow>Observe screen → plan → act via execute_dsl → verify (end script with wait_for stable + observe) → repeat until done.</workflow>
62
+
63
+ <screenshot-tools>
64
+ get_screenshot — fast low-quality image for LLM visual analysis.
65
+ save_screenshot — full-quality PNG for reporting, debugging, or sharing.
66
+ </screenshot-tools>
67
+
68
+ <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>
69
+
70
+ <troubleshooting>
71
+ Element not visible — use scroll with to_element to find it.
72
+ App launches and page transitions take time — use wait_for or delay.
73
+ Observe before acting on unfamiliar screens.
74
+ </troubleshooting>
75
+ </guide>
76
+
77
+ <common-types>
78
+ <coordinates>{"x": int, "y": int}</coordinates>
79
+ <target-element>{"predicate": Predicate}</target-element>
80
+
81
+ <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. Prefer text fields over label fields — text is what the user sees on screen and is more reliable.</note>
83
+ <field name="text" type="string">Exact match — use only when the full text is short, static, and unique</field>
84
+ <field name="text_contains" type="string">Substring, case-insensitive — preferred for most matching</field>
85
+ <field name="text_starts_with" type="string">Prefix match</field>
86
+ <field name="text_regex" type="string">Regex pattern — use for dynamic text (numbers, dates, counts)</field>
87
+ <field name="type" type="string">button, input, switch, text, image, cell, scrollview</field>
88
+ <field name="label" type="string">Accessibility label (exact) — use only when text fields are empty</field>
89
+ <field name="label_contains" type="string">Accessibility label (partial) — use only when text fields are empty</field>
90
+ <field name="enabled" type="bool">Enabled state</field>
91
+ <field name="visible" type="bool">Visible state</field>
92
+ <field name="selected" type="bool">Selected state</field>
93
+ <field name="near" type="NearPredicate">{"text_contains": "Email", "direction": "below|above|left|right|any", "max_distance": 100}</field>
94
+ <field name="bounds_hint" type="string">top_half, bottom_half, left_half, right_half, center</field>
95
+ <field name="parent_of" type="Predicate">Find parent containing child</field>
96
+ <field name="index" type="int">0-based Nth match disambiguator</field>
97
+ </predicate>
98
+
99
+ <predicate context="web">
100
+ <field name="css_selector" type="string">CSS selector</field>
101
+ <field name="xpath" type="string">XPath expression</field>
102
+ </predicate>
103
+
104
+ <failure-strategy>
105
+ Per-step or script-wide via on_fail.
106
+ abort (default) | skip | retry (+ max_retries, retry_delay_ms, fallback_strategy) | replan | require_user
107
+ <example>{"on_fail": {"strategy": "retry", "max_retries": 3, "retry_delay_ms": 1000, "fallback_strategy": {"strategy": "skip"}}}</example>
108
+ </failure-strategy>
109
+ </common-types>
110
+
111
+ <native-actions>
112
+ Most actions accept predicate and/or coords to target elements. Only unique fields listed.
113
+
114
+ <action name="open_app">
115
+ <field name="bundle_id" required="yes"/>
116
+ <example>{"action": "open_app", "bundle_id": "com.apple.Preferences"}</example>
117
+ <note>If open_app fails or the app disappears immediately after launch, the app has likely crashed. Do NOT retry or try alternative launch methods — start crash investigation instead. Use metrics_start with capture_logs: true to capture device logs, then diagnose.</note>
118
+ </action>
119
+
120
+ <action name="tap">
121
+ <field name="predicate" required="one-of"/>
122
+ <field name="coords" required="one-of"/>
123
+ <example>{"action": "tap", "predicate": {"text_contains": "Settings"}}</example>
124
+ </action>
125
+
126
+ <action name="double_tap">
127
+ <example>{"action": "double_tap", "predicate": {"text": "Photo"}}</example>
128
+ </action>
129
+
130
+ <action name="two_finger_tap">
131
+ <example>{"action": "two_finger_tap", "predicate": {"text": "Map"}}</example>
132
+ </action>
133
+
134
+ <action name="pinch">
135
+ <field name="scale" type="float" default="2.0">Greater than 1.0 zooms in, less than 1.0 zooms out</field>
136
+ <field name="velocity" type="float" default="1.0">Scale change per second</field>
137
+ <example>{"action": "pinch", "predicate": {"text_contains": "Map"}, "scale": 0.5}</example>
138
+ </action>
139
+
140
+ <action name="long_press">
141
+ <field name="predicate" required="yes"/>
142
+ <field name="duration_ms" type="int" default="500"/>
143
+ <example>{"action": "long_press", "predicate": {"text": "Item"}, "duration_ms": 1000}</example>
144
+ </action>
145
+
146
+ <action name="type">
147
+ <field name="text" required="yes"/>
148
+ <field name="predicate">Optional target element</field>
149
+ <field name="clear_first" type="bool"/>
150
+ <field name="dismiss_keyboard" type="bool" default="false"/>
151
+ <example>{"action": "type", "text": "Hello", "predicate": {"type": "input"}, "clear_first": true}</example>
152
+ </action>
153
+
154
+ <action name="swipe">
155
+ Direction = finger movement. Use direction OR from_coords/to_coords.
156
+ <field name="direction">up, down, left, right</field>
157
+ <field name="distance">short (25%), medium (50%), full (75%)</field>
158
+ <field name="from_coords" type="Coordinates"/>
159
+ <field name="to_coords" type="Coordinates"/>
160
+ <field name="duration_ms" type="int"/>
161
+ <example>{"action": "swipe", "direction": "up", "distance": "medium"}</example>
162
+ </action>
163
+
164
+ <action name="scroll">
165
+ Direction = semantic (where to look), not finger movement.
166
+ <field name="direction" required="yes">down (look below), up (look above)</field>
167
+ <field name="to_element" type="TargetElement"/>
168
+ <field name="max_scrolls" type="int"/>
169
+ <field name="amount">small, page, full</field>
170
+ <example>{"action": "scroll", "direction": "down", "to_element": {"predicate": {"text": "Privacy"}}, "max_scrolls": 10}</example>
171
+ </action>
172
+
173
+ <action name="drag">
174
+ <field name="from" type="TargetElement" required="one-of"/>
175
+ <field name="from_coords" type="Coordinates" required="one-of"/>
176
+ <field name="to_element" type="TargetElement" required="one-of"/>
177
+ <field name="to_coords" type="Coordinates" required="one-of"/>
178
+ <field name="duration_ms" type="int"/>
179
+ <field name="press_duration_ms" type="int">Press-and-hold before drag (for moving app icons)</field>
180
+ <example>{"action": "drag", "from": {"predicate": {"text": "Item"}}, "to_element": {"predicate": {"text": "Trash"}}}</example>
181
+ </action>
182
+
183
+ <action name="press_key">
184
+ <field name="key" required="yes"/>
185
+ <platform name="android">enter, tab, delete, escape, volume_up, volume_down, home, back, recent_apps, mute, power, play_pause, next, previous</platform>
186
+ <platform name="ios">enter, tab, delete, home, volume_up, volume_down (no back/recent_apps — use swipe gestures)</platform>
187
+ <note>Dismiss keyboard: press_key enter (submits), tap Done/Cancel button, or Android press_key back.</note>
188
+ <example>{"action": "press_key", "key": "enter"}</example>
189
+ </action>
190
+
191
+ <action name="toggle">
192
+ <field name="predicate" required="yes"/>
193
+ <field name="state" required="yes">on or off</field>
194
+ <example>{"action": "toggle", "predicate": {"type": "switch", "text_contains": "Wi-Fi"}, "state": "on"}</example>
195
+ </action>
196
+
197
+ <action name="screenshot">
198
+ <field name="file_path" required="yes">Directory to save</field>
199
+ <field name="name">Filename without extension (default: screenshot_TIMESTAMP)</field>
200
+ <example>{"action": "screenshot", "file_path": "/path/to/dir", "name": "login_screen"}</example>
201
+ <returns>screenshot_path</returns>
202
+ </action>
203
+
204
+ <action name="navigate">
205
+ <field name="target" required="yes">home, back, recent_apps</field>
206
+ <platform name="ios">back/recent_apps not supported — use swipe gestures.</platform>
207
+ <example>{"action": "navigate", "target": "home"}</example>
208
+ </action>
209
+
210
+ <action name="observe">
211
+ <field name="include">ui_tree, screenshot, activity, installed_apps, dom, ocr</field>
212
+ <field name="context">native (default) or web</field>
213
+ <field name="store_as" type="string">Store result in named variable</field>
214
+ <field name="include_keyboard" type="bool" default="false"/>
215
+ <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>
216
+ <field name="filter">text_regex or bounds {"x","y","width","height"} to reduce output</field>
217
+ <note>OCR (iOS only): returns text with tap coordinates. Useful for system dialogs missing from UI tree.</note>
218
+ <example>{"action": "observe", "include": ["ui_tree"], "filter": {"text_regex": "Settings|Wi-Fi"}}</example>
219
+ </action>
220
+
221
+ <action name="wait_for">
222
+ <field name="predicate">Required unless stable is true</field>
223
+ <field name="stable" type="bool" default="false">Wait for UI to stop changing</field>
224
+ <field name="timeout_ms" type="int" default="10000"/>
225
+ <field name="poll_interval_ms" type="int" default="500">300 for stable mode</field>
226
+ <note>Stable mode: use before screenshots/assertions after animations.</note>
227
+ <example>{"action": "wait_for", "predicate": {"text": "Welcome"}, "timeout_ms": 5000}</example>
228
+ </action>
229
+
230
+ <action name="delay">
231
+ <field name="duration_ms" required="yes"/>
232
+ <example>{"action": "delay", "duration_ms": 1000}</example>
233
+ </action>
234
+
235
+ <action name="if_exists">
236
+ <field name="predicate" required="yes"/>
237
+ <field name="then" type="[]Step" required="yes"/>
238
+ <field name="else" type="[]Step"/>
239
+ <example>{"action": "if_exists", "predicate": {"text": "Allow"}, "then": [{"action": "tap", "predicate": {"text": "Allow"}}]}</example>
240
+ </action>
241
+
242
+ <action name="kill_app">
243
+ <field name="bundle_id" required="yes"/>
244
+ <example>{"action": "kill_app", "bundle_id": "com.apple.mobilesafari"}</example>
245
+ </action>
246
+
247
+ <action name="set_location">
248
+ <field name="lat" type="float" required="yes">-90 to 90</field>
249
+ <field name="lon" type="float" required="yes">-180 to 180</field>
250
+ <platform name="ios">All versions</platform>
251
+ <platform name="android">Emulators all versions, real devices 12+ only</platform>
252
+ <example>{"action": "set_location", "lat": 40.7128, "lon": -74.0060}</example>
253
+ </action>
254
+
255
+ <action name="reset_location">
256
+ <example>{"action": "reset_location"}</example>
257
+ </action>
258
+ </native-actions>
259
+
260
+ <web-actions>
261
+ Require select_web_context first. Add "context": "web" to actions. Always try native automation first.
262
+ iOS physical devices only (no simulators). Android physical or emulator.
263
+
264
+ <action name="select_web_context">
265
+ <field name="page_id" type="int"/>
266
+ <field name="url_contains" type="string"/>
267
+ <field name="title_contains" type="string"/>
268
+ <example>{"action": "select_web_context", "url_contains": "google.com"}</example>
269
+ </action>
270
+
271
+ <action name="tap/type/press_key/wait_for/if_exists">
272
+ Same fields as native but use css_selector or xpath in predicate.
273
+ <example>{"action": "tap", "context": "web", "predicate": {"css_selector": "button.submit"}}</example>
274
+ <example>{"action": "type", "context": "web", "predicate": {"css_selector": "input#email"}, "text": "user@example.com", "clear_first": true}</example>
275
+ Web press_key keys: enter, tab, delete, escape.
276
+ </action>
277
+
278
+ <action name="navigate">
279
+ <field name="url" required="yes"/>
280
+ <example>{"action": "navigate", "url": "https://example.com"}</example>
281
+ </action>
282
+
283
+ <action name="execute_js">
284
+ <field name="script" required="yes"/>
285
+ <field name="async" type="bool"/>
286
+ <example>{"action": "execute_js", "script": "return document.title"}</example>
287
+ </action>
288
+ </web-actions>
289
+
290
+ <assertions>
291
+ <action name="assert_exists">
292
+ <field name="predicate" required="yes"/>
293
+ <field name="timeout_ms" type="int"/>
294
+ <example>{"action": "assert_exists", "predicate": {"text": "Success"}, "timeout_ms": 3000}</example>
295
+ </action>
296
+
297
+ <action name="assert_not_exists">
298
+ <field name="predicate" required="yes"/>
299
+ <example>{"action": "assert_not_exists", "predicate": {"text": "Error"}}</example>
300
+ </action>
301
+
302
+ <action name="assert_count">
303
+ <field name="predicate" required="yes"/>
304
+ <field name="count" type="int" required="yes"/>
305
+ <example>{"action": "assert_count", "predicate": {"type": "cell"}, "count": 5}</example>
306
+ </action>
307
+
308
+ <action name="assert_screen_changed">
309
+ <field name="threshold_percent" type="int"/>
310
+ <example>{"action": "assert_screen_changed", "threshold_percent": 15}</example>
311
+ <note>Pattern: observe(screenshot) then action then delay then assert_screen_changed. Do NOT observe after the action — it resets the baseline.</note>
312
+ </action>
313
+ </assertions>
314
+
315
+ <metrics>
316
+ <action name="metrics_start">
317
+ <field name="types">system_cpu, system_memory, fps, network, battery, process (requires bundle_id). Default: system_cpu, system_memory.</field>
318
+ <field name="interval_ms" type="int" default="1000"/>
319
+ <field name="label" type="string"/>
320
+ <field name="bundle_id" type="string">Required for process metrics</field>
321
+ <field name="capture_logs" type="bool">Captures iOS syslog / Android logcat at Warning/Error level as JSONL</field>
322
+ <field name="thresholds">{"cpu_high": 80, "fps_low": 45, "fps_jank": 30, "memory_growth_mb_min": 50, "memory_high": 85, "battery_drain_rate": 10}</field>
323
+ <example>{"action": "metrics_start", "types": ["system_cpu", "fps"], "label": "login_flow"}</example>
324
+ <example>{"action": "metrics_start", "capture_logs": true, "label": "debug_session"}</example>
325
+ </action>
326
+
327
+ <action name="metrics_stop">
328
+ <field name="format">summary (default) or detailed</field>
329
+ <returns>If capture_logs was enabled, includes logs_file path to JSONL.</returns>
330
+ <example>{"action": "metrics_stop"}</example>
331
+ </action>
332
+ </metrics>
333
+
334
+ <screen-recording>
335
+ Captures screenshots continuously to verify transitional behavior (animations, transitions, loading).
336
+ Confirm with user before using. Animations are non-deterministic — run multiple times for best coverage. Auto-stops on script exit.
337
+
338
+ <action name="record_start">
339
+ <field name="file_path">Output directory (default: auto-generated in temp)</field>
340
+ </action>
341
+
342
+ <action name="record_stop">
343
+ <field name="file_path">Override output directory</field>
344
+ <returns>recording_path, frame_count, transition_hints (anomalies: jump/flash/stutter/incoherent_motion with from_frame, to_frame, type, delta_percent, region, message)</returns>
345
+ </action>
346
+ </screen-recording>
347
+
348
+ </device-automation-reference>
349
+ `;
350
+ const TESTING_REF = `<testing-reference>
351
+
352
+ <important>Read mobai://reference/device-automation to learn how to control devices before interacting with them.</important>
353
+
354
+ <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
+ <rule>Never ask the user for information you can get yourself — use observe, list_apps, get_ui_tree.</rule>
357
+ <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
+ <rule>Always use predicates over coordinates — predicates survive layout changes.</rule>
359
+ <rule>Always prefer UI tree and OCR over screenshots for element discovery.</rule>
360
+ <rule>Your first action must be observe — never ask the user first.</rule>
361
+ </rules>
362
+
363
+ <workflow-create>
364
+ 1. Observe the current screen
365
+ 2. Plan the test steps from the user's description
366
+ 3. Execute each action via DSL — add wait_for before every element interaction
367
+ 4. Assert after key actions — verify expected state with assert_exists/assert_not_exists
368
+ 5. Output the full script using MCP test tools
369
+ 6. Verify — run the full script end-to-end
370
+ 7. Fix — if steps fail, observe the screen, fix the failing lines
371
+ 8. Re-run to verify fixes (max 3 retry cycles)
372
+ </workflow-create>
373
+
374
+ <workflow-fix>
375
+ 1. Read the current script
376
+ 2. Analyze the error messages — they reference exact line numbers
377
+ 3. Reproduce — run the failing line individually via DSL to observe device state
378
+ 4. Fix — update, insert, or delete lines as needed
379
+ 5. Verify — re-run the test
380
+ </workflow-fix>
381
+
382
+ <error-fixes>
383
+ "multiple elements found" — add disambiguators: type:button, type:cell, bounds:top_half, near "Label", or [index]
384
+ "no element found" — usually timing: add wait_for before the failing line. If present, check text/type or add scroll
385
+ "timeout" — increase timeout value or add a preceding wait_for
386
+ Modifier-only predicates are valid: tap type:button, wait_for type:dialog, assert_exists type:switch near "Label"
387
+ </error-fixes>
388
+
389
+ <cross-platform>
390
+ Keep common steps outside platform blocks. Wrap platform-specific alternatives in paired blocks:
391
+ # android
392
+ navigate back
393
+ # end
394
+ # ios
395
+ tap ~"back"
396
+ # end
397
+ </cross-platform>
398
+
399
+ <stop-conditions>
400
+ Pause and tell the user when:
401
+ - An error cannot be resolved after 3 attempts
402
+ - The observed screen state does not match any expected state
403
+ - A destructive action (uninstall, data deletion) would be required that was not requested
404
+ </stop-conditions>
405
+
406
+ <verification>
407
+ Check before every response:
408
+ 1. Did you use MCP tools for all script mutations? (bare .mob lines in text are silently ignored)
409
+ 2. Does every element interaction have a wait_for on the preceding line?
410
+ 3. Are predicates used instead of coordinates wherever possible?
411
+ 4. Did you observe the screen before acting?
412
+ </verification>
413
+
414
+ <mob-script-syntax>
415
+ Each line = one test step.
416
+
417
+ <actions>
418
+ app "com.example.app" — launch app
419
+ kill_app "com.example.app" — force-close app
420
+ tap "Text" — tap by text
421
+ tap "Field" near "Label" — tap near another element
422
+ tap "Edit" near "Profile" direction:below — near with direction
423
+ tap "Item" [2] — tap Nth match (0-indexed)
424
+ tap ~"partial" — partial text match
425
+ tap /"^\\d+ items"/ — regex match
426
+ tap "Submit" type:button — filter by type
427
+ tap "Settings" bounds:top_half — filter by region (top_half, bottom_half, left_half, right_half, center)
428
+ tap "Cell" parent_of:"Submit" — find parent containing child
429
+ tap "Submit" enabled:true — filter by enabled state
430
+ tap "Tab" visible:true selected:false — filter by state
431
+ tap 100,200 — tap coordinates
432
+ double_tap "Image" — double-tap element
433
+ long_press "Message" duration:1500 — long-press (ms)
434
+ type "Field" → "text" — type into field
435
+ type ~"Field" → "text" — partial match field
436
+ type "Field" → "text" clear — clear first
437
+ type "Field" → "text" keep_keyboard — keep keyboard open
438
+ swipe up|down|left|right — swipe direction
439
+ swipe up distance:long — short/medium/long
440
+ swipe up duration:500 — custom duration (ms)
441
+ swipe 100,200 to 100,800 — coordinate swipe
442
+ scroll down — scroll
443
+ scroll down to "Element" — scroll until visible
444
+ scroll down to "Footer" max_scrolls:10 — limit attempts
445
+ toggle "Wi-Fi" on|off — toggle switch
446
+ toggle type:switch near "Wi-Fi" on — modifier-only
447
+ drag "Item" to "Trash" — drag element
448
+ drag 100,200 to 300,400 duration:500 — coordinate drag
449
+ drag "App" to "Folder" press_duration:500 — press-and-drag
450
+ wait_for "Element" timeout:5000 — wait for element
451
+ wait_for type:button bounds:bottom_half timeout:3000 — modifier-only
452
+ delay 1000 — wait N ms
453
+ press_key home|back|enter — hardware key
454
+ navigate back|home — navigation shortcut
455
+ observe — observe screen
456
+ screenshot "path.png" — take screenshot
457
+ </actions>
458
+
459
+ <assertions>
460
+ assert_exists "Element" — element is on screen
461
+ assert_not_exists "Element" — element is NOT on screen
462
+ assert_exists "Header" bounds:top_right — with region filter
463
+ assert_count "Cell" expected:5 — element count
464
+ checkpoint "name" — mark checkpoint
465
+ </assertions>
466
+
467
+ <failure-handling>
468
+ tap "Submit" on_fail:skip — skip on failure
469
+ tap "Submit" on_fail:retry retries:3 retry_delay:1000 — retry on failure
470
+ </failure-handling>
471
+
472
+ <metadata>
473
+ # Comment text — section header
474
+ # Tags: smoke, login — tags for filtering
475
+ # Device: iPhone 15 — device filter
476
+ # Timeout: 30000 — global timeout (ms)
477
+ # On-Fail: abort — abort or continue
478
+ </metadata>
479
+
480
+ <platform-blocks>
481
+ # ios / # android — open platform block
482
+ # end — close block
483
+ Lines outside blocks run on BOTH platforms.
484
+ No nesting. Every open MUST have a matching # end.
485
+ </platform-blocks>
486
+
487
+ <conditionals>
488
+ if_exists "Element" {
489
+ tap "Element"
490
+ } else {
491
+ tap "Other"
492
+ }
493
+ </conditionals>
494
+ </mob-script-syntax>
495
+
496
+ </testing-reference>
497
+ `;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mobai-mcp",
3
- "version": "1.5.0",
3
+ "version": "2.0.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": "1.5.0",
9
+ "version": "2.0.0",
10
10
  "packages": [
11
11
  {
12
12
  "registryType": "npm",
13
13
  "identifier": "mobai-mcp",
14
- "version": "1.5.0",
14
+ "version": "2.0.0",
15
15
  "transport": {
16
16
  "type": "stdio"
17
17
  }