# Aster — full text > Every page of https://aster.matterwardlabs.com concatenated, generated 2026-09-13. Individual pages are listed in https://aster.matterwardlabs.com/llms.txt. # Aster — AI copilot for Android, or give your AI its own phone > Aster connects any Android to Claude, OpenClaw or MoltBot over MCP. 49 MCP tools, open source, self-hosted — your AI copilot on mobile, or its own phone. Aster here is the open-source Model Context Protocol server and Android companion app for AI-driven phone control, published on npm as aster-mcp. It is unrelated to Aster DM Healthcare, Aster Data Systems or the ASTER instrument on NASA’s Terra satellite. Source: https://aster.matterwardlabs.com/ · Part of Aster (https://github.com/satyajiit/aster-mcp) · Last generated 2026-09-13 ## What Aster is Aster is an MCP server plus an Android companion app. It gives an AI assistant — Claude, OpenClaw, MoltBot, ClawdBot, or any MCP client — the ability to see and control an Android phone: take screenshots, read the UI tree, tap and type, read notifications and SMS, send SMS, place calls, take photos, search media, and manage files. Aster here is the open-source Model Context Protocol server and Android companion app for AI-driven phone control, published on npm as aster-mcp. It is unrelated to Aster DM Healthcare, Aster Data Systems or the ASTER instrument on NASA’s Terra satellite. Two ways people run it: 1. **Copilot on your own phone.** Your assistant acts on the device you already carry. 2. **A phone of its own.** Plug a spare Android into a charger. The AI watches it, calls you, texts you, and acts without being asked. ## What people ask it Five example prompts and the tools each one runs. The replies these produce are illustrative; the tool names are exact. - "Find duplicate photos on my phone and free up space" — `aster_index_media_metadata` → `aster_search_media` - "Read my notifications — anything urgent?" — `aster_read_notifications` - "My storage is full. What is eating all the space?" — `aster_analyze_storage` → `aster_find_large_files` - "Open Maps and find the nearest coffee shop" — `aster_launch_intent` → `aster_input_text` → `aster_click_by_text` - "If my flight is delayed, call me and tell me the new time" — event forwarding, then `aster_make_call_with_voice` ## What changes when your AI can act ### Today — your AI can talk - **Chats on WhatsApp and Telegram** — It reads and answers messages for you - **Drafts email and documents** — It writes on your behalf - **Schedules and organises** — It keeps track of your time - **Writes and reviews code** — It builds software alongside you "Set an alarm for 6 AM" — and then you pick up the phone and do it yourself. ### With Aster — your AI can act - **Taps, swipes and types on the phone** — Full screen control through the accessibility service - **Takes photos and records video** — Either camera, on request or when an event fires - **Reacts to events as they happen** — New SMS, notifications and device status, pushed to your agent - **Reads notifications, sends SMS, places calls** — Including a call that speaks a message out loud "Set an alarm for 6 AM" — and it just does it. ## What it can do The eight capability groups the home route leads with. Tool names are the callable, prefixed ones; the app-automation and companion-face rows are on-device actions an MCP client cannot call — see https://aster.matterwardlabs.com/tools.md. ### Screen control Read the live UI tree with aster_get_screen_hierarchy, then tap, swipe, type and navigate. aster_take_screenshot sends back what the phone is showing right now. ### App automations, with a human in the loop When an automation run hits something only you should do, Aster puts it on screen and waits: a choice, an approve-or-deny card, a sign-in wall, a payment hand-off. These are on-device actions over Binder IPC — the runner is OpenAlly, not an MCP client. ### Media intelligence aster_index_media_metadata builds a local index of the camera roll; aster_search_media queries it by date, place and content without uploading a single photo. ### Files and storage List, read, write and delete any path the app can reach — the file tools are not folder-scoped, and withholding all-files access is what bounds them (see /security). aster_analyze_storage and aster_find_large_files explain where the free space went. ### Calls, SMS and voice aster_send_sms and aster_make_call do the obvious thing. aster_make_call_with_voice dials a number, turns on the speaker and speaks a message through text to speech. ### Notifications and events aster_read_notifications reads the shade on demand. Event forwarding pushes new SMS, notifications, calls and device-status changes to a webhook, so the assistant acts without being asked. ### Camera and video aster_take_photo and aster_record_video run either camera on request or on an event. A charging spare phone becomes a pet cam, a doorbell or a parcel watch. ### Companion face An animated on-screen companion that lip-syncs to text-to-speech and reacts to whatever is playing, driven by an on-device event classifier that never sends audio anywhere. Like App automations, this is driven on-device by OpenAlly over Binder IPC — the companion_overlay_* verbs are not among the 49 aster_* tools. ## Quick facts - **What it is**: An MCP server plus an Android companion app that lets an AI assistant see and control a phone. - **Server**: npm `aster-mcp`, Node.js >= 20 - **Phone**: Android 8.0+ (API 26), no root required - **Tools**: 49 MCP tools, all namespaced `aster_*` - **Clients**: Any MCP client: Claude Code, Claude Desktop, AnythingLLM, and the open-source agent clients OpenClaw, MoltBot and ClawdBot - **Ports**: 5987 device websocket, 5988 api + mcp http, 5989 web dashboard - **Licence**: MIT, free and open source - **Hosting**: Self-hosted on your own machine. No account, no telemetry, no vendor relay. ## How a command reaches the phone The phone never accepts a connection — it dials out and holds the socket open, which is why Aster works behind a home router with nothing forwarded. Three transports, and which one you use decides which tool names your client sees. 1. **Remote WebSocket** (Default) — The phone holds a WebSocket to the Node server on your machine; your AI client speaks MCP over HTTP to that same server. Tools it sees: aster_* (49 tools). 2. **On-device MCP server** (Standalone) — The app runs its own Streamable-HTTP MCP server (Ktor, default port 8080) on the phone. No desktop server involved. Tools it sees: unprefixed actions (77 in the on-device catalog). 3. **Binder IPC** (Same device) — An agent running on the same phone — such as OpenAlly — calls Aster directly over Android Binder IPC, with no network hop. Tools it sees: unprefixed actions (77 in the on-device catalog). Each transport is walked end to end, with measured latencies, at https://aster.matterwardlabs.com/architecture.md. ## What it protects, and what it does not Aster's own security claims, protections and admitted limits in one list. Each line is a section heading at https://aster.matterwardlabs.com/security.md, where it is explained in full. **What it protects:** - No root, no ADB exploit - Self-hosted, no account, no telemetry - An unapproved device connects, but cannot be commanded - A kill switch you can always reach - Banking and payment apps are refused by default **What it does not:** - The file tools are not path-scoped — know this before you approve - The device link is plain ws:// by default Both limits are fixable, and https://aster.matterwardlabs.com/security.md carries the 6 hardening steps plus the reason for each of the 32 Android permissions. ## The Android app on its own The companion is a normal Android app, and it does not need the server to be useful. Sideload it and the phone can serve MCP on its own: the app embeds a Streamable-HTTP MCP server and exposes 77 on-device actions to any client that can reach it. The Node server is what adds multi-device management, the approval gate and the 49 prefixed `aster_*` tools — it is not what makes the phone controllable. ### Serves MCP from the phone An embedded Ktor server speaks Streamable-HTTP MCP on port 8080 by default. Point a client on your LAN at the handset and it sees 77 unprefixed actions — no desktop, no Node, no account. ### Answers apps on the same device An agent already running on the phone binds the service directly over Android Binder IPC. No network hop at all, so it keeps working with the radio off. ### Runs its own console The app carries a device dashboard, a guided pass that requests every permission in one go, a live log of each tool call with its arguments and result, and a panel for the on-device server. - **Version**: 1.7.1, the current GitHub release - **Requires**: Android 8.0+ (API 26). Root is not required and is not used. - **Distribution**: A signed APK on GitHub Releases. It is not on the Google Play Store, so Android will ask you to allow installs from your browser or file manager the first time. - **Licence**: MIT, same as the server. The Android source is in the same repository. Download: https://github.com/satyajiit/aster-mcp/releases/latest ## What ships with it Aster ships an Android companion app and a web dashboard at http://localhost:5989 for device approval, file browsing, live screen control and MCP tool testing. Both ship in a dark and a light theme. ## Installing it Three commands and a sideload. Install aster-mcp from npm on any machine running Node >= 20, start the server, install the companion app on Android 8.0+ (API 26), approve the device, and point your MCP client at http://localhost:5988/mcp. MIT licensed, self-hosted, no account and no telemetry. Root is not required. ## Where to go next - [Use cases — what people actually ask their AI to do](https://aster.matterwardlabs.com/use-cases/) — Real prompts people give Aster: free up storage, find duplicate photos, read notifications, call someone and speak a message, auto-reply while driving. - [Give your AI its own phone](https://aster.matterwardlabs.com/ai-phone/) — Plug a spare Android into a charger and give your AI its own number. It watches notifications, calls you, texts you and acts without being asked. - [All 49 Aster MCP tools, with arguments](https://aster.matterwardlabs.com/tools/) — All 49 aster_* MCP tools with their exact names and arguments, plus the separate catalogue of 77 unprefixed actions the Android app dispatches on-device. - [Architecture — how a command reaches the phone](https://aster.matterwardlabs.com/architecture/) — How one command travels from your AI to the phone, and the three transports Aster speaks: remote WebSocket, on-device Ktor MCP server, and Binder IPC. - [Is Aster safe? No root, self-hosted, zero telemetry](https://aster.matterwardlabs.com/security/) — Self-hosted with zero telemetry and no root: device approval gate, screen-control kill switch, banking apps blocked, and where the link is not encrypted. - [Set up Aster — MCP server, Android app, AI client](https://aster.matterwardlabs.com/setup/) — Install aster-mcp from npm, run aster start, sideload the Android app, approve the device and point Claude Code at http://localhost:5988/mcp. - [FAQ and troubleshooting](https://aster.matterwardlabs.com/faq/) — Device stuck on pending, ws:// versus wss://, which ports to open, no audio on a voice call, why Aster has no chat screen, and how it compares to scrcpy. --- # Use cases — what people actually ask their AI to do > Real prompts people give Aster: free up storage, find duplicate photos, read notifications, call someone and speak a message, auto-reply while driving. Aster here is the open-source Model Context Protocol server and Android companion app for AI-driven phone control, published on npm as aster-mcp. It is unrelated to Aster DM Healthcare, Aster Data Systems or the ASTER instrument on NASA’s Terra satellite. Source: https://aster.matterwardlabs.com/use-cases/ · Part of Aster (https://github.com/satyajiit/aster-mcp) · Last generated 2026-09-13 Aster is an open-source Model Context Protocol server plus an Android companion app: it gives an AI client such as Claude Code, OpenClaw or MoltBot 49 tools, all named `aster_*`, for reading and driving a phone on Android 8.0+ (API 26) — no root, no cloud account. The 21 prompts below are real things people ask it for, each one listing the exact tools that run. The prompts are real. The replies are illustrative — counts, file sizes, place names and flight numbers are written as examples, not measured, and are not product statistics. Tool names are exact. Each entry is a prompt someone gives their assistant, the Aster tools that run, and what comes back. ## Find the duplicate photos on my phone and show me which ones to delete - **Tools used:** `aster_search_media`, `aster_index_media_metadata` - **Category:** Photos & media - **Result (illustrative):** Indexed the media library and compared metadata. There are 47 duplicate sets across 2,104 photos. I can show you each set side by side before anything is removed. ## Show me the photos I took in Goa last December - **Tools used:** `aster_search_media` - **Category:** Photos & media - **Result (illustrative):** 83 photos from December carry a location near Goa, including 12 beach shots and 6 sunset panoramas. Want them pulled onto this machine? ## Find the screenshots I took this week and bin the blurry ones - **Tools used:** `aster_search_media`, `aster_delete_file` - **Category:** Photos & media - **Result (illustrative):** 23 screenshots from the last seven days. Five look blurry or unreadable. Say the word and they go; the rest stay untouched. ## Set an alarm for 10 AM tomorrow for the standup - **Tools used:** `aster_set_alarm`, `aster_post_notification` - **Category:** Reminders & alerts - **Result (illustrative):** Alarm set on the device for 10:00, labelled Standup. I also posted a notification so it is visible on the lock screen tonight. ## Buzz my phone — I can't find it under the couch - **Tools used:** `aster_set_volume`, `aster_play_audio`, `aster_vibrate`, `aster_stop_audio` - **Category:** Reminders & alerts - **Result (illustrative):** Turning the ringer up, playing a tone and vibrating in a strong three-pulse pattern. Follow the sound; I will keep it going until you tell me to stop. ## Read my unread notifications and tell me if anything is urgent - **Tools used:** `aster_read_notifications` - **Category:** Reminders & alerts - **Result (illustrative):** 14 waiting. Two from Slack flagged urgent, one missed call from Mum, and the rest are promotions. Want the two Slack threads summarised? ## Copy meeting-notes.pdf off my phone and onto this machine - **Tools used:** `aster_list_files`, `aster_read_file` - **Category:** Files & cleanup - **Result (illustrative):** Found meeting-notes.pdf in Downloads, 2.3 MB, modified on Tuesday. Reading it off the device now so it lands in your working folder. ## I'm out of storage — find what is eating the space - **Tools used:** `aster_analyze_storage`, `aster_find_large_files` - **Category:** Files & cleanup - **Result (illustrative):** WhatsApp media 8.2 GB, cached data 3.1 GB, leftover installers 1.4 GB. The installers are the easy win. Shall I start there? ## Delete the downloaded PDFs older than six months - **Tools used:** `aster_list_files`, `aster_delete_file` - **Category:** Files & cleanup - **Result (illustrative):** 34 PDFs in Downloads have not been opened in six months, 890 MB between them. Here is the list — confirm and I will remove exactly those. ## Call Mum and tell her I'll be twenty minutes late - **Tools used:** `aster_search_contacts`, `aster_make_call_with_voice` - **Category:** Communication - **Result (illustrative):** Dialling now with the speaker on. When she picks up I will say your message out loud, then end the call and tell you how it went. ## Read the last text from the delivery driver and reply that I am home - **Tools used:** `aster_read_sms`, `aster_send_sms` - **Category:** Communication - **Result (illustrative):** Last message: "Package arriving between 2 and 4 PM." Reply sent: "Thanks, I will be home." Nothing else in the thread needs an answer. ## Put "Do not disturb, in a meeting" on my phone screen - **Tools used:** `aster_show_overlay`, `aster_set_volume` - **Category:** Communication - **Result (illustrative):** The notice is drawn over whatever is on screen and stays until you dismiss it. I dropped the ringer volume to match. ## What is on my phone screen right now? - **Tools used:** `aster_take_screenshot`, `aster_get_screen_hierarchy`, `aster_get_battery` - **Category:** Daily automations - **Result (illustrative):** A video app is in the foreground, playing a lo-fi mix. Battery is at 62% and Wi-Fi is connected. Screenshot attached if you want to see it. ## What is my battery at? Should I charge before heading out? - **Tools used:** `aster_get_battery`, `aster_get_device_info` - **Category:** Daily automations - **Result (illustrative):** 34% and not charging. At the rate it is draining that is roughly two hours. Worth a top-up before you leave. ## Open Maps and search for the nearest coffee shop - **Tools used:** `aster_launch_intent`, `aster_click_by_text`, `aster_input_text`, `aster_get_screen_hierarchy` - **Category:** Daily automations - **Result (illustrative):** Launched Maps, tapped the search field, typed "coffee shop near me" and submitted. Three results within a ten-minute walk. ## Dog is home alone — check on him every half hour and send me the photo - **Tools used:** `aster_take_photo`, `aster_send_sms` - **Category:** Proactive monitoring - **Result (illustrative):** I am holding the half-hour schedule on my side. Each time it comes round I ask the phone for a camera shot and text it to you. Aster itself runs nothing on a timer. ## While I am driving, tell anyone who texts that I will call back - **Tools used:** `aster_read_sms`, `aster_send_sms` - **Category:** Proactive monitoring - **Result (illustrative):** I will keep checking the inbox while drive mode is on. Alex asked "Are you free?" — answered with "Driving right now, will call you back in twenty minutes." ## When the delivery app says the rider is near, announce it out loud - **Tools used:** `aster_read_notifications`, `aster_set_volume`, `aster_speak_tts` - **Category:** Proactive monitoring - **Result (illustrative):** Spotted the notification — "Your rider is 1 minute away" — turned the media volume up and had the phone say it on the speaker. ## Ring me if my flight is delayed, and tell me the new time - **Tools used:** `aster_read_notifications`, `aster_make_call_with_voice` - **Category:** AI-owned phone - **Result (illustrative):** I am watching the airline app on my own phone and checking in on it periodically. AI-302 has slipped 45 minutes, so I am calling your number to say so. ## If the doorbell app goes off, record five seconds and send it to me - **Tools used:** `aster_read_notifications`, `aster_record_video`, `aster_send_sms` - **Category:** AI-owned phone - **Result (illustrative):** Doorbell notification picked up on the spare phone. Recorded a five-second clip from the camera and texted it to you. ## Leave the spare phone in the nursery and text me a photo check every twenty minutes - **Tools used:** `aster_take_photo`, `aster_send_sms` - **Category:** AI-owned phone - **Result (illustrative):** I hold the twenty-minute schedule. Each round I ask for a still from the camera and text it over. Aster has no microphone permission and no audio-capture tool, so nothing in the room is being listened to. --- # Give your AI its own phone > Plug a spare Android into a charger and give your AI its own number. It watches notifications, calls you, texts you and acts without being asked. Aster here is the open-source Model Context Protocol server and Android companion app for AI-driven phone control, published on npm as aster-mcp. It is unrelated to Aster DM Healthcare, Aster Data Systems or the ASTER instrument on NASA’s Terra satellite. Source: https://aster.matterwardlabs.com/ai-phone/ · Part of Aster (https://github.com/satyajiit/aster-mcp) · Last generated 2026-09-13 ## What "its own phone" means An AI’s own phone is a spare Android that belongs to your assistant rather than to you: it runs the Aster companion app, keeps its own SIM and number, and sits on a charger so the agent can see what arrives on it and act without being asked first. It is the same Aster, pointed the other way round. In copilot mode you drive and the AI works your phone for you. Here the phone is the AI’s, the events come to it, and the thing you notice is your own phone ringing — because your assistant decided the gate change was worth a call. Scheduling belongs to your AI client, not to Aster. Aster exposes tools and pushes events; the agent decides when to poll or act. ## What it takes Four things, none of them bought for the purpose. The drawer phone most people already own is the whole hardware budget. ### A spare Android Anything running Android 8.0+ (API 26). No root, no custom ROM, no unlocked bootloader — the companion app is sideloaded and asks only for the permissions the tools it exposes actually use. ### A charger it never leaves A phone the AI owns is a phone that is always awake. Leave it plugged in on Wi-Fi, screen off, and let Android keep the companion running in the foreground service it already uses. ### The Aster server, somewhere always on The npm package aster-mcp on Node >= 20 — a laptop that stays open, a mini PC, a home server. The phone holds a WebSocket to it on port 5987; your AI client speaks MCP to the same machine at http://localhost:5988/mcp. ### A SIM, if you want it to call and text Photos, notifications and screen work need no SIM at all. Giving the phone its own number is what lets your assistant call you, text you, and answer for you from an identity that is not yours. ## What changes A phone the AI holds stops being a thing it operates on request and becomes a thing it watches. ### It sees what arrives Texts, app notifications, a ringing call, the phone dropping offline. With event forwarding on, those reach your agent as they happen instead of waiting for you to ask what is new. ### It can act first An event that matters becomes a photo taken, a clip recorded, a reply sent, a call placed to you. Which events matter is your agent’s rule, not a setting buried in Aster. ### It uses a number that is not yours Deliveries, verification codes and the noise you would rather not receive can live on the assistant’s line. Your own phone hears from it only when the assistant decides you should. **One honest limit.** When the AI phones you, it speaks through the device loudspeaker and the call microphone picks that up — acoustic coupling, not audio routed into the call stream. It works, and it works best with the phone in a quiet room; quality depends on the handset. Plan for a call that reliably gets your attention, not a hands-free conversation. ## Proactive event forwarding Off by default. Turn it on with `aster set-event-forwarding` or in the dashboard. Aster POSTs tagged event text to either an OpenClaw-style agent webhook (with a Bearer token) or a Mattermost incoming webhook. - **Incoming SMS** — Tagged [event] sms, carrying the sender and the full message body, the moment the phone receives it. The agent answers from the push instead of polling an inbox. - **App notifications** — Tagged [event] notification, carrying the app name, its package id, and the notification title and text. This is the one that catches flight delays, delivery updates and the ride that just pulled up — anything an app already tells you about. - **Incoming call** — Tagged [event] incoming_call, carrying the number and the contact name when the phone can resolve one, while it is still ringing. This is the one event that is on unless you explicitly switch it off: a missing events.incomingCalls key does not drop RINGING events, only an explicit off does. - **Device online and offline** — Tagged [event] device_online or [event] device_offline when the companion's WebSocket connects or drops. An agent that has to know whether the phone is reachable before it queues work reads these. - **New pairing request** — Tagged [event] pairing with the status pending_approval when an unknown device asks to join. The payload names the next step — approve it from the dashboard, or with aster devices approve — so nobody quietly joins your server. ## Where the events go Aster POSTs to one destination you name. There is no Aster relay in the middle and no account to create: the server on your machine talks straight to the endpoint. - **An agent webhook** — the OpenClaw-style hook shape, which ClawdBot and MoltBot also speak. Aster POSTs to `{endpoint}{webhookPath}`, by default `http://localhost:18789/hooks/agent`, with an `Authorization: Bearer` token that has to match the token configured on the gateway. - **A Mattermost incoming webhook** — the same tagged text posted as `{ "text": "..." }`, with no Bearer token. Create the webhook under Integrations, paste the URL in, and optionally override the channel it lands in. Configure it with `aster set-event-forwarding` (the alias `aster set-openclaw-callbacks` still works) or in the dashboard at `http://localhost:5989/settings/event-forwarding`. The settings are written to `~/.aster/event-forwarding.json` — read that file before trusting a phone that has forwarding enabled, because it names the URL every SMS and notification body is sent to. What the agent receives: one tagged text block per event, no schema and no SDK. ```json { "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" } ``` ## Scenarios ### Record a clip when someone is at the door Leave the phone facing the entrance. When your doorbell or intercom app posts a notification, that event reaches the agent, which records a few seconds of video and texts it to you. The trigger is the notification the other app already posts, not motion detection — if that app stays silent, so does this. - **Tools:** `aster_record_video`, `aster_send_sms` ### Be called when your flight changes The airline app posts a gate change or a delay, the agent reads the forwarded notification and phones you to say it out loud — useful precisely when you are not looking at a screen. Understand how that audio works: Aster speaks through the loudspeaker and the call microphone picks it up, so it is acoustic coupling, not audio routed into the call stream. Keep the phone in a quiet room, expect quality to vary by device, and treat it as a nudge that gets through rather than a hands-free conversation. - **Tools:** `aster_read_notifications`, `aster_make_call_with_voice` ### Hear your ride arrive from the next room The ride-hailing app says the driver is two minutes out. The agent gets the notification, speaks the line aloud on the spare phone and texts you the driver and plate so you are not reading a map on the pavement. - **Tools:** `aster_read_notifications`, `aster_speak_tts`, `aster_send_sms` ### Look in on a pet while you are out Prop the phone where the dog usually sleeps and have the agent take a photo and send it to you. Aster runs no scheduler, so the cadence is your AI client's: a cron job, a scheduled agent run, or a standing instruction in the assistant itself. Aster's side of it is one camera call and one message per run. - **Tools:** `aster_take_photo`, `aster_send_sms` ### Check the nursery without opening the door The same shape as the pet check, and quieter than walking in. A photo goes out on whatever rhythm your agent keeps — again, that clock belongs to the AI client, not to Aster. Point the phone at the cot, not at the room, and remember the photo travels wherever you told the agent to send it. - **Tools:** `aster_take_photo`, `aster_send_sms` ### Answer for you while you are driving or in a meeting An SMS lands, the agent reads it, decides whether it can be answered without you, and replies with something true — that you are driving and will call back — rather than a canned auto-reply. You set the rules for what it may answer and what it must hold for you. - **Tools:** `aster_read_sms`, `aster_send_sms` ## Setting one up Five steps, start to finish. The first four are the ordinary Aster install — the fifth is the one that turns a phone your AI can use into a phone your AI watches. ### Step 1 — Install the server on a machine that stays on Aster's server is an npm package. Put it on whatever is already awake at 3am — a laptop that stays open, a mini PC, a home server — not on the phone. It needs Node >= 20. On the always-on machine: ```bash npm install -g aster-mcp aster start ``` ### Step 2 — Sideload the companion app on the spare phone Grab the APK from the releases page and install it on the phone you are dedicating. Anything on Android 8.0+ (API 26) works; no root, no custom ROM. Grant the permissions for the things you actually want it to do — camera, SMS and notification access are each optional. Download the companion APK: https://github.com/satyajiit/aster-mcp/releases ### Step 3 — Point the phone at the server and approve it Open the app and enter the server address the terminal printed — the device WebSocket listens on port 5987. The device arrives as pending and stays inert until you approve it, from the dashboard at http://localhost:5989 or with the CLI. Approve the device: ```bash aster devices list aster devices approve ``` ### Step 4 — Leave it on the charger and connect your AI client Plug the phone in, screen off, and leave it. Then point Claude Code, Claude Desktop, OpenClaw, MoltBot, ClawdBot or any MCP client at the server's Streamable-HTTP endpoint. That is the copilot half working — your AI can now use the phone. MCP client config: ```json { "mcpServers": { "aster": { "type": "http", "url": "http://localhost:5988/mcp" } } } ``` ### Step 5 — Turn on event forwarding This is the step that makes it the AI's phone rather than a phone it borrows. Choose an agent webhook with a Bearer token, or a Mattermost incoming webhook, pick which events to forward, and the phone starts pushing them the moment they happen. Enable proactive events: ```bash aster set-event-forwarding ``` The full walkthrough — permissions, the device approval gate, pointing an MCP client at the server, and what to do when the device sits on pending — is on https://aster.matterwardlabs.com/setup/. --- # All 49 Aster MCP tools, with arguments > All 49 aster_* MCP tools with their exact names and arguments, plus the separate catalogue of 77 unprefixed actions the Android app dispatches on-device. Aster here is the open-source Model Context Protocol server and Android companion app for AI-driven phone control, published on npm as aster-mcp. It is unrelated to Aster DM Healthcare, Aster Data Systems or the ASTER instrument on NASA’s Terra satellite. Source: https://aster.matterwardlabs.com/tools/ · Part of Aster (https://github.com/satyajiit/aster-mcp) · Last generated 2026-09-13 Every tool below is registered by the MCP server with the `aster_` prefix. The callable name is `aster_take_screenshot`; `take_screenshot` alone will not resolve. 49 tools over 8 categories. The on-device MCP server and the Binder IPC surface expose a DIFFERENT set of 77 unprefixed actions; that catalogue is listed in full after this one, and the transports are traced in https://aster.matterwardlabs.com/architecture/. ## Which catalogue you get, by connection mode Tool counts are surface-specific: the mode decides which of the two catalogues below is on the wire. - **Remote WebSocket** (Default) — The phone holds a WebSocket to the Node server on your machine; your AI client speaks MCP over HTTP to that same server. Tools: aster_* (49 tools) - **On-device MCP server** (Standalone) — The app runs its own Streamable-HTTP MCP server (Ktor, default port 8080) on the phone. No desktop server involved. Tools: unprefixed actions (77 in the on-device catalog) - **Binder IPC** (Same device) — An agent running on the same phone — such as OpenAlly — calls Aster directly over Android Binder IPC, with no network hop. Tools: unprefixed actions (77 in the on-device catalog) ## Screen and input Read what is on screen through the accessibility tree, then drive it back with taps, swipes and typed text. | Tool | Arguments | What it does | |---|---|---| | `aster_take_screenshot` | `deviceId` | Capture the current screen as an image. | | `aster_get_screen_hierarchy` | `deviceId`, `mode` (optional), `maxDepth` (optional), `includeInvisible` (optional), `searchText` (optional) | Return the accessibility node tree of the current screen, with filters to keep the payload small. | | `aster_find_element` | `deviceId`, `text`, `exact` (optional) | Locate UI elements by text or content description, without pulling the whole hierarchy. | | `aster_input_gesture` | `deviceId`, `gestureType`, `points`, `duration` (optional) | Perform a tap, swipe or long press on the screen. | | `aster_input_text` | `deviceId`, `text` | Type text into the currently focused field. | | `aster_click_by_text` | `deviceId`, `text` | Find and click an element by its text or content description in one call. | | `aster_click_by_id` | `deviceId`, `viewId` | Click an element by its view ID resource name. | ## Navigation and apps Move around the system, launch apps and intents, and take inventory of what is installed. | Tool | Arguments | What it does | |---|---|---| | `aster_global_action` | `deviceId`, `action` | Trigger a global navigation action such as BACK, HOME or RECENTS. | | `aster_launch_intent` | `deviceId`, `packageName` (optional), `action` (optional), `data` (optional) | Launch an app, or fire an Android intent, on the device. | | `aster_list_packages` | `deviceId`, `includeSystem` (optional) | List installed Android packages, optionally including system ones. | | `aster_list_installed_apps` | `deviceId`, `includeSystem` (optional), `cursor` (optional), `limit` (optional) | Page through installed apps with full metadata: label, version, install and update time, on-disk size, declared permissions, and last-used time when Usage access is granted. | | `aster_execute_shell` | `deviceId`, `command` | Run a shell command inside the app sandbox as an unprivileged process — no root, a 30-second timeout and a 1 MB output cap, with no access to system files or other apps’ data. | ## Files and storage Browse, read and write files, and work out where the storage actually went. | Tool | Arguments | What it does | |---|---|---| | `aster_list_files` | `deviceId`, `path` | List the files and directories at a path. | | `aster_read_file` | `deviceId`, `path` | Read the contents of a file on the device. | | `aster_write_file` | `deviceId`, `path`, `content` | Write content to a file on the device. | | `aster_delete_file` | `deviceId`, `path` | Delete a file from the device. | | `aster_analyze_storage` | `deviceId`, `path` (optional), `maxDepth` (optional), `minSizeMB` (optional), `includeHidden` (optional) | Break storage down by directory and file type and flag the large files, as statistics an assistant can reason over. | | `aster_find_large_files` | `deviceId`, `minSizeMB`, `path` (optional), `fileTypes` (optional), `limit` (optional) | Fast search for large files, with an optional file-type filter. | ## Media and camera Search the photo and video library by meaning or by metadata, and capture new media on demand. | Tool | Arguments | What it does | |---|---|---| | `aster_search_media` | `deviceId`, `query` (optional), `path` (optional), `dateFrom` (optional), `dateTo` (optional), `location` (optional), `fileTypes` (optional), `minSizeMB` (optional), `maxSizeMB` (optional), `cameraModel` (optional), `sortBy` (optional), `limit` (optional) | Search media with a natural-language query or explicit filters — "photos from last year same month", "pictures taken at mumbai" — or both together. | | `aster_index_media_metadata` | `deviceId`, `path` (optional), `includeLocation` (optional), `includeExif` (optional), `limit` (optional) | Scan photos and videos recursively and extract EXIF: date taken, GPS location, camera and dimensions. | | `aster_take_photo` | `deviceId`, `camera` (optional), `quality` (optional) | Capture a photo with the device camera at 1280x720 and return the image. | | `aster_record_video` | `deviceId`, `camera` (optional), `maxDuration` (optional) | Record up to 8 seconds at 480p with no audio; returns a base64 MP4 under 5 MB, otherwise a file path. | ## Communication Notifications, SMS and phone calls — the surfaces a phone is actually for. | Tool | Arguments | What it does | |---|---|---| | `aster_read_notifications` | `deviceId`, `limit` (optional) | Read the notifications currently sitting in the shade. | | `aster_read_sms` | `deviceId`, `limit` (optional), `threadId` (optional), `sinceDate` (optional) | Read SMS messages, filterable by conversation thread and date. | | `aster_send_sms` | `deviceId`, `number`, `message` | Send an SMS text message to a phone number. | | `aster_post_notification` | `deviceId`, `title`, `body`, `actions` (optional) | Post a notification on the device. | | `aster_make_call` | `deviceId`, `number` | Place a phone call. | | `aster_make_call_with_voice` | `deviceId`, `number`, `text`, `waitSeconds` (optional) | Place a call and speak text over the loudspeaker. The audio is acoustic coupling — the loudspeaker plays into the call microphone, it is not routed into the call audio — so it works best in a quiet room and quality is device-dependent. | ## Contacts Search, page through and prune the device address book. | Tool | Arguments | What it does | |---|---|---| | `aster_search_contacts` | `deviceId`, `name` (optional), `number` (optional), `limit` (optional) | Search contacts by name or phone number; each match comes back with all of its numbers and emails. | | `aster_list_contacts_full` | `deviceId`, `cursor` (optional), `limit` (optional) | Page through the whole address book — numbers, emails and account type per contact — with a cursor, for indexing. | | `aster_delete_contacts` | `deviceId`, `ids` | Delete contacts by id. Requires WRITE_CONTACTS and returns the count deleted plus a per-id failure list. | ## Audio and alarms Speak, play and silence audio, control the individual volume streams, and manage the clock app’s alarms. | Tool | Arguments | What it does | |---|---|---| | `aster_speak_tts` | `deviceId`, `text` | Speak text through the device text-to-speech engine. | | `aster_play_audio` | `deviceId`, `source` | Play audio from a URL or from base64 data. | | `aster_stop_audio` | `deviceId` | Stop the audio currently playing on the device. | | `aster_vibrate` | `deviceId`, `pattern` | Vibrate the device with a custom pattern. | | `aster_get_volume` | `deviceId` | Read every stream level — media, ring, notification, alarm, call, system — along with the ringer mode. | | `aster_set_volume` | `deviceId`, `stream`, `level` (optional), `mute` (optional) | Set the level, or mute and unmute, for one audio stream. | | `aster_get_alarms` | `deviceId` | List alarms from the stock clock provider, falling back to next-alarm-only. Not every OEM exposes the full list. | | `aster_set_alarm` | `deviceId`, `hour`, `minute`, `message` (optional), `days` (optional), `skipUi` (optional) | Create an alarm through the device clock app. Android exposes no standard way to edit one, so changing an alarm means creating another. | | `aster_dismiss_alarm` | `deviceId` | Dismiss an alarm while it is actually ringing. Does nothing otherwise; needs Android 6.0 or newer. | | `aster_delete_alarm` | `deviceId`, `alarmId` | Delete a saved alarm by id, on devices whose clock content provider is reachable (stock Android, Samsung). | ## Device and UI Device state — battery, location, clipboard — plus the overlay and toast surfaces Aster can draw on top of whatever is running. | Tool | Arguments | What it does | |---|---|---| | `aster_list_devices` | _none_ | List every paired device with its connection status and basic details. | | `aster_get_device_info` | `deviceId` | Report one device’s battery, storage and system specifications. | | `aster_get_battery` | `deviceId` | Return detailed battery information and statistics. | | `aster_get_location` | `deviceId` | Get the current GPS or network location of the device. | | `aster_get_clipboard` | `deviceId` | Read the device clipboard. Android 10 and later only let the foreground app read it, so a backgrounded Aster reports an empty clipboard rather than the real contents. | | `aster_set_clipboard` | `deviceId`, `text` | Copy text to the device clipboard. | | `aster_show_overlay` | `deviceId`, `url` (optional), `html` (optional), `showCloseButton` (optional), `timeout` (optional) | Draw a system overlay containing web content, with an optional close button and auto-timeout. | | `aster_show_toast` | `deviceId`, `message`, `duration` (optional) | Show a toast message on the device. | ## Flat list - `aster_take_screenshot` — deviceId - `aster_get_screen_hierarchy` — deviceId, mode?, maxDepth?, includeInvisible?, searchText? - `aster_find_element` — deviceId, text, exact? - `aster_input_gesture` — deviceId, gestureType, points, duration? - `aster_input_text` — deviceId, text - `aster_click_by_text` — deviceId, text - `aster_click_by_id` — deviceId, viewId - `aster_global_action` — deviceId, action - `aster_launch_intent` — deviceId, packageName?, action?, data? - `aster_list_packages` — deviceId, includeSystem? - `aster_list_installed_apps` — deviceId, includeSystem?, cursor?, limit? - `aster_execute_shell` — deviceId, command - `aster_list_files` — deviceId, path - `aster_read_file` — deviceId, path - `aster_write_file` — deviceId, path, content - `aster_delete_file` — deviceId, path - `aster_analyze_storage` — deviceId, path?, maxDepth?, minSizeMB?, includeHidden? - `aster_find_large_files` — deviceId, minSizeMB, path?, fileTypes?, limit? - `aster_search_media` — deviceId, query?, path?, dateFrom?, dateTo?, location?, fileTypes?, minSizeMB?, maxSizeMB?, cameraModel?, sortBy?, limit? - `aster_index_media_metadata` — deviceId, path?, includeLocation?, includeExif?, limit? - `aster_take_photo` — deviceId, camera?, quality? - `aster_record_video` — deviceId, camera?, maxDuration? - `aster_read_notifications` — deviceId, limit? - `aster_read_sms` — deviceId, limit?, threadId?, sinceDate? - `aster_send_sms` — deviceId, number, message - `aster_post_notification` — deviceId, title, body, actions? - `aster_make_call` — deviceId, number - `aster_make_call_with_voice` — deviceId, number, text, waitSeconds? - `aster_search_contacts` — deviceId, name?, number?, limit? - `aster_list_contacts_full` — deviceId, cursor?, limit? - `aster_delete_contacts` — deviceId, ids - `aster_speak_tts` — deviceId, text - `aster_play_audio` — deviceId, source - `aster_stop_audio` — deviceId - `aster_vibrate` — deviceId, pattern - `aster_get_volume` — deviceId - `aster_set_volume` — deviceId, stream, level?, mute? - `aster_get_alarms` — deviceId - `aster_set_alarm` — deviceId, hour, minute, message?, days?, skipUi? - `aster_dismiss_alarm` — deviceId - `aster_delete_alarm` — deviceId, alarmId - `aster_list_devices` - `aster_get_device_info` — deviceId - `aster_get_battery` — deviceId - `aster_get_location` — deviceId - `aster_get_clipboard` — deviceId - `aster_set_clipboard` — deviceId, text - `aster_show_overlay` — deviceId, url?, html?, showCloseButton?, timeout? - `aster_show_toast` — deviceId, message, duration? ## The on-device catalogue — 77 unprefixed actions A second, different catalogue. These are the actions the Android app dispatches itself, reachable two ways and both on the phone: an app on the same device over Binder IPC (OpenAlly, for example), or the on-device Ktor MCP server. They carry NO `aster_` prefix — the wire name is the name below. Neither catalogue contains the other. 48 names appear in both; 29 of the actions below cannot be reached from an MCP client at all; exactly 1 server tool (`aster_list_devices`) has no on-device action behind it, because brokering between phones is meaningless on the phone itself. The "From MCP" column carries the `aster_` tool that dispatches the action, or "on-device only" when nothing does. Note `click_by_view_id`, which an MCP client calls as `aster_click_by_id`: a rename, not a second capability. ### Screen Control | Action | What it does | From MCP | |---|---|---| | `click_by_text` | Find and tap element by visible text | `aster_click_by_text` | | `click_by_view_id` | Find and tap element by view ID | `aster_click_by_id` | | `find_element` | Search for UI elements by text | `aster_find_element` | | `get_screen_hierarchy` | Read the UI accessibility tree | `aster_get_screen_hierarchy` | | `global_action` | Home, back, recents, and other system actions | `aster_global_action` | | `input_gesture` | Tap, swipe, or long-press on screen | `aster_input_gesture` | | `input_text` | Type text into focused input field | `aster_input_text` | | `long_press` | Long-press an element by ref or coordinates | on-device only | | `observe` | Indexed actionable-element view of the current screen with stable refs; falls back to on-device OCR when the accessibility tree is sparse | on-device only | | `perform` | Invoke an accessibility action on an element by ref | on-device only | | `press_key` | Press a hardware/IME key (Enter, Back, Tab, arrows…) | on-device only | | `screen_approve` | Put a blocking approve/deny card on screen and wait for the owner to decide. | on-device only | | `screen_capability` | Read-only preflight: reports whether the accessibility service is enabled and bound before an automation run starts. | on-device only | | `screen_handoff` | Park an automation run at a step only a person should do — a payment, say — and show why. | on-device only | | `screen_prompt` | Put a blocking choice on screen and wait for the owner to pick. One prompt in flight at a time. | on-device only | | `screen_signin_wait` | Park an automation run at a login or register wall and wait for the owner to finish. | on-device only | | `scroll` | Scroll in any direction | on-device only | | `set_text` | Type text into a specific field by ref (replace or append) | on-device only | | `set_toggle` | Set a switch or checkbox on/off by ref | on-device only | | `take_screenshot` | Capture screen as JPEG; optional annotate adds numbered boxes (Set-of-Marks) | `aster_take_screenshot` | | `tap` | Tap an element by ref or coordinates | on-device only | | `wait_for` | Wait until an element appears or disappears | on-device only | | `wait_for_idle` | Wait until the screen stops changing | on-device only | ### Device | Action | What it does | From MCP | |---|---|---| | `get_battery` | Battery level, charging status, and health | `aster_get_battery` | | `get_device_info` | Model, OS, RAM, storage, and hardware details | `aster_get_device_info` | | `get_location` | Current GPS coordinates and location data | `aster_get_location` | ### Files | Action | What it does | From MCP | |---|---|---| | `delete_file` | Delete files or directories | `aster_delete_file` | | `files.list` | List a folder the owner approved for an app (App Builder host-dir) | on-device only | | `files.read` | Read a file from an owner-approved folder (App Builder host-dir) | on-device only | | `list_files` | Browse files and folders with metadata | `aster_list_files` | | `read_file` | Read text or binary file contents | `aster_read_file` | | `write_file` | Create or overwrite files on device | `aster_write_file` | ### Camera | Action | What it does | From MCP | |---|---|---| | `record_video` | Record short video clip from camera | `aster_record_video` | | `take_photo` | Capture photo from front or back camera | `aster_take_photo` | ### Communication | Action | What it does | From MCP | |---|---|---| | `count_sms` | Count messages in a date window | on-device only | | `delete_contacts` | Delete contacts from the address book by id. Requires WRITE_CONTACTS. | `aster_delete_contacts` | | `list_contacts_full` | Page through the whole address book — every number, email and account type. | `aster_list_contacts_full` | | `make_call` | Initiate a phone call | `aster_make_call` | | `make_call_with_voice` | Call and speak text when answered | `aster_make_call_with_voice` | | `read_sms` | Read inbox, sent, or all messages | `aster_read_sms` | | `search_contacts` | Find contacts by name or phone number | `aster_search_contacts` | | `send_sms` | Send text messages | `aster_send_sms` | ### Notifications | Action | What it does | From MCP | |---|---|---| | `dismiss_all_notifications` | Clear all active notifications | on-device only | | `dismiss_notification` | Dismiss a specific notification | on-device only | | `post_notification` | Show a local notification | `aster_post_notification` | | `read_notifications` | Get active and recent notifications | `aster_read_notifications` | ### Media | Action | What it does | From MCP | |---|---|---| | `get_now_playing` | Read the currently-playing track (title, artist, source app) from the OS media sessions; reuses notification access, no extra permission | on-device only | | `play_audio` | Play audio from URL, file, or data | `aster_play_audio` | | `speak_tts` | Speak text aloud using TTS engine | `aster_speak_tts` | | `stop_audio` | Stop current audio playback | `aster_stop_audio` | | `vibrate` | Vibrate with custom patterns | `aster_vibrate` | ### Storage | Action | What it does | From MCP | |---|---|---| | `analyze_storage` | Disk usage breakdown by directory and type | `aster_analyze_storage` | | `find_large_files` | Find files above a size threshold | `aster_find_large_files` | | `index_media_metadata` | Index photos/videos with EXIF and GPS | `aster_index_media_metadata` | | `search_media` | Search by date, location, type, or camera | `aster_search_media` | ### Apps | Action | What it does | From MCP | |---|---|---| | `launch_intent` | Launch apps or custom Android intents | `aster_launch_intent` | | `list_installed_apps` | Installed apps with package, version, install time, sizes, declared permissions and last-used time. | `aster_list_installed_apps` | | `list_packages` | List all apps with version info | `aster_list_packages` | ### System | Action | What it does | From MCP | |---|---|---| | `execute_shell` | Run commands in app sandbox | `aster_execute_shell` | | `get_clipboard` | Read current clipboard content | `aster_get_clipboard` | | `get_volume` | Volume levels for all audio streams | `aster_get_volume` | | `screen_set_policy` | Receive the owner's per-app screen-control allow/deny list from OpenAlly | on-device only | | `set_clipboard` | Copy text to clipboard | `aster_set_clipboard` | | `set_volume` | Adjust volume or mute audio streams | `aster_set_volume` | | `show_toast` | Show a brief on-screen message | `aster_show_toast` | ### Overlays | Action | What it does | From MCP | |---|---|---| | `companion_overlay_hide` | Take down OpenAlly's ambient companion face | on-device only | | `companion_overlay_recompute` | Recompute the companion face's position around the camera cutout | on-device only | | `companion_overlay_show` | Draw OpenAlly's ambient companion face beside the camera cutout | on-device only | | `companion_overlay_status` | Whether Aster can draw over other apps, and whether OpenAlly's companion face is up | on-device only | | `hide_all_overlays` | Remove all active overlays | on-device only | | `hide_overlay` | Hide a specific floating overlay | on-device only | | `list_overlays` | List active overlay IDs | on-device only | | `show_overlay` | Display floating HTML overlay on screen | `aster_show_overlay` | ### Alarms | Action | What it does | From MCP | |---|---|---| | `delete_alarm` | Remove a saved alarm | `aster_delete_alarm` | | `dismiss_alarm` | Stop a ringing alarm | `aster_dismiss_alarm` | | `get_alarms` | View scheduled alarms | `aster_get_alarms` | | `set_alarm` | Create a new alarm | `aster_set_alarm` | --- # Architecture — how a command reaches the phone > How one command travels from your AI to the phone, and the three transports Aster speaks: remote WebSocket, on-device Ktor MCP server, and Binder IPC. Aster here is the open-source Model Context Protocol server and Android companion app for AI-driven phone control, published on npm as aster-mcp. It is unrelated to Aster DM Healthcare, Aster Data Systems or the ASTER instrument on NASA’s Terra satellite. Source: https://aster.matterwardlabs.com/architecture/ · Part of Aster (https://github.com/satyajiit/aster-mcp) · Last generated 2026-09-13 ## How one command reaches the phone 1. **Your AI client** — The assistant picks a tool and calls it by its full name — aster_take_screenshot, not take_screenshot — over Streamable HTTP to http://localhost:5988/mcp. Arguments travel as JSON. Nothing in this hop knows a phone exists. 2. **Aster server — admit** — The MCP handler parses the arguments against that tool's schema and resolves the target device. A device that was never approved, or that has no live socket in this process, is refused here. A rejected call never reaches the phone and never touches a permission. 3. **Aster server — dispatch** — sendCommand mints a UUID, drops the aster_ prefix and pushes a single JSON frame down the device WebSocket on port 5987. The promise it returns is held open against that id. (30 s timeout) 4. **Android companion** — The command handler on the phone dispatches the action to whichever subsystem owns it — the accessibility service, MediaStore, telephony, CameraX. The kill-switch notification and the fail-closed package policy apply before any screen control runs, in every transport. (~90 ms to ~10 s) 5. **Result frame** — The phone answers on the same socket, carrying the id it was handed. The server matches that id to the waiting promise and clears the timeout. An answer that arrives late, or for a command nobody is waiting on, is dropped rather than resolved. 6. **Back to the model** — The handler shapes the payload into MCP content — text for structured results, a base64 image for screenshots and photos — and returns it as the tool result. It goes to your client and nowhere else; the server keeps no copy off your machine. ## Five commands, traced Five real commands through the hops above, each with the tools involved and a round-trip figure. The prompt is what a person types; everything after it is what the machinery does. ### Take a screenshot of my phone - **Tool call:** aster_take_screenshot with the device id — the only required argument. - **Server:** Resolves the approved device and sends the action take_screenshot down the socket. - **Device:** The accessibility service grabs the current frame and encodes it as a JPEG at quality 75. - **Result:** The capture comes back as base64 image content the model can actually look at. Large frames are written to device storage first and fetched with a follow-up read, so a big screenshot cannot blow up the WebSocket frame. - **Tools:** `aster_take_screenshot`, `aster_get_screen_hierarchy` ### Find all my beach photos from last December - **Tool call:** aster_search_media with the sentence as written; the server parses it into a date window plus keywords. - **Server:** Sends search_media with the parsed filter rather than the raw sentence, so the phone does no language work. - **Device:** A MediaStore query, then an EXIF and location pass over the matches. - **Result:** Matching photos come back as metadata with timestamps and locations intact. The images themselves stay on the phone until you ask for one. - **Tools:** `aster_index_media_metadata`, `aster_search_media` ### Vibrate my phone, I dropped it behind the couch - **Tool call:** aster_vibrate with a waveform pattern of [0, 500, 200, 500]. - **Server:** Nothing to marshal beyond the pattern array — the shortest path through the handler. - **Device:** Vibrator.vibrate with a waveform effect built from the pattern. - **Result:** Two 500 ms pulses with a 200 ms gap. The fastest trace on this page, about 90 ms round trip. - **Tools:** `aster_vibrate`, `aster_play_audio` ### Call Mom and tell her I will be about 20 minutes late - **Tool call:** aster_make_call_with_voice with the number, the sentence to speak, and waitSeconds: 8. - **Server:** Routes make_call_with_voice and then waits. The eight seconds are spent on the device while the call connects, not in the server. - **Device:** A call intent, speakerphone forced on, then text-to-speech says the sentence once the line is up. - **Result:** The message is delivered on speakerphone after the wait. About 10 s end to end, nearly all of it call setup. This is also the tool a dedicated AI phone uses to ring you unprompted — same call, nobody typed the prompt. - **Tools:** `aster_make_call_with_voice`, `aster_make_call`, `aster_speak_tts` ### Anything urgent on my phone? - **Tool call:** aster_read_notifications with a priority filter. - **Server:** Sends read_notifications; the filtering happens against what the phone reports, not a cached copy. - **Device:** The notification listener reads the shade and ranks what is there — 14 active at the time of this call. - **Result:** Two urgent messages, one missed call, one delivery window. About 150 ms round trip, which is why this is the one people leave running. - **Tools:** `aster_read_notifications`, `aster_post_notification` ## Transports | Mode | Where the MCP server runs | Tool namespace | When to use it | |---|---|---|---| | Remote WebSocket | Node server on your machine. Phone dials out to port 5987; clients speak MCP over HTTP on port 5988. | aster_* — 49 MCP tools | The default. You run the server on a laptop or a home box and point any MCP client at it. The only mode with the server-side device-approval gate. | | On-device MCP server | Ktor plus the MCP Kotlin SDK, embedded in the app and running on the phone itself. Default port 8080. No Node server in the middle. | unprefixed actions — 77 in the on-device catalog | You want a client to reach the phone directly, on the LAN or over a private mesh. Trust is whatever your own network controls give you. | | Binder IPC | Same device, no network hop at all. An app on the phone — OpenAlly, for example — binds Aster's service directly. | unprefixed actions — 77 in the on-device catalog | An agent already running on the phone drives it locally: a 32-character token checked in constant time plus an on-device approval prompt. Lowest latency, works with the radio off. | ## Ports Running `aster start` binds three ports on the machine you run it on. The phone only needs to reach the first one; you only need to reach the other two. Open nothing to the public internet — put the link on your LAN or a private mesh. - `5987` — Device WebSocket. The Android companion connects here. - `5988` — API + MCP HTTP. Streamable-HTTP MCP endpoint at /mcp; REST API at /api. - `5989` — Web dashboard. Device registry, approvals, live screen control, logs. The on-device MCP server is the exception: it runs inside the app on the phone and listens on port 8080 by default, so none of the three ports above exist in that mode. Binder IPC opens no port at all. ## Addresses - **MCP endpoint for clients:** `http://localhost:5988/mcp` - **Health check:** `http://localhost:5988/api/health` - **Web dashboard:** `http://localhost:5989` - **Device WebSocket:** `ws://:5987` --- # Is Aster safe? No root, self-hosted, zero telemetry > Self-hosted with zero telemetry and no root: device approval gate, screen-control kill switch, banking apps blocked, and where the link is not encrypted. Aster here is the open-source Model Context Protocol server and Android companion app for AI-driven phone control, published on npm as aster-mcp. It is unrelated to Aster DM Healthcare, Aster Data Systems or the ASTER instrument on NASA’s Terra satellite. Source: https://aster.matterwardlabs.com/security/ · Part of Aster (https://github.com/satyajiit/aster-mcp) · Last generated 2026-09-13 ## No root, no ADB exploit Aster drives the phone through the official Android Accessibility Service API — the same system that powers TalkBack and every screen reader. There is no rooting step, no unlocked bootloader, no shell exploit and no ADB persistence. The service is a toggle in Android Settings that you turn on yourself and can turn off at any time, and Android shows its own warning screen before it will let you enable it. ## Self-hosted, no account, no telemetry The server is an npm package you run on your own machine. There is no sign-up, no licence check, no vendor relay and no analytics call: device records, approvals and logs live in a local SQLite file on that machine. What Aster does not send, nobody can leak. There is exactly one outbound call Aster can make, and it is off until you turn it on: the optional event-forwarding webhook (`aster set-event-forwarding`). Know what it carries before you enable it — six event kinds, to the URL you supply and to nothing else: notification text, SMS sender and body, incoming-call number and resolved contact name, and device connected / disconnected / pairing-required status. Incoming calls in particular default to ON once forwarding is enabled; switch them off with `events.incomingCalls: false` in `~/.aster/event-forwarding.json`. Note the boundary carefully — this is a statement about Aster, not about your AI client. Whatever you type into Claude, OpenClaw or any other MCP client, and whatever the phone returns to it, is sent to that client’s own model provider under that provider’s terms. Aster neither sees nor governs that hop. ## An unapproved device connects, but cannot be commanded Approval is a gate on commands, not on the socket. A new phone that reaches the server is registered with status “pending”, keeps its WebSocket open and is told the handshake succeeded — it can be listed and named in the dashboard, which is how you recognise it well enough to approve it. What it cannot do is act: every command dispatch checks the device status first and refuses a device that is not approved. Nothing runs on the phone until you press approve. ## A kill switch you can always reach While the AI is driving the screen, Aster posts a persistent high-importance notification reading “AI is controlling your phone” with a STOP action. One tap severs the control session and aborts the agent loop within a single action. The notification is deliberately the primary control rather than the on-screen overlay, because the overlay depends on the draw-over-other-apps permission and the notification does not. ## Banking and payment apps are refused by default A companion-side guard blocks screen-control actions — tap, type, scroll, gestures, key presses and app launches — whenever the foreground app matches its bundled banking and payments denylist (bank, upi, wallet, payment, phonepe, paytm, paypal, venmo, cashapp, revolut, coinbase, binance and similar). It is fail-closed: if the live foreground package cannot be read at all, a gated action is refused rather than allowed. The guard ships with that denylist already loaded, so it protects a phone that has never synced a policy. Screen reads, device info and Aster’s own prompts stay ungated, because blinding the agent is not the same as protecting you. ## The file tools are not path-scoped — know this before you approve This is the sharpest edge in Aster, and the previous version of this page described it wrongly. On an approved device the MCP file tools reach any path the app itself can read. aster_list_files, aster_read_file, aster_write_file and aster_delete_file each take only a device id and a path; there is no folder parameter to narrow, and the app holds MANAGE_EXTERNAL_STORAGE, so an absolute path is used as given. Owner-approved folders are real, but they scope a different surface: the on-device IPC host-directory feature, which serves its own files.read and files.list actions to a local agent such as OpenAlly. Approving a device for MCP is therefore a grant over that device’s storage, not over one folder in it. Approve devices you own, on networks you trust. ## The device link is plain ws:// by default The phone-to-server WebSocket is unencrypted. The server opens a plain ws:// listener and terminates no TLS of its own, and the Android app ships a network-security config that permits cleartext, because Android cannot pin a certificate to a bare LAN IP literal. On a home or office network you control, that is a considered trade rather than an oversight. Off it, it is not acceptable: put the phone and the server on a Tailscale tailnet, which carries the same traffic inside an encrypted WireGuard tunnel, needs no port forwarding, and exposes nothing to the public internet. Aster detects the Tailscale CLI and reports the tailnet address for you to use. ## Permissions the app asks for Only the rows marked **Always** are needed for Aster to run at all; decline any of the rest and you lose only the tools that depend on it. | Permission | Needed | Why | |---|---|---| | BIND_ACCESSIBILITY_SERVICE | Per feature | The accessibility service that reads the screen hierarchy and performs taps, text entry and gestures. This is how screen control works at all. It is a separate toggle in Android Settings, off until you enable it, and Android shows its own warning first. | | BIND_NOTIFICATION_LISTENER_SERVICE | Per feature | Reads incoming notifications so the AI can answer “what did I miss” and react to alerts. Also a separate Settings toggle; without it the notification tools return nothing. | | INTERNET | Always | Opens the WebSocket to your server and, in on-device MCP mode, serves the local HTTP endpoint. Aster contacts no address you have not configured. | | FOREGROUND_SERVICE | Always | Keeps the companion running with a visible, permanent notification instead of silently in the background. The notification is the point: you can always see that Aster is up. | | FOREGROUND_SERVICE_SPECIAL_USE | Always | The Android 14+ subtype declaration for that service, registered as device_control. Android requires the category to be stated in the manifest. | | RECEIVE_BOOT_COMPLETED | Per feature | Restarts the companion after a reboot so a phone left on a charger reconnects without you unlocking it. Decline it and you simply reopen the app yourself. | | WAKE_LOCK | Always | Holds the CPU awake long enough to finish a command that arrives while the screen is off, so a request does not die halfway through. | | ACCESS_WIFI_STATE | Always | Reads the current network so the app can show which Wi-Fi it is on and reach the server by LAN address. | | ACCESS_NETWORK_STATE | Always | Detects when connectivity drops so the WebSocket reconnects instead of hanging. | | REQUEST_IGNORE_BATTERY_OPTIMIZATIONS | Per feature | Lets you opt the companion out of Doze, so an idle phone stays reachable. It only opens the system dialog — you decide, and the answer is reversible in Settings. | | POST_NOTIFICATIONS | Always | Shows the foreground-service notification and the screen-control STOP notification, and backs the aster_post_notification tool, so a remote caller can also post a notification of its own. Refusing it removes the kill switch from your notification shade, so grant this one. | | READ_SMS | Per feature | Backs the read-messages tools, so you can ask what a code was or what someone texted. Nothing is uploaded anywhere: a message is read only when a tool call asks for it, and the reply goes to the AI client you connected. | | SEND_SMS | Per feature | Sends a text on your instruction — the “tell her I am running late” case. Every send is an explicit tool call. | | RECEIVE_SMS | Per feature | Surfaces an incoming message as an event, so an agent can react to a one-time code or an alert without polling. | | CALL_PHONE | Per feature | Places a call, including the call-and-speak-a-message flow. Declining it leaves every other tool working. | | READ_PHONE_STATE | Per feature | Tells Aster whether a call is ringing, active or ended, so a spoken message is not played into a dead line. | | READ_CALL_LOG | Per feature | Supplies the caller number on incoming-call events for API 28 to 30 only. Android 12 and newer never deliver the number to an app this way, so on a current phone this grant buys nothing and can be declined. | | MODIFY_AUDIO_SETTINGS | Per feature | Routes audio to the earpiece or speaker for the call-and-speak flow, and restores your setting afterwards. | | ACCESS_FINE_LOCATION | Per feature | Answers “where is my phone” and location-aware requests with a precise fix. Location is read on a tool call and returned to your client; Aster stores no location history. | | ACCESS_COARSE_LOCATION | Per feature | The approximate fallback when you grant only coarse location, or when a precise fix is unavailable. | | MANAGE_EXTERNAL_STORAGE | Per feature | All-files access. This is the permission behind the file tools, and the reason those tools are not path-scoped: with it granted, an absolute path is read or written as given. It is a special-access grant you make in Settings, and it is the single grant to think hardest about. | | READ_EXTERNAL_STORAGE | Per feature | The legacy read path, capped at Android 12L and below. On newer versions the all-files grant supersedes it. | | WRITE_EXTERNAL_STORAGE | Per feature | The legacy write path, capped at Android 10 and below. Ignored entirely on anything newer. | | QUERY_ALL_PACKAGES | Per feature | Lists installed apps so the AI can open the right one by name, and so the banking denylist can identify the foreground package it must refuse to drive. | | PACKAGE_USAGE_STATS | Per feature | Adds a last-used time to the installed-apps list, for “which apps have I not opened in months”. Declaring it is not granting it: you must enable Usage access in Settings, and until you do, last_used comes back empty. | | READ_CONTACTS | Per feature | Resolves a name to a number, so “call Priya” works without you reciting digits. | | WRITE_CONTACTS | Per feature | Required by exactly one tool, and it is the destructive one: aster_delete_contacts, which permanently removes contacts from the address book. There is no save or edit path — ContactHandler.kt dispatches only search_contacts, list_contacts_full and delete_contacts, and checks this permission solely inside the delete. Decline it and the contact tools become read-only. | | CAMERA | Per feature | Captures a photo when a tool call asks for one. There is no background or continuous capture: the camera runs for that single request and stops. | | FOREGROUND_SERVICE_CAMERA | Per feature | The Android 14+ subtype that must be declared for the service to use the camera at all. Camera hardware is marked optional, so Aster installs on a phone without one. | | SET_ALARM | Per feature | Hands an alarm or timer to your existing clock app. Aster does not replace it or run a clock of its own. | | VIBRATE | Per feature | Haptic feedback in the app’s own interface, and the aster_vibrate tool — so a remote caller with an approved device can buzz the phone with a custom pattern (MediaHandler.kt dispatches the `vibrate` action). | | SYSTEM_ALERT_WINDOW | Per feature | Draws the interactive approval overlay and the companion face above other apps. The face itself is decorative — a bare tap on it opens a normal, tap-jacking-protected activity and performs nothing privileged. Without the grant, Aster falls back to a full activity for the same prompts. | ## Hardening Everything above is what Aster does on its own. These 6 changes to a default install are yours to make, in the order they matter. ### 1. Approve only devices you own Approval is the whole trust boundary. An approved device can be commanded, and on the file tools that means any path the app can read. A pending device is harmless — it can connect and be named, but every command dispatch refuses it. Check the name and model in the dashboard before you press approve, and revoke anything you do not recognise. ### 2. Encrypt the link with Tailscale before leaving your LAN The phone-to-server WebSocket is plain and unencrypted, and the server terminates no TLS of its own. On a network you control that is a considered trade. Anywhere else it is not: join both machines to a tailnet and point the phone at the tailnet address, so the same traffic rides an encrypted WireGuard tunnel with no port forwarding and no public exposure. Aster detects the Tailscale CLI and reports the address to use. On the server: ```bash tailscale status # then point the phone at the tailnet host instead of ws://:5987 ``` ### 3. Withhold all-files access unless you want the file and storage tools MANAGE_EXTERNAL_STORAGE is a separate Settings grant, and it is the one that makes the file tools unbounded. Withholding it is the real scoping control — but be clear about the price: it disables eight tools, not four. The four file tools stop reading, and so do aster_analyze_storage, aster_find_large_files, aster_index_media_metadata and aster_search_media, which walk the same external storage. Messages, calls, contacts, notifications, alarms, audio, camera capture and screen control are unaffected. ### 4. Keep notifications enabled so the kill switch stays reachable The STOP control for screen control lives in a persistent notification, deliberately, because it does not depend on the draw-over-other-apps permission the way the on-screen overlay does. Silencing Aster’s notifications removes your fastest way to stop a session mid-action. ### 5. Leave the banking denylist alone, and audit any allow-override The companion refuses to drive a foreground app matching its bundled banking and payments denylist, and refuses outright when it cannot read the foreground package at all. Owner overrides replace that policy wholesale rather than merging into it, so an override list is the exact thing to re-read after any change. ### 6. Leave event forwarding off unless you want it, and check where it points Forwarding is the only way Aster makes an outbound call, and it is off until you enable it. Once on, it POSTs six kinds of event to whatever URL is recorded in `~/.aster/event-forwarding.json`: notification text, SMS sender AND body, the number and resolved contact name of an incoming call, and device connected / disconnected / pairing-required status. Note the asymmetry in that file — every other event kind is opt-in, but incoming calls forward unless `events.incomingCalls` is explicitly `false`. Read the file before trusting a phone that has forwarding enabled, and prefer a destination on your own network. ## Common questions ### Does Aster need root access? No. Aster drives the phone through the official Android Accessibility Service API — the same system that powers TalkBack and every other screen reader — so there is no rooting step, no unlocked bootloader, no ADB exploit and no shell persistence. It runs on Android 8.0+ (API 26). Enabling the service is a toggle in Android Settings that you turn on yourself, behind Android's own warning screen, and can turn off at any time. ### Is the connection between the phone and the server encrypted? Not by default. The phone-to-server WebSocket is plain ws:// and the server terminates no TLS of its own; the Android app ships a network-security config that permits cleartext because Android cannot pin a certificate to a bare LAN IP address. On a home or office network you control, that is a considered trade. Off it, it is not acceptable — join the phone and the server to a Tailscale tailnet so the same traffic rides an encrypted WireGuard tunnel, with no port forwarding and nothing exposed to the public internet. ### Can Aster read or control my banking apps? Driving them is refused by default. A companion-side guard blocks screen-control actions — tap, type, scroll, gestures, key presses and app launches — whenever the foreground app matches its bundled banking and payments denylist, and it is fail-closed: if the live foreground package cannot be read at all, a gated action is refused rather than allowed. The guard ships with that denylist loaded, so it protects a phone that has never synced a policy. Screen reads are deliberately not gated by it, because blinding the agent would also blind the check that decides whether acting is safe; the guard stops the agent acting, not looking. ### Which folders can the file tools reach? Any path the app itself can read, on a device you have approved. aster_list_files, aster_read_file, aster_write_file and aster_delete_file each take only a device id and a path — there is no folder parameter to narrow — and the app holds the all-files MANAGE_EXTERNAL_STORAGE grant, so an absolute path is used as given. Owner-approved folders are real but scope a different surface: the on-device IPC host-directory feature used by a local agent such as OpenAlly. Withholding all-files access is what bounds this, and it costs more than the four file tools — see the next answer. ### What exactly stops working if I decline all-files access? Eight tools, not four. MANAGE_EXTERNAL_STORAGE is a separate Settings grant, and declining it disables the four file tools — aster_list_files, aster_read_file, aster_write_file, aster_delete_file — and also the four storage and media tools that walk the same external storage: aster_analyze_storage, aster_find_large_files, aster_index_media_metadata and aster_search_media. So "find what is eating my storage" and "find my beach photos from December" stop working too. Messages, calls, contacts, notifications, alarms, audio, camera capture and screen control are unaffected. ### Does a new phone get access as soon as it connects? No. Approval gates commands, not the socket. An unknown phone that reaches the server is registered with status "pending", keeps its WebSocket open and is told the handshake succeeded, so you can see and name it in the dashboard well enough to recognise it. It cannot act: every command dispatch checks the device status first and refuses anything that is not approved. Nothing runs on the phone until you press approve. ### What happens if I lose the phone? Reject the device in the dashboard. That flips its stored status, pushes a rejection to the phone and closes its WebSocket, and any later reconnection is refused at command dispatch — so the AI can no longer act on it even if the handset is powered on and online. Because the link only works across your own LAN or tailnet, a phone off that network cannot be commanded in the first place. Aster holds no cloud account to compromise; the device records live in a local SQLite file on your own machine. ### Does Aster phone home? No, with one opt-in exception you turn on yourself. There is no sign-up, no licence check, no analytics call and no vendor relay: the server is a MIT-licensed npm package you run yourself, and device records, approvals and logs stay in a local SQLite file on that machine. The one outbound call Aster can make is the optional event-forwarding webhook — off until you run `aster set-event-forwarding`, and then it POSTs only to the URL you supplied. It carries six event kinds, and the list is worth reading before you switch it on: notification text, SMS sender and body, the number and resolved contact name of an incoming call, and device connected / disconnected / pairing-required status. Incoming-call forwarding is on by default once forwarding is enabled — set `events.incomingCalls: false` in `~/.aster/event-forwarding.json` to stop it. Scope the rest of the claim correctly, too: it is about Aster, not about your AI client. Whatever you type into Claude, OpenClaw or any other MCP client, and whatever the phone returns to it, still goes to that client's own model provider under that provider's terms. Aster neither sees nor governs that hop. ### Where is my Aster data stored, and how do I delete it? Two places, both on the machine running the server, and both plain files you can delete. The database — devices, approvals, tool-call logs — is SQLite at `./aster.db`, resolved relative to the directory you ran `aster start` from, or wherever `DB_PATH` points. Runtime state lives in `~/.aster`: `~/.aster/status.json`, `~/.aster/aster.pid` and, if you enabled forwarding, `~/.aster/event-forwarding.json`. Stop the server, delete both, and nothing of Aster's is left. There is no cloud copy to request. --- # Set up Aster — MCP server, Android app, AI client > Install aster-mcp from npm, run aster start, sideload the Android app, approve the device and point Claude Code at http://localhost:5988/mcp. Aster here is the open-source Model Context Protocol server and Android companion app for AI-driven phone control, published on npm as aster-mcp. It is unrelated to Aster DM Healthcare, Aster Data Systems or the ASTER instrument on NASA’s Terra satellite. Source: https://aster.matterwardlabs.com/setup/ · Part of Aster (https://github.com/satyajiit/aster-mcp) · Last generated 2026-09-13 ## Before you start - **Server — Node.js >= 20.** Any machine that stays awake: laptop, desktop, mini PC or NAS. macOS, Linux and Windows all work. - **Phone — Android 8.0+ (API 26).** The companion APK pins minSdk 26. Anything older cannot install it, and no root is needed on anything newer. - **Network — Same LAN, or Tailscale.** The phone dials out to the server, so the two only need to see each other — no inbound port forwarding. No root, no ADB cable and no Google account are required at any point. If the phone and the server cannot sit on the same LAN, join both to [Tailscale](https://tailscale.com/) and use the tailnet address everywhere this page says “server address”. ## Step 1 — Install the MCP server The server is an npm package, aster-mcp, currently version 0.1.16. Install it globally on the machine that will stay awake — a laptop, a desktop, a NAS or a mini PC. ```bash npm install -g aster-mcp ``` Note: Node.js >= 20 is required. Running the server inside Termux on the phone itself is untested and unsupported; use the app's on-device MCP mode instead. ## Step 2 — Start the server One command brings up all three listeners: 5987 for the Device WebSocket, 5988 for the API + MCP HTTP, 5989 for the Web dashboard. The terminal prints the exact WebSocket address to type into the phone. ```bash aster start ``` Note: Keep these ports on your LAN or your tailnet. Do not port-forward 5987 or 5988 to the public internet — the device link is plain ws:// and terminates no TLS of its own. ## Step 3 — Install the Android companion Download the latest APK from GitHub Releases and sideload it. It runs on Android 8.0+ (API 26) and needs no root. Use your daily phone, or a spare Android you want to hand over to your AI entirely. Note: Accessibility Service is the one permission Aster cannot work without — it is what reads the screen and drives taps. Notifications, SMS, phone, location and storage are each optional and asked for separately. ## Step 4 — Point the phone at the server Open Aster on the phone, enter the WebSocket address the terminal printed — ws://:5987 — and tap Connect. For a dedicated AI phone, plug it into a charger and leave it there. Note: The address is a LAN IP and is only valid on that network. If you move between Wi-Fi networks, join both ends to Tailscale and use the stable tailnet address instead. ## Step 5 — Approve the device A new device arrives as pending and can connect but not be commanded. Approve it from the CLI, or click Approve in the dashboard at http://localhost:5989. ```bash aster devices approve ``` Note: A device left pending is the single most common setup failure: it shows as connected while every tool call times out. There are no tokens or per-device keys — approval is the whole access-control model. ## Step 6 — Point your AI client at the MCP endpoint Add Aster to your client as a streamable-HTTP MCP server at http://localhost:5988/mcp. All 49 tools then appear namespaced aster_*, so a call looks like aster_take_screenshot. ```json { "mcpServers": { "aster": { "type": "http", "url": "http://localhost:5988/mcp" } } } ``` Note: One address per audience: 5987 goes in the phone, 5988/mcp goes in the AI client, 5989 goes in your browser. Pasting the wrong port into the wrong app is the second most common setup failure. ## Connecting an AI client ### Claude Code and Claude Desktop Add the entry below to your project's .mcp.json, or to the Claude Desktop MCP settings, and restart the client. Aster registers 49 tools, every one of them prefixed aster_. ```json { "mcpServers": { "aster": { "type": "http", "url": "http://localhost:5988/mcp" } } } ``` ### OpenClaw, MoltBot and ClawdBot Aster ships as a skill on ClawHub, the registry agent clients such as OpenClaw, MoltBot and ClawdBot install skills from. Install it from the hub, or fetch the skill file straight from the repository if your client takes a raw URL. ```bash clawhub install aster # or take the skill file directly: curl -O https://raw.githubusercontent.com/satyajiit/aster-mcp/main/skill/SKILL.md ``` ### AnythingLLM and any MCP client There is nothing Aster-specific to install. The server speaks standard streamable-HTTP MCP, so any compliant client works by pointing it at the endpoint. On a LAN, swap localhost for the server machine’s address. ```text http://localhost:5988/mcp ``` ### OpenAlly OpenAlly runs on the same phone and drives Aster over Android Binder IPC — no server, no network hop, nothing to configure. Install both apps, approve the on-device handshake once, and OpenAlly has the full on-device action catalog (77 actions, unprefixed). This is the only path that needs no MCP endpoint at all. ## Checking it works Two commands separate “the server is up” from “the phone is reachable”. Run both before blaming the AI client. ```bash aster status curl http://localhost:5988/api/health ``` What each check proves: - **aster status** — Prints the human-readable snapshot the running server writes to ~/.aster/status.json: the advertised addresses, and every device with its approval state. A device listed as pending is connected but cannot be commanded yet. - **curl http://localhost:5988/api/health** — Returns { "status": "ok", "timestamp": ... }. This is the endpoint to poll from scripts and monitors; GET /api/stats next to it returns device counts. - **Your AI client** — Ask it to list its tools. You should see 49 names, all beginning aster_ — aster_take_screenshot is the cheapest one to try first. If the list is empty, the client never reached http://localhost:5988/mcp; if a call hangs, the device is still pending. --- # FAQ and troubleshooting > Device stuck on pending, ws:// versus wss://, which ports to open, no audio on a voice call, why Aster has no chat screen, and how it compares to scrcpy. Aster here is the open-source Model Context Protocol server and Android companion app for AI-driven phone control, published on npm as aster-mcp. It is unrelated to Aster DM Healthcare, Aster Data Systems or the ASTER instrument on NASA’s Terra satellite. Source: https://aster.matterwardlabs.com/faq/ · Part of Aster (https://github.com/satyajiit/aster-mcp) · Last generated 2026-09-13 Everything here comes out of the source: the repo's troubleshooting notes, the tool definitions on the MCP server, and the Android module's own build configuration. Where a published claim disagrees with the code, the code wins and the answer says so. ## Requirements and cost ### What Android version do I need to run the Aster companion app? The Aster Android companion app requires Android 8.0+ (API 26), because apps/android/app/build.gradle.kts sets minSdk = 26 and API 26 is Android 8.0 Oreo. A phone on Android 7 cannot install it at all. The app compiles against and targets API 36, ships an arm64-v8a build, and needs its Accessibility Service switched on by hand after install — that toggle, not the OS version, is what actually unlocks screen control. ### Does Aster need root, a custom ROM or USB debugging? Aster needs none of those: it runs on a stock, unrooted phone. Screen control comes from an Android AccessibilityService, notifications from a NotificationListenerService, and everything else from ordinary runtime permissions you grant in the app — all of them public Android APIs. There is no root requirement, no custom ROM, no ADB shell and no USB cable in the loop; the phone reaches the server over the network, which is the main practical difference from scrcpy and raw ADB. ### Is Aster free? What is the licence? Aster is free and open source under the MIT licence — both halves of it, the Node MCP server published to npm as aster-mcp and the Android companion app. There is no account to create, no paid tier, no licence key and no usage metering, because Aster is self-hosted: you run the server on your own machine, so there is nothing for anyone to bill. The only costs you can incur are the ones your own AI client already has, such as tokens billed by whichever model provider it talks to. ### Does Aster work with an iPhone or an iPad? No. Aster is Android only, and not by omission — it drives the phone through Android's AccessibilityService and NotificationListenerService, and reads SMS, contacts and call state through Android content providers. iOS exposes no equivalent to a third-party app, so there is nothing to port. The companion is an Android APK (Android 8.0+ (API 26), arm64-v8a); the aster-mcp server itself runs on macOS, Linux or Windows, but the phone at the other end has to be an Android device. ### Can one server drive more than one phone? Yes. Devices are addressed individually: every tool but one takes a deviceId, and the exception is aster_list_devices, which takes nothing and returns every paired phone with its connection status so your assistant can pick one. Each phone holds its own WebSocket to the same server and is approved separately, so a spare handset dedicated to the AI and your daily phone can be paired at once with different approval states. This is the one thing the on-device modes cannot do — aster_list_devices is the single server tool with no on-device equivalent, because brokering between phones is meaningless on the phone itself. ### Is the Aster app on the Google Play Store? No. The companion app is distributed as an APK from GitHub Releases and installed by sideloading — an accessibility app that can read the screen and drive other apps is not a comfortable fit for Play's policies, and shipping it as a signed release you install deliberately is the honest packaging. Android will ask you to allow installs from that source once. The server half is on npm as aster-mcp, which needs no such step. ### How do I update Aster once it is installed? The two halves update separately. For the server, `npm install -g aster-mcp` again — it is an ordinary global npm package, so the same command installs and upgrades; `aster --version` tells you where you are. For the phone, download the newer APK from Releases and install it over the existing one; the signature matches, so it upgrades in place and keeps your approvals, permission grants and the accessibility toggle. Approved devices survive both, because the approval lives in the server's database, not on the handset. ## Connecting and troubleshooting ### My device shows as connected but every command fails or times out. Why? A device that shows as connected in Aster but times out on every command is almost always still pending approval, because connecting and being approved are two separate states. A new phone self-reports its identity (a hashed Android ID, its name and model) and lands as pending: connected, but hard-blocked — the server refuses every command for any device whose status is not approved. Approve it from the dashboard at http://localhost:5989, or run "aster devices approve DEVICE_ID" on the server. A device you reject instead is disconnected with WebSocket close code 4003. ### The device disconnects when I switch between Wi-Fi and mobile data. How do I fix it? The Aster companion app now reconnects by itself after a Wi-Fi or mobile-data switch: it backs off and retries, showing "Reconnecting" in the app, instead of sitting on a socket that died with the old network. If it never comes back, the problem is the address rather than the socket — "aster status" advertises a LAN IP that is only valid on the network the server was started on, so the phone is retrying an address that no longer exists. Keep both ends on one network, or point the app at a stable Tailscale address so the server's address survives the switch. ### Which ports does Aster need open in my firewall? Aster listens on three local ports: 5987 is the WebSocket device link the phone connects to, 5988 is the API plus MCP HTTP endpoint that serves POST /mcp and the health route, and 5989 serves the web dashboard on the server machine. In practice the phone needs to reach 5987 and your AI client needs to reach 5988; 5989 only has to be reachable from whatever browser you open the dashboard in. Allow those on your LAN or your Tailscale network, and do not port-forward any of them to the public internet — there is no shared secret on the device link, so exposure is the whole risk. ### How do I check that the Aster server is reachable from a script? To check an Aster server programmatically, request its health endpoint: "curl http://localhost:5988/api/health" returns a small JSON body with a status of ok and a timestamp, so a non-zero exit or a missing ok is your liveness signal. For device counts, GET /api/stats on the same port returns the registry totals. For a human-readable snapshot instead, run "aster status" on the server: it prints the advertised LAN address, the listening ports and the connected devices with their approval state. ## Security and privacy ### Is the connection encrypted? Do I need SSL? The Aster device link is not encrypted by default: on a trusted LAN the phone connects over plain ws:// to port 5987, and the Node ws server does not terminate TLS itself, so aiming wss:// at that port with nothing in front of it fails with a TLS parse error rather than upgrading. For remote or encrypted access you put the encryption in front of the socket — Tailscale Serve gives the app a wss:// MagicDNS address while MCP stays plain HTTP on the Tailscale IP at port 5988, or you terminate TLS at Traefik or Caddy and proxy to ws:// on localhost. Encryption for remote control therefore comes from WireGuard or from your own TLS terminator, never from a padlock on the Node socket. ### Does Aster send my data anywhere? Aster sends nothing anywhere: it has no telemetry, no analytics and no vendor relay. The server runs on hardware you own, the device registry and logs live in a local SQLite file next to it, and your AI client reaches the tools over a local MCP HTTP endpoint on your own network. The single outbound call Aster can make is the optional event-forwarding webhook, which stays off until you run "aster set-event-forwarding" and then POSTs only to the URL you supplied. Your AI client is a separate question: if it is backed by a hosted model, that client still sends your prompts — and whatever screenshots or text Aster hands back — to its own model provider. ### Can the AI open or control my banking apps? Aster blocks AI screen control over banking and payments apps by default, through an on-device guard called PackagePolicyGuard that runs regardless of what the AI asks for. It is fail-closed: it ships a bundled denylist covering apps such as PhonePe, Paytm, PayPal, Venmo and Binance, it refuses control actions while one of those is in the foreground unless you have explicitly allowed that app, and it also refuses when the foreground app cannot be identified at all. The guard gates acting rather than looking — read-only actions such as observe, screenshot, view hierarchy and find element stay permitted — so treat it as a control block rather than a blindfold, and use the persistent kill-switch notification to end a screen-control session in one tap. ### Where does Aster store my data, and how do I delete it? Two plain files on the machine running the server, and nothing anywhere else. Devices, approvals and tool-call logs go into a SQLite database at `./aster.db` — resolved relative to the directory you ran `aster start` from, or wherever the `DB_PATH` environment variable points. Runtime state lives in `~/.aster`: `~/.aster/status.json`, `~/.aster/aster.pid`, and `~/.aster/event-forwarding.json` if you turned forwarding on. Stop the server and delete both and nothing of Aster's remains. There is no cloud copy, no account and therefore no deletion request to file. ## Tools and behaviour ### How many tools does Aster have, and what are they called? The number depends on which surface you are talking to. The Aster MCP server registers 49 tools, and every one of them carries the aster_ prefix — an MCP client sees aster_take_screenshot, aster_send_sms and aster_make_call_with_voice, so calling take_screenshot without the prefix is not a valid tool name. The on-device catalog used by the app's own MCP server and by Binder IPC is a different set of 77 unprefixed actions, and neither catalogue contains the other. 48 of the on-device actions are reachable from an MCP client, 29 are not — observe, tap, scroll, set_text, perform, press_key, the wait verbs, three of the four overlay verbs, the companion-face verbs, and the screen_* human-in-the-loop verbs. Going the other way, exactly 1 server tool has no on-device counterpart, aster_list_devices, because brokering between phones is meaningless on the phone itself; aster_click_by_id looks like a second one but is only a rename of the device action click_by_view_id. Quoting a single number without naming the surface is what makes tool counts wrong. ### The AI placed a call but the other side heard no audio. What went wrong? When aster_make_call_with_voice dials successfully but nobody hears the message, that is usually the design rather than a fault: the text is spoken by the phone's own text-to-speech over the loudspeaker and reaches the call by acoustic coupling — the loudspeaker feeding the call microphone — because Android does not let an app inject audio into the call path. It follows that quality is device-dependent and a quiet room matters a great deal. Check that CALL_PHONE, READ_PHONE_STATE and MODIFY_AUDIO_SETTINGS are granted, that the phone is not on silent and the call audio is not routed to a headset or a car, and remember the tool waits for the call to be answered before it speaks (8 seconds by default, adjustable with the waitSeconds parameter). ### Why does the Aster app have no chat screen? The Aster app has no chat screen on purpose. Aster is the device-side companion that holds the sensitive permissions — accessibility, SMS, contacts, files, camera — so it stays deliberately small and auditable, with no model providers, no API keys and no conversation storage inside the app that holds those permissions. The app you chat with is OpenAlly (https://openally.ai), which drives Aster on the same phone over Binder IPC, so in practice there is no app-switching: you type in OpenAlly and Aster does the device work. Any other MCP client — Claude Code, Claude Desktop, AnythingLLM, OpenClaw — reaches exactly the same tools through the server or through the on-device MCP mode. ## Still stuck? Run these three checks on the machine hosting the server before opening an issue. Between them they separate the three failures that account for nearly everything: the server is not running, the device is connected but not approved, or the phone is pointed at an address that no longer exists. 1. Confirm the server is up, and note the LAN address it advertises — that is the address the phone must be able to reach. 2. Check the device's approval state. A device listed as pending is connected and still refused on every command. 3. Hit the health endpoint from wherever your AI client runs, not just from the server itself — that is what proves the path, not the process. ```bash aster status aster devices list curl http://localhost:5988/api/health ``` ## Aster compared to scrcpy and raw ADB Both are excellent — for a person at a keyboard with a USB cable. Aster solves a different problem: it is not a screen mirror but a tool layer an AI agent calls by name, with the approval gate and kill switch a mirror was never meant to have. | | scrcpy / raw ADB | Aster | |---|---|---| | **Connection** | A USB cable, or USB debugging enabled over the LAN | A companion app over the network, with no USB debugging | | **Remote access** | Needs tunnelling or ADB-over-TCP set up by hand | Works from anywhere over Tailscale, with no port forwarding | | **Interface** | A mirrored screen plus raw shell commands | High-level MCP tools an AI calls by name: aster_take_screenshot, aster_click_by_text, aster_send_sms, aster_make_call_with_voice | | **Built for** | A person driving the phone manually at a keyboard | An AI agent acting on its own, behind an approval gate and a kill switch | | **Safety rails** | None beyond what the shell itself enforces | Per-device approval, a fail-closed banking denylist, and a persistent stop notification | | **Events** | Pull only — you ask, it answers | Pushes SMS and notification events to your AI as they arrive |