Control your Android device with AI via MCP (Model Context Protocol)
概览
Your AI CoPilot on Mobile — or give your AI its own phone Features • Quick Start • Usage • Integrations • MCP Tools bridges any Android device to AI assistants like Claude through the Model Context Protocol (MCP). Use it as your AI CoPilot on mobile — or plug a spare Android into a charger, install Aster, and give your AI its own device. It can call you, text you, monitor notifications, and act on its own. Screenshots, UI automation, file management, media search, and 49 tools — all through natural language.
README
Aster
by OpenAlly™
Your AI CoPilot on Mobile — or give your AI its own phone
Website • Features • Quick Start • Usage • Integrations • MCP Tools
Aster bridges any Android device to AI assistants like Claude through the Model Context Protocol (MCP). Use it as your AI CoPilot on mobile — or plug a spare Android into a charger, install Aster, and give your AI its own device. It can call you, text you, monitor notifications, and act on its own. Screenshots, UI automation, file management, media search, and 49 MCP tools — all through natural language.
Every screen ships in both themes — dark on the top row, light on the bottom. Aster follows your device setting.
Features
- Screen Control — Take screenshots, analyze UI hierarchy, tap, swipe, and type
- App Automation — Launch apps, click buttons by text/ID, navigate with gestures
- Device Access — Read notifications, SMS, clipboard, location, and battery info
- File Management — Browse, read, write, and delete files on device storage
- Media Search — Find photos and videos using natural language queries
- Calls, SMS & Voice — Make calls, send SMS, or use
make_call_with_voiceto dial and speak a TTS message on speakerphone - System Actions — Back, Home, Recents, volume control, and more
- Camera & Video — Take photos or record video remotely — pet cam, baby monitor, security checks
- Proactive Events — Your AI reacts to incoming SMS, notifications, and device events in real-time via webhooks
- AI’s Own Phone — Dedicate a spare Android to your AI. It monitors, calls, texts, and acts on its own
- App Automations — Record a flow on-device (taps, text, scrolls) with the recording overlay, replay it as an automation
- Companion Face — An animated on-screen companion that talks along with TTS and reacts to your music, built on a privacy-focused event classifier
- Safety Rails — A persistent kill-switch notification while the AI controls the screen, and a fail-closed package policy that blocks control of banking apps by default
What’s new
The last few releases (v1.3 → v1.7) added a lot:
- Companion face overlay — an animated companion with speech articulation and music reactivity (
get_now_playing), driven by a pulse classifier that keeps raw accessibility events on-device - App Automations — interactive overlay support plus an on-device recorder that captures taps and scroll steps for replay
- Screen-control kill switch + PackagePolicyGuard — a STOP notification during control sessions and a fail-closed denylist for financial apps
- Two connection modes beyond the server: on-device MCP server (Ktor, Streamable HTTP) and IPC (Binder) for same-device agents like OpenAlly
- Owner-approved folders — the AI can read/list only directories you’ve explicitly shared
- Deeper device data — full contacts paging, installed apps with icons, and SMS date-window reads (
sinceDate) - Multi-window observation — a two-bucket element budget so dialogs and split-screen apps are seen correctly, with a guaranteed reading order
- “Ask all together” — one tap on the Permissions screen walks every remaining grant in a guided flow
Web Dashboard
The server ships with a web dashboard on :5989 — device registry and approvals, live telemetry, click-through screen control, filtered logs, a file browser, panels for messages, apps, storage and media, and an explorer for all 49 MCP tools.
Every page ships in both themes — dark on the top row, light on the bottom. The dashboard follows your system setting.
What the dashboard covers
| Page | What it does |
|---|---|
| Overview | Device counts, online state, recent activity, server health |
| Devices | Registry with approve / reject / un-reject / delete, live online state |
| Device detail | Full telemetry — ABIs, build tags, radio, RAM, storage, display, uptime, live battery |
| Screen control | Live screenshot, click-through tapping, gestures, text input, global actions, element search |
| Messages | Read and send SMS |
| Notifications | Read the notification stream, post notifications |
| Apps | Installed-app inventory with paging, package list, launch by intent |
| Storage & media | Storage analytics, large-file finder, media indexing, natural-language media search |
| Contacts / Alarms / Audio / Location / Shell / Utilities | The remaining tool families as purpose-built panels |
| Logs | Level, device and full-text filters, pagination, structured event payloads |
| Connect | MCP, dashboard, WebSocket and Tailscale URLs with a ready-to-paste .mcp.json |
| Tools | Every registered MCP tool, grouped exactly as the Android app groups them |
Run it with aster start (or aster dashboard to open it), then visit .
Quick Start
1. Install the MCP Server
npm install -g aster-mcp
2. Install the Android App
Download the Aster companion app from Releases and install it on your Android device — your daily phone or a spare one you want to dedicate to your AI.
3. Start the Server
aster start
4. Connect Your Device
Open the Aster app on your Android device and enter the server address shown in the terminal. For a dedicated AI phone, just plug it into a charger and leave it connected — your AI now has its own device.
5. Configure Your AI Client
See Integrations for Claude, OpenClaw, Moltbot, Clawbot, and other MCP clients.
Usage
CLI Commands
aster start # Start the server (with optional event forwarding setup)
aster stop # Stop the server
aster status # Show server and device status
aster dashboard # Open web dashboard
aster devices list # List connected devices
aster devices approve # Approve a pending device
aster devices reject # Reject a device
aster devices remove # Remove a device
aster set-event-forwarding # Configure proactive event forwarding
# (alias: aster set-openclaw-callbacks)
Status & Health Checks
Need to confirm the server is up before pointing an agent at it? Three surfaces:
aster status # human-readable server + device snapshot
curl http://localhost:5988/api/health # -> { "status": "ok", "timestamp": ... }
curl http://localhost:5988/api/stats # device counts
aster status reads ~/.aster/status.json, written by the running server. The /api/health endpoint is the one to poll from scripts and monitors.
Example Prompts
Once connected, try these with your AI assistant:
Your CoPilot on Mobile:
“Take a screenshot of my phone”
“Open YouTube and search for cooking videos”
“Read my latest notifications”
“Find photos from my trip to Mumbai last month”
“What apps are using the most storage?”
AI’s own phone — let it act for you:
“Call me if my flight gets delayed and tell me the new time” (uses make_call_with_voice)
“Text me when my delivery arrives” (uses send_sms)
“Back up new photos to Google Drive every night”
Proactive Event Forwarding
Aster can push real-time events from the phone to your AI agent via webhook. Your AI reacts the moment something happens — no polling needed.
| Event | Example |
|---|---|
| SMS | Auto-reply while driving, forward messages, extract info |
| Notifications | Flight delays, delivery updates, ride arrivals — instant alerts |
| Device status | Device online/offline, new pairing requests |
| Incoming call | Phone rings — log it, ping you, or wait for pickup |
Works out of the box with OpenClaw, ClawdBot, and MoltBot. Configure via dashboard or CLI:
aster set-event-forwarding
Mattermost incoming webhooks
Aster can POST the same tagged event text to a Mattermost incoming webhook ({ "text": "..." }, no Bearer token).
- In Mattermost go to Integrations → Incoming Webhooks and add a webhook for the channel that should receive Aster events.
- Copy the webhook URL (
https:///hooks/). - In the Aster dashboard Event Forwarding settings, set Channel type to Mattermost and paste the webhook URL. OpenClaw endpoint, token, and WhatsApp/Telegram delivery fields stay hidden.
- Optional: set a Mattermost channel name to override the webhook default. Leave it empty to use the webhook’s channel. Do not reuse
whatsapp/telegramhere — those values are ignored for Mattermost.
events.incomingCalls defaults to on. A missing key does not drop RINGING events; only an explicit off does.
Sample OpenClaw hooks
Aster POSTs to {endpoint}{webhookPath} (default http://localhost:18789/hooks/agent) with Authorization: Bearer :
{
"message": "[skill] aster\n[event] incoming_call\n[device_id] …\n[model] …\n[data-number] +15551212\n[data-contact] Jane",
"wakeMode": "now",
"deliver": true,
"channel": "whatsapp",
"to": "+15550001111"
}
Enable the matching hook on the OpenClaw gateway (token must match the one saved in Aster):
{
"hooks": {
"enabled": true,
"token": "",
"path": "/hooks"
}
}
Integrations
Claude Code / Claude Desktop
Add to your .mcp.json or Claude settings:
{
"mcpServers": {
"aster": {
"type": "http",
"url": "http://localhost:5988/mcp"
}
}
}
OpenClaw / Moltbot / Clawbot
Aster is available as a skill on ClawHub. Install it directly:
clawhub install aster
Or add manually to your OpenClaw/Moltbot/Clawbot skills directory and configure the MCP endpoint.
Any MCP-Compatible Client
Aster exposes a standard MCP HTTP endpoint at http://localhost:5988/mcp that works with any MCP-compatible AI client.
Which URL goes where (topologies)
The most common setup mistake is pasting the wrong port into the wrong app. Rule of thumb: :5987 is for the phone, :5988/mcp is for your AI client, :5989 is the dashboard in your browser.
| Topology | aster server runs on |
Android app connects to | MCP client connects to |
|---|---|---|---|
| Same machine (recommended) | your PC / Mac / NAS | ws://:5987 |
http://localhost:5988/mcp |
| LAN, separate machines | PC / Mac / NAS | ws://:5987 |
http://:5988/mcp |
| Tailscale | any tailnet box | wss:// (via tailscale serve) or ws://:5987 |
http://:5988/mcp |
| Everything on the phone | — none needed | n/a | Use the app’s Local MCP Server mode (see Connection Modes) and point the client at http://:8080/mcp |
Running the npm server itself inside Termux on the phone is untested and unsupported — the Local MCP mode exists precisely so you don’t have to.
MCP Tools
49 tools, exposed over MCP at http://localhost:5988/mcp. Each does one job your AI can call by name:
| Category | Tools | What they do |
|---|---|---|
| Screen | take_screenshot, get_screen_hierarchy, find_element |
Capture the screen, read the live UI tree, locate elements |
| Input | input_gesture, input_text, click_by_text, click_by_id |
Tap, swipe, type, and click elements by visible text or ID |
| Navigation | global_action, launch_intent |
Back / Home / Recents / Notifications / power & lock, and launch apps or deep links |
| Device | list_devices, get_device_info, get_battery, get_location |
Enumerate devices and read status, battery, GPS |
| Notifications & SMS | read_notifications, read_sms, send_sms, post_notification |
Read notifications and SMS, send texts, push a notification |
| Calls | make_call, make_call_with_voice |
Dial a number — optionally speak a TTS message on speakerphone |
| Contacts | search_contacts, list_contacts_full, delete_contacts, delete_contacts_verified |
Look up, page through, and delete address-book entries (delete_contacts_verified re-checks each name first; it is what OpenAlly sends after its owner approves a clean-up) |
| Files | list_files, read_file, write_file, delete_file |
Browse and manage on-device storage |
| Storage & Media | analyze_storage, find_large_files, index_media_metadata, search_media |
Audit space, find big files, index EXIF, search photos/videos by query |
| Camera | take_photo, record_video |
Capture a photo or a short (max 8s) clip remotely |
| Audio | speak_tts, play_audio, stop_audio, vibrate, get_volume, set_volume |
Speak text, play/stop audio, buzz, read and set stream volumes |
| Clipboard | get_clipboard, set_clipboard |
Read and write the device clipboard |
| Alarms | get_alarms, set_alarm, dismiss_alarm, delete_alarm |
List, create, dismiss, and delete clock alarms |
| UI overlay | show_overlay, show_toast |
Render a web overlay or a toast on the device |
| Apps & shell | list_packages, list_installed_apps, execute_shell |
List installed apps (with metadata); run a shell command in the app’s own unprivileged sandbox — no root, with 30s / 1MB limits |
Architecture
┌─────────────────┐ WebSocket ┌─────────────────┐
│ │◄──────────────────►│ │
│ Aster Server │ │ Android App │
│ (Node.js) │ │ (Accessibility │
│ │ │ Service) │
└────────┬────────┘ └─────────────────┘
│
│ MCP (HTTP)
│
┌────────▼────────┐
│ │
│ Claude / AI │
│ │
└─────────────────┘
Connection Modes
The diagram above is the default path, but it’s not the only one. The companion app speaks three transports — pick one per device:
| Mode | How a client reaches the phone | Use it when |
|---|---|---|
| Remote WebSocket (default) | The phone dials out to the aster Node server; your AI talks MCP-over-HTTP to that server (the flow above). |
You run the server on a laptop / home box and point any MCP client at it. |
| Local MCP Server | The phone runs its own MCP server on-device — embedded Ktor + the MCP Kotlin SDK (Streamable HTTP), default port 8080. No Node server in the middle. |
You want an MCP client (local, or remote over Tailscale) to hit the phone directly. |
| IPC (Binder) | An app on the same device — e.g. the OpenAlly.ai app — drives Aster over Android Binder IPC, with 32-char token auth (constant-time check) plus an on-device approval prompt. Never touches the network. | An on-device agent (like OpenAlly.ai) controls the phone locally — lowest latency, fully offline. |
All three run on the same CommandHandler registry, but they do not expose the same catalogue. Remote WebSocket goes through the Node server, which registers 49 tools, every one prefixed aster_. Local MCP Server and IPC talk to the handler map directly and expose 78 unprefixed actions instead — a different set, not a superset: 47 names overlap, 30 on-device actions have no aster_* equivalent (observe, tap, scroll, the screen_* human-in-the-loop verbs, the overlay and companion-face verbs, and delete_contacts_verified), and exactly one server tool has no on-device action behind it (aster_list_devices, which brokers between phones and so means nothing on the phone itself). Full catalogues: . They also differ in how a client reaches the phone and how it’s trusted: the server’s device-approval gate (Remote WS), a token + approval prompt (IPC), or your own network controls (Local MCP). The on-device Kill Switch and PackagePolicyGuard (below) apply in every mode.
Security & Privacy
Aster is self-hosted and local-first. The server, the device, and your data stay on hardware you own. Here’s exactly how the link is secured — and where it isn’t.
The Node ws server does not terminate TLS
On a trusted LAN the device ↔ server link is an unencrypted WebSocket (ws://, port 5987). The Node ws server does not speak TLS itself — pointing wss:// at :5987 without something in front fails with a TLS parse error. That is not the whole story:
- Trusted LAN —
ws://:5987in the app is expected. Fine behind your router; do not port-forward5987/5988to the open internet. - Remote — put TLS in front of the socket: Tailscale Serve (
wss://for the app) or a reverse proxy. See Securing the connection.
Encryption for remote control comes from Tailscale (WireGuard) or your TLS terminator, not from a padlock on the Node socket.
Device approval gate (status-based, no shared secret)
There are no tokens, passwords, or per-device keys. Access control is a single status: pending -> approved | rejected.
- A new device connects and self-reports its identity (hashed Android ID, name, model) and lands as
pending— connected, but unable to run any command. - Every command is hard-gated: the server refuses anything for a device whose status isn’t
approved. - You approve or reject from the dashboard (
POST /api/devices/:id/approve|reject) or CLI (aster devices approve). Rejected devices are disconnected (close code4003).
Because there’s no shared secret, any client on the network that knows the port can register as pending — so the trust boundary is your network plus your approval tap. Keep the server off untrusted LANs.
Nothing leaves your network by default
- No telemetry, no analytics, no tracking. None. (Grep the source if you don’t believe us.)
- Devices, logs, and the registry live in a local SQLite file (
./aster.db). - The AI/MCP client pulls data over the local MCP HTTP endpoint — the server and device are on your network.
- The only outbound call is the optional event-forwarding webhook, which is off unless you run
aster set-event-forwarding. Even then it POSTs to an endpoint you specify (defaulthttp://localhost:18789) — never a vendor server.
Android permissions — and why each one
Aster is a remote-control tool, so it asks for a lot. No root required. Every sensitive permission maps to a capability:
| Permission(s) | Why |
|---|---|
BIND_ACCESSIBILITY_SERVICE |
The core: read the UI tree, tap, swipe, type. This is screen control. |
BIND_NOTIFICATION_LISTENER_SERVICE |
Read notifications and forward them as proactive events. |
READ_SMS / SEND_SMS / RECEIVE_SMS |
Read and send texts; react to incoming SMS. |
CALL_PHONE, READ_PHONE_STATE, MODIFY_AUDIO_SETTINGS |
make_call / make_call_with_voice (speakerphone TTS). |
ACCESS_FINE_LOCATION / ACCESS_COARSE_LOCATION |
get_location. |
CAMERA, FOREGROUND_SERVICE_CAMERA |
Take photos / record video remotely. |
READ_CONTACTS / WRITE_CONTACTS |
Resolve and manage contacts for calls/SMS. |
MANAGE_EXTERNAL_STORAGE (+ legacy read/write) |
Browse, read, write, delete files. |
QUERY_ALL_PACKAGES, PACKAGE_USAGE_STATS |
List installed apps and usage. |
SYSTEM_ALERT_WINDOW |
Control overlay and the kill-switch banner. |
FOREGROUND_SERVICE / ..._SPECIAL_USE (device_control), RECEIVE_BOOT_COMPLETED, WAKE_LOCK, REQUEST_IGNORE_BATTERY_OPTIMIZATIONS |
Keep the always-on control service alive for a dedicated AI phone. |
On-device safety rails
Two Android-side guards run regardless of what the AI asks:
- Kill Switch — while a screen-control session is active, a persistent high-priority “AI is controlling your phone — STOP” notification is shown. Tapping STOP aborts the control loop immediately and clears the overlay.
- PackagePolicyGuard — a fail-closed denylist. If the foreground app can’t be identified, control is refused. A bundled banking/payments denylist (PhonePe, Paytm, PayPal, Venmo, Binance, …) blocks screen control over financial apps by default unless you explicitly allow it. Read-only actions (
observe/ screenshot / hierarchy /find_element) are always permitted.
Securing the connection
Three sanctioned setups. Pick one; mixing wss:// with a port that only speaks ws:// is the usual failure.
Trusted LAN — ws://
Same Wi-Fi / ethernet. In the Aster app:
ws://:5987
MCP client (Claude, AnythingLLM, OpenClaw, …):
http://:5988/mcp
aster status prints this as MCP: http://:5988/mcp (paste into your MCP client). Do not expose these ports on the public internet.
On-device Local MCP (no npm server) listens on :8080, not :5988.
Tailscale Serve — wss:// for the app
If Tailscale is running, Aster runs tailscale serve for the device WebSocket (https://:443 → local :5987) and the Nuxt dashboard (:8443 → local :5989). Copy the wss:// URL printed by aster status into the Android app. That is automatic TLS for the device link.
Serve does not terminate Fastify. The MCP HTTP endpoint stays on the API port. Paste this into your MCP client, not the Serve dashboard URL:
http://:5988/mcp
Never https://:8443/mcp.
Reverse proxy (Traefik / Caddy)
Terminate TLS in front of :5987 and point the app at wss://your.domain. The Node process still speaks plaintext locally.
Caddy (Caddyfile):
aster.example.com {
reverse_proxy localhost:5987
}
App URL: wss://aster.example.com
Optional MCP + dashboard:
mcp.aster.example.com {
reverse_proxy localhost:5988
}
dash.aster.example.com {
reverse_proxy localhost:5989
}
MCP client: https://mcp.aster.example.com/mcp
Traefik (file-provider sketch; HTTP routers already upgrade WebSockets):
http:
routers:
aster-ws:
rule: Host(`aster.example.com`)
entryPoints: [websecure]
tls:
certResolver: letsencrypt
service: aster-ws
services:
aster-ws:
loadBalancer:
servers:
- url: http://127.0.0.1:5987
Same idea as Caddy: TLS at the proxy, ws:// on localhost. Don’t put wss://:5987 in the app — that port does not speak TLS.
Why not just scrcpy / ADB?
Those are great — for a human at a keyboard with a USB cable. Aster solves a different problem.
| scrcpy / raw ADB | Aster | |
|---|---|---|
| Connection | USB cable or USB debugging on the LAN | App over the network — no USB debugging |
| Remote | Needs tunneling/ADB-over-TCP setup | Works anywhere via Tailscale |
| Interface | Mirrored screen + raw shell commands | High-level MCP tools an AI calls in natural language (take_screenshot, click_by_text, send_sms, make_call_with_voice…) |
| Built for | A person driving manually | An AI agent acting on its own, with an approval gate and kill switch |
| Proactive | Pull-only | Pushes SMS/notification events to your AI in real-time |
Aster isn’t a screen mirror — it’s a tool layer that lets an AI operate the phone, with safety rails scrcpy was never meant to have.
Project Structure
Aster/
├── apps/android/ # Android companion app — Kotlin, Jetpack Compose (Material3),
│ # Hilt, Room, DataStore, OkHttp WS client; core is an
│ # AccessibilityService + NotificationListener + foreground
│ # 'device_control' service. Also ML Kit OCR, CameraX, Ktor.
│ # minSdk 26 (Android 8), compileSdk 36, arm64-v8a, com.aster
├── mcp/ # MCP server (npm: aster-mcp) — Node.js + TypeScript (ESM, pnpm):
│ │ # Fastify (HTTP/API + MCP-over-HTTP), ws (device WebSocket),
│ │ # better-sqlite3, zod, commander CLI, @modelcontextprotocol/sdk
│ └── dashboard/ # Web dashboard — Nuxt 4 (Vue) + Tailwind v4, SSR child process
└── skill/ # ClawHub / OpenClaw skill — a single SKILL.md manifest (no code)
Requirements
- Server: Node.js >= 20
- Android: Android 8.0+ (API 26) with Accessibility Service enabled
- Network: Device and server on same network (or Tailscale)
Troubleshooting & FAQ
Development
Want to hack on Aster or add a tool? Here’s the loop.
Prerequisites
- Node.js >= 20, pnpm 9.15.0 (the server), JDK 17 (the Android app)
MCP server
git clone https://github.com/satyajiit/aster-mcp
cd aster-mcp/mcp
pnpm install # postinstall rebuilds better-sqlite3 native bindings
pnpm dev # tsx watch (server only)
pnpm dev:all # server + Nuxt dashboard concurrently
pnpm build # tsc -> dist/
pnpm build:all # server + dashboard bundle
node dist/index.js # or: aster start
pnpm test # vitest
pnpm lint # eslint
Android app
cd apps/android
./gradlew assembleDebug # debug APK
./gradlew assembleRelease # signed release (arm64-v8a, minify+shrink on)
Release signing reads a keystore from env vars (KEYSTORE_PATH, KEYSTORE_PASSWORD, KEY_ALIAS, KEY_PASSWORD; default aster-release.jks).
Adding a new MCP tool
A tool is a server-side triad + one device handler:
mcp/src/mcp/tools.ts— add a zod input schema (e.g.export const FooSchema = z.object({ deviceId: z.string(), ... })).mcp/src/mcp/tools.ts— add an entry toTOOL_DEFINITIONS(nameaster_foo, description, JSON-schemainputSchema) so MCP clients can discover it.mcp/src/mcp/handler.ts— add a case inhandleToolCallthat validates args and callssendCommand(deviceId, '', params).- Android — implement a
CommandHandler(supportedActions()+handle()) underapps/android/.../service/handlers, then register it inprovideCommandHandlersindi/ModeModule.kt. Each handler’ssupportedActions()auto-maps action names into the@CommandHandlerMap.
Tailscale Support
Aster automatically detects Tailscale and prints a wss:// URL for the app, plus http://:5988/mcp for your MCP client. Serve TLS-terminates the device WebSocket and the dashboard; it does not expose /mcp. See Securing the connection. Perfect for a dedicated AI phone that stays plugged in at home while you’re away.
OpenAlly.ai
OpenAlly.ai — the cross-platform AI agent platform Aster was built alongside.
Aster also ships as the end-to-end companion for the OpenAlly.ai app. OpenAlly drives the phone entirely on-device through Aster’s IPC (Binder) mode — no server, no network, nothing leaves the device. It works out of the box: install both, approve the on-device handshake once, and OpenAlly has the full on-device surface locally — all 78 unprefixed actions, which is a different and larger set than the 49 aster_* tools the Node server registers.
Star the repo
If Aster is useful to you, a star is the cheapest way to say so — it is how other people find the project.
Backlog
Things deliberately left undone — unreachable device actions, the unauthenticated API surface, manual Android capture — are recorded in BACKLOG.md with the method used to find each one.
License
MIT © Satyajit Pradhan
Built with ❤️ for the AI-first future
OpenAlly is a trademark of Matterward Labs Private Limited.
安装
This server does not publish a one-line install command.
Open the repository installation guide配置
{
"mcpServers": {
"aster": {
"type": "http",
"url": "http://localhost:5988/mcp"
}
}
}