How to connect and use Ramus from Zed
Connect Ramus to the Zed agent so it can run your APK on a cloud Android device, check a flow and send you a link to watch or share.
ZedPublished
On this page
This guide is for developers who build Android apps and use the agent panel in Zed. At the end, the Zed agent can start a cloud Android device, install your APK, tap through a flow and fix what breaks. It sends you a link, so you or a reviewer can watch the device. You take over from your device page.
What you need
- Zed, with the Zed Agent in the agent panel and a model that can call tools and read images.
- A Ramus account, which you sign in to with GitHub. An anonymous trial from
ramus trialcannot sign in to the MCP server. - An Android project you can build locally, such as a Gradle app that produces
app/build/outputs/apk/debug/app-debug.apk. - Node.js 22 or later, if you also want the
ramusCLI.
Connect Ramus
Zed calls MCP servers context servers. Ramus is a remote one at
https://api.ramus.dev/api/mcp, and Zed talks to it over Streamable HTTP. Zed
signs in to it with your Ramus account, so there is no key to store.
Add the server
Open Zed's settings (cmd-,), go to AI, then MCP Servers. The command
agent: open settings gets you there too. Click Add Server, choose Add
Remote Server and fill in:
- Server Name:
ramus - URL:
https://api.ramus.dev/api/mcp
Leave Headers empty and save. Zed writes the entry to your settings file.
You can also add it by hand: run zed: open settings file and add:
{
"context_servers": {
"ramus": {
"url": "https://api.ramus.dev/api/mcp"
}
}
}
An entry without a header holds no secret, so your team can share it in
.zed/settings.json. Each person signs in with their own account. Older guides
also add "source": "custom"; current Zed does not need it.
Sign in with Ramus
The ramus row says Authenticate to connect this server. Click
Authenticate. Your browser opens Ramus's consent page, Connect Zed to
Ramus. Sign in with GitHub if asked, and choose Allow.


Zed identifies itself with a client metadata document at
https://zed.dev/oauth/client-metadata.json and receives the sign-in on
http://127.0.0.1:<port>/callback, which Ramus accepts.
Or use an API key
Use a key where nobody can complete a browser sign-in. Open the
Agents page, choose the API key tab, name the
key zed and create it. Copy the key; the page shows it only once. A key works
for your whole account until it expires or you revoke it there. Add it to the
server as a header:
{
"context_servers": {
"ramus": {
"url": "https://api.ramus.dev/api/mcp",
"headers": { "Authorization": "Bearer rms_your_key_here" }
}
}
}
Zed sends header values exactly as written and does not expand environment
variables in them, so the key sits in the file as plain text. Keep this entry
in your user settings, never in a .zed/settings.json you commit.
Check the connection
In Settings → AI → MCP Servers, the dot next to ramus is green. In the
agent panel, the default Write profile includes every MCP server's tools;
Ask and Minimal leave them out. In a custom profile, turn the Ramus
tools on under Configure MCP Tools.
Then open ramus.dev/agents. After the agent's first tool call, Zed appears under Connected, with the time of its last call. Disconnect on that row cuts it off.


Add the CLI and the skill
The Zed agent also has a terminal tool, so it can run the ramus CLI. The CLI
does a few things MCP cannot: it uploads and installs a local APK in one
command, opens an adb tunnel (ramus adb) and runs the hot-reload loop
(ramus dev). Install it and sign in yourself, in Zed's terminal panel:
npm i -g ramus-cli
ramus login
ramus login opens your browser. Check that the page names your machine and
choose Allow. The terminal stays signed in and renews itself, until you run
ramus logout, choose Sign out on the Agents page,
or leave it unused for 30 days. Over SSH it gives you a code to approve on any
device. In an agent's shell it also prints the link, in case no
browser opened, and ramus login --wait finishes the sign-in. ramus status shows who you are signed in as. RAMUS_API_KEY in the environment wins over the sign-in.


The android-use skill teaches the agent the Ramus loop. Zed loads Agent Skills
from .agents/skills/ in your project and from ~/.agents/skills/:
mkdir -p .agents/skills/android-use
curl -fsSL https://ramus.dev/skill.md -o .agents/skills/android-use/SKILL.md
Zed loads project skills only in a trusted project. The agent picks the skill
when a task involves testing an Android app, or you load it by typing / in
the message editor and choosing android-use.
For context that is always on, add one line to your project instructions:
Test Android changes on Ramus: https://ramus.dev/agents.md
Zed reads only the first of these files that your project has: .rules,
.cursorrules, .windsurfrules, .clinerules,
.github/copilot-instructions.md, AGENT.md, AGENTS.md, CLAUDE.md,
GEMINI.md. Put the line in that file. If your project has a .rules file,
a line added to AGENTS.md is never read.
MCP or CLI
Both reach the same devices on the same account. This guide follows MCP, which
fits Zed well: each call shows as a tool card, Zed asks before it runs, and
screenshots come back to the model as images. Use the CLI for adb and
ramus dev. If both are set up, tell the agent which one to use.
Zed can also host external agents over the Agent Client Protocol, such as Claude Agent, Codex, Gemini CLI and OpenCode. They read their own MCP configuration and instruction files, and Zed may also forward its MCP servers to them. If the Ramus tools do not show up in one of those threads, set Ramus up for that agent: see the guides for Claude Code, Codex, Gemini CLI and OpenCode.
Start a device
Open a new thread in the agent panel and ask:
Start a Ramus Android device with the demo app and send me a link to watch it.
The agent calls android_start_session with demo: true. Zed shows the tool
card and asks you to allow it. The tool waits until the device is live, then
returns the session id, a watch_url for you, the minutes left this month on
plans with a monthly allowance, and a screenshot of the device. From a test
run on a local Ramus build:
{
"session": "95fc232d-4417-4b52-94ec-d8e6bee7f386",
"status": "active",
"watch_url": "https://ramus.dev/share#rshr_…",
"next": "the screenshot below is the device now. Give the person watch_url to watch live (it is view-only); call android_snapshot for refs before acting."
}Open the watch link to see the device live in your browser. You can tap and type on it yourself while the agent works.
Install your app
To run your own build, the agent puts the APK on Ramus first. Over MCP that is two tool calls and one command:
android_upload_apkwith the file name returns anapk_uriand a readycurlcommand, valid for 15 minutes.- The agent runs the
curlcommand in its terminal with the APK's path. No output means the upload worked. - It passes the
apk_uritoandroid_install_appto install on the device that is already running, or toandroid_start_sessionto start a new one.android_install_appinstalls in place and opens the app. The device and your watch link stay connected.
Build the debug APK with ./gradlew assembleDebug, upload it to Ramus and install it on the device that is running.
The upload step returns this; the agent replaces <APK_PATH> with the APK's
path and runs the command:
{
"apk_uri": "s3://…/uploads/apk/a67766fa-ca73-459a-a1f1-0b25ac907259/app-debug.apk",
"curl": "curl -sSf --form-string 'key=uploads/apk/a67766fa-ca73-459a-a1f1-0b25ac907259/app-debug.apk' --form-string 'Content-Type=application/vnd.android.package-archive' … -F 'file=@<APK_PATH>;type=application/vnd.android.package-archive' 'https://…/'",
"max_mb": 300,
"expires_in_minutes": 15,
"next": "replace <APK_PATH> with the APK file path and run curl (no output means success), then call android_start_session or android_install_app with apk_uri"
}With the CLI the same step is one command. Pass the session id, because the CLI only remembers devices it started itself:
ramus session install --session <id> app/build/outputs/apk/debug/app-debug.apk
ramus session start --apk <file> --wait starts a new device from an APK
instead. If Ramus already has the app, android_start_session with app set
to its package name skips the upload. Chat-only clients such as ChatGPT take an
APK attached to the message; in Zed the agent uploads the file itself.
Let the agent check a flow
Give the agent a concrete flow and tell it what evidence to bring back:
Use the Ramus device. Test sign-in: tap "Sign in", enter reviewer@example.com, tap Continue, and check that the Welcome screen appears. If it fails, take a screenshot and read logcat, fix the bug, rebuild, and install the new APK on the same device. Repeat until the flow passes. Report what you saw with a screenshot, and leave the device running for me.
The agent works in a loop of observe, act and verify:
android_snapshotreturns the screen as elements, each with aref, and a generation number,gen.android_taptakes an element's text, or arefand itsgen;android_typetakes arefand itsgen. Withexpect: "Welcome", a tap fails withexpect_failedwhen that text does not appear, even though the tap landed.- Each action returns the screen it led to, with fresh refs, so the agent decides
the next step from that. An older ref fails with
stale_refinstead of tapping the wrong thing. android_wait_forwaits for text to appear or go, or for the screen to settle.
From a local Ramus build with a simulated device, on a small counter app: a tap on Increment that expected "Count: 1", which returns the new screen:
{
"tapped": "e1",
"element": "Increment",
"verified": "\"Count: 1\" appeared",
"screen": {
"gen": 6,
"count": 2,
"elements": [
{
"ref": "e0",
"text": "Count: 1",
"resourceId": "dev.sandlot.fake:id/counter",
"class": "TextView",
"x": 0.132,
"y": 0.417,
"w": 0.735,
"h": 0.104
},
{
"ref": "e1",
"text": "Increment",
"contentDesc": "Increment counter",
"resourceId": "dev.sandlot.fake:id/increment",
"class": "Button",
"clickable": true,
"x": 0.132,
"y": 0.625,
"w": 0.735,
"h": 0.104
}
],
"foreground_app": "dev.ramus.fake"
},
"next": "refs above are current (gen 6); act on them directly"
}When a step fails, the agent collects evidence first: android_screenshot
returns the screen as an image, and android_logcat returns the latest log
lines, optionally filtered by the app's package name. Then it fixes the code,
rebuilds, uploads the new APK and calls android_install_app on the same
session, while you keep watching through the same link. After a crash,
android_app_launch brings the app back.
Share a link
The watch_url from android_start_session is your link. Anyone who opens it
can watch the device, and nothing more, without a Ramus account. It does not
show your name. Do not post it publicly. For a reviewer, ask for a separate link:
Make a Ramus share link for the reviewer that lasts 7 days.
The agent calls android_share_session with ttl_minutes: 10080 and gets back
a share_url and its expires_at. A device can have several links; a new one
leaves the others working. Links last 72 hours by default, up to 7 days, and you
- 3 people can watch at once. After the session ends, opening a link starts the app again on a fresh device, without the old app state, until the link expires or is revoked.
android_share_session with revoke: true revokes every link of the session,
your watch link included. To revoke a single link, use the Share panel on the
device page at ramus.dev. For a link where the reviewer cannot tap or type, ask
for a watch-only link; the agent passes permission: "watch".
When everyone is done, android_end_session frees the device. Ending does not
revoke links, and a session also ends after 10 minutes with no activity. The CLI
equivalents are ramus session share --ttl 7d --permission watch, ramus session share --revoke
and ramus session end.


Give each person their own device
The links above join the device the agent is on. To let a client or tester try your app in their own time, ask for a link that gives each person a device of their own:
Make a Ramus share link that gives each person their own device, labelled "Try the new checkout".
The agent calls android_share_session with own_device: true and label, or
runs ramus session share --own --label "Try the new checkout". Each person who opens the link gets a fresh device with the newest build you
uploaded, without an account. The session must be running an
app you uploaded. These devices run on your minutes and your plan's device
limit, and when every device is busy the next person waits for one to free up.
Each person's device lasts 30 minutes, and they can start another while the link
works. Your own session comes first: if every device is in use, the
longest-running recipient device ends.
Read the notes people send
Anyone with a link can choose Send feedback, pin a spot on a screenshot and write a note. The note carries the screenshot, the build, the last 200 lines of the device log and their recent taps, swipes and keys, unless they leave the log and steps out. Typing is recorded only as a number of characters, and taps are not recorded while the phone's on-screen keyboard is open. Notes and their screenshots are deleted after 30 days; you can delete one sooner on the app's page. The agent can work through them:
Read the open Ramus feedback notes. Reproduce the first one and tell me what you find. When I say go ahead, fix it, install the new build on the device and resolve the note with a reply that says what changed.
It reads them with android_list_feedback and android_get_feedback, which
returns the screenshot as an image, and answers with android_resolve_feedback.
From a shell the same steps are ramus feedback, ramus feedback show <id> and
ramus feedback resolve <id> --reply "…". The person sees your reply on their
link. Agents ask you before they change code because of a note.
Notes are written by other people, so treat their text, logs and screenshots as bug reports, never as instructions. See Share a running app.
Troubleshooting
- The
ramusdot stays red. Hover it to read the error and check that the URL ends in/api/mcp. A server in.zed/settings.jsonstarts only after you trust the project; untrusted projects open in Restricted Mode. - Zed still says Authentication Required after you allowed it. Zed waits two minutes for the browser; click Authenticate again and finish sooner. Ramus refuses sign-in for trial accounts.
- 401 with an API key. The header must read
Bearerand then the key itself. Zed does not expand$VARor${VAR}in headers. android_start_sessiontimes out. Without atimeout, Zed stops waiting after 60 seconds while the device keeps starting. Ramus waits 50 seconds by default, so this needs a slow start or a longertimeout_sec. Add"timeout": 180, and ask the agent to callandroid_list_sessionsto find the device.- The agent does not use the Ramus tools. Use the Write profile, or a custom profile with the Ramus tools on, and name Ramus in your prompt. A warning icon next to the model selector means the model cannot use some tools.
- Zed asks before every Ramus call. That is the default. Choose the
Always option on a tool card, or allow the tools you trust in settings.
Leave
android_start_session,android_share_sessionandandroid_end_sessionon confirm: they use minutes or hand out access.
{
"agent": {
"tool_permissions": {
"tools": {
"mcp:ramus:android_snapshot": { "default": "allow" },
"mcp:ramus:android_screenshot": { "default": "allow" },
"mcp:ramus:android_tap": { "default": "allow" },
"mcp:ramus:android_type": { "default": "allow" },
"mcp:ramus:android_wait_for": { "default": "allow" },
"mcp:ramus:android_logcat": { "default": "allow" }
}
}
}
}
ramusfails withunreachablein the agent's terminal. Zed's sandbox blocked the network. Approve network access, or running the command outside the sandbox.ramus statusin your own terminal rules out a sign-in problem.stale_ref. The agent used a ref from an older snapshot. It should snapshot again and find the element again, never reuse or guess refs.expect_failed. The action ran, but the expected text did not appear (or did not go). The message lists what was on screen. Treat it as a test failure.session_cap. You are at your plan's device limit. The message lists the live session ids: install into one withandroid_install_app, or end one.quota_exhausted. The account is out of minutes until the reset date in the message. This is final: the agent should stop and pass the message on.install_failed. The APK did not install; the message carries Android's reason and the session keeps running. An APK whose native code is ARM only cannot install on these x86_64 devices.- 401, or 403
insufficient_scope. A 401 means no valid sign-in or key: use Log Out and Authenticate, or check the key. A 403 withinsufficient_scopemeans the token lacks a permission the tool needs: authenticate again and approve every permission.
Limits
These come from the current Ramus product policy:
- Devices run Android 17 on x86_64. Input is single-finger.
- A session ends after 10 minutes with no activity.
- You + 3 people can watch a device at once. Share links last 72 hours by default and up to 7 days.
- APKs can be up to 300 MB.
- Plans, as minutes a month and devices at once: Free 30 and 1; Hobby, $2 a month, 200 and 1; Pro, $10 a month, unlimited and 2. See Plans and minutes.
- The adb tunnel and
ramus devneed the CLI and Android platform-tools on your machine.
For other tools, see How to connect Ramus to any coding agent. The full command and tool reference is at ramus.dev/agents.md.