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.
- package/dist/index.d.ts +3 -3
- package/dist/index.js +293 -1452
- package/dist/resources.d.ts +7 -0
- package/dist/resources.js +497 -0
- package/package.json +1 -1
- package/server.json +2 -2
|
@@ -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
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": "
|
|
9
|
+
"version": "2.0.0",
|
|
10
10
|
"packages": [
|
|
11
11
|
{
|
|
12
12
|
"registryType": "npm",
|
|
13
13
|
"identifier": "mobai-mcp",
|
|
14
|
-
"version": "
|
|
14
|
+
"version": "2.0.0",
|
|
15
15
|
"transport": {
|
|
16
16
|
"type": "stdio"
|
|
17
17
|
}
|