An Android application that runs as an MCP (Model Context Protocol) server, enabling AI models to fully control an Android device remotely using accessibility services and screenshot capture. The app runs directly on your Android device (or emulator) and exposes an HTTP server (with optional HTTPS) implementing the MCP protocol. AI models like Claude can connect to it and interact with any app on
Add this skill
npx mdskills install danielealbano/android-remote-control-mcpComprehensive MCP server with 54 tools for full Android device control via accessibility APIs
1# Android Remote Control MCP23[](https://github.com/danielealbano/android-remote-control-mcp/actions/workflows/ci.yml)4[](https://opensource.org/licenses/MIT)56An Android application that runs as an **MCP (Model Context Protocol) server**, enabling AI models to **fully control an Android device** remotely using accessibility services and screenshot capture.78The app runs directly on your Android device (or emulator) and exposes an HTTP server (with optional HTTPS) implementing the MCP protocol. AI models like Claude can connect to it and interact with any app on the device — reading UI elements, tapping buttons, typing text, swiping, capturing screenshots, managing files, launching apps, and more.910> **⚠️ Disclaimer:** This software is provided "as-is" without warranty of any kind, for **research and educational purposes only**. The authors do not condone the use of this tool for any illegal, unauthorized, or unethical activities. Users are solely responsible for ensuring their use complies with all applicable laws and regulations. By using this software, you agree to use it responsibly and at your own risk.1112---1314## Features1516### MCP Server17- HTTP server running directly on Android (Ktor + Netty), with optional HTTPS18- Streamable HTTP transport at `/mcp` (MCP specification compliant, JSON-only, no SSE)19- Bearer token authentication (global, all requests)20- Auto-generated self-signed TLS certificates (or custom certificate upload)21- Configurable binding: localhost (127.0.0.1) or network (0.0.0.0)22- Auto-start on boot23- Remote access tunnels via Cloudflare Quick Tunnels or ngrok (public HTTPS URL)2425### 54 MCP Tools across 12 Categories2627All tool names use the `android_` prefix by default (e.g., `android_tap`). When a device slug is configured (e.g., `pixel7`), the prefix becomes `android_pixel7_` (e.g., `android_pixel7_tap`). See [docs/MCP_TOOLS.md](docs/MCP_TOOLS.md) for the full naming convention.2829| Category | Tools | Description |30|----------|-------|-------------|31| **Screen Introspection** (1) | `android_get_screen_state` | Consolidated screen state: app info, screen dimensions, filtered UI node list (TSV), hierarchy section, optional annotated low-res screenshot with bounding boxes and node ID labels |32| **System Actions** (6) | `android_press_back`, `android_press_home`, `android_press_recents`, `android_open_notifications`, `android_open_quick_settings`, `android_get_device_logs` | Global device actions and log retrieval |33| **Touch Actions** (5) | `android_tap`, `android_long_press`, `android_double_tap`, `android_swipe`, `android_scroll` | Coordinate-based touch interactions |34| **Gestures** (2) | `android_pinch`, `android_custom_gesture` | Multi-touch and complex gestures |35| **Node Actions** (5) | `android_find_nodes`, `android_click_node`, `android_long_click_node`, `android_tap_node`, `android_scroll_to_node` | Accessibility node-based interactions |36| **Text Input** (5) | `android_type_append_text`, `android_type_insert_text`, `android_type_replace_text`, `android_type_clear_text`, `android_press_key` | Natural text input via InputConnection and key events |37| **Utilities** (5) | `android_get_clipboard`, `android_set_clipboard`, `android_wait_for_node`, `android_wait_for_idle`, `android_get_node_details` | Helper tools for automation and node inspection |38| **File Operations** (8) | `android_list_storage_locations`, `android_list_files`, `android_read_file`, `android_write_file`, `android_append_file`, `android_file_replace`, `android_download_from_url`, `android_delete_file` | File system access via Storage Access Framework (SAF) |39| **App Management** (3) | `android_open_app`, `android_list_apps`, `android_close_app` | Launch, list, and close applications |40| **Camera** (6) | `android_list_cameras`, `android_list_camera_photo_resolutions`, `android_list_camera_video_resolutions`, `android_take_camera_photo`, `android_save_camera_photo`, `android_save_camera_video` | Camera photo/video capture via CameraX, list capabilities and resolutions |41| **Intent** (2) | `android_send_intent`, `android_open_uri` | Send explicit/implicit intents and open URIs via the system |42| **Notification** (6) | `android_notification_list`, `android_notification_open`, `android_notification_dismiss`, `android_notification_snooze`, `android_notification_action`, `android_notification_reply` | Read, interact with, and manage device notifications via NotificationListenerService |4344See [docs/MCP_TOOLS.md](docs/MCP_TOOLS.md) for full tool documentation with input/output schemas and examples.4546### Android App47- Material Design 3 UI with tabbed layout (Server / Settings / About) and dark mode48- Server status monitoring (running/stopped) with permission warning banner49- Connection info display (IP, port, token, tunnel URL)50- Per-tool and per-parameter permissions (enable/disable individual MCP tools)51- Permission management (Accessibility, Notifications, Camera, Microphone)52- Remote access tunnel configuration (Cloudflare / ngrok)53- Storage location management (SAF authorization for file tools)54- Server log viewer (MCP tool calls, tunnel events)55- Headless setup via ADB (configure, grant permissions, start/stop server without UI)5657### Comparison with Alternatives5859| Feature | This project | [mobile-mcp] | [Android-MCP] | [android-mcp-server] | [adb-mcp] | [droidrun-mcp] |60|---------|:-:|:-:|:-:|:-:|:-:|:-:|61| MCP tools | 54 | 21 | 11 | 5 | 10 | 11 |62| Runs on the phone (no ADB) | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ |63| Action latency | 10-100 ms | 1-4 s | 1-4 s | 1-4 s | 1-4 s | 1-4 s |64| Works over the internet | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ |65| Token-efficient screen state | ✅ | ❌ | ✅ | ❌ | ❌ | ✅ |66| Annotated screenshots | ✅ | ❌ | ✅ | ❌ | ❌ | ❌ |67| Configurable screenshot resolution/quality | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ |68| Per-tool enable/disable | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ |69| Multi-device support | ✅ | ✅ | ❌ | ❌ | ❌ | ❌ |70| Camera, clipboard, files, downloads | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ |71| iOS support | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ |7273[mobile-mcp]: https://github.com/mobile-next/mobile-mcp74[Android-MCP]: https://github.com/CursorTouch/Android-MCP75[android-mcp-server]: https://github.com/minhalvp/android-mcp-server76[adb-mcp]: https://github.com/srmorete/adb-mcp77[droidrun-mcp]: https://github.com/chukfinley/droidrun-mcp-server7879Most alternatives rely on ADB running on a host machine, which means a USB cable or local network connection and a computer sitting next to the phone. This project runs entirely on the device itself, so you can expose the MCP endpoint through a tunnel and control your phone from anywhere.8081On the token efficiency side, ADB-based tools typically return raw `uiautomator` XML dumps which can easily be 10-50x more verbose than the compact representation used here. Combined with numbered screenshot annotations, configurable image quality, and the ability to disable tools you don't need (every tool definition costs tokens on every turn), this significantly reduces the per-interaction cost in agentic loops.8283---8485## Requirements8687### For Building88- **JDK 17** (e.g., [Eclipse Temurin](https://adoptium.net/))89- **Android SDK** with API 34 (Android 14)90- **Gradle** 8.x (wrapper included, no global install needed)9192### For Running93- Android device or emulator running **Android 13+** (API 33+), targeting **Android 14** (API 34)94- **adb** (Android Debug Bridge) for device/emulator management9596### For E2E Tests97- **Podman** (rootful, for `redroid/redroid` Android container image)9899Check all dependencies:100```bash101make check-deps102```103104---105106## Quick Start107108### 1. Build the App109110```bash111git clone https://github.com/danielealbano/android-remote-control-mcp.git112cd android-remote-control-mcp113make build114```115116### 2. Install on Device/Emulator117118```bash119# Start an emulator (if no device connected)120make setup-emulator121make start-emulator122123# Install the debug APK124make install125```126127### 3. Configure Permissions1281291. **Enable Accessibility Service**: Open the app, tap "Enable Accessibility Service", and toggle it on in Android Settings. This also enables screenshot capture via the `takeScreenshot()` API (Android 11+).1302. **Grant Camera & Microphone** (optional, for camera tools): Grant via the app UI or `make grant-permissions`.131132### 4. Start the MCP Server133134Tap the "Start Server" button in the app. The server starts on `http://127.0.0.1:8080` by default (HTTPS is disabled by default).135136### 5. Connect from Host Machine137138Set up port forwarding (if server is bound to localhost):139```bash140make forward-port141```142143Test the connection:144```bash145curl -X POST http://localhost:8080/mcp \146 -H "Authorization: Bearer YOUR_TOKEN" \147 -H "Content-Type: application/json" \148 -d '{"jsonrpc":"2.0","id":1,"method":"ping"}'149```150151### 6. Make MCP Tool Calls152153All requests are sent as JSON-RPC 2.0 via `POST /mcp` (Streamable HTTP transport):154155```bash156# List available tools157curl -X POST http://localhost:8080/mcp \158 -H "Authorization: Bearer YOUR_TOKEN" \159 -H "Content-Type: application/json" \160 -d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'161162# Get the current screen state (UI nodes + optional screenshot)163curl -X POST http://localhost:8080/mcp \164 -H "Authorization: Bearer YOUR_TOKEN" \165 -H "Content-Type: application/json" \166 -d '{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"android_get_screen_state","arguments":{}}}'167168# Tap at coordinates169curl -X POST http://localhost:8080/mcp \170 -H "Authorization: Bearer YOUR_TOKEN" \171 -H "Content-Type: application/json" \172 -d '{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"android_tap","arguments":{"x":540,"y":1200}}}'173```174175The bearer token is displayed in the app's connection info section. You can copy it directly from the app.176177---178179## Building180181### Debug Build182183```bash184make build185# APK: app/build/outputs/apk/debug/app-debug.apk186```187188### Release Build189190```bash191make build-release192# APK: app/build/outputs/apk/release/app-release.apk193```194195For signed release builds, create `keystore.properties` in the project root:196```properties197storeFile=path/to/your.keystore198storePassword=your_store_password199keyAlias=your_key_alias200keyPassword=your_key_password201```202203### Clean Build204205```bash206make clean207```208209---210211## Testing212213### Unit Tests214215```bash216make test-unit217```218219Runs JUnit 5 unit tests with MockK for mocking. Tests cover accessibility tree parsing, node finding, screenshot encoding, settings repository, network utilities, and all 54 MCP tool handlers.220221### Integration Tests222223```bash224make test-integration225```226227Runs JVM-based integration tests using Ktor `testApplication` (no device or emulator required). Tests the full HTTP stack: authentication, JSON-RPC protocol, tool dispatch for all 12 tool categories, and error handling.228229> **Note**: Some integration tests (e.g., `NgrokTunnelIntegrationTest`) require environment variables. Copy `.env.example` to `.env` and fill in the required values. The Makefile sources `.env` automatically.230231### E2E Tests232233```bash234make test-e2e235```236237Requires rootful Podman. Starts a redroid Android container via Podman, installs the app, and performs real MCP tool calls. Includes:238- **Calculator test**: 7 + 3 = 10 via MCP tools (verifies full stack)239- **Screenshot test**: Capture with different quality settings240- **Error handling test**: Authentication, unknown tools, invalid params241242### All Tests243244```bash245make test246```247248### Code Coverage249250```bash251make coverage252```253254Generates a Jacoco HTML report at `app/build/reports/jacoco/jacocoTestReport/html/index.html`. Minimum coverage target: 80%.255256---257258## Architecture259260The application is a **service-based Android app** with three main components:2612621. **McpAccessibilityService** - UI introspection, action execution, and screenshot capture via Android Accessibility APIs (`takeScreenshot()` on Android 11+)2632. **McpServerService** - Foreground service running the Ktor HTTP/HTTPS server2643. **MainActivity** - Jetpack Compose UI for configuration and control265266```mermaid267graph TB268 Client["MCP Client (AI Model)"]269 Client -->|"HTTP/HTTPS POST /mcp + Bearer Token"| McpServer270271 subgraph Device["Android Device"]272 subgraph McpServerService["McpServerService (Foreground Service)"]273 McpServer["McpServer (Ktor)"]274 McpServer -->|"Streamable HTTP /mcp"| SDK["SDK Server (MCP Kotlin SDK)"]275 SDK -->|"54 MCP Tools"| Tools["Tool Handlers"]276 TunnelMgr["TunnelManager (optional)"]277 TunnelMgr -->|"Cloudflare / ngrok"| PublicURL["Public HTTPS URL"]278 end279280 subgraph Accessibility["McpAccessibilityService"]281 TreeParser["AccessibilityTreeParser"]282 ElemFinder["ElementFinder"]283 ActionExec["ActionExecutor"]284 ScreenEnc["ScreenshotEncoder"]285 end286287 subgraph Storage["Storage & App Services"]288 StorageProv["StorageLocationProvider"]289 FileOps["FileOperationProvider"]290 AppMgr["AppManager"]291 end292293 subgraph CameraSvc["Camera Services"]294 CamProv["CameraProvider\n(CameraX)"]295 end296297 subgraph IntentSvc["Intent Services"]298 IntentDisp["IntentDispatcher"]299 end300301 subgraph NotifSvc["Notification Services"]302 NotifProv["NotificationProvider"]303 NotifListener["McpNotificationListenerService"]304 end305306 MainActivity["MainActivity (Compose UI)"]307308 Tools --> Accessibility309 Tools --> Storage310 Tools --> CameraSvc311 Tools --> IntentSvc312 Tools --> NotifSvc313 MainActivity -->|"StateFlow (status)"| McpServerService314 end315```316317See [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md) for detailed architecture documentation.318319---320321## Configuration322323### Server Settings (via App UI)324325| Setting | Default | Description |326|---------|---------|-------------|327| Port | `8080` | HTTP/HTTPS server port |328| Binding Address | `127.0.0.1` | `127.0.0.1` (localhost, use with adb port forwarding) or `0.0.0.0` (network, all interfaces) |329| Bearer Token | Auto-generated UUID | Authentication token for MCP requests |330| HTTPS | Disabled | Enable HTTPS with auto-generated self-signed certificate (configurable hostname) or upload custom .p12/.pfx |331| Auto-start on Boot | Disabled | Start MCP server automatically when device boots |332| Device Slug | Empty | Optional device identifier for tool name prefix (e.g., `pixel7` makes tools `android_pixel7_tap`) |333| Remote Access Tunnel | Disabled | Expose server via public HTTPS URL (Cloudflare Quick Tunnels or ngrok) |334| Tool Permissions | All enabled | Per-tool and per-parameter enable/disable (Settings > MCP Tools) |335| File Size Limit | 50 MB | Maximum file size for file operations (range 1-500 MB) |336| Allow HTTP Downloads | Disabled | Allow non-HTTPS downloads via `android_download_from_url` |337| Download Timeout | 60 seconds | Timeout for file downloads (range 10-300 seconds) |338339### Headless Setup via ADB340341The app can be fully configured and controlled from the command line without opening the UI. This is useful for automated setups, CI pipelines, or headless devices.342343Replace `<app-id>` with the application ID for your build:344- **Debug**: `com.danielealbano.androidremotecontrolmcp.debug`345- **Release**: `com.danielealbano.androidremotecontrolmcp`346347#### Grant Permissions348349```bash350# Enable Accessibility Service (required for UI introspection, actions, and screenshots)351adb shell settings put secure enabled_accessibility_services \352 <app-id>/com.danielealbano.androidremotecontrolmcp.services.accessibility.McpAccessibilityService353354# Grant notification permission (Android 13+)355adb shell pm grant <app-id> android.permission.POST_NOTIFICATIONS356357# Grant camera permission358adb shell pm grant <app-id> android.permission.CAMERA359360# Grant microphone permission361adb shell pm grant <app-id> android.permission.RECORD_AUDIO362```363364#### Configure the App365366All extras are optional — only the ones provided are updated. The app does **not** need to be open.367368```bash369adb shell am broadcast \370 -a com.danielealbano.androidremotecontrolmcp.ADB_CONFIGURE \371 -n <app-id>/com.danielealbano.androidremotecontrolmcp.services.mcp.AdbConfigReceiver \372 --es bearer_token "my-secret-token" \373 --es binding_address "0.0.0.0" \374 --ei port 8080 \375 --ez auto_start_on_boot true \376 --ez https_enabled false \377 --es certificate_source "AUTO_GENERATED" \378 --es certificate_hostname "mcp.local" \379 --ez tunnel_enabled false \380 --es tunnel_provider "CLOUDFLARE" \381 --es ngrok_authtoken "your-ngrok-token" \382 --es ngrok_domain "your-domain.ngrok-free.app" \383 --ei file_size_limit_mb 50 \384 --ez allow_http_downloads false \385 --ez allow_unverified_https_certs false \386 --ei download_timeout_seconds 60 \387 --es device_slug "pixel8" \388 --es tool_permissions '{"disabled_tools":["tap"],"disabled_params":{"swipe":["duration_ms"]}}'389```390391| Extra | Type | Description |392|-------|------|-------------|393| `bearer_token` | string | Authentication token for MCP requests |394| `binding_address` | string | `127.0.0.1` (localhost) or `0.0.0.0` (network) |395| `port` | int | HTTP/HTTPS server port (1-65535) |396| `auto_start_on_boot` | boolean | Start MCP server when device boots |397| `https_enabled` | boolean | Enable HTTPS with TLS |398| `certificate_source` | string | `AUTO_GENERATED` or `CUSTOM` |399| `certificate_hostname` | string | Hostname for auto-generated certificate |400| `tunnel_enabled` | boolean | Enable remote access tunnel |401| `tunnel_provider` | string | `CLOUDFLARE` or `NGROK` |402| `ngrok_authtoken` | string | ngrok authentication token |403| `ngrok_domain` | string | ngrok custom domain (optional) |404| `file_size_limit_mb` | int | Max file size for file operations (1-500) |405| `allow_http_downloads` | boolean | Allow non-HTTPS downloads |406| `allow_unverified_https_certs` | boolean | Allow unverified HTTPS certificates for downloads |407| `download_timeout_seconds` | int | Download timeout (10-300) |408| `device_slug` | string | Device identifier for tool name prefix |409| `tool_permissions` | string (JSON) | Per-tool and per-parameter permissions: `{"disabled_tools":["tool_name"],"disabled_params":{"tool_name":["param"]}}` |410411#### Start the MCP Server412413The server must be started via a trampoline Activity (required on Android 12+ to gain foreground service exemption). This works even when the app is force-stopped.414415```bash416adb shell am start \417 -n <app-id>/com.danielealbano.androidremotecontrolmcp.services.mcp.AdbServiceTrampolineActivity \418 --es action start419```420421#### Stop the MCP Server422423```bash424adb shell am start \425 -n <app-id>/com.danielealbano.androidremotecontrolmcp.services.mcp.AdbServiceTrampolineActivity \426 --es action stop427```428429### Using with adb Port Forwarding (Recommended)430431When the server is bound to `127.0.0.1` (default, most secure):432433```bash434# Forward device port to host435adb forward tcp:8080 tcp:8080436437# Test connection from host438curl -X POST http://localhost:8080/mcp \439 -H "Authorization: Bearer YOUR_TOKEN" \440 -H "Content-Type: application/json" \441 -d '{"jsonrpc":"2.0","id":1,"method":"ping"}'442```443444### Using over Network445446When the server is bound to `0.0.0.0`:4474481. Find the device's IP address (shown in the app's connection info)4492. Connect directly via `POST http://DEVICE_IP:8080/mcp` with bearer token450451**Warning**: Binding to `0.0.0.0` exposes the server to all devices on the same network. Only use on trusted private networks.452453### Using Remote Access Tunnels454455For connecting from outside the local network without port forwarding:4564571. **Cloudflare Quick Tunnels** (default, no account required): Creates a temporary tunnel with a random `*.trycloudflare.com` HTTPS URL.4582. **ngrok** (account required): Supports optional custom domains. Requires an ngrok authtoken (free tier available). Only available on ARM64 devices.459460Enable the tunnel in the app's "Remote Access" section. The public URL is displayed in the connection info and server logs.461462---463464## Security465466### HTTPS (Optional, Disabled by Default)467- HTTPS can be enabled in the app settings for encrypted TLS communication468- When enabled, uses auto-generated self-signed certificates (or upload your own CA-signed certificate)469- Certificate is stored in app-private storage470- Server defaults to HTTP; enable HTTPS when operating on untrusted networks471472### Bearer Token Authentication473- Every MCP request requires `Authorization: Bearer <token>` header474- Token is auto-generated on first launch (UUID)475- Token can be viewed, copied, and regenerated in the app476- Constant-time comparison prevents timing attacks477478### Binding Address479- **Default `127.0.0.1`**: Only accessible via adb port forwarding (most secure)480- **Optional `0.0.0.0`**: Accessible over network (use only on trusted networks)481- Security warning dialog displayed when switching to network mode482483### Permissions484- **Accessibility Service**: Required for UI introspection, actions, and screenshots (user must enable manually)485- **Camera**: Required for camera photo/video tools (runtime permission, system dialog)486- **Record Audio**: Required for video recording with audio (runtime permission, system dialog)487- **Internet**: For running the HTTP/HTTPS server488- **Foreground Service**: For keeping services alive in background489- **Receive Boot Completed**: For auto-start on boot490- **Query All Packages**: For listing installed applications (`android_list_apps`)491- **Kill Background Processes**: For closing background applications (`android_close_app`)492- **Notification Listener**: Required for notification tools (user must enable manually in Settings > Notifications > Notification access)493- **Storage Access Framework**: Per-location authorization via system file picker (for file tools)494- No root access required495496---497498## Linting499500```bash501# Check for issues502make lint503504# Auto-fix issues505make lint-fix506```507508Uses ktlint for code style and detekt for static analysis.509510---511512## Contributing5135141. Fork the repository5152. Create a feature branch: `git checkout -b feat/your-feature`5163. Make your changes following the project conventions (see [docs/PROJECT.md](docs/PROJECT.md))5174. Ensure all checks pass: `make lint && make test-unit && make build`5185. Commit with descriptive messages (e.g., `feat: add new MCP tool for ...`)5196. Open a pull request520521### Development Conventions522523- **Language**: Kotlin with Android (Jetpack Compose, Ktor)524- **Architecture**: Service-based with SOLID principles525- **Testing**: JUnit 5 + MockK (unit), Ktor testApplication (JVM integration), Testcontainers (E2E)526- **Linting**: ktlint + detekt527- **DI**: Hilt (Dagger-based)528529See [docs/PROJECT.md](docs/PROJECT.md) for the complete project bible.530531---532533## License534535This project is licensed under the MIT License. See [LICENSE.md](LICENSE.md) for details.536
Full transparency — inspect the skill content before installing.