# mcadb

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 &mdash; 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 mcadb

# Or install and run
uv add mcadb
mcadb
```

### MCP Client Configuration

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

```json
{
  "mcpServers": {
    "mcadb": {
      "command": "uvx",
      "args": ["mcadb"]
    }
  }
}
```

For Claude Code:
```bash
claude mcp add mcadb -- uvx mcadb
```

For local development:
```bash
claude mcp add mcadb -- uv run --directory /path/to/mcp-adb mcadb
```

## 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 mcadb .
docker run --privileged -v /dev/bus/usb:/dev/bus/usb mcadb
```

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

MCP client config for Docker:
```json
{
  "mcpServers": {
    "mcadb": {
      "command": "docker",
      "args": ["run", "-i", "--privileged", "-v", "/dev/bus/usb:/dev/bus/usb", "mcadb"]
    }
  }
}
```

## 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 mcadb

# 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
