Manos MCP
Provides tools for controlling Android emulators and physical devices via ADB, enabling UI testing actions such as launching apps, tapping, inputting text, inspecting screen hierarchy, and capturing network traffic.
Provides tools for controlling iOS simulators via xcrun simctl and idb, enabling UI testing actions such as launching apps, tapping, inputting text, inspecting screen hierarchy, and capturing network traffic.
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@Manos MCP tap the login button and inspect the screen"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
Manos
A CLI Model Context Protocol server for ad-hoc UI testing of Android & iOS apps — purpose-built for the exploratory, test-free loop where an LLM agent pokes at an app and reacts to what it sees.
It controls Android emulators/devices (adb) and iOS simulators (xcrun simctl, with idb for native UI interaction) and exposes 39 tools over stdio.
manos gives an agent a tight act → observe loop, device-condition control, crash/log capture, network capture for debug builds, OCR targeting for off-tree elements, an accessibility audit, and session recording that promotes an ad-hoc exploration into a replayable regression test in one call. See IMPROVEMENTS.md for the design rationale and roadmap.
Install
npm install -g manos-mcp # install the CLI, or run on demand with: npx -y manos-mcp
manos doctor # check toolchain + list devices & capabilitiesRequires Node 20+. Most MCP clients can launch manos on demand with npx — no global install needed; see Register with an MCP client. Working on manos itself? See From source.
Backend | Used for | Install |
| all Android control | Android platform-tools (auto-detected from |
| iOS lifecycle, conditions, logs, screenshots, push |
|
| fast native iOS UI inspect/tap/type |
|
| under the hood: |
iOS UI interaction works without idb by falling back to Maestro (slower; call launch_app first). Android UI inspection falls back from uiautomator dump to maestro hierarchy automatically when the on-device UiAutomation connection is contended.
Related MCP server: MCP Android Agent
Register with an MCP client
Each tab launches manos with npx -y manos-mcp serve, which fetches and runs the published package on demand — no clone or global install required. (If you installed globally with npm install -g manos-mcp, use a bare manos serve instead.)
claude mcp add manos -- npx -y manos-mcp serve
claude mcp list # verifyEdit claude_desktop_config.json (macOS ~/Library/Application Support/Claude/, Windows %APPDATA%\Claude\) and restart the app:
{
"mcpServers": {
"manos": { "command": "npx", "args": ["-y", "manos-mcp", "serve"] }
}
}.cursor/mcp.json (project) or ~/.cursor/mcp.json (global), then enable manos under Settings → MCP:
{
"mcpServers": {
"manos": { "command": "npx", "args": ["-y", "manos-mcp", "serve"] }
}
}.vscode/mcp.json — note the servers key and explicit type:
{
"servers": {
"manos": { "type": "stdio", "command": "npx", "args": ["-y", "manos-mcp", "serve"] }
}
}~/.codeium/windsurf/mcp_config.json, then hit Refresh in the Windsurf MCP panel:
{
"mcpServers": {
"manos": { "command": "npx", "args": ["-y", "manos-mcp", "serve"] }
}
}Any MCP-capable client speaks the same stdio protocol. Configure a server that runs:
command: npx
args: ["-y", "manos-mcp", "serve"]
transport: stdioFrom source
For working on manos itself, or to pin a local build instead of the published package:
git clone https://github.com/ryanperkins/Manos-MCP.git && cd Manos-MCP
npm install # also builds via the prepare script
npm run build # or rebuild after changes
node dist/cli.js doctorRegister the local build by pointing your client's command/args at node /ABS/PATH/to/Manos-MCP/dist/cli.js serve instead of the npx form above.
CLI
manos serve Start the MCP server on stdio (default)
manos doctor Toolchain + connected devices + per-device capabilities
manos devices List connected devices (tab-separated)
manos --helpThe tools
Full reference and the Android vs iOS comparison matrix are in docs/index.html. Highlights:
Core:
list_devices,device_capabilities,inspect_screen,take_screenshot.Authored flows:
run_flowruns a declarative flow locally;cheat_sheetgives the syntax.export_flow(below) turns a recorded session into one.Act + observe:
tap,long_press,input_text,press_key,swipe— each takes a selector (id/text/resource_id/accessibility) or coordinates and returns the resulting screen (observe: screen | diff | screenshot | none).Smart waits / assertions / search:
wait_for,assert,find_elements, andfind_text(OCR the screenshot to locate text the accessibility tree misses — styled buttons, canvas/Flutter/game UIs, WebViews). Targeting falls back to OCR automatically when a text selector finds nothing in the tree, or force it withtap{text, ocr:true}.App state:
launch_app,stop_app,clear_app_state,open_deeplink,set_permission.Device conditions:
set_appearance,set_orientation,set_locale,set_network,set_location,set_font_scale,set_status_bar,push_notification, andset_conditions(apply many at once / named presets likeoffline,accessibility,screenshot).Diagnostics:
get_logs(with crash/ANR detection),a11y_audit.Network capture (debug apps):
network_start/network_requests/network_clear/network_stop— capture decrypted HTTP filtered to specific endpoints. Android hooks OkHttp via Frida (works through HTTP/2, pinning, proxy-bypass); iOS Simulator uses mitmproxy + asimctl-trusted CA. See NETWORK.md.Network mocking (Android):
network_mock— manipulate live API responses to test hard-to-reproduce states: overridestatus/headers, replace thebody, regex-rewritea field in the live body (keep the rest), or inject latency. Rule-based (URL regex + method), hot-reloads, no separate mock server or build change. Rides the Frida/OkHttp capture hook (per-process, no host-proxy disruption —network_startfirst). iOS response mocking is in development. See NETWORK.md.Recording:
start_recording→ act →export_flow(replayable Maestro flow) orexport_report(self-contained HTML report: screenshot timeline + flow + logs + captured network).
A typical loop:
list_devices → inspect_screen → tap{text:"Login", observe:"diff"} → input_text{...} → wait_for{text:"Welcome"}How element targeting works
inspect_screen returns a compact tree where every node has a stable id derived from its semantic identity (resource-id / accessibility / class + digit-normalized text), not its position. So a counter ticking from 5 to 6 keeps its id and shows up as a changed node in a diff, while a newly-appeared element is added. Act tools accept that id, a text/resource-id selector, or raw coordinates; when you use a selector, the recorded flow stores the selector (resilient replay) rather than brittle coordinates.
Performance
Hierarchy reads use a three-tier backend, chosen per device:
adb
uiautomator dump(~2.5s, no extra process) — the default on Android. Tried first.Warm hierarchy engine — when uiautomator can't reach UI-idle (e.g. apps with constant animations/watermarks, where
uiautomator dumperrors withcould not get idle state), manos keeps one long-livedmaestro mcpchild resident (used under the hood) and reuses its connected driver. First call pays a one-time warm-up; subsequent inspects are ~150–300ms — the payoff of reusing a resident engine instead of cold-starting the JVM per call.Cold
maestro hierarchyCLI — last resort if the warm session can't start.
Once a device needs the warm session, manos remembers it (per-device) so it doesn't re-pay the uiautomator timeout on every inspect. Screen size/density are cached. The warm child (and its simulator-server) are killed via a process-tree cleanup on exit.
Measured on a hard case (an app that never idles, so everything routes through the warm session):
First inspect (one-time) | Steady-state inspect (median) | Full tap+observe loop | |
manos | ~11s | ~175ms | ~3s (was ~35s with cold per-call CLI) |
So: on apps where uiautomator works, the adb path is fast with no extra process; on apps that force the fallback, the resident warm engine keeps steady-state inspect in the ~175ms range, and the act+observe loop avoids the per-action JVM cold-start that made it slow before. The remaining first-call cost is the one-time uiautomator probe before switching to warm.
Architecture
src/
cli.ts serve / doctor / devices
server.ts McpServer wiring (stdio)
tools/
register.ts all 39 tools
context.ts shared state: resolveTarget + act/observe + last-screen cache
drivers/
types.ts Driver contract + Capability model
android.ts adb
ios.ts simctl + idb (+ maestro fallback)
registry.ts device → driver routing
core/
hierarchy.ts compact JSON, stable ids, screen diff, search
a11y.ts accessibility heuristics
waits.ts condition polling
session.ts action journal
flow.ts Maestro-flow emitter
maestro.ts maestro CLI passthrough (run_flow, cheat sheet)
maestroDriver.ts cold maestro hierarchy + one-shot action fallbacks
maestroSession.ts warm long-lived `maestro mcp` backend (fast hierarchy/actions)
netcapture.ts network capture (Frida OkHttp / mitmproxy) — see NETWORK.md
ocr.ts OCR fallback (Apple Vision / Tesseract) for off-tree elements
assets/frida/ okhttp-capture.js + sidecar.py (injected by netcapture)
util/ exec + toolchain discoveryEach driver method may throw a CapabilityError; tools surface it as an actionable message instead of an opaque subprocess failure. The capability model is reported live so an agent can check support before relying on a platform-specific action.
Test
npm test # unit tests for hierarchy/diff/a11y/flow (no device needed)The hierarchy parsing, stable-id diffing, accessibility math, screenshot capture, log/crash scan, and the recording→export_flow→maestro check-syntax pipeline have also been verified end-to-end against a live Android emulator.
License
MIT
Available Tools
40 toolsa11y_auditAccessibility auditA
Audit the current screen for accessibility issues: undersized touch targets, unlabeled interactive controls, and duplicate labels. (Color-contrast needs pixels and is not evaluated.)
Inputs: device_id (required).
assertAssertA
Assert an element is visible or not visible on the current screen. Returns pass/fail without polling (use wait_for to wait).
Inputs: device_id (required), id, text, index, +3 more.
cheat_sheetMaestro cheat sheetA
Return Maestro flow syntax guidance and best practices for writing/exporting flows.
Inputs: no named parameters.
clear_app_stateClear app stateA
Reset app state. Android: full data wipe (pm clear). iOS: resets permissions (full data wipe needs reinstall).
Inputs: app_id (required), device_id (required).
device_capabilitiesDevice capabilitiesA
Report which actions are supported on a device (full/partial/unavailable) with the backend and any caveats. Check this before relying on a platform-specific action.
Inputs: device_id (required).
export_flowExport flowA
Export the recorded session as a replayable Maestro flow (YAML) plus a human-readable report. Run it later with run_flow.
Inputs: no named parameters.
export_reportExport session report (HTML)A
Write a self-contained HTML report of the recorded session — a step timeline with screenshots (if report capture was on) plus an appendix with the replayable flow, captured network requests, and recent logs/crashes. Returns the file path. Best with start_recording(report=true).
Inputs: output_path, include_logs, include_network.
find_elementsFind elementsA
Search the current screen for elements matching a query. Returns matching elements (id, bounds, text, etc.) — handy to disambiguate before acting.
Inputs: device_id (required), id, text, index, +3 more.
find_textFind text (OCR)A
OCR the current screenshot and return on-screen text with pixel bounding boxes — finds elements the accessibility tree misses (styled buttons, canvas/Flutter/game UIs, WebViews). Optionally filter by a query. Tap a result with tap{text, ocr:true}.
Inputs: device_id (required), query, min_confidence.
get_logsGet device logsA
Fetch recent device logs (Android logcat / iOS unified log) with automatic crash & ANR detection. Filter by app_id, substring, time window, and line cap.
Inputs: device_id (required), app_id, filter, max_lines, +1 more.
input_textInput textA
Type text into the focused field. Optionally target a field first (id/text/resource_id) to tap it before typing.
Inputs: text (required), device_id (required), id, index, +4 more.
inspect_screenInspect screenA
Get the current screen's view hierarchy as compact JSON with stable element ids. Use the 'id' field with act tools (tap/input_text/...), or target by text/resource-id. Copy text verbatim — never retype from a screenshot.
Inputs: device_id (required).
launch_appLaunch appA
Launch an app by bundle id (iOS) / package name (Android). Optionally clear state first.
Inputs: app_id (required), device_id (required), observe, clear_state.
list_devicesList devicesA
List all available local devices (Android emulators/devices via adb, iOS simulators via simctl). Returns id, platform, name, OS version, and state.
Inputs: no named parameters.
long_pressLong pressA
Long-press an element or coordinates for a duration (ms).
Inputs: device_id (required), x, y, id, +7 more.
network_clearClear captured requestsA
Clear the captured-request buffer without stopping the capture.
Inputs: device_id (required).
network_mockMock network responsesA
Override API responses to test hard-to-reproduce states (Android). Each rule matches requests by URL regex (+ optional method) and can override the status/headers, replace the body, regex-rewrite the live body (change one field, keep the rest), or inject latency. First match wins; rules hot-reload; capture keeps logging (mocked exchanges flagged). Android (Frida/OkHttp): call network_start with app_id first — mocking rides the capture hook (abort not yet supported). iOS response mocking is in development (network_start still captures). replace:true (default) replaces all rules; an empty list clears mocking.
Inputs: device_id (required), rules, replace.
network_requestsGet captured requestsA
Return captured HTTP exchanges (method, url, status). Compact by default; set include_headers/include_body for detail. Filter by URL regex and limit count to avoid flooding.
Inputs: device_id (required), limit, filter, since_ts, +2 more.
network_startStart network captureA
Capture decrypted HTTP, filtered to specific endpoints. Android: hooks OkHttp via Frida in a debuggable app (app_id required; needs frida-server). iOS simulator: routes the sim through mitmproxy with a simctl-trusted CA (app_id optional; temporarily sets the macOS proxy, restored on stop). filter is a URL regex (e.g. 'v2/search').
Inputs: device_id (required), spawn, app_id, filter.
network_stopStop network captureA
Stop capturing (detach Frida / stop mitmproxy and restore the macOS proxy).
Inputs: device_id (required).
open_deeplinkOpen deep linkB
Open a deep link / universal link URL on the device.
Inputs: url (required), device_id (required), observe.
press_keyPress keyA
Press a hardware/system key. Android: back, home, enter, tab, delete, search, menu, volume_up, volume_down, app_switch, power. iOS: enter, delete, tab, space, escape, home, lock, siri.
Inputs: key (required), device_id (required), observe.
push_notificationPush notificationA
Deliver a push notification. iOS: injects an APNs payload via simctl push. Android: unavailable (needs FCM server key + token).
Inputs: app_id (required), payload (required), device_id (required).
run_flowRun Maestro flowA
Run a Maestro flow (delegates to the maestro CLI). Provide exactly one of: yaml (inline), files (paths), or dir. Optional include_tags/exclude_tags (for dir) and env vars.
Inputs: dir, env, yaml, files, +3 more.
set_appearanceSet appearanceB
Switch the system appearance between light and dark mode.
Inputs: device_id (required), appearance (required).
set_conditionsSet device conditions (preset)A
Apply multiple device conditions in one call — the edge-case states where bugs hide. Use a named preset and/or explicit fields (explicit overrides the preset). Each condition reports applied/skipped/failed (unsupported ones are skipped with a reason, not a failure). Presets: reset (Restore defaults: light mode, 1.0 text, online.); screenshot (Clean marketing state: light mode + a pristine 9:41 status bar.); accessibility (Accessibility stress: largest text + dark mode.); offline (No connectivity (airplane mode / radios off).); dark (Dark appearance.); international (Foreign locale (defaults to fr-FR; override with locale). Needs app_id.).
Inputs: device_id (required), app_id, locale, preset, +5 more.
set_font_scaleSet font scaleA
Set text size for accessibility testing. scale 1.0 = default. Android sets font_scale directly; iOS maps to the nearest Dynamic Type size.
Inputs: scale (required), device_id (required).
set_localeSet localeA
Set app locale (BCP-47, e.g. 'fr-FR'). Android: per-app, requires API 33+. iOS: applied by relaunching the app. Provide app_id.
Inputs: locale (required), device_id (required), app_id.
set_locationSet GPS locationA
Set the simulated GPS location. Android: emulator only. iOS: any simulator.
Inputs: latitude (required), device_id (required), longitude (required).
set_networkSet network conditionA
Toggle wifi/cellular/airplane mode. Android only (emulator/device); iOS simulators share the host network (use Network Link Conditioner).
Inputs: device_id (required), wifi, cellular, airplane_mode.
set_orientationSet orientationA
Set device orientation (portrait, landscape-left, landscape-right, upside-down). Android only; iOS simulators must be rotated via the Simulator menu.
Inputs: device_id (required), orientation (required).
set_permissionSet permissionA
Grant or revoke a runtime permission for an app. Aliases: camera, microphone, location, contacts, photos, notifications, calendar, etc.
Inputs: app_id (required), device_id (required), permission (required), grant.
set_status_barOverride status barA
Override the status bar (time, battery, signal) for clean screenshots. iOS: rich (simctl). Android: clock/battery/signal via SystemUI demo mode.
Inputs: device_id (required), time, wifi_bars, battery_level, +3 more.
start_recordingStart recordingA
Begin recording subsequent act tools as a session that can be exported to a replayable Maestro flow (export_flow) or an annotated HTML report (export_report). Set report=true to also screenshot after each action for the report.
Inputs: app_id, report.
stop_appStop appC
Force-stop / terminate an app.
Inputs: app_id (required), device_id (required).
stop_recordingStop recordingA
Stop recording the current session (keeps recorded steps for export_flow).
Inputs: no named parameters.
swipeSwipeA
Swipe by direction (up/down/left/right) or between two coordinate points. Direction swipes are centered on the screen.
Inputs: device_id (required), x1, x2, y1, +4 more.
take_screenshotTake screenshotA
Capture a PNG screenshot of the current device screen.
Inputs: device_id (required).
tapTapA
Tap an element (by id/text/resource_id/accessibility) or absolute coordinates. Returns the resulting screen state (act+observe) so you don't need a separate inspect call.
Inputs: device_id (required), x, y, id, +6 more.
wait_forWait forB
Poll the screen until an element becomes visible or not_visible, or timeout. Replaces fixed sleeps — fast when ready, patient when not.
Inputs: device_id (required), id, text, index, +5 more.
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
Loading tool schema changelog…
TDQS
Scored across 40 tools
Each tool targets a distinct action or resource with clear boundaries (assert vs wait_for, find_elements vs find_text vs inspect_screen, network_start vs network_mock). Overlapping purposes are differentiated by polling vs non-polling, OCR vs accessibility tree, and capture vs mock.
Most tools follow a verb_noun pattern, but the network_* group reverses it (network_start, network_stop, network_mock, network_clear), and several noun-style names (device_capabilities, cheat_sheet, a11y_audit) break the convention. This mix is readable but not uniform.
With 40 tools, the server is well above the 25+ 'too many' threshold. While each tool has a distinct testing purpose, the count is unwieldy for a single server and could be split into focused subdomains (device, network, recording, etc.).
The tool set covers the core mobile testing lifecycle: app launch/stop, UI interaction and inspection, assertions/waits, device conditions, network capture/mocking, flow recording/export, logs, and accessibility. Minor gaps like missing install/uninstall and explicit device selection are workarounds, not dead ends.
Maintenance
Related MCP Connectors
MCP server for building and testing AI agents with multi-model experimentation and insights.
Let AI agents control Android and iOS devices on Mobilerun Cloud: screen, input, apps, workflows.
A comprehensive Model Context Protocol (MCP) server that enables AI assistants to interact with yo…
Related MCP Servers
- AlicenseBqualityBmaintenanceA Model Context Protocol server that enables scalable mobile automation through a platform-agnostic interface for iOS and Android devices, allowing agents and LLMs to interact with mobile applications using accessibility snapshots or coordinate-based interactions.3533,974 npm10,383Apache 2.0
- AlicenseNot gradedqualityDmaintenanceA Model Context Protocol server that enables AI agents to control and automate Android devices through natural language, supporting actions like app management, UI interactions, and device monitoring.59MIT
- AlicenseAqualityDmaintenanceA Model Context Protocol server that enables scalable mobile automation for iOS and Android through a platform-agnostic interface, allowing LLMs to interact with mobile applications via accessibility snapshots or screenshot-based inputs.1933,974 npm2Apache 2.0
- AlicenseNot gradedqualityDmaintenanceA MCP server that enables LLMs to control Android devices via ADB, supporting input, UI hierarchy, device management, and shell commands.16MIT