{"id":"5d70122e-dc9b-4f3c-8fc3-92ced8d7b465","slug":"ef-code-android-agent-mcp","kind":"mcp","name":"android-agent-mcp","summary":"Secure MCP server that enables AI agents to observe and control an authorized Android device using ADB, UIAutomator, and optional scrcpy live view.","owner":"EF-Code","repoUrl":"https://github.com/EF-Code/android-agent-mcp","license":"MIT","licenseTier":"permissive","trust":"community","stars":3,"match":null,"fewStars":true,"format":"mcp","createdAt":"2026-08-13T04:42:32.000Z","addedAt":"2026-10-21T17:54:00.699Z","logoUrl":"https://avatars.githubusercontent.com/u/111162892?u=19417af84341d165f0c3318b88837660ef629ca1&v=4","linkOnly":false,"collection":null,"skillPath":null,"categories":["cloud-infra"],"tags":[],"runtime":"node","local":true,"cloudCapable":false,"cloudCapability":"unknown","cloudReadyOnPlatform":false,"needsApiKey":false,"reviewed":false,"syncedAt":"2026-10-25T19:35:09.458Z","sample":false,"url":"https://conduits.dev/tools/ef-code-android-agent-mcp","language":"TypeScript","transports":["stdio"],"install":{"npm":"android-agent-mcp"},"requirements":{"env":[],"upstreamMayCharge":false},"tools":null,"readme":"# Android Agent MCP (`android-agent-mcp`)\n\nAndroid Agent MCP is a local MCP server that enables AI agents to control an explicitly authorized Android phone over ADB, with a visible server-owned scrcpy mirror. It exposes screenshots, UIAutomator state, semantic interaction, package diagnostics, bounded logcat, evidence recording, and reversible phone controls. scrcpy is the live-view subsystem, not the product's control plane.\n\nThe server does not call a language model and does not bypass Android locks, authentication, Play Integrity, DRM, root detection, permission prompts, or enterprise policy.\n\n## Status\n\nImplemented and locally verified:\n\n- TypeScript build and strict type-checking\n- Automated unit, integration, protocol, and scrcpy tests, plus a separate opt-in physical-device smoke test\n- ADB/scrcpy adapters with injectable command runners\n- Device discovery, explicit selection, screenshots, UIAutomator parsing, semantic selectors, input, app inspection, logcat, scrcpy ownership, and evidence sessions\n- Persistent scrcpy control and JPEG frame channels with safe ADB fallback, session-pinned geometry, bounded foreground probes, phase telemetry, and stateful action-observation sessions\n- Path-restricted APK installation and approval-gated mutations\n\nPhysical-device validation is opt-in and remains a separate gate. The full MCP stdio path has been validated on an authorized Samsung SM-A075F: discovery, selection, device information, visible scrcpy, app launch, screenshot image content, UI dump/find/tap, bounded logcat, evidence completion, and owned-mirror shutdown.\n\n## Requirements\n\n- Linux desktop is the supported initial platform; Manjaro/Arch is the primary tested distribution.\n- Node.js 22 or newer.\n- Android platform-tools (`adb`).\n- `scrcpy` is required by the default visible-mirror workflow; FFmpeg enables the persistent low-latency JPEG stream. Both degrade safely to ADB capture/control when unavailable. Set `ANDROID_AGENT_MCP_MIRROR_AUTO_START=false` for headless operation.\n- A phone with Developer Options and USB debugging enabled, with this host’s RSA key accepted.\n\nThe server does not install ADB or scrcpy. Verify the host tools without changing system state:\n\n```zsh\nadb version\nscrcpy --version\nadb devices -l\n```\n\nThe repository also provides a read-only environment check:\n\n```zsh\nnpm run check:environment\n# Add -- --require-scrcpy when mirroring is required.\n```\n\nIt never installs packages, changes udev rules, restarts ADB, or uses elevated privileges.\n\n## Install from a checkout\n\n```zsh\ngit clone https://github.com/EF-Code/android-agent-mcp.git\ncd android-agent-mcp\nnpm run install:guided\nnpm run verify\n```\n\n`npm run install:guided` runs the locked local dependency install, builds the server, detects supported MCP hosts, and writes their native local stdio configuration. The compatibility alias `npm run install:local` remains available. It does not install system packages, change udev rules, restart ADB, or use elevated privileges. Use `npm run install:guided -- --skip-dependencies` when dependencies are already installed; add `--check-environment` to run the read-only ADB/scrcpy preflight during installation.\n\nThe same guided setup can be run after dependencies are installed:\n\n```zsh\nnpm run setup\nnode dist/index.js setup --client codex\nnode dist/index.js doctor\n```\n\nSee [the installer guide](docs/installation.md) for supported hosts, native configuration locations, backups, preview mode, and generic configuration output.\n\nThe executable is then:\n\n```text\n/absolute/path/to/android-agent-mcp/dist/index.js\n```\n\nRun it directly over stdio:\n\n```zsh\nnode /absolute/path/to/android-agent-mcp/dist/index.js\n```\n\n## Install from npm\n\nThe easiest path for a published release is:\n\n```zsh\nnpx -y android-agent-mcp@0.5.1 setup\n```\n\nThis downloads the package, detects supported hosts, and configures only the hosts whose commands are available. No repository checkout is required. For a reusable global installation:\n\n```zsh\nnpm install --global android-agent-mcp@0.5.1\nandroid-agent-mcp setup\n```\n\nThe package also exposes `android-agent-mcp doctor` and `android-agent-mcp setup --dry-run`. The executable is a stdio server during normal MCP operation; `setup` and `doctor` are the installer subcommands.\n\nAlternatively, let an MCP client download a pinned release when it starts the server. Configure the command as `npx` with these arguments:\n\n```json\n{\n  \"command\": \"npx\",\n  \"args\": [\"--yes\", \"android-agent-mcp@0.5.1\"]\n}\n```\n\nInstalling from npm provides the MCP server and its Node.js dependencies. It does not install host prerequisites such as `adb` or `scrcpy`; install those with your operating system's documented Android platform-tools and scrcpy packages first. The package runs locally on the computer connected to the authorized phone.\n\nThe process reads MCP messages from stdin and writes MCP messages to stdout. Diagnostics go to stderr.\n\n## Codex registration\n\nResolve the checkout path first, then register the local stdio server:\n\n```zsh\nPROJECT_DIR=\"$(pwd)\"\ncodex mcp add android-device -- node \"$PROJECT_DIR/dist/index.js\"\ncodex mcp list\n```\n\nThe [official OpenAI MCP guidance](https://learn.chatgpt.com/docs/extend/mcp) documents local stdio servers and the same `codex mcp add <name> -- <command>` shape. Restart Codex after registration so the server and its tools are loaded. A project-scoped `.codex/config.toml` can also be used for trusted projects.\n\n## Configuration\n\nConfiguration is optional. Without a file, the server enables broad non-sensitive phone control and a visible scrcpy mirror. Select a JSON file with `ANDROID_AGENT_MCP_CONFIG`:\n\n```zsh\nANDROID_AGENT_MCP_CONFIG=/absolute/path/to/android-agent-mcp.json \\\n  node /absolute/path/to/android-agent-mcp/dist/index.js\n```\n\nExample:\n\n```json\n{\n  \"adbPath\": \"adb\",\n  \"scrcpyPath\": \"scrcpy\",\n  \"autoSelectSingleDevice\": true,\n  \"allowedPackages\": [\"*\"],\n  \"sensitivePackages\": [\"*.bank.*\", \"*.wallet.*\", \"*.password*\"],\n  \"allowedRuntimePermissions\": [],\n  \"allowedApkRoots\": [\"/home/user/projects\"],\n  \"evidenceRoot\": \"/home/user/android-agent-mcp-evidence\",\n  \"maxScreenshotBytes\": 25000000,\n  \"maxApkBytes\": 500000000,\n  \"maxLogBytes\": 2000000,\n  \"maxCommandOutputBytes\": 4000000,\n  \"maxEvidenceBytes\": 100000000,\n  \"maxEvidenceFiles\": 500,\n  \"evidenceRetentionMaxAgeMs\": 604800000,\n  \"defaultTimeoutMs\": 15000,\n  \"uiSnapshotMaxAgeMs\": 3000,\n  \"displayGeometryMaxAgeMs\": 10000,\n  \"approvalMode\": \"prompt\",\n  \"mirror\": {\n    \"autoStart\": true,\n    \"maxSize\": 1600,\n    \"maxFps\": 30,\n    \"audio\": false\n  }\n}\n```\n\nEnvironment overrides use the `ANDROID_AGENT_MCP_` prefix. The former `ANDROID_MCP_` and `ANDROID_DEVICE_MCP_` variables remain accepted as deprecated compatibility aliases, with canonical variables taking precedence. Lists are comma-separated. Configuration never contains a phone PIN, password, account credential, API key, cookie, or authorization token.\n\nThe default `allowedPackages` policy is `*`, so the agent does not require manual app selection. Banking, wallet, and password-style package patterns remain blocked by default; set `ANDROID_AGENT_MCP_SENSITIVE_PACKAGES=''` only for a deliberately trusted session that needs literal all-app control.\n\n## Preferred operating loop\n\nVisual control sessions follow the same frame → structured action → ADB → next-frame pattern as [Google's Android Computer Use quickstart](https://github.com/google-gemini/gemini-android-computer-use-quickstart), while remaining model-neutral and retaining this server's device, package, approval, and evidence boundaries.\n\n1. Call `device_list`.\n2. Call `device_select` when selection is not unambiguous.\n3. Call `device_info` and confirm the target.\n4. For semantic controls, use `ui_dump`/`ui_find` followed by `ui_tap`.\n5. For games, canvases, video, and other visual surfaces, start one `visual_control_start` session. It returns the initial frame, prefers fast JPEG capture with automatic PNG fallback, and defaults to Google-style normalized `0-999` coordinates.\n6. Use `visual_control_action` for every visual step. It batches a complete move, executes it without UI hierarchy verification, and returns the next frame in the same response. Do not call `screen_capture` between visual actions. Prefer one swipe for drag-based boards. Keep wait options at zero unless the app needs a bounded transition or opponent-response window.\n7. General packages are available by default; honor configured sensitive-package blocks.\n8. Mutating tools remain fail-closed unless the host explicitly uses `approvalMode: \"allow\"`; keep Codex write approval enabled as an additional client control.\n9. Selecting a device makes one best-effort attempt to start the visible scrcpy mirror unless `mirror.autoStart` is disabled. A scrcpy failure is returned as a warning and never blocks ADB tools. After `mirror_stop`, use `mirror_start` to reopen it explicitly.\n10. Use `evidence_begin`, explicitly capture the desired artifacts, then `evidence_finish`.\n\nFor a two-tap visual move inside a session, prefer one call rather than two model round trips:\n\n```json\n{\n  \"session_id\": \"session-id-from-visual_control_start\",\n  \"actions\": [\n    { \"type\": \"tap\", \"x\": 375, \"y\": 700 },\n    { \"type\": \"tap\", \"x\": 458, \"y\": 700 }\n  ],\n  \"coordinate_space\": \"normalized_1000\",\n  \"wait_for_change_ms\": 0\n}\n```\n\nThe visual session accepts taps, swipes, and allowlisted non-sensitive keys. With scrcpy and FFmpeg available it keeps persistent control and JPEG channels warm; otherwise it uses the existing foreground-guarded ADB action/capture path. It checks foreground policy before direct input and before returning a streamed frame. Results identify both transports as `scrcpy` or `adb` alongside phase timings. Stop it with `visual_control_stop` when finished. Use `screen_input_sequence` for one-off native-pixel batches when a persistent visual loop is unnecessary. Run `npm run benchmark:visual -- --iterations 10 --actions 1` to measure a chess-like single-action path separately from model latency.\n\nIf the selected phone disconnects or becomes unauthorized, the server invalidates retained UI state and requires an explicit `device_select` again after reconnecting, even when the serial is unchanged.\n\nThe server also finalizes an active evidence session during graceful shutdown after cleaning up its owned scrcpy process.\n\nNo generic shell tool is exposed.\n\n## Tests\n\n```zsh\nnpm run typecheck\nnpm test\nnpm run build\nnpm run verify\nnpm pack --dry-run\nnpm audit --omit=dev\n# With an authorized device and non-sensitive foreground app:\nnpm run benchmark:visual -- --iterations 10\n```\n\nThe automated suite uses fake/injectable command boundaries and a child-process MCP client. It does not require a phone and does not install packages or alter device state.\n\nOpt-in physical tests are separate:\n\n```zsh\nexport ANDROID_AGENT_MCP_PHYSICAL=1\nexport ANDROID_AGENT_MCP_TEST_PACKAGE=com.example.androiddevicetest\nexport ANDROID_AGENT_MCP_TEST_SELECTOR='{\"text\":\"7\"}'\nnpm run test:physical\n```\n\nThe physical harness remains deliberately explicit: it requires a designated test package, a repeatable harmless selector, and exactly one connected authorized phone. It starts the actual stdio MCP server and drives the complete protocol path. This fixture is separate from normal MCP operation, where the default policy permits all valid non-sensitive packages. Destructive tests are not run automatically.\n\n## Tool groups\n\nRead-only tools include `device_list`, `device_info`, `mirror_status`, `screen_capture`, `ui_dump`, `ui_find`, `app_list`, `app_info`, `permissions_list`, `logcat_capture`, and `logcat_crashes`.\n\nInteractive tools include `device_select`, `mirror_start`, `mirror_stop`, `ui_tap`, `visual_control_start`, `visual_control_action`, `visual_control_stop`, `screen_tap`, `screen_swipe`, `screen_input_sequence`, `screen_long_press`, `key_press`, `text_type`, `app_launch`, `app_stop`, and `wait_for_ui`.\n\nApproval-required tools include `app_install`, `app_clear_data`, and `permissions_set`. Evidence tools are local artifact operations with bounded storage and redaction.\n\n## Limitations\n\n- USB ADB and display 0 are the initial supported targets.\n- UIAutomator may omit WebViews, games, video, Compose accessibility gaps, and custom canvases; warnings are surfaced.\n- Node IDs are snapshot-local and expire after the configured freshness window or a foreground change.\n- Default text entry is printable ASCII only and rejects password fields.\n- scrcpy audio/control flags are mapped only when supported by the detected version.\n- Logcat duration captures are bounded live reads; `since` selects a bounded timestamp dump.\n- Direct native input defaults to no UI/pixel verification for low latency; request `verify_change: true` when the action must be checked by the server.\n- Display geometry is cached for `displayGeometryMaxAgeMs`; the cache is invalidated when the selected device disconnects or is reselected.\n- Visual control sessions bind to one selected device session, return one screenshot per action, and expire on disconnect or explicit stop.\n- No wireless pairing, multiple-device parallel control, OCR, continuous video MCP frames, root features, iOS, or remote listener is provided.\n\nSee [SECURITY.md](SECURITY.md) and the documents under [docs/](docs/) for operational details.\n\n## Name compatibility\n\nThe npm distribution and primary executable are both `android-agent-mcp`. The `android-mcp` and `scrcpy-agent` executables remain deprecated aliases for existing local setups. The MCP server registration name and protocol identity remain `android-device`, so existing Codex MCP configuration does not need to be renamed. Environment precedence is `ANDROID_AGENT_MCP_*`, then deprecated `ANDROID_MCP_*`, then older `ANDROID_DEVICE_MCP_*`.\n"}