droid-mcp
Health Pass
- License — License: Apache-2.0
- Description — Repository has a description
- Active repo — Last push 0 days ago
- Community trust — 19 GitHub stars
Code Pass
- Code scan — Scanned 5 files during light audit, no dangerous patterns found
Permissions Pass
- Permissions — No dangerous permissions requested
No AI report is available for this listing yet.
SDK for Android — gives local LLMs structured access to phone data (calendar, contacts, SMS, files). Everything stays on device.
droid-mcp
Give your Android AI app access to the entire phone.
Calendar, contacts, SMS, camera, location, sensors, notification reply + push subscription, accessibility-driven UI control, IME typing, floating overlay, shell-UID admin via Shizuku, root-UID admin via libsu, and more — 145 tools across 53 modules.
droid-mcp is an Android SDK that gives LLMs typed, structured access to phone capabilities. It implements the Model Context Protocol (MCP) — the same standard used by Claude, Cursor, and other AI tools — so any MCP client can call your phone's APIs over a local network.
It also works without MCP. If you're building an on-device AI app, you can call tools directly as Kotlin functions.
// Build with the modules you need
val mcp = DroidMcp.builder()
.addTools(CalendarTools.all(context))
.addTools(ContactsTools.all(context))
.addTools(LocationTools.all(context))
.build()
// Your LLM decides to read the calendar
val result = mcp.callTool("read_calendar", mapOf("start_date" to "2026-04-12"))
// result.data = { "events": [...], "count": 3 }
// Or start an HTTP server and connect Claude Code over WiFi
mcp.startServer()
// Connect from desktop: http://<phone-ip>:8080/mcp
Why
On-device LLMs and AI agents are getting good, but they can't do much without access to the phone. droid-mcp bridges that gap:
- For on-device LLM apps — call tools directly from your model's output. No server needed.
- For desktop AI tools — connect Claude Code, Cursor, or any MCP client to your phone over WiFi.
- For agent builders — 145 pre-built, validated tools covering the full Android API surface. Skip the boilerplate.
Installation
Add the modules you need:
// settings.gradle.kts
dependencyResolutionManagement {
repositories {
maven { url = uri("https://jitpack.io") }
}
}
// build.gradle.kts
dependencies {
// Core (required)
implementation("com.github.stixez.droid-mcp:droid-mcp-core:0.10.1")
// Pick what you need
implementation("com.github.stixez.droid-mcp:droid-mcp-calendar:0.10.1")
implementation("com.github.stixez.droid-mcp:droid-mcp-contacts:0.10.1")
implementation("com.github.stixez.droid-mcp:droid-mcp-sms:0.10.1")
implementation("com.github.stixez.droid-mcp:droid-mcp-location:0.10.1")
implementation("com.github.stixez.droid-mcp:droid-mcp-camera:0.10.1")
implementation("com.github.stixez.droid-mcp:droid-mcp-mlkit:0.10.1")
implementation("com.github.stixez.droid-mcp:droid-mcp-accessibility:0.10.1")
implementation("com.github.stixez.droid-mcp:droid-mcp-ime:0.10.1")
// ... see full list below
// Or include everything (except Tier 4/5 power-user modules)
implementation("com.github.stixez.droid-mcp:droid-mcp-all:0.10.1")
// Power-user tiers — opt in only if you want them (they pull third-party deps)
implementation("com.github.stixez.droid-mcp:droid-mcp-shizuku:0.10.1") // Tier 4: shell-UID admin (pulls dev.rikka.shizuku)
implementation("com.github.stixez.droid-mcp:droid-mcp-root:0.10.1") // Tier 5: root-UID admin (pulls libsu)
}
Usage
On-device (direct calls)
val mcp = DroidMcp.builder()
.addTools(DeviceTools.all(context))
.addTools(CalendarTools.all(context))
.addTools(SmsTools.all(context))
.build()
// Get tool definitions as JSON — pass this to your LLM
val toolsJson = mcp.listToolsJson()
// Execute whatever tool the LLM picks
val result = mcp.callTool("send_message", mapOf(
"to" to "+1234567890",
"body" to "On my way!"
))
if (result.isSuccess) {
val data = result.data // Map<String, Any?>
} else {
val error = result.errorMessage
}
Desktop connection (MCP over HTTP)
Start the server on the phone:
val mcp = DroidMcp.builder()
.addTools(DeviceTools.all(context))
.addTools(CalendarTools.all(context))
.enableHttpServer(
port = 8080,
// token = null → auto-generated via SecureRandom (read it from mcp.serverToken)
// readOnly = true → hide destructive tools from clients
context = context, // enables mDNS broadcast on _mcp._tcp
)
.build()
mcp.startServer()
val token = mcp.serverToken // share via QR / pairing
Connect from Claude Code (~/.claude/settings.json):
{
"mcpServers": {
"my-phone": {
"type": "http",
"url": "http://<phone-ip>:8080/mcp",
"headers": {
"Authorization": "Bearer <token-from-mcp.serverToken>"
}
}
}
}
The phone broadcasts itself on the local network via mDNS (_mcp._tcp) — the sample app's pairing QR encodes the URL and token, so clients can scan and connect without copying the IP by hand.
Now Claude can read your calendar, search contacts, check battery level, and use any other registered tool — directly from your terminal.
Permission handling
The library never requests permissions. Your app stays in control:
if (CalendarTools.hasPermissions(context)) {
mcp.addTools(CalendarTools.all(context))
} else {
val needed = CalendarTools.requiredPermissions()
// Show your own permission UI
}
Modules
53 modules, 145 tools. Each module is independent — only the permissions for included modules are added to your manifest. Root (Tier 5) reuses the same 17 shell tools as Shizuku (Tier 4) via the shared ShellBackend interface.
The table below lists 48 of them (core plus the tool modules; overlay is listed too, though it exposes a programmatic API rather than LLM tools). The remaining five are infrastructure: two support modules (notification-listener, shell-core) that the listener-based and shell-based modules wire against, plus three opt-in hardening modules added in 0.10.0 (audit, tls, server-service) — see Hardening modules below.
| Module | Tools | Permissions |
|---|---|---|
| core | MCP protocol, transports | INTERNET |
| device | get_device_info get_battery_info get_connectivity get_storage_info |
ACCESS_NETWORK_STATE |
| calendar | read_calendar create_event search_events |
READ_CALENDAR WRITE_CALENDAR |
| contacts | search_contacts read_contact list_contacts |
READ_CONTACTS |
| sms | read_messages send_message search_messages |
READ_SMS SEND_SMS |
| files | browse_files read_file search_files |
READ_EXTERNAL_STORAGE (< API 33) |
| notifications | get_active_notifications |
None |
| calllog | read_call_log search_call_log |
READ_CALL_LOG |
| media | search_media get_media_metadata list_albums |
READ_MEDIA_IMAGES READ_MEDIA_VIDEO |
| location | get_current_location get_location_address |
ACCESS_FINE_LOCATION |
| health | get_step_count get_activity_info |
ACTIVITY_RECOGNITION |
| clipboard | read_clipboard write_clipboard |
None |
| apps | list_installed_apps get_app_info launch_app |
None |
| alarms | create_alarm create_timer create_reminder |
SET_ALARM READ_CALENDAR WRITE_CALENDAR |
| settings | get_settings set_brightness set_volume toggle_wifi |
WRITE_SETTINGS (write) CHANGE_WIFI_STATE |
| bluetooth | get_bluetooth_status list_paired_devices |
BLUETOOTH_CONNECT BLUETOOTH |
| wifi | get_wifi_info list_saved_networks |
ACCESS_WIFI_STATE ACCESS_NETWORK_STATE ACCESS_FINE_LOCATION |
| downloads | list_downloads search_downloads |
READ_EXTERNAL_STORAGE (< API 33) |
| screen | get_screen_state get_display_info |
None |
| tts | speak_text get_tts_info |
None |
| web | web_search fetch_webpage |
INTERNET |
| flashlight | toggle_flashlight set_flashlight_brightness |
CAMERA FLASHLIGHT |
| network | get_data_usage get_cellular_signal is_vpn_active |
ACCESS_NETWORK_STATE + PACKAGE_USAGE_STATS (special, for get_data_usage) |
| telephony | get_phone_number get_sim_info get_network_operator get_call_state |
READ_PHONE_STATE READ_SMS |
| vibration | vibrate vibrate_pattern |
VIBRATE |
| biometric | check_biometric_availability get_biometric_enrollments |
None |
| sensors | get_accelerometer get_gyroscope get_light_level get_proximity |
None |
| qr | scan_qr_code scan_barcode generate_qr_code |
CAMERA |
| camera | take_photo capture_video get_camera_capabilities |
CAMERA |
| audio | get_audio_devices |
None |
| nfc | get_nfc_status read_nfc_tag write_nfc_tag |
NFC |
| intent | send_intent share_content open_deep_link |
None |
| playback | get_now_playing media_control |
Notification Listener (special) |
| notifications-reply | list_repliable_notifications reply_to_notification dismiss_notification invoke_notification_action |
Notification Listener (special) |
| notification-watch | watch_notifications unwatch_notifications list_notification_watches (+ NotificationListenerBus SharedFlow API) |
Notification Listener (special) |
| accessibility | query_screen find_node wait_for_text click_node long_click_node set_node_text scroll_node gesture global_action get_active_window_info take_screenshot_via_a11y tap long_press find_and_tap scroll_to_find |
Accessibility Service (special) |
| ime | is_ime_active type_text commit_keystroke delete_text set_selection get_text_around_cursor switch_to_previous_ime |
Input Method enabled + selected (special) |
| overlay | (programmatic OverlayController only — no LLM tools) |
SYSTEM_ALERT_WINDOW (special) |
| shizuku | install_apk uninstall_app clear_app_data force_stop_app disable_app enable_app grant_permission revoke_permission list_app_permissions put_secure_setting put_global_setting put_system_setting get_top_window set_app_standby_bucket make_app_inactive capture_screen_quiet run_shell |
Shizuku service running + permission granted (special). Backed by ShellBackend interface shared with :droid-mcp-root. |
| root | (same 17 tools as shizuku, routed via su instead of Shizuku binder) |
Device rooted + host granted root via superuser manager (Magisk / KernelSU / SuperSU). Strictly more powerful: /system writes, pm hide, /data/data/<pkg> access. |
| screenshot | capture_screen |
MediaProjection (special) |
| dnd | get_dnd_status set_dnd_mode |
ACCESS_NOTIFICATION_POLICY + DND Access (special) |
| keyguard | get_lock_state get_keyguard_info |
None |
| wallpaper | get_wallpaper_info set_wallpaper |
SET_WALLPAPER |
| ringtone | list_ringtones get_active_ringtone set_ringtone |
WRITE_SETTINGS (special, write only) |
| usb | list_usb_devices get_usb_device_info |
None |
list_printers print_content |
None | |
| mlkit | recognize_text label_image detect_faces |
None (operates on local image files) |
Full parameter reference: docs/TOOLS.md. Generated API docs (KDoc): stixez.github.io/droid-mcp.
Hardening modules (0.10.0)
Three opt-in infrastructure modules harden the HTTP transport and operations. They expose no LLM tools — they're programmatic APIs the host wires in. Each pulls a third-party dependency, so all three stay outside :droid-mcp-all.
| Module | API | Dependency |
|---|---|---|
| audit | RoomAuditSink — persists every HTTP tools/call to a private Room DB (retention, observe(), exportJson(), clear()). Wire via DroidMcp.Builder.withAuditSink(...). The dependency-free AuditSink hook itself lives in core. |
Room + KSP |
| tls | SelfSignedCert.loadOrCreate(file) → a TlsConfig for DroidMcp.Builder.enableTls(...). Self-signed cert; clients pin DroidMcp.tlsFingerprint. |
BouncyCastle |
| server-service | DroidMcpServerService — abstract foreground service keeping the HTTP server alive across screen-off / task-killers. |
androidx.core |
Per-tool gating (DroidMcp.setToolEnabled / setDisabledTools) and token rotation / per-client pairing (rotateToken / pairClient / revokeClient) are built into core — no extra module. See docs/SECURITY.md for the threat model.
Capability tiers
Modules are grouped into five tiers by the kind of permission they need. Tiers 1–3 are the core surface; Tiers 4–5 are opt-in power tools for hosts that need shell-UID or root-UID admin — they pull third-party native dependencies and stay outside :droid-mcp-all to keep the default APK lean.
| Tier | What | Modules |
|---|---|---|
| 1. Runtime / install-time perms | Standard <uses-permission> grants |
device calendar contacts sms files media location calllog health apps alarms settings bluetooth wifi downloads screen tts web flashlight network telephony vibration biometric sensors qr camera audio nfc intent clipboard notifications keyguard wallpaper usb print mlkit |
| 2. Notification Listener | Settings > Apps > Special access > Notification access | playback notifications-reply notification-watch |
| 3. Accessibility / IME / Overlay / DND / Ringtone / Screenshot | Per-feature Settings toggles | accessibility ime overlay dnd ringtone screenshot |
| 4. Shell-UID (opt-in) | Shizuku activated + permission granted to host app | shizuku (pulls dev.rikka.shizuku; see docs/SHIZUKU.md) |
| 5. Root-UID (opt-in) | Device rooted + superuser-manager grant | root (pulls com.github.topjohnwu.libsu; see docs/ROOT.md) |
Special permissions
Some modules require permissions that can't be requested at runtime. The tools work without them for read operations, and return clear error messages for write operations that need the grant.
| Module | Permission | How to grant |
|---|---|---|
| playback | Notification Listener | Settings > Apps > Special access > Notification access |
| notifications-reply | Notification Listener (service must extend McpNotificationListenerServiceBase) |
Settings > Apps > Special access > Notification access |
| notification-watch | Notification Listener (shares listener service with notifications-reply) |
Settings > Apps > Special access > Notification access |
| accessibility | Accessibility Service (service must extend DroidMcpAccessibilityService) |
Settings > Accessibility > Installed apps |
| ime | Input Method enabled + selected (service must extend DroidMcpInputMethodService) |
Settings > System > Languages & input > On-screen keyboard, plus the IME picker |
| overlay | Draw over other apps | Settings > Apps > Special access > Display over other apps |
| shizuku | Shizuku service running + permission granted to host app | Install Shizuku, activate via wireless debugging (Android 11+) or ADB, grant the runtime permission. See docs/SHIZUKU.md. |
| root | Device rooted + superuser manager grants root to host app | Root via Magisk / KernelSU / SuperSU; on first Shell.cmd(...) call the manager surfaces a permission prompt. See docs/ROOT.md. |
| screenshot | MediaProjection | Host app calls MediaProjectionManager.createScreenCaptureIntent() and passes result to MediaProjectionHolder.set() |
| dnd | DND Access (for set_dnd_mode) |
Settings > Apps > Special access > Do Not Disturb access |
| ringtone | WRITE_SETTINGS (for set_ringtone) |
Settings > Apps > Special access > Modify system settings |
| network | PACKAGE_USAGE_STATS (for get_data_usage) |
Settings > Apps > Special access > Usage access |
Architecture
Your App
|
+--------+--------+
| |
On-device LLM Desktop MCP Client
| (Claude Code, etc.)
v |
InProcessTransport v
| HttpTransport
| (Ktor/Netty)
+--------+--------+
|
ToolRegistry
|
+-----+-----+----+----+-----+-----+
| | | | | | |
Device Cal SMS Files Loc Camera ...
Tools Tools Tools Tools Tools Tools
| | | | | | |
Android System APIs
Safety
| Feature | How |
|---|---|
| Path sandboxing | File tools restricted to external storage. System paths rejected. |
| Input validation | All params validated and clamped. Phone numbers checked before SMS send. |
| Permission isolation | Each module declares only its own permissions. Library never triggers permission requests. |
| Network security | HTTP server on local network only. Bearer token auth required by default — auto-generated via SecureRandom if not supplied (accessible via DroidMcp.serverToken). |
| Read-only mode | enableHttpServer(readOnly = true) filters destructive tools from tools/list and rejects tools/call for non-read-only tools. |
| Tool annotations | Every tool advertises readOnlyHint / destructiveHint / idempotentHint per MCP spec so clients can decide which to expose. |
| No telemetry | No analytics, crash reporting, or phone-home calls. The web module accesses the internet only when explicitly invoked by the LLM. |
Requirements
- Android 9+ (API 28)
- Kotlin 2.0+
- Gradle 8.12+
Sample App
The sample-app module includes a Compose UI that exercises the full tool surface — every module is registered (subject to permission availability) with quick-test buttons for representative tools in each category. Categories that require special permissions show a "Grant Access" button that opens the relevant system settings page. Start the HTTP server from the app to connect desktop MCP clients — pair via the QR code or copy the bearer token shown on the home screen. See docs/PAIRING.md.
It also demonstrates the 0.10.0 hardening features end-to-end:
- Tools tab — quick-test buttons per category.
- Gating tab — a per-tool switch grid (with filter + bulk enable/disable) that toggles tools off the live
tools/list/tools/callsurface without restarting the server. - Audit tab — browse the persisted
RoomAuditSinklog of HTTP calls (tool, client, outcome, duration, arguments), clear it, or export to JSON. - TLS toggle — serve over HTTPS with a self-signed cert; the SHA-256 fingerprint is shown (copyable) and folded into the pairing QR for the client to pin.
- The server runs inside a foreground service (
DroidMcpServerService) so it survives screen-off / backgrounding.
Contributing
Contributions welcome. Please open an issue before submitting a PR for non-trivial changes.
License
Copyright 2026
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Reviews (0)
Sign in to leave a review.
Leave a reviewNo results found