android-remote-control-mcp

mcp
Security Audit
Pass
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
Purpose
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.
SUMMARY

An MCP Server for Android running on the phone, optmized for token usage, supports also files downloads and cloudflare (free) and ngrok automated tunnelling.

README.md

Android Remote Control MCP

CI
License: MIT

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/redroid Android 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

  1. 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+).
  2. 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.example to .env and fill in the required values. The Makefile sources .env automatically.

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:

  1. McpAccessibilityService - UI introspection, action execution, and screenshot capture via Android Accessibility APIs (takeScreenshot() on Android 11+)
  2. McpServerService - Foreground service running the Ktor HTTP/HTTPS server
  3. 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:

  1. Find the device's IP address (shown in the app's connection info)
  2. Connect directly via POST http://DEVICE_IP:8080/mcp with 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:

  1. Cloudflare Quick Tunnels (default, no account required): Creates a temporary tunnel with a random *.trycloudflare.com HTTPS URL.
  2. 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

  1. Fork the repository
  2. Create a feature branch: git checkout -b feat/your-feature
  3. Make your changes following the project conventions (see docs/PROJECT.md)
  4. Ensure all checks pass: make lint && make test-unit && make build
  5. Commit with descriptive messages (e.g., feat: add new MCP tool for ...)
  6. 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)

No results found