Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

2 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

📱 android-mcp

Give your coding agent eyes on the actual Android device it's building for — screenshot, tap, swipe, read the UI tree, install and launch apps — the same way Antigravity's browser tool gives it control of a browser.

License: MIT Python 3.10+ MCP Platform: adb


💡 Why this exists

I was building my second Android app — a kana learning app, in Kotlin — and skipped installing full Android Studio for it. Just a command-line toolchain: Gradle, adb, USB debugging, and Antigravity as the coding agent.

That worked fine for writing code. It broke down the moment something needed checking. The agent could write a UI change, but it had no way to actually see the app running — no screenshot, no way to tap through it, nothing. Every time something looked wrong, I was the one launching the app, describing what I saw in English, and hoping the description was precise enough for the agent to find the bug. For anything visual — spacing, alignment, sizing — that back-and-forth was slower than just fixing it myself.

Antigravity already had a browser tool that could screenshot, click, and scroll a real browser for web work. There was no reason the same idea couldn't work for a physical Android device over adb. So with Claude's help, I built this: an MCP server that gives the agent the same kind of hands-on access to a phone that the browser tool gives it to a browser.

It worked well enough that the agent could fix a UI bug entirely on its own — build, install, screenshot, compare, adjust, repeat — without me describing anything. See below.


🔍 Before / after

I asked the agent to fix uneven padding on a toggle component, gave it access to this tool, and told it not to stop until a screenshot actually confirmed the fix.

❌ Before ✅ After
Uneven toggle padding Fixed toggle

No back-and-forth. The agent made the change, ran the debug build, installed it, took a screenshot through this tool, compared it against what I'd asked for, and only reported back once the screenshot actually matched.


🛠️ What it gives the agent

Tool What it does
📋 list_devices See what's attached (USB or Wi-Fi)
📶 enable_wireless_debugging / connect_network / pair_network / disconnect_network Attach a device over Wi-Fi
📦 install_apk / launch_app / stop_app / clear_app_data / list_packages App lifecycle
🖼️ screenshot See the current screen, as an image
🌳 get_ui_hierarchy Read every element's text, id, and tap coordinates — the "DOM" equivalent
👆 find_and_tap Tap an element by visible text or resource-id, no coordinates needed
tap / swipe / input_text / press_key Drive the UI directly
📜 get_logcat Pull recent logs, e.g. a crash stack trace
🚪 adb_shell Escape hatch for anything else — off by default, see Security

Everything runs through adb, so it works against a real phone or any adb-visible emulator — nothing here is specific to my app.


⚙️ How it works

It's a plain MCP server, run over stdio, spawned as a local subprocess by whatever agent host you're using (Antigravity, Claude Code, anything that speaks MCP). Every tool call shells out to adb and returns the result — screenshots come back as real image content the agent can see, not just a file path.

agent  <-- MCP (stdio) -->  android-mcp  <-- adb -->  phone

No cloud, no account, nothing installed on the device beyond what adb already needs. Everything stays on your machine.


🚀 Setup

✅ Prerequisites

  • Android platform-tools (adb on PATH — run adb version to check)
  • Python 3.10+
  • On the phone: Developer options unlocked, USB debugging on (Settings > About phone > tap Build number 7x, then Settings > Developer options)

📥 Install

pip install -r android_mcp/requirements.txt

🔌 Attach a device

USB: plug in, accept the debugging prompt, adb devices should list it.

Wireless, two ways:

  • Bootstrap over USB once: call enable_wireless_debugging(), then connect_network("<ip>:5555") with the address it returns. You can unplug USB after connecting.
  • Fully cable-free (Android 11+): Settings > Developer options > Wireless debugging > Pair device with pairing code, then pair_network(address, code) followed by connect_network() with the main address shown on that screen.

🤖 Register it with your agent

Copy mcp_config.example.json, fill in the absolute path to android_mcp/android_mcp.py on your machine, and drop it into your MCP config location (for Antigravity: .agents/mcp_config.json in your project, or ~/.gemini/config/mcp_config.json globally):

{
  "mcpServers": {
    "android-control": {
      "command": "python",
      "args": ["/absolute/path/to/android_mcp/android_mcp.py"]
    }
  }
}

💡 Tip: If your setup also needs environment activation (a separate SDK/toolchain script, a virtualenv, etc.), point command at a wrapper script instead — see android_mcp/run_android_mcp.bat.example for the pattern. Chaining multiple activation commands directly inside the JSON tends to break on quoting; a small wrapper script that does the activation and then runs python android_mcp.py is far more reliable.

🗣️ Tell the agent what it has

Something like, in your AGENTS.md or equivalent:

You have android-control tools to run and check the app you're building,
the same way you'd use a browser tool for a web app. After any UI change:
build, install_apk, launch_app, then screenshot to confirm it actually
looks right before saying you're done. get_ui_hierarchy gives exact tap
coordinates and lets you read on-screen text you can't trust OCR for.

🧪 Testing it yourself first

⚠️ Don't wire this straight into an agent without checking it works — use the MCP Inspector to call tools by hand first:

npx @modelcontextprotocol/inspector python android_mcp/android_mcp.py

That opens a web UI listing every tool with a form to call it and see the raw result — screenshot, tap, read the UI tree, all without an agent in the loop. Confirm the basics work, confirm adb_shell correctly refuses until you explicitly enable it, then wire it in.


🔒 Security

This gives an LLM shell-level access to a device. That's real risk — a hallucinated command could delete photos, clear messages, or worse. A few things are built in to reduce that:

  1. 🧯 Run this against an emulator or a spare device with nothing irreplaceable on it. This is the actual fix. Android Studio's AVD Manager gives you a free emulator that adb treats identically to a real phone. Everything below reduces risk if you use your daily driver anyway — it doesn't eliminate it.
  2. 🔐 adb_shell is off by default. It's the one unrestricted tool, so it only turns on if you set ANDROID_MCP_ALLOW_SHELL=1 in the server's own environment — outside the conversation, where the agent can't touch it.
  3. 🚫 Even when enabled, destructive patterns are hard-blocked in code: rm -rf, mkfs, dd if=, format/wipe/factory-reset, raw SMS/contacts/call-log access, su.
  4. 🛡️ stop_app / clear_app_data refuse a fixed list of system and comms packages — Contacts, Messaging, Phone, Play Services, SystemUI, the launcher — regardless of anything else.
  5. 🛡️ Every value that reaches adb shell is shell-quoted (shlex.quote) before it's sent, including package names, filter strings, and log tags — adb shell reconstructs its arguments into a single string and runs it through a remote shell on the device, so an unquoted value containing shell metacharacters could be reinterpreted there instead of treated as a literal string.

None of that makes the shell tool safe to point at data you can't lose. It makes an accidental or malformed call much less likely, which is a different guarantee than "safe" — testing it yourself a number of times with no incident is evidence the common paths work, not proof of an edge case that hasn't come up yet, especially once other people are running this against their own devices and agents.


⚠️ Limitations

  • 🔤 input_text is ASCII-only at the adb layer — for verifying non-Latin text (kana, etc.), read it back via get_ui_hierarchy instead of typing it in.
  • 📐 get_ui_hierarchy reports (0,0) for elements without proper accessibility bounds — common with some custom-rendered UI (e.g. certain Compose components missing semantics). That's an app-side accessibility gap, not something this tool can work around — and worth fixing on the app side anyway, since real screen-reader users hit the same gap.
  • 🖥️ Tested on Windows + a real device; should work anywhere adb does, but I haven't tried it on Linux/macOS or against an emulator myself yet.

🤝 Contributing

See CONTRIBUTING.md.

🌱 Why open source

I built this because I needed it, not to start a project. If it's useful to you, use it. If you improve it, I'd genuinely appreciate a mention — that's really the only ask here.

📄 License

MIT — see LICENSE.

About

MCP server that gives coding agents (Antigravity, Claude, etc.) hands-on control of a real Android device over adb — screenshot, tap, install, launch, read the UI tree — so agents can verify UI changes instead of guessing.

Topics

Resources

Contributing

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages