MCP tools
The MCP server at https://api.fones.ai/mcp has these tools. They share their code with the REST API, so the two always agree. A tool your key has no scope for is not listed to your agent.
The loop
Phones change under your agent: a dialog opens, a page loads late. The tools are built for a short loop:
- Look.
screenshotfor an image,ui_snapshotfor the screen as text.ui_snapshotis cheaper and gives exact centers; take a screenshot when the screen is mostly pictures. - Act. One
tap,swipe,type_textorpress_key. - Look again before the next action, to see what the action did.
Coordinates
Every point is on a grid from 0 to 1000 on both axes, whatever the phone’s resolution: (0, 0) is the top left corner, (1000, 1000) the bottom right, (500, 500) the middle. ui_snapshot returns each node’s center on the same grid, so a node’s x and y can go straight into tap.
Which phone
Tools that act on a phone take phone_id (ph_..., from list_phones). When the account has exactly one phone, phone_id can be left out.
Tools
Look
| Tool | Arguments | Scope | What |
|---|---|---|---|
list_phones |
limit?, before? |
phones:read |
Your phones with status, number and IP. |
get_phone |
phone_id |
phones:read |
One phone. |
screenshot |
phone_id |
phones:read |
The screen as an image. |
ui_snapshot |
phone_id |
phones:read |
The screen as nodes: text, description, id, class, clickable, center x and y and bounds on the grid. |
Act
| Tool | Arguments | Scope | What |
|---|---|---|---|
tap |
phone_id, x, y |
phones:control |
Tap a point. |
long_press |
phone_id, x, y, ms? |
phones:control |
Press and hold a point. |
swipe |
phone_id, x1, y1, x2, y2, ms? |
phones:control |
Swipe from one point to another. |
type_text |
phone_id, text |
phones:control |
Type into the focused field. |
press_key |
phone_id, key |
phones:control |
One key: home, back, enter, recents, delete, tab, power, wake. |
open_app |
phone_id, package |
phones:control |
Start an app by package name. |
shell |
phone_id, command |
phones:control |
Run a root shell command. Output is cut at 64 KB. |
Apps
| Tool | Arguments | Scope | What |
|---|---|---|---|
list_apps |
none | phones:read |
The app catalog. |
install_app |
phone_id, app |
phones:control |
Install an app from the catalog. |
Texts
| Tool | Arguments | Scope | What |
|---|---|---|---|
read_sms |
phone_id, since?, limit?, before? |
phones:read |
Texts on the phone’s number, newest first, with the number that got each and the code when one is found. |
Buy
| Tool | Arguments | Scope | What |
|---|---|---|---|
get_catalog |
none | phones:read |
What can be bought, the prices and your balance. |
buy_phone |
tier, model?, number?, proxy?, label?, idempotency_key? |
phones:buy |
Buy a phone from the balance. |
add_number |
phone_id, idempotency_key? |
phones:buy |
Add a US number to a ready phone: the rest of the period now, then renewed with it. |
add_proxy |
phone_id, idempotency_key? |
phones:buy |
Add a static IP to a ready phone: the rest of the period now, then renewed with it. |
release_phone |
phone_id, now? |
phones:buy |
Stop renewing, or release at once with now. One still provisioning is cancelled and refunded. |
Keys for press_key: home, back, enter, recents, delete, tab, power, wake. ms is a duration in milliseconds. since is an ISO 8601 time.
What comes back
screenshot: an image (PNG or JPEG).ui_snapshot:{ "nodes": [...] }, each withtext,description,id,class,clickable, its centerx,yand itsbounds[left, top, right, bottom], all on the grid; empty nodes nothing can tap are left out.read_sms: texts newest first, each withnumber(the number that received it),from,text,code(the verification code found in the text, or null) andreceived_at.buy_phone: the new phone with statusprovisioning. It is ready whenget_phonesaysready, or when your webhook getsphone.ready. Passidempotency_keyand a retry never buys twice.add_number,add_proxy: the phone, withnumber_statusorproxy_statusprovisioninguntil the number or IP shows. They charge the rest of the phone’s current period now; once ready the add-on is part of the phone’s renewal price. They acceptidempotency_keytoo.list_phones,read_sms,list_apps:{ "data": [...] }; the first two page withlimitandbefore(next_before).- Actions:
{ "ok": true }, or forshell{ "ok": true, "output": "..." }.
Scopes
| Scope | Tools |
|---|---|
phones:read |
look at phones, screens, texts, apps, catalog |
phones:control |
gestures, text, keys, apps, shell |
phones:buy |
buy phones, numbers and static IPs, release phones |
Errors
A failed call returns an error result with { "error": { "code", "message" } }, the same body as REST. The codes are listed in the REST API. Three worth knowing: phone_not_ready (the phone is still provisioning, try again shortly), insufficient_balance (top up first; missing_cents says how much) and phone_error (the phone did not answer: retry, or look first when the message says so).
Two cases are protocol errors (JSON-RPC -32602) instead, and nothing runs: arguments that do not fit the tool (a wrong type, a value out of range, or an argument the tool does not have, such as numbr), with a message naming the argument; and a tool outside your key’s scopes, which answers Tool <name> not found. That is on purpose: such a tool is not listed for that key either, so to your agent it does not exist.
The server is stateless: tools/list and tools/call work without an initialize first.