Skip to content

Repository files navigation

Aster Logo

Aster

by OpenAlly

Your AI CoPilot on Mobile — or give your AI its own phone

Website npm version npm downloads License ClawHub OpenClaw 49 MCP tools Android 8+ No root required Self-hosted

Ages 20-30 Approved by George Costanza Made with Vue Made with TypeScript Built with Nuxt Porsche Cayman Fuck it, ship it Ages 18+ Powered by Jeff's keyboard Open Source

WebsiteFeaturesQuick StartUsageIntegrationsMCP Tools

Aster — Your AI CoPilot on Mobile


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"
Connection Setup, dark theme Device Dashboard, dark theme Tool Call Logs, dark theme Permissions, dark theme
Connection Setup, light theme Device Dashboard, light theme Tool Call Logs, light theme Permissions, light theme
Companion Face Overlay On-device MCP Server — Local MCP mode
Companion Face Overlay, dark theme On-device MCP Server, dark theme
Companion Face Overlay, light theme On-device MCP Server, light theme

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_voice to 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.

Overview Device Telemetry
Overview, dark theme Device Telemetry, dark theme
Overview, light theme Device Telemetry, light theme
Screen Control MCP Tool Explorer
Screen Control, dark theme MCP Tool Explorer, dark theme
Screen Control, light theme MCP Tool Explorer, light theme

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
Device Registry, dark theme Messages, dark theme
Device Registry, light theme Messages, light theme
Apps Inventory Storage & Media
Apps Inventory, dark theme Storage & Media, dark theme
Apps Inventory, light theme Storage & Media, light theme
Logs Connect
Logs, dark theme Connect, dark theme
Logs, light theme Connect, light theme
File Browser Event Forwarding
File Browser, dark theme Event Forwarding, dark theme
File Browser, light theme Event Forwarding, light theme

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.

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).

  1. In Mattermost go to Integrations → Incoming Webhooks and add a webhook for the channel that should receive Aster events.
  2. Copy the webhook URL (https://<your-mattermost>/hooks/<id>).
  3. 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.
  4. Optional: set a Mattermost channel name to override the webhook default. Leave it empty to use the webhook's channel. Do not reuse whatsapp / telegram here — 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 <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"
  }
}

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://<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.

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 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

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 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.

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 LANws://<lan-ip>:5987 in the app is expected. Fine behind your router; do not port-forward 5987/5988 to 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 <deviceId>). Rejected devices are disconnected (close code 4003).

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 (default http://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://<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.

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://<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.

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://<lan-ip>: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

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
The device needs 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.

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:

  1. mcp/src/mcp/tools.ts — add a zod input schema (e.g. export const FooSchema = z.object({ deviceId: z.string(), ... })).
  2. mcp/src/mcp/tools.ts — add an entry to TOOL_DEFINITIONS (name aster_foo, description, JSON-schema inputSchema) so MCP clients can discover it.
  3. mcp/src/mcp/handler.ts — add a case in handleToolCall that validates args and calls sendCommand(deviceId, '<action>', params).
  4. Android — implement a CommandHandler (supportedActions() + handle()) under apps/android/.../service/handlers, then register it in provideCommandHandlers in di/ModeModule.kt. Each handler's supportedActions() auto-maps action names into the @CommandHandlerMap.

Tailscale Support

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

OpenAlly

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.

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.

Star aster-mcp on GitHub

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.

About

Control your Android device with AI via MCP (Model Context Protocol)

Resources

Stars

37 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages