# Android ADB MCP Server A [Model Context Protocol](https://modelcontextprotocol.io/) 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](https://gofastmcp.com/) with a modular mixin architecture. 50 tools across 6 domains. Tested on real hardware. ## Quick Start ```bash # 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.): ```json { "mcpServers": { "android-adb": { "command": "uvx", "args": ["android-mcp-server"] } } } ``` For Claude Code: ```bash claude mcp add android-adb -- uvx android-mcp-server ``` For local development: ```bash 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](https://gofastmcp.com/) 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 ```bash 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: ```json { "mcpServers": { "android-adb": { "command": "docker", "args": ["run", "-i", "--privileged", "-v", "/dev/bus/usb:/dev/bus/usb", "android-mcp-server"] } } } ``` ## Development ```bash # 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): ```json { "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