2.7.0npm · mobai-mcp · latest release
Observed 2026-09-04T23:14:33.002Z using mcpSecurity-inventory. Protocol 2025-06-18.
| Tool | Category | Risk |
|---|---|---|
claim_deviceClaim exclusive use of a device for this session. You rarely need this: a host that requires claims auto-claims on your first action, and a host that does not require them needs no claim at all. Do not call it to fix a device that is missing, still connecting, or busy - none of those are lease problems. Use it only to reserve a device up front or to set a custom holder label. A session can hold several devices at once; each lease is attached automatically to subsequent calls targeting its device and auto-renews on every action, expiring only after the app's configured idle timeout. Call release_device when you are done with a device.Input schema{
"type": "object",
"properties": {
"device_id": {
"type": "string",
"description": "Device ID to claim. Empty = claim any free local device."
},
"holder": {
"type": "string",
"description": "Short label shown in the MobAI device list, e.g. your agent or task name."
}
},
"required": []
} | — | — |
debug_appLaunch an app in debug mode and write logs to a file. Returns the log file path — use Read/Grep to inspect logs. Use kill_app to stop.Input schema{
"type": "object",
"properties": {
"device_id": {
"type": "string",
"description": "Device ID"
},
"bundle_id": {
"type": "string",
"description": "Bundle ID of the app to debug"
},
"log_path": {
"type": "string",
"description": "Directory for log file (supports ~/). Defaults to OS temp directory."
}
},
"required": [
"device_id",
"bundle_id"
]
} | — | — |
debug_attachStart a debug session for an iOS app. Provide either bundle_id (launches and attaches) or pid (attaches to a running process). Optional breakpoints[] are armed before the target resumes. Read mobai://reference/debugging first.Input schema{
"type": "object",
"properties": {
"device_id": {
"type": "string",
"description": "Device ID"
},
"bundle_id": {
"type": "string",
"description": "App bundle ID to launch and attach. Either this or pid is required."
},
"pid": {
"type": "number",
"description": "Attach to an already-running PID. Either this or bundle_id is required."
},
"breakpoints": {
"type": "array",
"items": {
"type": "string"
},
"description": "Initial breakpoint specs. \"File.swift:42\" (preferred), \"Module.Type.method\" (no parameter signature), \"-[Class method:]\", or runtime symbol."
},
"stop_on_entry": {
"type": "boolean",
"description": "Simulator only — pause at first instruction."
}
},
"required": [
"device_id"
]
} | — | — |
debug_breakpointAdd or remove a breakpoint in the active debug session. For action=add provide spec; for action=remove provide id.Input schema{
"type": "object",
"properties": {
"device_id": {
"type": "string",
"description": "Device ID"
},
"action": {
"type": "string",
"enum": [
"add",
"remove"
],
"description": "\"add\" or \"remove\""
},
"spec": {
"type": "string",
"description": "Breakpoint spec for action=add. \"File.swift:42\", \"Module.Type.method\", \"-[Class method:]\", or runtime symbol."
},
"id": {
"type": "number",
"description": "Breakpoint id for action=remove."
}
},
"required": [
"device_id",
"action"
]
} | — | — |
debug_detachEnd the debug session. Pass kill=true to terminate the debuggee; otherwise it keeps running.Input schema{
"type": "object",
"properties": {
"device_id": {
"type": "string",
"description": "Device ID"
},
"kill": {
"type": "boolean",
"description": "Terminate debuggee on detach."
}
},
"required": [
"device_id"
]
} | — | — |
debug_evalEvaluate a Swift/ObjC expression at the current pause. Session must be paused. Examples: "p defaultPrivate", "po self.viewModel.user.email", "frame variable".Input schema{
"type": "object",
"properties": {
"device_id": {
"type": "string",
"description": "Device ID"
},
"expression": {
"type": "string",
"description": "Expression to evaluate"
},
"frame_id": {
"type": "number",
"description": "Optional frame id to evaluate in"
}
},
"required": [
"device_id",
"expression"
]
} | — | — |
debug_stateQuery the current debug session. Returns {state, breakpoints} by default. Set include_stack=true to also fetch the stack of the stopped thread; include_vars=true to also fetch frame[0] locals; include_threads=true to enumerate all threads.Input schema{
"type": "object",
"properties": {
"device_id": {
"type": "string",
"description": "Device ID"
},
"include_stack": {
"type": "boolean",
"description": "Include stack of stopped thread."
},
"include_vars": {
"type": "boolean",
"description": "Include frame[0] locals."
},
"include_threads": {
"type": "boolean",
"description": "Include all threads."
}
},
"required": [
"device_id"
]
} | — | — |
debug_stepAdvance the target.
"in" — step into next call (blocks ~ms, returns {state, breakpoints, stack, frame0_locals})
"over" — step over next call (same shape)
"out" — run until current frame returns (same shape)
"continue" — resume until next breakpoint (fire-and-forget; returns just {state, breakpoints} — poll debug_state for next stop)Input schema{
"type": "object",
"properties": {
"device_id": {
"type": "string",
"description": "Device ID"
},
"direction": {
"type": "string",
"enum": [
"in",
"over",
"out",
"continue"
],
"description": "\"in\" | \"over\" | \"out\" | \"continue\""
},
"include_stack": {
"type": "boolean",
"description": "Include the new stack. Default true. Ignored for direction=\"continue\"."
},
"include_vars": {
"type": "boolean",
"description": "Include the new frame[0] locals. Default true. Ignored for direction=\"continue\"."
}
},
"required": [
"device_id",
"direction"
]
} | — | — |
execute_dslThree rules that decide whether this tool is fast or slow, plus how to recover from the most common failure. They live here, not only in the reference, because agents that never open the reference default to the slow shape (measured: one step per call and 4x the screenshots):
1. BATCH. Pack every step you can confidently predict into ONE call, and end the script with wait_for plus observe to see the result. Split only when the next step depends on screen content you have not seen yet.
2. Prefer observe with "ui_tree" over screenshots. The tree is faster and far cheaper in context; use a screenshot only for genuinely visual checks (layout, colour, images).
3. TARGET BY PREDICATE, not coordinates. A predicate is precise and survives re-renders, scrolling and other screen sizes; a raw point is none of those, and a coordinate tap reports success even when it lands on the wrong element. Fall back to coords only when the element appears in neither the ui_tree nor OCR (observe with "ocr").
4. On NO_MATCH (the most common failure by far), read the error before retrying: it lists "candidates". An off-screen candidate cannot be tapped, scroll with to_element to bring it into view. Empty candidates means the element is genuinely absent or not rendered yet, so observe or wait_for instead of guessing another predicate.
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.
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.
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.
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".
Every action below appears at least once - this is the full command surface (semantics, defaults, and platform notes are in mobai://reference/device-automation):
{"version":"0.2","params":{"query":"shoes"},"on_fail":{"strategy":"retry","max_retries":2,"retry_delay_ms":1000,"fallback_strategy":{"strategy":"skip"}},"steps":[
{"action":"open_app","bundle_id":"com.apple.Preferences","fresh":true,"debug":false},
{"action":"kill_app","bundle_id":"com.apple.mobilesafari"},
{"action":"open_link","url":"myapp://profile/42"},
{"action":"tap","predicate":{"text_contains":"Wi-Fi","type":"button"}},
{"action":"tap","coords":{"x":200,"y":640}},
{"action":"double_tap","predicate":{"text":"Photo"}},
{"action":"two_finger_tap","predicate":{"text":"Map"}},
{"action":"long_press","predicate":{"text":"Item"},"duration_ms":1000},
{"action":"pinch","predicate":{"text_contains":"Map"},"scale":0.5,"velocity":1.0},
{"action":"type","text":"${query}","predicate":{"type":"input"},"clear_first":true,"dismiss_keyboard":true},
{"action":"type_secret","secret_id":"test-account-password","predicate":{"type":"input","text_contains":"Password"}},
{"action":"clear","predicate":{"type":"input"}},
{"action":"toggle","predicate":{"type":"switch","text_contains":"Wi-Fi"},"state":"on"},
{"action":"press_key","key":"enter"},
{"action":"swipe","direction":"up","distance":"medium"},
{"action":"swipe","from_coords":{"x":200,"y":600},"to_coords":{"x":200,"y":200},"duration_ms":300},
{"action":"scroll","direction":"down","to_element":{"predicate":{"text":"Privacy"}},"predicate":{"type":"scrollview"},"max_scrolls":10,"amount":"page"},
{"action":"drag","from":{"predicate":{"text":"Item"}},"to_element":{"predicate":{"text":"Trash"}},"press_duration_ms":500,"hold_duration_ms":200,"duration_ms":500},
{"action":"drag","from_coords":{"x":100,"y":400},"to_coords":{"x":300,"y":400}},
{"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}]},
{"action":"navigate","target":"home"},
{"action":"set_location","lat":40.7128,"lon":-74.0060},
{"action":"reset_location"},
{"action":"siri","prompt":"Search YouTube for cat videos"},
{"action":"if_exists","predicate":{"text":"Allow"},"then":[{"action":"tap","predicate":{"text":"Allow"}}],"else":[{"action":"tap","predicate":{"text":"Don't Allow"}}]},
{"action":"repeat","times":"3","body":[{"action":"tap","predicate":{"text":"Increment"}}]},
{"action":"repeat","while":{"text":"Loading"},"max_iterations":20,"body":[{"action":"delay","duration_ms":500}]},
{"action":"run_script","script":"var r = http.get('https://api.example.com/seed'); vars.seed_id = JSON.parse(r.body).id;"},
{"action":"eval_script","script":"1 + 1","store_as":"two"},
{"action":"delay","duration_ms":1000},
{"action":"wait_for","predicate":{"text":"Welcome"},"timeout_ms":5000,"poll_interval_ms":500},
{"action":"assert_exists","predicate":{"text":"Success"},"timeout_ms":3000},
{"action":"assert_not_exists","predicate":{"text":"Error"}},
{"action":"assert_count","predicate":{"type":"cell"},"count":5},
{"action":"assert_screen_changed","threshold_percent":15},
{"action":"metrics_start","types":["system_cpu","fps"],"interval_ms":1000,"label":"login_flow","capture_logs":true,"thresholds":{"cpu_high":80,"fps_low":45}},
{"action":"metrics_stop","format":"summary"},
{"action":"record_start","file_path":"/tmp/mobai/rec"},
{"action":"record_stop"},
{"action":"select_web_context","url_contains":"google.com"},
{"action":"navigate","context":"web","url":"https://example.com"},
{"action":"tap","context":"web","predicate":{"css_selector":"button.submit"}},
{"action":"type","context":"web","predicate":{"css_selector":"input#email"},"text":"user@example.com","clear_first":true},
{"action":"execute_js","context":"web","script":"return document.title","async":false},
{"action":"screenshot","file_path":"/tmp/mobai","name":"final_state"},
{"action":"wait_for","stable":true,"timeout_ms":3000},
{"action":"observe","include":["ui_tree"],"only_visible":true,"filter":{"text_regex":"Settings|Wi-Fi"},"store_as":"end_state"},
{"action":"audit"}
]}
Predicate - object passed in a step's "predicate"/"from"/"to_element". Combine fields (AND); prefer text_contains over exact text. All native fields:
{"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"}}
Web predicate (context:"web") uses instead: {"css_selector":"button.submit"}.Input schema{
"type": "object",
"properties": {
"device_id": {
"type": "string",
"description": "Device ID"
},
"commands": {
"type": "string",
"description": "DSL script as JSON string with version and steps"
}
},
"required": [
"device_id",
"commands"
]
} | — | — |
get_deviceGet details about a specific deviceInput schema{
"type": "object",
"properties": {
"device_id": {
"type": "string",
"description": "Device ID"
}
},
"required": [
"device_id"
]
} | — | — |
get_screenshotCapture a fast, low-quality screenshot for LLM visual analysis. Returns the file path to the saved image. The image may be downscaled by an integer factor so its long edge stays ≤ 2000px; when that happens the response includes a scale factor — multiply any coordinates you read off the image by that factor before using them in device actions (tap, swipe, drag, long-press, etc.). UI tree coordinates are already in device pixels, do not scale those. Use this for AI/LLM processing only — for full-quality screenshots use save_screenshot instead. For tap coordinates of what is on screen (OCR text + named icons), use execute_dsl observe with screenshot/ocr instead.Input schema{
"type": "object",
"properties": {
"device_id": {
"type": "string",
"description": "Device ID"
}
},
"required": [
"device_id"
]
} | — | — |
install_appInstall an app on the device from a local file path (.apk for Android, .ipa for iOS). For cloud devices nothing is installed directly: the build is uploaded to the provider's app storage and the response returns an appRef - pass it to start_bridge as "app" (restart the bridge if a session is already running).Input schema{
"type": "object",
"properties": {
"device_id": {
"type": "string",
"description": "Device ID"
},
"path": {
"type": "string",
"description": "Local file path to the app (.apk or .ipa)"
},
"resign": {
"type": "boolean",
"description": "Sign the IPA with iloader before installing (iOS only)"
},
"apple_id": {
"type": "string",
"description": "Apple ID for signing (optional; used when resign=true)"
},
"password": {
"type": "string",
"description": "Apple ID password for signing (optional; uses cached credentials if empty)"
},
"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"
},
"cert_path": {
"type": "string",
"description": "Signing certificate PEM to reuse for an offline re-sign"
},
"key_path": {
"type": "string",
"description": "Signing private key PEM to reuse for an offline re-sign"
}
},
"required": [
"device_id",
"path"
]
} | — | — |
list_appsList installed apps on the deviceInput schema{
"type": "object",
"properties": {
"device_id": {
"type": "string",
"description": "Device ID"
}
},
"required": [
"device_id"
]
} | — | — |
list_devicesList all connected Android and iOS devices. Devices with "remote": true are physically attached to ANOTHER machine (a peer MobAI node, hostname in "node") and devices with "cloud": true live in a cloud device farm - all MobAI tools (tap, observe, DSL, screenshots, recording) work on them transparently, but host-local tooling (ffmpeg, simctl, adb, xcodebuild) cannot reach them; stay within MobAI tools for those devices. Devices with "inUse": true are claimed by the holder named in "inUseBy" - claim a different device or wait for the lease to expire.Input schema{
"type": "object",
"properties": {},
"required": []
} | — | — |
release_deviceRelease device lease(s) held by this session. With device_id, releases that device's lease; without, releases every lease this session holds. Succeeds quietly if nothing is held.Input schema{
"type": "object",
"properties": {
"device_id": {
"type": "string",
"description": "Device to release. Empty = release all devices claimed by this session."
}
},
"required": []
} | — | — |
save_screenshotSave a full-quality PNG screenshot to disk. Use this when you need a high-quality image for reporting, debugging, or sharing — not for LLM processing (use get_screenshot instead).Input schema{
"type": "object",
"properties": {
"device_id": {
"type": "string",
"description": "Device ID"
},
"path": {
"type": "string",
"description": "Directory to save screenshot to (supports ~/). Defaults to OS temp directory."
},
"name": {
"type": "string",
"description": "Optional filename (without .png extension)"
}
},
"required": [
"device_id"
]
} | — | — |
start_bridgeStart the automation bridge on a device. Required before interacting with the device. For cloud devices this allocates the provider session; pass "app" to pick the app under test (e.g. the appRef returned by install_app).Input schema{
"type": "object",
"properties": {
"device_id": {
"type": "string",
"description": "Device ID"
},
"app": {
"type": "string",
"description": "Cloud devices only: provider app ref to install at session start (bs://…, storage:…, AWS upload ARN). Overrides the saved session config. Ignored for local devices."
},
"run_options": {
"type": "object",
"description": "Cloud devices only: free-form provider run options (string values) merged into the session config."
}
},
"required": [
"device_id"
]
} | — | — |
stop_bridgeStop the automation bridge on a deviceInput schema{
"type": "object",
"properties": {
"device_id": {
"type": "string",
"description": "Device ID"
}
},
"required": [
"device_id"
]
} | — | — |
test_get_activeGet the currently active test project directory and its .mob test cases. Use this to discover the project path and available tests. The agent can then read/write/create/delete .mob files directly in the returned directory.Input schema{
"type": "object",
"properties": {},
"required": []
} | — | — |
test_list_projectsList all known test project directories with their .mob test cases. Each project is a directory containing .mob script files.Input schema{
"type": "object",
"properties": {},
"required": []
} | — | — |
test_runRun a .mob test case on a device. The case_path is relative to the project directory. Pass params to supply values for ${name} substitution in the script.Input schema{
"type": "object",
"properties": {
"project_dir": {
"type": "string",
"description": "Absolute path to the project directory"
},
"case_path": {
"type": "string",
"description": "Relative path to the .mob file within the project, e.g. auth/login.mob"
},
"device_id": {
"type": "string",
"description": "Device ID to run the test on"
},
"params": {
"type": "object",
"additionalProperties": {
"type": "string"
},
"description": "Optional key-value parameters for ${name} substitution in the script"
}
},
"required": [
"project_dir",
"case_path",
"device_id"
]
} | — | — |
uninstall_appUninstall an app from the deviceInput schema{
"type": "object",
"properties": {
"device_id": {
"type": "string",
"description": "Device ID"
},
"bundle_id": {
"type": "string",
"description": "App bundle ID (iOS) or package name (Android)"
}
},
"required": [
"device_id",
"bundle_id"
]
} | — | — |
How to attach lldb, set breakpoints, inspect stack/variables, evaluate Swift/ObjC expressions — read before any debug_* tool
{
"resource_key": "mobai://reference/debugging",
"uri": "mobai://reference/debugging",
"name": "App Debugging Reference",
"description": "How to attach lldb, set breakpoints, inspect stack/variables, evaluate Swift/ObjC expressions — read before any debug_* tool",
"mime_type": "text/plain",
"annotations": null,
"metadata_hash": "9f396ec9a0c1f1a247074ea986add73827796eda43ad1d4b06f23f765de47d55"
}How to preview a MobAI device's control UI inside Claude Code's preview panel
{
"resource_key": "mobai://claude-code-preview",
"uri": "mobai://claude-code-preview",
"name": "Claude Code Preview Setup",
"description": "How to preview a MobAI device's control UI inside Claude Code's preview panel",
"mime_type": "text/plain",
"annotations": null,
"metadata_hash": "64e9f80e8270e5ea992638c46433b44388371900f1d522f67908f6f3f279e0cd"
}How to control Android and iOS devices — guide, all actions, predicates, and failure strategies
{
"resource_key": "mobai://reference/device-automation",
"uri": "mobai://reference/device-automation",
"name": "Device Automation Reference",
"description": "How to control Android and iOS devices — guide, all actions, predicates, and failure strategies",
"mime_type": "text/plain",
"annotations": null,
"metadata_hash": "d54bc011c1eec2e1034e5473a7ae72b7d37701ea8039c8b38f64f5471c2c85e4"
}Testing workflow, rules, error fixes, and .mob script syntax for test generation
{
"resource_key": "mobai://reference/testing",
"uri": "mobai://reference/testing",
"name": "Testing Reference",
"description": "Testing workflow, rules, error fixes, and .mob script syntax for test generation",
"mime_type": "text/plain",
"annotations": null,
"metadata_hash": "e9c2dcdc50eefa7100ba76559caadfb09ec5fd8c5cb9b20717a7c5cae5e53341"
}No completed comparison is available.
| Risk | Change | Subject |
|---|---|---|
| No material changes recorded. | ||
| Severity | Finding | Advisory |
|---|---|---|
| No confirmed vulnerability is published for this version. | ||
Artifact SHA-256: b2f15012d8e27b625aea13b02d49554ebd5e48fd2a8379176001ab592eca151d
Scanner: mcp-proof-engine 0.1.0.