Skip to main content
Glama
run-sequence-android-mcp

run-sequence-android-mcp

by nasiosheva
README.md
# run-sequence-android-mcp

Maintained by [Nasio Sheva](https://github.com/nasiosheva). This project is a fork of android-mcp with a `run_sequence` tool. Existing Android tools remain available.
Build with `npm ci && npm run build`, then configure an MCP stdio client to run
`node /absolute/path/to/run-sequence-android-mcp/dist/index.js`.

Example tool arguments:

```json
{"device":"DEVICE_SERIAL","steps":[{"action":"tap","x":540,"y":700},{"action":"wait","durationMs":500},{"action":"key","key":"BACK"},{"action":"wait_for_element","text":"Tasks","timeoutMs":10000}]}
```

To verify an interaction, add an assertion step:

```json
{"steps":[{"action":"tap","x":540,"y":700},{"action":"assert_element","resourceId":"success_message","timeoutMs":5000},{"action":"assert_no_element","text":"Loading","timeoutMs":5000},{"action":"assert_app","packageName":"com.example.dashboard","timeoutMs":5000}]}
```

`run_sequence` also accepts `variables`, whose string values can be used as `{{name}}` in text, selectors, and `assert_app`:

```json
{"variables":{"email":"user@example.com"},"steps":[{"action":"text","text":"{{email}}"}]}
```

## Saved flows

Use `android_save_flow` once to persist a named sequence, then call `android_run_flow` with its name whenever the agent needs to repeat it. Saved flows keep default variables, which can be overridden at run time; `android_run_flow` also accepts the same optional `record` setting as `run_sequence`. `android_list_flows` shows the available flows and `android_delete_flow` removes one after an explicit `confirm: true`.

To permanently remove every saved flow and its stored variables, stop active recordings, and remove temporary MCP artifacts, call `android_clear_saved_data` with `{"confirm": true}`. Calling it without confirmation is a dry run. Only flows saved explicitly with `android_save_flow` persist across MCP sessions.

Flows are stored in `~/.android-mcp/flows.json` by default. Set `ANDROID_MCP_FLOWS_PATH` to place the flow store elsewhere.

Supported actions are `tap`, `swipe` (fromX, fromY, toX, toY, durationMs), `key` (BACK, HOME, ENTER, TAB), `wait` (durationMs), `text` (text, optional clear and submit), `wait_for_element`, `assert_element`, `assert_no_element` (selector-based), and `assert_app` (packageName, optional activity; all assertions accept timeoutMs). Consecutive input steps run in one ADB shell; waits and assertions run on the host. Non-ASCII text is pasted through the Android clipboard when the device supports its shell command.

## Lightweight test evidence

Recording defaults are tuned for quick test evidence: 480px maximum dimension, 1.5 Mbps bitrate, and a 30-second time limit. Use `record.mode: "always"` to keep a video for every run, or `"on_failure"` to keep a video only for failures. Failed recorded runs also save a screenshot, UI hierarchy XML when available, and recent logcat in the temporary MCP directory.

```json
{
  "steps": [{"action":"tap","x":540,"y":700},{"action":"assert_element","resourceId":"success_message","timeoutMs":5000}],
  "record": {"mode":"on_failure"}
}
```

To record outside a sequence, use `android_start_recording`. It accepts `maxSize`, `bitRate`, and `timeLimit`; for example `{"maxSize":480,"bitRate":1500000,"timeLimit":30}`. `android_pause_recording` finalizes the current MP4 segment and `android_resume_recording` starts the next one in the same session. Android's native `screenrecord` has no true pause support, so the session manifest lists each segment instead of pretending they are one continuous video. To create a combined file when `ffmpeg` is installed, pass `mergeTo` to `android_stop_recording`.

For organized evidence across multiple scenarios, call `android_start_test_run` first and pass its id as `record.testRunId`. `android_capture_checkpoint` saves a screenshot, UI XML when available, and logcat; `android_finish_test_run` finalizes the manifest. A sequence may alternatively set `record.testName` to create an implicit run, and `record.capture` to `"before_after"` or `"on_assertion_failure"` for automatic checkpoints.

```json
// android_start_test_run
{"name":"checkout-regression"}

// run_sequence, using the returned test-run id
{
  "steps":[{"action":"tap","x":540,"y":700},{"action":"assert_element","resourceId":"order_confirmed","timeoutMs":5000}],
  "record":{"mode":"on_failure","testRunId":"test-run-...","capture":"on_assertion_failure"}
}
```

For a manual recording session, call `android_start_recording`, `android_pause_recording`, `android_resume_recording`, then `android_stop_recording`. The final call returns every segment and the manifest; add `mergeTo` when `ffmpeg` is installed and a combined MP4 is needed.

[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
[![Node.js 18+](https://img.shields.io/badge/Node.js-18%2B-green.svg)](https://nodejs.org)
[![MCP](https://img.shields.io/badge/protocol-Model%20Context%20Protocol-8A2BE2)](https://modelcontextprotocol.io)

An MCP (Model Context Protocol) server that gives AI agents full control of Android devices and emulators through plain ADB — no companion APK, no extra daemon, no telemetry.

With this server, agents like Claude Code, Claude Desktop, or Cursor can **see** the screen (screenshots + UI hierarchy) and **act** on it (tap, swipe, type, launch apps, read logs, record video) on any device that `adb` can reach.

```
You: "Open Settings, turn on dark mode, and show me a screenshot"
Agent: launches the app, navigates by reading the UI hierarchy,
       taps the right elements, and returns a screenshot — hands-free.
```

## Why another Android MCP?

This project merges the best ideas of two excellent servers into one dependency-light TypeScript implementation:

| Inspiration | What was adopted |
|---|---|
| [mobile-mcp](https://github.com/mobile-next/mobile-mcp) | App management, uiautomator-based element listing, screenshots, screen recording, orientation control |
| [Android-MCP](https://github.com/CursorTouch/Android-MCP) | WiFi ADB + mDNS auto-discovery, selector-based taps, wait-for-element, smart default-device selection |

Differences by design:

- **ADB only.** No uiautomator2 server APK on the device, no `mobilecli` binary on the host.
- **Zero telemetry.** Nothing is phoned home, ever.
- **Android-first.** No iOS code paths to carry around.
- **Agent-friendly errors.** Failures return actionable messages that tell the agent what to try next.

## Requirements

- **Node.js 18+**
- **Android platform-tools** (`adb`) — auto-detected from `ANDROID_HOME`, `~/Library/Android/sdk` (macOS), or `%LOCALAPPDATA%\Android\Sdk` (Windows), with `PATH` as fallback
- An Android device with **USB debugging** or **wireless debugging** enabled, or a running **emulator**

## Installation

```bash
git clone https://github.com/nasiosheva/run-sequence-android-mcp.git
cd run-sequence-android-mcp
npm install
npm run build
```

### Claude Code

```bash
claude mcp add run-sequence-android -- node /path/to/run-sequence-android-mcp/dist/index.js
```

### Claude Desktop

Add to `claude_desktop_config.json`:

```json
{
  "mcpServers": {
    "android": {
      "command": "node",
      "args": ["/path/to/run-sequence-android-mcp/dist/index.js"]
    }
  }
}
```

### Cursor / other MCP clients

Any client that speaks MCP over stdio works the same way: run `node /path/to/run-sequence-android-mcp/dist/index.js` as the server command.

### Environment variables (all optional)

| Variable | Purpose |
|---|---|
| `ANDROID_MCP_DEVICE` | Default device id used when a tool call omits `device` |
| `ANDROID_MCP_ALLOW_UNSAFE_URLS` | Set to `1` to allow non-http(s) URLs (e.g. deep links) in `android_open_url` |
| `ANDROID_MCP_FLOWS_PATH` | JSON file used to persist named automation flows |
| `ANDROID_HOME` | Android SDK location, used to locate `adb` |

## Device selection

Every tool accepts an optional `device` parameter. When omitted, the server resolves the target in this order:

1. `ANDROID_MCP_DEVICE` environment variable
2. The only online device, if exactly one is connected
3. The first **physical** device (USB or WiFi preferred over emulators)

`android_list_devices` also auto-connects wireless-debugging peers advertised via mDNS (`adb mdns services`) when the device list is empty — devices on the same network appear without any manual `adb connect`.

## Capabilities — 41 tools

### Test automation

| Tool | Description | Key parameters |
|---|---|---|
| `run_sequence` | Run a batched action sequence with selectors, assertions, variables, optional recording, and checkpoint capture. | `steps`, `variables?`, `record?` |
| `android_start_test_run` | Create a per-run artifact directory and manifest. | `name?` |
| `android_capture_checkpoint` | Save screenshot, UI XML, and logcat as test evidence. | `name`, `testRunId?` |
| `android_finish_test_run` | Mark a test-run manifest passed or failed. | `testRunId`, `status` |
| `android_save_flow` | Save a named sequence for repeatable testing. | `name`, `steps`, `variables?` |
| `android_list_flows` | List saved sequences. | — |
| `android_run_flow` | Run a saved sequence with variable overrides and optional recording. | `name`, `variables?`, `record?` |
| `android_delete_flow` | Delete a saved sequence after confirmation. | `name`, `confirm: true` |
| `android_clear_saved_data` | Delete all saved flows and temporary MCP artifacts after confirmation. | `confirm: true` |

### Device management

| Tool | Description | Key parameters |
|---|---|---|
| `android_list_devices` | List connected devices with name, Android version, connection type (usb/wifi/emulator), and state. Auto-discovers mDNS wireless peers. | — |
| `android_connect_wifi` | Connect over WiFi ADB. Port defaults to 5555. | `host` |
| `android_device_info` | Model, Android version, SDK level, screen size, orientation, battery level, foreground app. | `device?` |

### App management

| Tool | Description | Key parameters |
|---|---|---|
| `android_list_apps` | List installed apps that have a launcher activity. | `device?` |
| `android_launch_app` | Launch an app by package name. | `packageName` |
| `android_terminate_app` | Force-stop a running app. | `packageName` |
| `android_install_app` | Install an APK, optionally granting all runtime permissions. | `apkPath`, `grantPermissions?` |
| `android_install_split_apps` | Install a base APK and split APK files together. | `apkPaths`, `grantPermissions?` |
| `android_clear_app_data` | Reset an app's local state after confirmation. | `packageName`, `confirm: true` |
| `android_uninstall_app` | Uninstall an app. | `packageName` |

### Screen observation

| Tool | Description | Key parameters |
|---|---|---|
| `android_take_screenshot` | Screenshot returned inline as an image the agent can see. | `device?` |
| `android_save_screenshot` | Screenshot saved to a local `.png` file. | `saveTo` |
| `android_list_elements` | UI hierarchy: element type, text, accessibility label, resource id, focus/clickable state, and center tap coordinates. | `device?` |
| `android_capture_state` | Save a screenshot and return the foreground app, UI elements, and recent logcat. | `device?`, `saveTo?` |
| `android_wait_for_element` | Poll until an element appears — use instead of fixed sleeps for dynamic content. | selector, `timeout?` |

### Interaction

| Tool | Description | Key parameters |
|---|---|---|
| `android_tap` | Tap at pixel coordinates. | `x`, `y` |
| `android_tap_element` | Find an element by selector and tap its center. Waits up to `timeout` (default 5 s) for it to appear. | selector, `index?`, `timeout?` |
| `android_double_tap` | Double-tap at coordinates. | `x`, `y` |
| `android_long_press` | Long-press at coordinates. | `x`, `y`, `duration?` |
| `android_swipe` | Directional swipe from screen center or from given coordinates. | `direction`, `x?`, `y?`, `distance?`, `duration?` |
| `android_drag` | Drag and drop between two points. | `fromX`, `fromY`, `toX`, `toY`, `duration?` |
| `android_type_text` | Type into the focused field, optionally clearing it first and/or submitting with ENTER. Non-ASCII text uses the Android clipboard when supported. | `text`, `submit?`, `clear?` |
| `android_press_key` | Press a key: `BACK`, `HOME`, `ENTER`, `APP_SWITCH`, `VOLUME_UP`, any `KEYCODE_*` name, or a numeric keycode. | `key` |
| `android_open_url` | Open an http(s) URL in the default browser. | `url` |
| `android_open_notifications` | Expand the notification shade. | `device?` |

### System

| Tool | Description | Key parameters |
|---|---|---|
| `android_set_orientation` | Force portrait/landscape (disables auto-rotate). | `orientation` |
| `android_start_recording` | Start lightweight background recording session (480px, 1.5 Mbps, 30 s defaults; max 180 s). | `timeLimit?`, `maxSize?`, `bitRate?`, `testRunId?` |
| `android_pause_recording` | Finalize the current MP4 segment without ending the session. | `device?` |
| `android_resume_recording` | Start the next MP4 segment in a paused session. | `device?`, recording options |
| `android_stop_recording` | Stop recording and return every segment plus the session manifest; can opt into `ffmpeg` merging. | `saveTo?`, `mergeTo?` |
| `android_logcat` | Read recent logs with buffer selection (`main`/`system`/`crash`/`events`/`all`) and substring filter. Great for debugging Flutter/React Native crashes. | `lines?`, `buffer?`, `filter?` |

### Element selectors

`android_tap_element` and `android_wait_for_element` accept any combination of:

| Selector | Matching |
|---|---|
| `text` | Exact visible text |
| `textContains` | Substring of visible text, case-insensitive |
| `textRegex` | Visible text matching a JavaScript regular expression |
| `resourceId` | Full id (`com.app:id/btn_login`) or short id (`btn_login`, auto-expanded using the foreground app package) |
| `contentDesc` | Substring of accessibility label, case-insensitive |
| `contentDescExact` | Exact accessibility label / content description |
| `className` | Full class (`android.widget.Button`) or suffix (`Button`) |
| `clickable`, `enabled`, `focused`, `checked` | Exact element state |

## Usage examples

Prompts you can give an agent once the server is connected:

- *"List my devices and take a screenshot of the current screen."*
- *"Open the Settings app and toggle dark mode."*
- *"Install ~/Downloads/app-release.apk with all permissions granted, launch it, and check logcat for errors."*
- *"Fill in the login form: tap the field with resource id `email`, type `user@example.com`, then tap the Login button."*
- *"Record the screen while you walk through the onboarding flow, then save the video to my desktop."*
- *"My Flutter app crashed — read the crash buffer and tell me why."*

## How it works (spec)

```
┌──────────────┐   stdio (JSON-RPC / MCP)   ┌─────────────┐   adb CLI   ┌─────────────┐
│  MCP client   │ ◄────────────────────────► │ android-mcp │ ◄─────────► │   device /  │
│ (Claude, ...) │                            │  (Node.js)  │             │   emulator  │
└──────────────┘                            └─────────────┘             └─────────────┘
```

- **Transport:** stdio, stateless — one server process per client session.
- **Screenshots:** `adb exec-out screencap -p`, returned as PNG (inline base64 image or file).
- **UI hierarchy:** `adb exec-out uiautomator dump /dev/tty`, parsed with `fast-xml-parser`, retried up to 10× when the bridge returns a null root. Elements with no size or no useful text/id are filtered out.
- **Input:** `adb shell input` (tap/swipe/text/keyevent/draganddrop). ASCII text uses `input text`; non-ASCII text is set with `cmd clipboard set` and pasted with `KEYCODE_PASTE` when supported by the device.
- **Recording:** `adb shell screenrecord` is spawned in the background at 480px/1.5 Mbps by default. Stop sends `SIGINT` to the on-device process (`killall -2 screenrecord`), waits for the file to finalize, then `adb pull`s it. Pause/resume is implemented as finalized MP4 segments recorded in a session manifest, because Android does not provide native pause/resume. `run_sequence` can control recording and checkpoint capture automatically.
- **Foreground app detection:** `dumpsys activity activities` (`ResumedActivity`, covering both pre- and post-Android-13 formats) with `dumpsys window` (`mFocusedApp`) as fallback.
- **Safety rails:** package names validated against `[a-zA-Z0-9_.]`, output paths must be absolute with allowed extensions, URLs restricted to http(s) unless explicitly overridden.

### Project layout

```
src/
├── index.ts    entry point — stdio transport
├── server.ts   MCP server + 41 tool registrations
├── adb.ts      adb discovery/execution, device resolution, WiFi + mDNS connect
└── ui.ts       uiautomator dump parsing, selector matching, wait-for-element
```

## Development

```bash
npm run watch      # rebuild on change
npm run inspector  # test tools interactively with MCP Inspector
npm test           # lifecycle regression tests without a connected Android device
```

## Troubleshooting

| Symptom | Fix |
|---|---|
| `adb not found` | Install Android platform-tools and/or set `ANDROID_HOME`. |
| `No Android devices connected` | Enable USB debugging (or wireless debugging), accept the RSA prompt on the device, check `adb devices`. |
| Screenshot is black | The screen is off — send `android_press_key` with `WAKEUP` first. |
| `Failed to dump UI hierarchy` | The foreground screen is secure (password field, DRM). Use `android_take_screenshot` instead. |
| Non-ASCII text fails | The device does not support the Android clipboard shell command. Use its keyboard or a compatible Android version. |
| WiFi connect fails | Ensure wireless debugging or `adb tcpip 5555` is active and the host is reachable. |

## Contributing

Issues and pull requests are welcome. Keep changes small and focused:

1. Fork and create a feature branch.
2. `npm run build` must pass with no TypeScript errors.
3. Verify against a real device or emulator where possible (MCP Inspector makes this easy).
4. Describe the behavior change in the PR.

## License

[MIT](LICENSE). This fork is maintained by Nasio Sheva ([@nasiosheva](https://github.com/nasiosheva)); the original copyright notice is retained in the license.

TDQS

B3.4/5.0

Scored across 27 tools

Disambiguation4/5

Most tools target a distinct action on a distinct resource, and descriptions explicitly cross-reference each other (tap → list_elements, tap_element preference over tap). Minor overlap exists between android_tap and android_tap_element, and between android_swipe and android_drag, but the descriptions clarify when to use which. run_sequence nominally overlaps all input tools, though it is clearly framed as a batching orchestrator.

Naming Consistency4/5

Almost the whole surface follows an android_verb_noun snake_case pattern (android_list_elements, android_launch_app, android_take_screenshot). The only outlier is run_sequence, which drops the android_ prefix and uses a different verb style. That single deviation keeps it from a 5 but the convention is otherwise highly predictable.

Tool Count3/5

27 tools is above the comfortable range, but the domain (device connection, app lifecycle, input, capture, debugging) is genuinely broad and each tool maps to a real capability. Still, some are borderline granular (android_take_screenshot vs android_save_screenshot, android_double_tap/long_press vs coordinate taps) and could be consolidated.

Completeness4/5

The surface covers device discovery/connection, app install/uninstall/launch/terminate, rich input, screenshots and video recording, notifications, orientation, and logcat — no major dead ends for UI automation workflows. Gaps remain for file push/pull, clipboard access, arbitrary shell commands, and multi-touch gestures like pinch/zoom.

Maintenance

ActivityMaintained
ResponsivenessNo recent community issues