mcp-adb/README.md
Ryan Malloy 7c414f8015 Refactor to MCPMixin architecture with injection-safe shell execution
Replaces single-file server with modular mixin architecture:
- 6 domain mixins (devices, input, apps, screenshot, ui, files)
- Injection-safe run_shell_args() using shlex.quote() for all tools
- Persistent developer mode config (~/.config/adb-mcp/config.json)
- Pydantic models for typed responses
- MCP elicitation for destructive operations
- Dynamic screen dimensions for scroll gestures
- Intent flag name resolution for activity_start
- 50 tools, 5 resources, tested on real hardware
2026-02-10 18:30:34 -07:00

8.8 KiB

Android ADB MCP Server

A Model Context Protocol server that gives AI assistants direct control over Android devices through ADB. Point any MCP-compatible client at a phone plugged into USB, and it can take screenshots, tap buttons, launch apps, inspect UI elements, transfer files, and run shell commands — all through structured, type-safe tool calls.

Built on FastMCP with a modular mixin architecture. 50 tools across 6 domains. Tested on real hardware.

Quick Start

# Run directly (no install)
uvx android-mcp-server

# Or install and run
uv add android-mcp-server
android-mcp-server

MCP Client Configuration

Add to your MCP client's config (Claude Desktop, Claude Code, etc.):

{
  "mcpServers": {
    "android-adb": {
      "command": "uvx",
      "args": ["android-mcp-server"]
    }
  }
}

For Claude Code:

claude mcp add android-adb -- uvx android-mcp-server

For local development:

claude mcp add android-adb -- uv run --directory /path/to/mcp-adb android-mcp-server

Prerequisites

  • Python 3.11+
  • ADB installed and on PATH (adb devices should work)
  • USB debugging enabled on the Android device
  • Device connected via USB (or adb connect for network)

What Can It Do?

Standard Tools (always available)

Domain Tool What it does
Devices devices_list Discover connected devices (USB + network)
devices_use Set active device for multi-device setups
devices_current Show which device is selected
device_info Battery, WiFi, storage, Android version, model
Input input_tap Tap at screen coordinates
input_swipe Swipe between two points
input_scroll_down Scroll down (auto-detects screen size)
input_scroll_up Scroll up (auto-detects screen size)
input_back Press Back
input_home Press Home
input_recent_apps Open app switcher
input_key Send any key event (VOLUME_UP, ENTER, etc.)
input_text Type text into focused field
clipboard_set Set clipboard (handles special chars), optional auto-paste
Apps app_launch Launch app by package name
app_open_url Open URL in default browser
app_close Force stop an app
app_current Get the foreground app and activity
Screen screenshot Capture screen as PNG
screen_size Get display resolution
screen_density Get display DPI
screen_on / screen_off Wake or sleep the display
UI ui_dump Dump accessibility tree (all visible elements)
ui_find_element Search for elements by text, ID, class, or description
wait_for_text Poll until text appears on screen
wait_for_text_gone Poll until text disappears
tap_text Find an element by text and tap it
Config config_status Show current settings
config_set_developer_mode Toggle developer tools
config_set_screenshot_dir Set where screenshots are saved

Developer Mode Tools

Enable with config_set_developer_mode(true) to unlock power-user tools. Destructive operations (uninstall, clear data, reboot, delete) require user confirmation via MCP elicitation.

Domain Tool What it does
Shell shell_command Run any shell command on device
Input input_long_press Press and hold gesture
Apps app_list_packages List installed packages (with filters)
app_install Install APK from host
app_uninstall Remove an app (with confirmation)
app_clear_data Wipe app data (with confirmation)
activity_start Launch activity with full intent control
broadcast_send Send broadcast intents
Screen screen_record Record screen to MP4
screen_set_size Override display resolution
screen_reset_size Restore original resolution
Device device_reboot Reboot device (with confirmation)
logcat_capture Capture system logs
logcat_clear Clear log buffer
Files file_push Transfer file to device
file_pull Transfer file from device
file_list List directory contents
file_delete Delete file (with confirmation)
file_exists Check if file exists

Resources

URI Description
adb://devices Connected device list
adb://device/{id} Detailed device properties
adb://apps/current Currently focused app
adb://screen/info Screen resolution and DPI
adb://help Tool reference and tips

Usage Examples

Screenshot + UI inspection loop (how an AI assistant typically navigates):

1. screenshot()           → See what's on screen
2. ui_dump()              → Get element tree with tap coordinates
3. tap_text("Settings")   → Tap the "Settings" element
4. wait_for_text("Wi-Fi") → Wait for the screen to load
5. screenshot()           → Verify the result

Open a URL and check what loaded:

1. app_open_url("https://example.com")
2. wait_for_text("Example Domain")
3. screenshot()

Install and launch an APK (developer mode):

1. config_set_developer_mode(true)
2. app_install("/path/to/app.apk")
3. app_launch("com.example.myapp")
4. logcat_capture(filter_spec="MyApp:D *:S")

Multi-device workflow:

1. devices_list()                → See all connected devices
2. devices_use("SERIAL_NUMBER")  → Select target device
3. device_info()                 → Check battery, WiFi, storage
4. screenshot()                  → Capture from selected device

Architecture

The server uses FastMCP's MCPMixin pattern to organize 50 tools into focused, single-responsibility modules:

src/
  server.py          ← FastMCP app, ADBServer (thin orchestrator)
  config.py          ← Persistent config (~/.config/adb-mcp/config.json)
  models.py          ← Pydantic models (DeviceInfo, CommandResult, ScreenshotResult)
  mixins/
    base.py          ← ADB command execution, injection-safe shell quoting
    devices.py       ← Device discovery, info, logcat, reboot
    input.py         ← Tap, swipe, scroll, keys, text, clipboard, shell
    apps.py          ← Launch, close, install, intents, broadcasts
    screenshot.py    ← Capture, recording, display settings
    ui.py            ← Accessibility tree, element search, text polling
    files.py         ← Push, pull, list, delete, exists

ADBServer inherits all six mixins. Each mixin calls run_shell_args() (injection-safe) or run_adb() on the base class. The base handles device targeting, subprocess execution, and timeouts.

Security Model

All tools that accept user-provided values use injection-safe command execution:

  • run_shell_args() quotes every argument with shlex.quote() before sending to the device shell. This is the default for all tools.
  • run_shell() (string form) is only used by the developer-mode shell_command tool, where the user intentionally provides a raw command.
  • input_text() rejects special characters ($ ( ) ; | & < > etc.) and directs users to clipboard_set() instead.
  • input_key() strips non-alphanumeric characters from key codes.
  • Destructive operations (uninstall, clear data, delete, reboot) require user confirmation via MCP elicitation.
  • Developer mode is off by default and must be explicitly enabled. Settings persist at ~/.config/adb-mcp/config.json.

Docker

docker build -t android-mcp-server .
docker run --privileged -v /dev/bus/usb:/dev/bus/usb android-mcp-server

The --privileged flag and USB volume mount are required for ADB to detect physical devices.

MCP client config for Docker:

{
  "mcpServers": {
    "android-adb": {
      "command": "docker",
      "args": ["run", "-i", "--privileged", "-v", "/dev/bus/usb:/dev/bus/usb", "android-mcp-server"]
    }
  }
}

Development

# Clone and install
git clone https://git.supported.systems/MCP/mcp-adb.git
cd mcp-adb
uv sync --group dev

# Run locally
uv run android-mcp-server

# Lint
uv run ruff check src/

# Format
uv run ruff format src/

# Type check
uv run mypy src/

Configuration

Settings are stored at ~/.config/adb-mcp/config.json (override with ADB_MCP_CONFIG_DIR env var):

{
  "developer_mode": false,
  "default_screenshot_dir": null,
  "auto_select_single_device": true
}
Setting Default Description
developer_mode false Unlock advanced tools (shell, install, reboot, etc.)
default_screenshot_dir null Directory for screenshots/recordings (null = cwd)
auto_select_single_device true Skip device selection when only one is connected

License

MIT