---
title: "Give any agent a sandbox over MCP"
description: "Add exe.dev/mcp to Claude Code, Codex, Cursor, claude.ai, or your own agent, and it can create VMs, run code in them, and put what it builds on the web."
url: https://exe.dev/deploy/exe-mcp
category: "AI agents"
updated: 2026-10-27
---

# 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.

- **Software:** exe.dev MCP (https://exe.dev/mcp)
- **Source:** https://github.com/boldsoftware/exe.dev
- **License:** MIT (the Claude and ChatGPT plugins)
- **Machine-readable:** https://exe.dev/deploy/exe-mcp/manifest.json

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.

## 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:**

```bash
claude mcp add --transport http --scope user exe https://exe.dev/mcp
```

Then type `/mcp` in Claude Code, pick `exe`, and sign in.

**Codex:**

```bash
codex mcp add exe --url https://exe.dev/mcp
```

If no browser opens, run `codex mcp login exe`.

**Cursor:**
<a class="dp-btn" href="https://cursor.com/en/install-mcp?name=exe&amp;config=eyJ1cmwiOiJodHRwczovL2V4ZS5kZXYvbWNwIn0%3D">Add to Cursor</a>
or add this to `~/.cursor/mcp.json`:

```json
{
  "mcpServers": {
    "exe": { "url": "https://exe.dev/mcp" }
  }
}
```

Then sign in when **Settings → Tools & MCP** shows `exe` as needing login.

**VS Code:**
<a class="dp-btn" href="https://vscode.dev/redirect/mcp/install?name=exe&amp;config=%7B%22type%22%3A%22http%22%2C%22url%22%3A%22https%3A%2F%2Fexe.dev%2Fmcp%22%7D">Add to VS Code</a>
or run:

```bash
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`:

```json
{
  "$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](https://github.com/boldsoftware/exe.dev/tree/main/exe-openai-plugin).
The
[exe.dev plugin for Claude](https://github.com/boldsoftware/exe.dev/tree/main/exe-claude-plugin)
also adds instructions for the agent. Both use a simpler set of tools; see
[Curated endpoints](#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` and `wait` 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:

```bash
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 tag `agent`. Any other VM
  is "not found" to it.
- `--cmds`: the commands it may run. Without `share set-public`, the
  agent can build web apps but not publish them. Without `rm`, it can't
  delete anything. `ssh` lets 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 with
  `ssh exe.dev ssh-key remove agent`.

Send it as a bearer token instead of signing in, from an environment
variable:

```bash
export EXE_TOKEN=exe1.…
```

**Claude Code** saves the token itself in `~/.claude.json`:

```bash
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:

```bash
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:

```json
{
  "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`):

```text
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](/docs/integrations), including the LLM gateway;
see [egress policy](/docs/internet-access). 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:

```text
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](/docs/integrations-llm), 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](/docs/integrations-llm)
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](https://platform.claude.com/docs/en/agents-and-tools/mcp-connector)),
with `pip install anthropic`:

```python
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):

```text
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](https://developers.openai.com/api/docs/guides/tools-connectors-mcp)),
with `pip install openai`:

```python
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 three tools.

**`exec`** runs one exe.dev command:

- `command`: as you'd type it after `ssh exe.dev`. `help` lists commands,
  and `help new` describes one. `ssh <vm> <shell command>` runs as
  `exedev`, who has `sudo`. 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. `new` reads it for
  `--prompt=/dev/stdin` and `--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; see below.

Things worth knowing:

- **VM names are global** across exe.dev. Leave out `--name` to get a
  free one; otherwise expect `VM name "..." is not available` now and
  then, and try another.
- **`cp <vm>`** copies the disk to a new VM, with the same tags.
- **No internet**: `new --egress=integrations` or
  `cp <vm> --egress=integrations` makes 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
  with `share port <vm> <port>`), private until `share set-public <vm>`.
  Ports 3000 to 9999 are at `https://<vm>.exe.xyz:<port>/`, for the owner
  only. Listen on `0.0.0.0`, and test from inside the VM with
  `curl localhost:<port>`: the agent can't open a private URL.
- **Servers**: a systemd unit with `Restart=always`, enabled with
  `systemctl 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_seconds` keeps running, even if the agent disconnects, and `exec`
  returns its output so far and a job name like `my-vm/abcd2345`. `wait`
  with that name waits up to 45 more seconds, then returns the output so
  far and, once the command has ended, its exit status. With
  `wait_seconds` at `0`, `exec` starts 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 one
  `exec` and two `wait` calls, with default settings.
- **Stopping**: if you stop the agent during `exec` and the client
  cancels the call, as clients built on the MCP TypeScript SDK do, the
  command stops too. Stop a job with the `kill` command shown in its
  result; stopping during `wait` leaves the job running. `wait` can 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 -50` or `2>&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` follows 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?

Nothing extra for the MCP server. VMs your agent creates share your plan's CPU and memory and count toward its VM limit (50 on [Personal](/docs/billing/personal)); disk and bandwidth beyond your allowance are billed as [usage](/docs/billing/usage). Ask the agent to delete sandboxes it no longer needs. See [pricing](https://exe.dev/pricing).

### 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](#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](/docs/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](/deploy/typescript-sandboxes) or [Python](/deploy/python-sandboxes) 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](/docs/integrations-llm) 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.


## Related

- [Create sandboxes from TypeScript](https://exe.dev/deploy/typescript-sandboxes.md): Full VMs as sandboxes. Create, control, and destroy them from your code.
- [Create sandboxes from Python](https://exe.dev/deploy/python-sandboxes.md): Full VMs as sandboxes. A with block creates one, runs your code, and deletes it.
- [Running Agents](https://exe.dev/deploy/agents.md): Use exe.dev VMs as a sandbox
- [Run Claude Code on a remote server](https://exe.dev/deploy/claude-code.md): A computer for Claude. A breather for your laptop.
- [Host OpenClaw](https://exe.dev/deploy/openclaw.md): An AI sidekick with its own computer. Your laptop can take a nap.
- [Run Codex on a remote server](https://exe.dev/deploy/codex.md): A computer for Codex. An early night for your laptop.
