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.
| Connection Setup | Device Dashboard | Tool Call Logs | Permissions — "Ask all together" |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
| Companion Face Overlay | On-device MCP Server — Local MCP mode |
![]() |
![]() |
![]() |
![]() |
Every screen ships in both themes — dark on the top row, light on the bottom. Aster follows your device setting.
- 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
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
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.
| Overview | Device Telemetry |
![]() |
![]() |
![]() |
![]() |
| Screen Control | MCP Tool Explorer |
![]() |
![]() |
![]() |
![]() |
Every page ships in both themes — dark on the top row, light on the bottom. The dashboard follows your system setting.
More screenshots — registry, panels, logs, connect, files, event forwarding
| Device Registry | Messages |
![]() |
![]() |
![]() |
![]() |
| Apps Inventory | Storage & Media |
![]() |
![]() |
![]() |
![]() |
| Logs | Connect |
![]() |
![]() |
![]() |
![]() |
| File Browser | Event Forwarding |
![]() |
![]() |
![]() |
![]() |
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 http://localhost:5989.
npm install -g aster-mcpDownload 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.
aster startOpen 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.
See Integrations for Claude, OpenClaw, Moltbot, Clawbot, and other MCP clients.
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)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 countsaster status reads ~/.aster/status.json, written by the running server. The /api/health endpoint is the one to poll from scripts and monitors.
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"
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-forwardingAster 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://<your-mattermost>/hooks/<id>). - 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.
Aster POSTs to {endpoint}{webhookPath} (default http://localhost:18789/hooks/agent) with Authorization: Bearer <token>:
{
"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": "<same token as Aster event forwarding>",
"path": "/hooks"
}
}Add to your .mcp.json or Claude settings:
{
"mcpServers": {
"aster": {
"type": "http",
"url": "http://localhost:5988/mcp"
}
}
}Aster is available as a skill on ClawHub. Install it directly:
clawhub install asterOr add manually to your OpenClaw/Moltbot/Clawbot skills directory and configure the MCP endpoint.
Aster exposes a standard MCP HTTP endpoint at http://localhost:5988/mcp that works with any MCP-compatible AI client.
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://<pc-ip>:5987 |
http://localhost:5988/mcp |
| LAN, separate machines | PC / Mac / NAS | ws://<server-lan-ip>:5987 |
http://<server-lan-ip>:5988/mcp |
| Tailscale | any tailnet box | wss://<magicdns> (via tailscale serve) or ws://<ts-ip>:5987 |
http://<ts-ip>: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://<phone-ip>: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.
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 |
Look up, page through, and delete address-book entries |
| 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 |
┌─────────────────┐ WebSocket ┌─────────────────┐
│ │◄──────────────────►│ │
│ Aster Server │ │ Android App │
│ (Node.js) │ │ (Accessibility │
│ │ │ Service) │
└────────┬────────┘ └─────────────────┘
│
│ MCP (HTTP)
│
┌────────▼────────┐
│ │
│ Claude / AI │
│ │
└─────────────────┘
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 77 unprefixed actions instead — a different set, not a superset: 47 names overlap, 29 on-device actions have no aster_* equivalent (observe, tap, scroll, the screen_* human-in-the-loop verbs, the overlay and companion-face verbs), 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: https://aster.matterwardlabs.com/tools/. 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.
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.
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://<lan-ip>: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.
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 <deviceId>). 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.
- 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.
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. |
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.
Three sanctioned setups. Pick one; mixing wss:// with a port that only speaks ws:// is the usual failure.
Same Wi-Fi / ethernet. In the Aster app:
ws://<server-lan-ip>:5987
MCP client (Claude, AnythingLLM, OpenClaw, …):
http://<server-lan-ip>:5988/mcp
aster status prints this as MCP: http://<ip>: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.
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://<magicdns> 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://<tailscale-ip>:5988/mcp
Never https://<magicdns>:8443/mcp.
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:5987Same idea as Caddy: TLS at the proxy, ws:// on localhost. Don't put wss://<lan-ip>:5987 in the app — that port does not speak TLS.
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.
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)
- Server: Node.js >= 20
- Android: Android 8.0+ (API 26) with Accessibility Service enabled
- Network: Device and server on same network (or Tailscale)
Device shows "connected" but every command fails or times out
The device is probably still
pending. A connected device can't be commanded until you approve it. Run aster devices approve <deviceId> or click Approve in the dashboard. (Rejected devices are disconnected with close code 4003.)
Is the connection encrypted? Do I need SSL?
Not by default. On a trusted LAN the device link is plain
ws:// — the Node ws server does not terminate TLS. For remote/encrypted access, use Tailscale Serve (wss://<magicdns> in the app; MCP stays http://<ts-ip>:5988/mcp) or put Traefik/Caddy in front. Don't expose ports 5987/5988 to the public internet. See Securing the connection.
Device disconnects when I switch Wi-Fi / mobile networks
The companion now reconnects automatically after a Wi-Fi / mobile switch (backoff, "Reconnecting…" in the app) instead of staying dead on a dropped socket. If it still never comes back, the advertised address itself changed —
aster status prints a LAN IP that is only valid on that network. Keep both ends on the same network, or use a stable Tailscale IP so the address survives network changes.
make_call_with_voice dials but there's no audio
The TTS plays over the call audio path on speakerphone, so it needs
CALL_PHONE, READ_PHONE_STATE, and MODIFY_AUDIO_SETTINGS granted, plus speakerphone reachable. It also waits a few seconds after dialing (default 8s, waitSeconds) for the call to be answered before speaking. Confirm those permissions in the app and that the device isn't on silent / call audio routed to a headset.
Firewall / which ports do I need open?
Three local ports:
5987— WebSocket device link (WS_PORT)5988— API + MCP HTTP endpoint (POST /mcp) and health (DASHBOARD_PORT)5989— Nuxt web dashboard
5987; your AI client needs 5988. Allow these on your LAN (or Tailscale) and keep them off the public internet.
Why doesn't Aster have a chat screen?
On purpose. Aster is the device-side companion that holds the sensitive permissions, so it stays small and auditable — no LLM providers, API keys, or conversation storage inside the permission-holder app. The chat-with-your-agent app is OpenAlly, which drives Aster on-device over IPC (Binder) mode — no app-switching in practice: you chat in OpenAlly, Aster does the device work. Any other MCP client (Claude, AnythingLLM, OpenClaw...) reaches the same tools via the server or the Local MCP mode.
How do I check the server is reachable programmatically?
Hit the health endpoint:
curl http://localhost:5988/api/health -> { "status": "ok", "timestamp": ... }. For device counts, GET /api/stats; for a human-readable snapshot, aster status. See Status & Health Checks.
Want to hack on Aster or add a tool? Here's the loop.
- Node.js >= 20, pnpm 9.15.0 (the server), JDK 17 (the Android app)
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 # eslintcd 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).
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, '<action>', 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.
Aster automatically detects Tailscale and prints a wss://<magicdns> URL for the app, plus http://<ts-ip>: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 — 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 77 unprefixed actions, which is a different and larger set than the 49 aster_* tools the Node server registers.
If Aster is useful to you, a star is the cheapest way to say so — it is how other people find the project.
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.
MIT © Satyajit Pradhan
Built with ❤️ for the AI-first future
OpenAlly is a trademark of Matterward Labs Private Limited.





































