android-remote-control-mcp
Health Pass
- License — License: MIT
- Description — Repository has a description
- Active repo — Last push 0 days ago
- Community trust — 24 GitHub stars
Code Pass
- Code scan — Scanned 4 files during light audit, no dangerous patterns found
Permissions Pass
- Permissions — No dangerous permissions requested
This application is an MCP server that runs directly on an Android device, enabling connected AI models to remotely view the screen and fully control the UI via touch gestures, text input, and accessibility services. It also supports automated Cloudflare and ngrok tunneling for remote access.
Security Assessment
By design, the tool requires extensive, highly sensitive permissions to function. It explicitly captures screenshots, reads the full UI hierarchy (which can include sensitive text and passwords), and executes global device actions. It runs a local HTTP/HTTPS server that an AI connects to, inherently making network requests. While the automated scan (4 files) found no hardcoded secrets or malicious code patterns, and the app offers optional bearer token authentication, exposing it via public tunnels carries significant risk. Because it grants full remote control over a physical device, the overall risk is rated High.
Quality Assessment
The project appears to be actively developed, with its most recent push occurring today. It is properly licensed under the permissive MIT license. Community trust is currently low but growing, represented by 24 GitHub stars. The codebase uses standard Kotlin frameworks (Ktor + Netty) and includes CI pipelines for automated testing.
Verdict
Use with caution—while the code is safe, granting an AI full remote access to a physical Android device poses extreme inherent privacy and security risks, especially if exposed via public network tunnels.
An MCP Server for Android running on the phone, optmized for token usage, supports also files downloads and cloudflare (free) and ngrok automated tunnelling.
Android Remote Control MCP
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 the device — reading UI elements, tapping buttons, typing text, swiping, capturing screenshots, managing files, launching apps, and more.
⚠️ 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.
Features
MCP Server
- HTTP server running directly on Android (Ktor + Netty), with optional HTTPS
- Streamable HTTP transport at
/mcp(MCP specification compliant, JSON-only, no SSE) - Bearer token authentication (global, all requests)
- Auto-generated self-signed TLS certificates (or custom certificate upload)
- Configurable binding: localhost (127.0.0.1) or network (0.0.0.0)
- Auto-start on boot
- Remote access tunnels via Cloudflare Quick Tunnels or ngrok (public HTTPS URL)
54 MCP Tools across 12 Categories
All 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 for the full naming convention.
| Category | Tools | Description |
|---|---|---|
| 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 |
| 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 |
| Touch Actions (5) | android_tap, android_long_press, android_double_tap, android_swipe, android_scroll |
Coordinate-based touch interactions |
| Gestures (2) | android_pinch, android_custom_gesture |
Multi-touch and complex gestures |
| Node Actions (5) | android_find_nodes, android_click_node, android_long_click_node, android_tap_node, android_scroll_to_node |
Accessibility node-based interactions |
| 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 |
| 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 |
| 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) |
| App Management (3) | android_open_app, android_list_apps, android_close_app |
Launch, list, and close applications |
| 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 |
| Intent (2) | android_send_intent, android_open_uri |
Send explicit/implicit intents and open URIs via the system |
| 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 |
See docs/MCP_TOOLS.md for full tool documentation with input/output schemas and examples.
Android App
- Material Design 3 UI with tabbed layout (Server / Settings / About) and dark mode
- Server status monitoring (running/stopped) with permission warning banner
- Connection info display (IP, port, token, tunnel URL)
- Per-tool and per-parameter permissions (enable/disable individual MCP tools)
- Permission management (Accessibility, Notifications, Camera, Microphone)
- Remote access tunnel configuration (Cloudflare / ngrok)
- Storage location management (SAF authorization for file tools)
- Server log viewer (MCP tool calls, tunnel events)
- Headless setup via ADB (configure, grant permissions, start/stop server without UI)
Comparison with Alternatives
| Feature | This project | mobile-mcp | Android-MCP | android-mcp-server | adb-mcp | droidrun-mcp |
|---|---|---|---|---|---|---|
| MCP tools | 54 | 21 | 11 | 5 | 10 | 11 |
| Runs on the phone (no ADB) | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ |
| Action latency | 10-100 ms | 1-4 s | 1-4 s | 1-4 s | 1-4 s | 1-4 s |
| Works over the internet | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ |
| Token-efficient screen state | ✅ | ❌ | ✅ | ❌ | ❌ | ✅ |
| Annotated screenshots | ✅ | ❌ | ✅ | ❌ | ❌ | ❌ |
| Configurable screenshot resolution/quality | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ |
| Per-tool enable/disable | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ |
| Multi-device support | ✅ | ✅ | ❌ | ❌ | ❌ | ❌ |
| Camera, clipboard, files, downloads | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ |
| iOS support | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ |
Most 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.
On 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.
Requirements
For Building
- JDK 17 (e.g., Eclipse Temurin)
- Android SDK with API 34 (Android 14)
- Gradle 8.x (wrapper included, no global install needed)
For Running
- Android device or emulator running Android 13+ (API 33+), targeting Android 14 (API 34)
- adb (Android Debug Bridge) for device/emulator management
For E2E Tests
- Podman (rootful, for
redroid/redroidAndroid container image)
Check all dependencies:
make check-deps
Quick Start
1. Build the App
git clone https://github.com/danielealbano/android-remote-control-mcp.git
cd android-remote-control-mcp
make build
2. Install on Device/Emulator
# Start an emulator (if no device connected)
make setup-emulator
make start-emulator
# Install the debug APK
make install
3. Configure Permissions
- 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+). - Grant Camera & Microphone (optional, for camera tools): Grant via the app UI or
make grant-permissions.
4. Start the MCP Server
Tap the "Start Server" button in the app. The server starts on http://127.0.0.1:8080 by default (HTTPS is disabled by default).
5. Connect from Host Machine
Set up port forwarding (if server is bound to localhost):
make forward-port
Test the connection:
curl -X POST http://localhost:8080/mcp \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","id":1,"method":"ping"}'
6. Make MCP Tool Calls
All requests are sent as JSON-RPC 2.0 via POST /mcp (Streamable HTTP transport):
# List available tools
curl -X POST http://localhost:8080/mcp \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'
# Get the current screen state (UI nodes + optional screenshot)
curl -X POST http://localhost:8080/mcp \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"android_get_screen_state","arguments":{}}}'
# Tap at coordinates
curl -X POST http://localhost:8080/mcp \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"android_tap","arguments":{"x":540,"y":1200}}}'
The bearer token is displayed in the app's connection info section. You can copy it directly from the app.
Building
Debug Build
make build
# APK: app/build/outputs/apk/debug/app-debug.apk
Release Build
make build-release
# APK: app/build/outputs/apk/release/app-release.apk
For signed release builds, create keystore.properties in the project root:
storeFile=path/to/your.keystore
storePassword=your_store_password
keyAlias=your_key_alias
keyPassword=your_key_password
Clean Build
make clean
Testing
Unit Tests
make test-unit
Runs 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.
Integration Tests
make test-integration
Runs 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.
Note: Some integration tests (e.g.,
NgrokTunnelIntegrationTest) require environment variables. Copy.env.exampleto.envand fill in the required values. The Makefile sources.envautomatically.
E2E Tests
make test-e2e
Requires rootful Podman. Starts a redroid Android container via Podman, installs the app, and performs real MCP tool calls. Includes:
- Calculator test: 7 + 3 = 10 via MCP tools (verifies full stack)
- Screenshot test: Capture with different quality settings
- Error handling test: Authentication, unknown tools, invalid params
All Tests
make test
Code Coverage
make coverage
Generates a Jacoco HTML report at app/build/reports/jacoco/jacocoTestReport/html/index.html. Minimum coverage target: 80%.
Architecture
The application is a service-based Android app with three main components:
- McpAccessibilityService - UI introspection, action execution, and screenshot capture via Android Accessibility APIs (
takeScreenshot()on Android 11+) - McpServerService - Foreground service running the Ktor HTTP/HTTPS server
- MainActivity - Jetpack Compose UI for configuration and control
graph TB
Client["MCP Client (AI Model)"]
Client -->|"HTTP/HTTPS POST /mcp + Bearer Token"| McpServer
subgraph Device["Android Device"]
subgraph McpServerService["McpServerService (Foreground Service)"]
McpServer["McpServer (Ktor)"]
McpServer -->|"Streamable HTTP /mcp"| SDK["SDK Server (MCP Kotlin SDK)"]
SDK -->|"54 MCP Tools"| Tools["Tool Handlers"]
TunnelMgr["TunnelManager (optional)"]
TunnelMgr -->|"Cloudflare / ngrok"| PublicURL["Public HTTPS URL"]
end
subgraph Accessibility["McpAccessibilityService"]
TreeParser["AccessibilityTreeParser"]
ElemFinder["ElementFinder"]
ActionExec["ActionExecutor"]
ScreenEnc["ScreenshotEncoder"]
end
subgraph Storage["Storage & App Services"]
StorageProv["StorageLocationProvider"]
FileOps["FileOperationProvider"]
AppMgr["AppManager"]
end
subgraph CameraSvc["Camera Services"]
CamProv["CameraProvider\n(CameraX)"]
end
subgraph IntentSvc["Intent Services"]
IntentDisp["IntentDispatcher"]
end
subgraph NotifSvc["Notification Services"]
NotifProv["NotificationProvider"]
NotifListener["McpNotificationListenerService"]
end
MainActivity["MainActivity (Compose UI)"]
Tools --> Accessibility
Tools --> Storage
Tools --> CameraSvc
Tools --> IntentSvc
Tools --> NotifSvc
MainActivity -->|"StateFlow (status)"| McpServerService
end
See docs/ARCHITECTURE.md for detailed architecture documentation.
Configuration
Server Settings (via App UI)
| Setting | Default | Description |
|---|---|---|
| Port | 8080 |
HTTP/HTTPS server port |
| Binding Address | 127.0.0.1 |
127.0.0.1 (localhost, use with adb port forwarding) or 0.0.0.0 (network, all interfaces) |
| Bearer Token | Auto-generated UUID | Authentication token for MCP requests |
| HTTPS | Disabled | Enable HTTPS with auto-generated self-signed certificate (configurable hostname) or upload custom .p12/.pfx |
| Auto-start on Boot | Disabled | Start MCP server automatically when device boots |
| Device Slug | Empty | Optional device identifier for tool name prefix (e.g., pixel7 makes tools android_pixel7_tap) |
| Remote Access Tunnel | Disabled | Expose server via public HTTPS URL (Cloudflare Quick Tunnels or ngrok) |
| Tool Permissions | All enabled | Per-tool and per-parameter enable/disable (Settings > MCP Tools) |
| File Size Limit | 50 MB | Maximum file size for file operations (range 1-500 MB) |
| Allow HTTP Downloads | Disabled | Allow non-HTTPS downloads via android_download_from_url |
| Download Timeout | 60 seconds | Timeout for file downloads (range 10-300 seconds) |
Headless Setup via ADB
The 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.
Replace <app-id> with the application ID for your build:
- Debug:
com.danielealbano.androidremotecontrolmcp.debug - Release:
com.danielealbano.androidremotecontrolmcp
Grant Permissions
# Enable Accessibility Service (required for UI introspection, actions, and screenshots)
adb shell settings put secure enabled_accessibility_services \
<app-id>/com.danielealbano.androidremotecontrolmcp.services.accessibility.McpAccessibilityService
# Grant notification permission (Android 13+)
adb shell pm grant <app-id> android.permission.POST_NOTIFICATIONS
# Grant camera permission
adb shell pm grant <app-id> android.permission.CAMERA
# Grant microphone permission
adb shell pm grant <app-id> android.permission.RECORD_AUDIO
Configure the App
All extras are optional — only the ones provided are updated. The app does not need to be open.
adb shell am broadcast \
-a com.danielealbano.androidremotecontrolmcp.ADB_CONFIGURE \
-n <app-id>/com.danielealbano.androidremotecontrolmcp.services.mcp.AdbConfigReceiver \
--es bearer_token "my-secret-token" \
--es binding_address "0.0.0.0" \
--ei port 8080 \
--ez auto_start_on_boot true \
--ez https_enabled false \
--es certificate_source "AUTO_GENERATED" \
--es certificate_hostname "mcp.local" \
--ez tunnel_enabled false \
--es tunnel_provider "CLOUDFLARE" \
--es ngrok_authtoken "your-ngrok-token" \
--es ngrok_domain "your-domain.ngrok-free.app" \
--ei file_size_limit_mb 50 \
--ez allow_http_downloads false \
--ez allow_unverified_https_certs false \
--ei download_timeout_seconds 60 \
--es device_slug "pixel8" \
--es tool_permissions '{"disabled_tools":["tap"],"disabled_params":{"swipe":["duration_ms"]}}'
| Extra | Type | Description |
|---|---|---|
bearer_token |
string | Authentication token for MCP requests |
binding_address |
string | 127.0.0.1 (localhost) or 0.0.0.0 (network) |
port |
int | HTTP/HTTPS server port (1-65535) |
auto_start_on_boot |
boolean | Start MCP server when device boots |
https_enabled |
boolean | Enable HTTPS with TLS |
certificate_source |
string | AUTO_GENERATED or CUSTOM |
certificate_hostname |
string | Hostname for auto-generated certificate |
tunnel_enabled |
boolean | Enable remote access tunnel |
tunnel_provider |
string | CLOUDFLARE or NGROK |
ngrok_authtoken |
string | ngrok authentication token |
ngrok_domain |
string | ngrok custom domain (optional) |
file_size_limit_mb |
int | Max file size for file operations (1-500) |
allow_http_downloads |
boolean | Allow non-HTTPS downloads |
allow_unverified_https_certs |
boolean | Allow unverified HTTPS certificates for downloads |
download_timeout_seconds |
int | Download timeout (10-300) |
device_slug |
string | Device identifier for tool name prefix |
tool_permissions |
string (JSON) | Per-tool and per-parameter permissions: {"disabled_tools":["tool_name"],"disabled_params":{"tool_name":["param"]}} |
Start the MCP Server
The 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.
adb shell am start \
-n <app-id>/com.danielealbano.androidremotecontrolmcp.services.mcp.AdbServiceTrampolineActivity \
--es action start
Stop the MCP Server
adb shell am start \
-n <app-id>/com.danielealbano.androidremotecontrolmcp.services.mcp.AdbServiceTrampolineActivity \
--es action stop
Using with adb Port Forwarding (Recommended)
When the server is bound to 127.0.0.1 (default, most secure):
# Forward device port to host
adb forward tcp:8080 tcp:8080
# Test connection from host
curl -X POST http://localhost:8080/mcp \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","id":1,"method":"ping"}'
Using over Network
When the server is bound to 0.0.0.0:
- Find the device's IP address (shown in the app's connection info)
- Connect directly via
POST http://DEVICE_IP:8080/mcpwith bearer token
Warning: Binding to 0.0.0.0 exposes the server to all devices on the same network. Only use on trusted private networks.
Using Remote Access Tunnels
For connecting from outside the local network without port forwarding:
- Cloudflare Quick Tunnels (default, no account required): Creates a temporary tunnel with a random
*.trycloudflare.comHTTPS URL. - ngrok (account required): Supports optional custom domains. Requires an ngrok authtoken (free tier available). Only available on ARM64 devices.
Enable the tunnel in the app's "Remote Access" section. The public URL is displayed in the connection info and server logs.
Security
HTTPS (Optional, Disabled by Default)
- HTTPS can be enabled in the app settings for encrypted TLS communication
- When enabled, uses auto-generated self-signed certificates (or upload your own CA-signed certificate)
- Certificate is stored in app-private storage
- Server defaults to HTTP; enable HTTPS when operating on untrusted networks
Bearer Token Authentication
- Every MCP request requires
Authorization: Bearer <token>header - Token is auto-generated on first launch (UUID)
- Token can be viewed, copied, and regenerated in the app
- Constant-time comparison prevents timing attacks
Binding Address
- Default
127.0.0.1: Only accessible via adb port forwarding (most secure) - Optional
0.0.0.0: Accessible over network (use only on trusted networks) - Security warning dialog displayed when switching to network mode
Permissions
- Accessibility Service: Required for UI introspection, actions, and screenshots (user must enable manually)
- Camera: Required for camera photo/video tools (runtime permission, system dialog)
- Record Audio: Required for video recording with audio (runtime permission, system dialog)
- Internet: For running the HTTP/HTTPS server
- Foreground Service: For keeping services alive in background
- Receive Boot Completed: For auto-start on boot
- Query All Packages: For listing installed applications (
android_list_apps) - Kill Background Processes: For closing background applications (
android_close_app) - Notification Listener: Required for notification tools (user must enable manually in Settings > Notifications > Notification access)
- Storage Access Framework: Per-location authorization via system file picker (for file tools)
- No root access required
Linting
# Check for issues
make lint
# Auto-fix issues
make lint-fix
Uses ktlint for code style and detekt for static analysis.
Contributing
- Fork the repository
- Create a feature branch:
git checkout -b feat/your-feature - Make your changes following the project conventions (see docs/PROJECT.md)
- Ensure all checks pass:
make lint && make test-unit && make build - Commit with descriptive messages (e.g.,
feat: add new MCP tool for ...) - Open a pull request
Development Conventions
- Language: Kotlin with Android (Jetpack Compose, Ktor)
- Architecture: Service-based with SOLID principles
- Testing: JUnit 5 + MockK (unit), Ktor testApplication (JVM integration), Testcontainers (E2E)
- Linting: ktlint + detekt
- DI: Hilt (Dagger-based)
See docs/PROJECT.md for the complete project bible.
License
This project is licensed under the MIT License. See LICENSE.md for details.
Reviews (0)
Sign in to leave a review.
Leave a reviewNo results found