Repository avatar
AI Tools
v0.1.0
active

mcp-android

io.github.dockndevai/mcp-android

Control Android devices over ADB from AI agents — safe-by-default, read-only by default, audited.

Documentation

mcp-android

CI License: MIT npm

A Model Context Protocol server to control an Android phone over ADB — the phone equivalent of the Mac-control servers. Claude can see the screen (screenshots + UI hierarchy) and tap, type, press keys, launch apps, set alarms/timers, and read notifications. Safe-by-default, with the suite's access-mode + flag model.

Runs on your computer and drives a phone connected by USB or wireless debugging (great for the Z Fold 7). It is not installed on the phone and does not need root.

iOS? A stock iPhone can't be controlled this way — Apple's sandbox forbids cross-app/system automation. See the separate mcp-ios repo for the realistic (Appium/WebDriverAgent over USB from a Mac) approach.

Quickstart

brew install android-platform-tools        # provides adb
# On the phone: Settings → Developer options → enable USB (or Wireless) debugging, then authorize this computer
adb devices                                 # confirm your device shows "device"

Run it straight from npm — no clone or build needed:

npx -y @dockndevai/mcp-android

Or add it to Claude Code in one line (start read-only):

claude mcp add android -e ANDROID_MODE="read-only" -- npx -y @dockndevai/mcp-android

See docs/CLIENTS.md for Claude Desktop, Cursor, Codex, Windsurf, and VS Code configs.

Setup from source

brew install android-platform-tools        # provides adb
adb devices                                 # confirm your device shows "device"
npm install && npm run build

Tools

Read (read-only+): list_devices, device_info, screenshot, ui_dump, current_app, list_apps, get_notifications

Write (read-write+): tap, swipe, input_text, press_key, launch_app, open_url, set_alarm, set_timer

Admin (admin): install_apk, uninstall_app, clear_app_data (need ANDROID_ALLOW_INSTALL), run_shell (needs ANDROID_ALLOW_SHELL; not registered otherwise)

The typical loop for driving the UI: screenshot → ui_dump (find bounds) → tap/input_text.

Security model

ConcernFlagDefault
What it can doANDROID_MODEread-only
Which packages in scopeANDROID_PACKAGE_ALLOWLIST(all)
Protected packages (never uninstall/clear)ANDROID_PROTECTED_PACKAGESsettings, systemui, gms
Allow install/uninstall/clearANDROID_ALLOW_INSTALLfalse
Allow raw adb shellANDROID_ALLOW_SHELLfalse
Extra open_url schemes (beyond http, https, tel, mailto, geo, market)ANDROID_EXTRA_URL_SCHEMES(none)
Interactive confirmation (destructive ops)MCP elicitation (client-driven)(on when the client supports it)
Require a humanANDROID_REQUIRE_HUMAN_APPROVALfalse
Preview without touching deviceANDROID_DRY_RUNfalse
Audit log (stderr)ANDROID_AUDIT_LOGtrue

When the connected client supports MCP elicitation, destructive tools (uninstall_app, clear_app_data, install_apk, run_shell) pause and ask the human to approve the exact action before it runs — the model can't approve on your behalf. The prompt shows the tool, the exact (secret-redacted) arguments and a fingerprint; a version token for the target is read before the prompt and again after you answer, and the action is refused if it changed; a prompt nobody answers is a refusal. When the client can't elicit, the access-mode + flag gates above are the control — unless ANDROID_REQUIRE_HUMAN_APPROVAL=true, in which case destructive ops are refused instead.

Shell injection. adb shell joins its arguments into one string that the device's shell parses, so this server single-quotes every argument that reaches the device shell (one shared helper, src/adb/shell.ts) and passes adb exactly one string: text for input_text, URLs for open_url and alarm/timer messages are always literal data, and a read-write agent cannot use them to run commands or get around the ANDROID_ALLOW_SHELL gate (run_shell, which runs your string as-is by design, is the only exception). open_url additionally refuses any URL scheme other than http, https, tel, mailto, geo and market (plus ANDROID_EXTRA_URL_SCHEMES). input_text encodes spaces as %s and types a literal %s in two steps, because Android's input text would otherwise turn it into a space. See SECURITY.md.

Every tool also advertises MCP tool annotations (readOnlyHint, destructiveHint, idempotentHint, openWorldHint) so hosts can decide what to auto-approve.

Run with Claude Desktop / Claude Code

{
  "mcpServers": {
    "android": {
      "command": "node",
      "args": ["/absolute/path/to/mcp-android/dist/index.js"],
      "env": { "ANDROID_MODE": "read-write" }
    }
  }
}

Example prompts

  • "Take a screenshot of my phone and tell me what's on screen."
  • "Set an alarm for 6:30am labeled Gym."
  • "Open the Clock app and start a 10-minute timer."
  • "What notifications do I have piling up?"

Develop

npm run dev
npm test          # security policy
npm run typecheck

License

MIT