# mobai-mcp [Health: Active]

**Category:** 📂 Browser Automation  
**Repository:** https://github.com/MobAI-App/mobai-mcp  
**GitHub Stars:** 254  
**npm Downloads (last month):** 987  
**Views:** 4  
**Installs:** 0  
**Upvotes:** 0  
**Directory Page:** https://allmcps.com/mcp/mobai-mcp

## Description
AI-powered mobile device automation for Android and iOS devices, emulators, and simulators

## Tools
Capabilities this server exposes over MCP:

- **list_devices** — List 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.
- **get_device** — Get details about a specific device
- **start_bridge** — Start 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).
- **stop_bridge** — Stop the automation bridge on a device
- **claim_device** — Claim 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.
- **release_device** — Release 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.
- **get_screenshot** — Capture 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.
- **save_screenshot** — Save 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).
- **debug_app** — Launch 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.
- **list_apps** — List installed apps on the device
- **install_app** — Install 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).
- **uninstall_app** — Uninstall an app from the device
- **execute_dsl** — Three 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; add "fully_visible":true when the element is present but cut off by a toolbar or the screen edge, since a partly covered control does not reliably accept a tap. 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":"fold","state":"closed"},
  {"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":"scroll","direction":"down","to_element":{"predicate":{"text":"General"},"fully_visible":true}},
  {"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"}.
- **test_get_active** — Get 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.
- **test_list_projects** — List all known test project directories with their .mob test cases. Each project is a directory containing .mob script files.
- **test_run** — Run 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.
- **debug_attach** — Start 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.
- **debug_state** — Query 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.
- **debug_breakpoint** — Add or remove a breakpoint in the active debug session. For action=add provide spec; for action=remove provide id.
- **debug_eval** — Evaluate a Swift/ObjC expression at the current pause. Session must be paused. Examples: "p defaultPrivate", "po self.viewModel.user.email", "frame variable".
- **debug_step** — Advance 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)
- **debug_detach** — End the debug session. Pass kill=true to terminate the debuggee; otherwise it keeps running.

## Claude Desktop Quick Installation
Install path detected from listing signals. Uses `npx` (confidence: high):

```json
"mcpServers": {
  "mobai-mcp": {
    "command": "npx",
    "args": ["-y","mobai-mcp"]
  }
}
```

## Documentation

## What mobai-mcp MCP server does

The mobai-mcp MCP server exposes MobAI mobile automation through MCP. It can discover connected Android and iOS devices, start and stop the device automation bridge, manage exclusive device leases, inspect screens, and operate installed applications. Supported targets include physical devices, emulators, and simulators.

The main automation entry point is `execute_dsl`. An assistant sends a JSON script containing ordered steps rather than calling separate MCP tools for every tap or swipe. The DSL supports interaction actions such as tapping, typing, and swiping, along with waits, predicates, assertions, conditional behavior, web automation, metrics, screen recording, and observation.

## How it works

The MCP process communicates with the MobAI desktop application, which must be running locally. The desktop app provides an HTTP API at `http://127.0.0.1:8686`. A typical workflow is to list devices, start the bridge for the selected device, and pass its identifier to `execute_dsl`. Calling an interaction on an unclaimed device normally claims it automatically, although `claim_device` can be used when explicit session ownership is needed.

Before attempting device interaction, read the server resources at `mobai://reference/device-automation` and `mobai://reference/testing`. They describe the DSL schema, actions, predicates, failure strategies, and `.mob` file syntax.

Test cases are stored as `.mob` files in project directories. The assistant can discover the active project or list known projects, then run a selected case with `test_run`. Project files are read, written, and edited through filesystem tools; MCP provides discovery and execution rather than file editing.

## Setup and configuration

The mobai-mcp MCP server requires Node.js 18 or newer, the MobAI desktop app, and a connected Android or iOS device, emulator, or simulator. Install it through an MCP client's stdio configuration with `npx -y mobai-mcp`. Claude Code, Cursor, Claude Desktop, Windsurf, and Cline are documented configuration targets; other clients can use the same command if they support stdio MCP servers.

No environment variables are documented as required. If the server reports a connection refusal, verify that the MobAI desktop app is installed and running and that the local API is reachable. A bridge must be started before device interaction, and the iOS bridge may take up to a minute to become available.

## Tools and capabilities

- Discover devices and retrieve details with `list_devices` and `get_device`.
- Control bridge state and device leases with `start_bridge`, `stop_bridge`, `claim_device`, and `release_device`.
- Inspect the display with `get_screenshot` for fast, potentially downscaled analysis or `save_screenshot` for a full-quality PNG.
- List, install, remove, and debug applications with `list_apps`, `install_app`, `uninstall_app`, and `debug_app`. Installation accepts local `.apk` or `.ipa` paths.
- Run batched automation scripts with `execute_dsl`.
- Find test projects and cases with `test_get_active` and `test_list_projects`, then execute a `.mob` case with `test_run`, including optional `${name}` parameter substitution.

## Limitations and notes

The server depends on the MobAI desktop app and does not operate devices independently. It also requires a connected or running target device and an available automation bridge. `get_screenshot` favors speed over image quality and may downscale its result; it returns a scale factor. Screenshots are saved under `/tmp/mobai/screenshots/` by default, including screenshots extracted from DSL observations.

Device interaction is intended to be batched through the DSL, so scripts should include suitable predicates, waits, assertions, and failure handling. The iOS bridge startup can be slower than Android. The project documentation identifies Apache 2.0 as the license.

_Full upstream README: https://allmcps.com/mcp/mobai-mcp/readme_

