Skip to main content
Glama
README.md
# Code Sandbox MCP

A remote [Model Context Protocol](https://modelcontextprotocol.io) server that gives AI clients a
persistent Linux sandbox. The model runs any bash command as root in a Docker container with
internet access, installs whatever it needs, and hands files back to the user through download
links.

There is deliberately no command filtering. Isolation comes from the container: it is not
privileged, has no Docker socket and no host directories mounted, and has memory and CPU limits.

It started as a fork of [philschmid/code-sandbox-mcp](https://github.com/philschmid/code-sandbox-mcp)
and now uses a different design: one long-lived container instead of a container per snippet, and
streamable HTTP with OAuth instead of stdio.

## Tools

- `execute(command, timeout=3600)`: run a bash command in `/sandbox`. Returns stdout, stderr and
  the exit code. Output above `MAX_OUTPUT_BYTES` per stream is truncated in the middle.
- `share_file(path)`: return a signed, expiring public download link for a file in the sandbox.
  Use it for PDFs, images, audio, video or anything else that cannot be returned as text.

## Behavior

- `/sandbox` lives on a named Docker volume. A top-level entry with no change inside it for
  `FILE_TTL` seconds (default 168 hours) is deleted by an hourly cleanup.
- Packages installed with `pip`, `npm` or `apt-get` persist in the container. A stopped container
  is restarted, not recreated.
- Each call is a fresh bash process: `cd` and exported variables do not carry over.
- Long-running processes can be detached with `setsid nohup <cmd> > log 2>&1 &`.

## Authentication

The server is its own OAuth 2.1 authorization server with dynamic client registration, so MCP
clients such as Claude connect with only the URL `https://<your-host>/mcp`. When a client
connects, the user is sent to a sign-in page that asks for `SANDBOX_API_KEY`. Registered clients
and tokens are kept in `oauth-state.json` so restarts do not force a new sign-in.

Download links from `share_file` are signed with the same key and valid for `LINK_TTL` seconds.
Rotating the key invalidates them.

## Setup

Requires Docker and Python 3.10+.

```bash
docker build -f containers/Dockerfile.unified -t code-sandbox:latest .
python3 -m venv .venv
.venv/bin/pip install fastmcp docker uvicorn
```

Create `.env` (keep it private, `chmod 600 .env`):

```bash
SANDBOX_API_KEY=<random string, e.g. python3 -c "import secrets;print(secrets.token_urlsafe(32))">
SANDBOX_PUBLIC_URL=https://sandbox.example.com
SANDBOX_HOST=127.0.0.1
SANDBOX_PORT=8891
```

Run it, for example as a systemd service:

```ini
[Unit]
Description=Code Sandbox MCP Server
After=network-online.target docker.service
Requires=docker.service

[Service]
WorkingDirectory=/path/to/code-sandbox-mcp
EnvironmentFile=/path/to/code-sandbox-mcp/.env
ExecStart=/path/to/code-sandbox-mcp/.venv/bin/python sandbox_server.py
Restart=always

[Install]
WantedBy=multi-user.target
```

Put it behind HTTPS at `SANDBOX_PUBLIC_URL` (a reverse proxy or a Cloudflare Tunnel). Commands
can run longer than a proxy's idle timeout because the MCP response stream sends keep-alive
pings.

### Settings

| Variable | Default | Meaning |
|---|---|---|
| `SANDBOX_API_KEY` | required | Sign-in key and link-signing secret |
| `SANDBOX_PUBLIC_URL` | required | Public base URL, used as OAuth issuer and for links |
| `SANDBOX_HOST` / `SANDBOX_PORT` | `0.0.0.0` / `8891` | Listen address |
| `SANDBOX_IMAGE` | `code-sandbox:latest` | Container image |
| `SANDBOX_CONTAINER` / `SANDBOX_VOLUME` | `mcp-sandbox-persistent` / `mcp-sandbox-data` | Container and volume names |
| `SANDBOX_MEMORY` / `SANDBOX_CPUS` | `3g` / `3` | Container limits, applied when it is created |
| `EXEC_TIMEOUT` | `3600` | Default per-command timeout in seconds |
| `MAX_OUTPUT_BYTES` | `200000` | Output kept per stream |
| `FILE_TTL` / `CLEANUP_INTERVAL` | `604800` / `3600` | Idle-file expiry and scan interval, `FILE_TTL=0` disables cleanup |
| `LINK_TTL` | `86400` | Download link lifetime |
| `OAUTH_STATE_FILE` | `oauth-state.json` | Where clients and tokens are stored |

## The image

`containers/Dockerfile.unified` is based on `python:3.13-slim` and adds Node.js 22 with npm,
pnpm, yarn, bun, TypeScript and tsx; uv; a broad set of Python data, document and web libraries;
Playwright with headless Chromium; and ffmpeg, ImageMagick, pandoc, poppler, build tools and CJK
fonts. Edit it to change what is preinstalled.

## Legacy code

`src/`, `examples/`, `tests/`, `.gemini/`, `containers/Dockerfile.python` and
`containers/Dockerfile.nodejs` belong to the original stdio server and are not used by
`sandbox_server.py`.

## License

MIT, see [license](license).