Give any agent a sandbox over MCP
One URL gives your agent real VMs to run code on, host apps from, and hand work to.
exe.dev serves an MCP server at https://exe.dev/mcp. Add it to Claude
Code, Codex, Cursor, VS Code, claude.ai, or any MCP client, and your agent
can create Linux VMs, run commands on them, put what it builds on the
web, copy them, and delete them. Give the URL and a token to an agent you
build on the Claude API or OpenAI's Responses API, and the model provider
makes the calls, with no SDK or tool loop on your side.
Each VM is a whole Ubuntu machine, not a container, with sudo, Docker, a
persistent disk, and an HTTPS address that stays private until you share
it. It keeps running after the conversation ends, so it serves as both a
throwaway sandbox for code you don't trust and a server your agent can
keep.
sdk-test, so it
tagged the VM.Connect your agent#
The first time the agent uses the server, your browser opens so you can sign in to exe.dev. To check, ask the agent to "list my exe.dev VMs".
Claude Code:
claude mcp add --transport http --scope user exe https://exe.dev/mcp
Then type /mcp in Claude Code, pick exe, and sign in.
Codex:
codex mcp add exe --url https://exe.dev/mcp
If no browser opens, run codex mcp login exe.
Cursor:
Add to Cursor
or add this to ~/.cursor/mcp.json:
{
"mcpServers": {
"exe": { "url": "https://exe.dev/mcp" }
}
}
Then sign in when Settings โ Tools & MCP shows exe as needing login.
VS Code: Add to VS Code or run:
code --add-mcp '{"name":"exe","type":"http","url":"https://exe.dev/mcp"}'
OpenCode: add this to ~/.config/opencode/opencode.json, then run
opencode mcp auth exe:
{
"$schema": "https://opencode.ai/config.json",
"mcp": {
"exe": { "type": "remote", "url": "https://exe.dev/mcp" }
}
}
claude.ai and the Claude apps: in Settings โ Connectors, choose
Add custom connector and enter https://exe.dev/mcp. It then works
from your phone too.
ChatGPT: use the exe.dev app for ChatGPT. The exe.dev plugin for Claude also adds instructions for the agent. Both use a simpler set of tools; see Curated endpoints.
Anything else: the server speaks Streamable HTTP and signs in with OAuth, with dynamic client registration. Clients that can't sign in can send a token; see the next section.
Limit what the agent can touch#
Signing in lets the agent do anything your account can, on every VM you
have. exec is marked destructive, so most clients ask before each call
unless you tell them not to. exec_read_only, wait, and jobs only
read, so you can allow those once and still approve each exec. For an agent that
runs on its own, or reads web pages and other text that might tell it to
do something else, use a token that can do less.
On your computer, make a token limited to VMs tagged agent and to these
commands:
ssh exe.dev ssh-key generate-api-key --label=agent --tag=agent --exp=30d \
'--cmds="help,whoami,ls,new,rm,ssh,cp,stat,comment,share show,share port,share set-public,share set-private"'
It prints a token that starts with exe1.:
--tag=agent: VMs the token creates get the tagagent. Any other VM is "not found" to it.--cmds: the commands it may run. Withoutshare set-public, the agent can build web apps but not publish them. Withoutrm, it can't delete anything.sshlets it run anything inside its VMs; a token without it, such as'--cmds="ls,stat,share show"', can only look.--exp=30d: it stops working in 30 days. Revoke it sooner withssh exe.dev ssh-key remove agent.
Send it as a bearer token instead of signing in, from an environment variable:
export EXE_TOKEN=exe1.โฆ
Claude Code saves the token itself in ~/.claude.json:
claude mcp add --transport http --scope user exe https://exe.dev/mcp \
--header "Authorization: Bearer $EXE_TOKEN"
Codex reads the variable each time it starts:
codex mcp add exe --url https://exe.dev/mcp --bearer-token-env-var EXE_TOKEN
For codex exec, which runs without asking, also set
default_tools_approval_mode = "approve" under [mcp_servers.exe] in
~/.codex/config.toml, or every call fails as needing approval.
Cursor reads the variable when it starts:
{
"mcpServers": {
"exe": {
"url": "https://exe.dev/mcp",
"headers": { "Authorization": "Bearer ${env:EXE_TOKEN}" }
}
}
}
Other clients send the header Authorization: Bearer exe1.โฆ. When the
agent connects, the server tells it which tag and commands it's limited
to, so it doesn't waste turns on the rest.
What to ask for#
These prompts worked as written in our tests, and each says which exe.dev commands the agent ends up running. If your agent fumbles because it doesn't know exe.dev, add "See https://exe.dev/deploy/exe-mcp.md" to the prompt.
Build something and host it#
On a new exe.dev VM, make a page that shows a random cat fact each time I click a button. Serve it on port 8000, make sure it keeps running, and give me the link.
Claude Code did this in 20 seconds with these calls (shortened; the page
and a systemd unit went in as stdin):
help new
new --name=cat-facts --json
ssh cat-facts "which python3; whoami; sudo -n true && echo sudo-ok"
ssh cat-facts "mkdir -p /home/exedev/site && cat > /home/exedev/site/index.html"
ssh cat-facts "sudo tee /etc/systemd/system/catfacts.service >/dev/null && sudo systemctl daemon-reload && sudo systemctl enable --now catfacts && curl -s -o /dev/null -w '%{http_code}\n' localhost:8000/"
share show cat-facts
It answered with https://cat-facts.exe.xyz, which only you can open, after
signing in to exe.dev. Ask it to "make it public" (share set-public cat-facts), or to share it with one person by email (share add, if the
token allows it). From claude.ai on your phone, you can describe an app
on the bus and have it running when you get home.
Run something you don't trust#
Make a new exe.dev VM, clone https://github.com/OWNER/REPO, and install what its tests need. Then copy the VM with
--egress=integrations, so the copy has no internet access, run the tests on the copy, and tell me what fails. Delete both VMs at the end.
The agent runs new, clones and installs over ssh, runs
cp <vm> --egress=integrations and the tests on the copy, then rm.
Nothing runs on your computer, the tests can't send anything out, and
both VMs go away with whatever the code did to them. The copy can still
reach your integrations, including the LLM gateway;
see egress policy. Claude Code ran all 297 tests
of pallets/itsdangerous on an offline copy and deleted both VMs in 42
seconds. For code with nothing to install, new --egress=integrations
makes an offline VM directly.
Copy a VM before a risky change#
Copy my-app to a new VM, upgrade it to PostgreSQL 17 there, and tell me whether the app still works. Don't touch my-app.
cp my-app copies its disk to a new VM in about a second. Services that
start at boot come back up on the copy; processes started by hand don't.
Try three fixes at once the same way:
Copy my-app three times. On each copy, try a different fix for the failing checkout test. Keep the copy where the tests pass, and delete the other two.
A token made with --tag can only copy VMs that carry its tag.
Hand the work to another agent#
Every VM comes with Shelley, an agent with its own web UI at
https://<vm>.shelley.exe.xyz/. new --prompt creates a VM, gives
Shelley a task, and returns within seconds, so your agent can start
several, go on with something else, and check on them later:
Start three new exe.dev VMs, each with a Shelley prompt to build a different small browser game and serve it on port 8000 with systemd. Check on them until all three are done, then give me the links.
Claude Code had Snake, Memory Match, and Breakout running, each checked and enabled at boot, three and a half minutes later, mostly spent on Shelley's work. The commands:
new --prompt=/dev/stdin # with the task as stdin
ssh <vm> shelley client list # one JSON line per task, "working": false once Shelley is done
ssh <vm> shelley client read <id> # the conversation: what Shelley did and said
ssh <vm> 'shelley client chat -c <id> -p "$(cat)"' # tell it more, with the message as stdin;
# without -c, start a new task and print its ID at once
Shelley runs on your LLM integration, not your agent's model, so it costs your agent no context. You can watch it, or take over, in its web UI.
Race three models#
--prompt-model picks Shelley's model, so one task can go to three models
at once, each on its own VM:
Start three exe.dev VMs, each with the same Shelley prompt: build a one-page Tetris game and serve it on port 8000 with systemd. Use a different model on each with --prompt-model: claude-opus-5.5, gpt-6.1-sol, and grok-4.6. When all three are done, give me the links so I can compare.
In our test, Claude Code had all three games running two minutes later,
each with a link to Shelley's conversation, so you can see how each model
went about it.
ssh <vm> shelley models lists the models. Some are much slower: Kimi K3
took nine minutes on the same task.
Build an app that uses AI#
On a new exe.dev VM, build a small web app that summarizes any URL I paste into it, using the Claude API at https://llm.int.exe.xyz (no API key needed). Keep it running and give me the link.
VMs reach models through your LLM integration with no key on the VM, so the agent never asks you for one. Claude Code had this running in 40 seconds, using only Python's standard library, and made the app refuse URLs on private addresses without being asked.
Give your own agent a sandbox#
The Claude API and OpenAI's Responses API can call a remote MCP server
themselves. Give them https://exe.dev/mcp and a token made with --tag,
and the model creates VMs, runs code in them, and reads the results. It
all happens between the provider and exe.dev, within one API call. Your
code runs no tools, and the token can't reach
VMs outside its tag.
Claude API (MCP connector),
with pip install anthropic:
import os
import anthropic
client = anthropic.Anthropic()
msg = client.beta.messages.create(
model="claude-sonnet-5-5",
max_tokens=4000,
betas=["mcp-client-2025-11-20"],
mcp_servers=[{
"type": "url",
"name": "exe",
"url": "https://exe.dev/mcp",
"authorization_token": os.environ["EXE_TOKEN"],
}],
tools=[{"type": "mcp_toolset", "mcp_server_name": "exe"}],
messages=[{"role": "user", "content":
"Create a new exe.dev VM. On it, use Python to find the 10,000th prime. "
"Then delete the VM and tell me the answer."}],
)
for block in msg.content:
if block.type == "mcp_tool_use":
print(block.name, *block.input.values())
elif block.type == "text":
print(block.text)
It printed (shortened):
exec help
exec new --help
exec new --name=prime-calc --no-email --json
exec ssh prime-calc.exe.xyz "python3 -c \"โฆ\""
exec rm prime-calc
The 10,000th prime is **104,729**. โฆ
OpenAI Responses API
(remote MCP),
with pip install openai:
import os
from openai import OpenAI
client = OpenAI()
resp = client.responses.create(
model="gpt-5.5",
tools=[{
"type": "mcp",
"server_label": "exe",
"server_url": "https://exe.dev/mcp",
"authorization": os.environ["EXE_TOKEN"],
"require_approval": "never",
}],
input="Create a new exe.dev VM. On it, use Python to find the 10,000th prime. "
"Then delete the VM and tell me the answer.",
)
for item in resp.output:
if item.type == "mcp_call":
print(item.name, item.arguments)
print(resp.output_text)
Each took about 15 seconds. A VM the model doesn't delete stays, so say
in your prompt when to rm it, and now and then list what's left with a
token that can ls.
Agent frameworks with an MCP client (the MCP SDKs, OpenAI Agents SDK,
Pydantic AI, LangChain, Mastra, and others) connect the same way, with
the URL and the header Authorization: Bearer <token>.
How the tools work#
This section is for agents, and for people debugging one.
https://exe.dev/mcp has four tools.
exec runs one exe.dev command:
command: as you'd type it afterssh exe.dev.helplists commands, andhelp newdescribes one.ssh <vm> <shell command>runs asexedev, who hassudo. The shell command is parsed again on the VM, so quote it as one argument.stdin: optional, sent byte for byte. Put file contents here (ssh <vm> "cat > app.py"), not in the command.newreads it for--prompt=/dev/stdinand--setup-script=/dev/stdin, so a long Shelley prompt needs no quoting.wait_seconds: how long to wait for a command on a VM, 0 to 45 seconds (default 45), before leaving it running as a job.
It returns the output: JSON for lobby commands like ls, new, and
share show, an image if ssh prints one, and base64 for other binary
output. A non-zero exit status ends the output with
[remote command exited with status N].
exec_read_only takes just command, and runs only commands that
change nothing, such as ls, stat, share show, and help. It
refuses ssh.
wait follows a job that exec left running, and jobs lists
a VM's jobs; see below.
Things worth knowing:
- VM names are global across exe.dev. Leave out
--nameto get a free one; otherwise expectVM name "..." is not availablenow and then, and try another. cp <vm>copies the disk to a new VM, with the same tags.- No internet:
new --egress=integrationsorcp <vm> --egress=integrationsmakes a VM that can reach only your exe.dev integrations, such as the LLM gateway. - Web address:
https://<vm>.exe.xyz/serves port 8000 (change it withshare port <vm> <port>), private untilshare set-public <vm>. Ports 3000 to 9999 are athttps://<vm>.exe.xyz:<port>/, for the owner only. Listen on0.0.0.0, and test from inside the VM withcurl localhost:<port>: on the VM, its ownexe.xyzname points at itself, where nothing answers on port 443, and the agent can't open a private URL anyway. - Servers: a systemd unit with
Restart=always, enabled withsystemctl enable --now, survives reboots and runs on copies. For something quick,ssh <vm> "cd site && nohup python3 -m http.server 8000 > log 2>&1 &"starts a server in the background and returns right away. - Long commands become jobs. A command on a VM still running after
wait_secondskeeps running, even if the agent disconnects, andexecreturns its output so far and a job name likemy-vm/abcd2345.waitwith that name waits up to 45 more seconds and returns the output since the last call; once the command has ended, it returns the start and end of all of it, and its exit status.from_startrepeats everything, as after a lost result, andjobslists a VM's jobs, for an agent that lost the name. Withwait_secondsat0,execstarts the command and returns at once, so an agent can start tests on three VMs, then check each. In our tests, Claude Code and Codex followed a 100-second test run with oneexecand twowaitcalls, with default settings. - Stopping: if you stop the agent during
execand the client cancels the call, as clients built on the MCP TypeScript SDK do, the command stops too. Stop a job with thekillcommand shown in its result; stopping duringwaitleaves the job running.waitcan read a job for a day after it ends. A job that was killed, or whose VM restarted, ends without an exit status. - Big output costs the agent context. Output over about 35 KB comes
back as its start and end, with a note of how much was left out. Trim
it on the VM anyway, with
| tail -50or2>&1 | grep -i error. - Docs for every command and feature are at https://exe.dev/docs.md.
Curated endpoints#
The exe.dev plugin for Claude and the app for ChatGPT use
https://exe.dev/mcp/claude and https://exe.dev/mcp/openai, with
simpler tools: list_vms, create_vm, run_vm_command (which is ssh),
and get_cli_help, which explains any command but can't run it. They suit
chat apps that show each tool call for approval. Use https://exe.dev/mcp
for everything else, such as copying or sharing VMs.
Troubleshooting#
401 or "missing or invalid Authorization header". The sign-in
expired or never finished. Sign in again from /mcp in Claude Code; in
Codex, run codex mcp login exe. With a token, check that it's sent as
Bearer exe1.โฆ and hasn't expired: ssh exe.dev ssh-key list shows it.
"not found" for a VM you can see. The VM doesn't carry the token's
--tag. To give the agent that VM, run ssh exe.dev tag <vm> agent.
A command isn't allowed. The token's --cmds leaves it out. You
can't add commands to a token; make a new one.
A job ended without an exit status. It was killed, or its VM restarted. Run it again, and put anything that has to survive a restart, such as a server, in a systemd unit.
The link asks you to sign in. The VM is private until you share it. Sign in with the account that owns it, or ask the agent to make it public.
Your client doesn't list the server, or never opens the browser. It may not support remote MCP servers or OAuth yet. Use a token, or upgrade the client.
Frequently asked questions#
What can an agent do with exe.dev/mcp?
Anything you can do with ssh exe.dev, within what its sign-in or token allows: create, copy, resize, pause, and delete VMs, run shell commands on them with sudo, and choose who can open their web pages. exec runs one exe.dev command per call, exec_read_only runs those that only read, and wait and jobs follow commands that run longer than 45 seconds.
Is it a container or a VM?
A VM with its own kernel. The default image is Ubuntu 24.04 with sudo, Docker, Python, uv, Go, git, and the Shelley agent, so the agent can install packages, run containers, and start services. The VM keeps its disk and keeps running after the conversation ends, until it's deleted.
What does it cost?
Can the agent see or delete my other VMs?
If you sign in, yes: it acts as you. Connect it with a token made with --tag instead, and it sees only VMs with that tag, which are the ones it creates. See Limit what the agent can touch.
Who can open the apps it builds?
Only you, until you or the agent runs share set-public <vm> or shares the VM with someone by email. Visitors to a private VM must sign in to exe.dev first. See sharing.
Should I use this or an SDK?
Use MCP when a model decides what to do: in a chat, a coding agent, or an agent you build on the Claude or OpenAI APIs. Use the TypeScript or Python SDK when your own code decides, for example to run every submission in a fresh VM.
Can code in the VM use AI models?
Yes. Your LLM integration is attached to new VMs by default, so code there can call https://llm.int.exe.xyz without an API key, billed to you. Your agent can build apps that use a model without asking you for a key.
Updated Oct 27, 2026 · tested end to end Oct 27, 2026 · Markdown · manifest.json · Something wrong? Tell us