How a command reaches the phone
Aster is two pieces of software and one socket between them: an MCP server you run on your own machine, and an Android companion app that holds the permissions. This page traces a single command across both, then lays out the three transports a client can use to get to the phone and the ports each one occupies.
Nothing here goes through a hosted service. The server binds to your machine, the phone dials out to it, and the tool result goes back to your client. There is no account and no telemetry in the path.
Six hops, end to end
Ask for a screenshot and six things happen, in order, on hardware you own. Your assistant calls an MCP tool; the Aster server admits or refuses it; one JSON frame crosses a WebSocket to the phone; Android does the work; the answer comes back on the same socket. No vendor relay sits in the middle of any hop.
// ws://<server-ip>:5987 server -> phone
{
"type": "command",
"id": "6f1c9f2e-6d3a-4a51-9a0e-2b7c5d8e1f43",
"action": "take_screenshot",
"params": {}
} Note the action: the server strips the aster_ prefix on dispatch. Your client calls aster_take_screenshot; the phone receives take_screenshot. Only the prefixed name is callable from an MCP client.
- 01
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.
- 02
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.
- 03
Aster server — dispatch
30 s timeoutsendCommand 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.
- 04
Android companion
~90 ms to ~10 sThe 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.
- 05
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.
- 06
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.
Three ways a client reaches the phone
The trace above is the default path. The companion app speaks three transports and you pick one per device. They differ in where the server runs, how the phone decides to trust the caller, and — the part that bites people — which tool names a client actually sees.
| Transport | Where the server runs | Tool names a client sees | Choose it when |
|---|---|---|---|
| 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. |
The catalogues are not the same list. The server exposes 49 prefixed tools; the on-device catalogue holds 77 unprefixed actions. 48 of those are reachable from an MCP client and 29 are not; going the other way, exactly 1 server tool has no on-device action behind it. Anything that tells you all three modes share one set of 49 tools is out of date.
Five commands, traced
The same six hops, with the details filled in. Each of these is a real prompt, the tool it resolves to, and what comes back. Round-trip figures are from a phone and a server on the same network; a phone on mobile data is slower, and a voice call is dominated by the seconds the call itself takes to connect.
“Take a screenshot of my phone”
- 1
Tool call. aster_take_screenshot with the device id — the only required argument.
- 2
Server. Resolves the approved device and sends the action take_screenshot down the socket.
- 3
Device. The accessibility service grabs the current frame and encodes it as a JPEG at quality 75.
- 4
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 involved
- aster_take_screenshot
- aster_get_screen_hierarchy
- 1
“Find all my beach photos from last December”
- 1
Tool call. aster_search_media with the sentence as written; the server parses it into a date window plus keywords.
- 2
Server. Sends search_media with the parsed filter rather than the raw sentence, so the phone does no language work.
- 3
Device. A MediaStore query, then an EXIF and location pass over the matches.
- 4
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 involved
- aster_index_media_metadata
- aster_search_media
- 1
“Vibrate my phone, I dropped it behind the couch”
- 1
Tool call. aster_vibrate with a waveform pattern of [0, 500, 200, 500].
- 2
Server. Nothing to marshal beyond the pattern array — the shortest path through the handler.
- 3
Device. Vibrator.vibrate with a waveform effect built from the pattern.
- 4
Result. Two 500 ms pulses with a 200 ms gap. The fastest trace on this page, about 90 ms round trip.
Tools involved
- aster_vibrate
- aster_play_audio
- 1
“Call Mom and tell her I will be about 20 minutes late”
- 1
Tool call. aster_make_call_with_voice with the number, the sentence to speak, and waitSeconds: 8.
- 2
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.
- 3
Device. A call intent, speakerphone forced on, then text-to-speech says the sentence once the line is up.
- 4
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 involved
- aster_make_call_with_voice
- aster_make_call
- aster_speak_tts
- 1
“Anything urgent on my phone?”
- 1
Tool call. aster_read_notifications with a priority filter.
- 2
Server. Sends read_notifications; the filtering happens against what the phone reports, not a cached copy.
- 3
Device. The notification listener reads the shade and ranks what is there — 14 active at the time of this call.
- 4
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 involved
- aster_read_notifications
- aster_post_notification
- 1
The full catalogue of 49 tools, each with its arguments, is on the tools page, and the separate 77-action on-device catalogue is listed there too. What a phone will refuse to do, and why, is on the security page.
Ports the server opens
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.
| Port | What listens | What it is for |
|---|---|---|
| 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. |
Addresses in full
- MCP endpoint for clients
- http://localhost:5988/mcp
- Health check
- http://localhost:5988/api/health
- Web dashboard
- http://localhost:5989
- Device WebSocket
- ws://<server-ip>:5987
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. Installing the server and pointing a client at it is covered on the setup page.