sandbox-mcp
MCP server that gives AI agents a real working environment: persistent
shells, a filesystem, and multi-machine management โ backed by Docker
containers or remote SSH hosts.
Features
- Persistent shells โ stateful bash or PowerShell sessions that
survive across tool calls. Set env vars, activate venvs, change
directories, and they stay.
- Multi-machine โ manage several Docker containers and SSH hosts
simultaneously. Each has its own isolated workspace and shell pool.
- Full filesystem access โ read, write, patch, and search files on
any target machine. All writes are atomic (temp-file + rename).
- Zero-config startup โ creates a default Docker container
automatically on first run. One command, ready to go.
- Progressive discovery โ
env tool exposes capabilities step by
step. Agents call env(action="help") to see what's available.
- Docker lifecycle โ create, stop, start, restart, remove containers.
Build images, inspect configs, commit state, view logs.
- SSH remote access โ connect to Linux and Windows machines over
SSH. Windows targets get automatic code-page probing.
- Safety net โ sensitive-path warnings (
.ssh, .aws, .env*)
without blocking access. Pre-write syntax lint for JSON/YAML/TOML.
- Audit trail โ every tool call is logged with timestamps,
parameters, and outcomes. Queryable from within the agent session.
Quick start
pip install sandbox-env-mcp
sandbox-mcp
sandbox-mcp-http
On first run a default container (python:3.14-slim, named admin)
starts automatically with a persistent bash shell. No other setup.
Requirements: Python 3.12+, Docker SDK, running Docker daemon.
SSH mode needs openssh-client.
All tools target the default machine unless an explicit machine
parameter is passed.
| Tool | What it does |
|---|
shell_exec | Run a command in a persistent shell. Blocks until the command finishes (wait=true, 10 s timeout) or fire-and-forget with wait=false. |
shell_read | Read buffered output from a running or finished command. |
shell_new | Create a fresh shell on a machine. Returns a shell_id. |
shell_remove | Terminate and remove a shell by shell_id. |
shell_list | List all shells with state, machine, uptime, last command. |
write_stdin | Write raw bytes to a running shell โ interrupt with Ctrl-C (\x03) or feed input to interactive programs like read / Read-Host. On Windows/PowerShell, Ctrl-C is unsupported (pipe mode has no terminal driver); kill the shell instead. |
machine_list | List all registered machines with backend, status, purpose, shell count. |
default_set | Set the default machine or default shell for a machine. |
file_read | Read a file with line numbers. Supports offset + limit pagination. |
file_write | Write content atomically. Creates parent directories automatically. |
file_patch | Targeted edits with fuzzy matching. mode=replace (find-and-replace) or mode=patch (unified diff). |
file_search | Search file contents (ripgrep) or find files (glob). Sorted by modification time. |
env | Progressive-discovery portal. Start with env(action="help"). |
audit_query is exposed when the audit log is a SQLite database โ
it lets the agent search historical tool calls.
Shell states
Every shell is in one of four states:
| State | What it means | What the agent can do |
|---|
init | Shell just created; booting up. Times out โ terminated at 10 s. | Wait โ shell_exec returns an error until ready. |
ready | At a prompt, accepting commands. | Send commands, read output, write stdin. |
waiting | A command is running. | Poll output with shell_read. Send Ctrl-C with write_stdin. |
terminated | Shell process exited (signal, exit, timeout, broken pipe). Last output is preserved. | Read remaining output, then shell_remove + shell_new to continue. Default shells are never auto-replaced. |
Key shell_exec parameters:
wait (default true): block until the command completes.
timeout (default 10 s): on expiry returns status="waiting"
with a hint to switch to wait=false + shell_read for
long-running commands.
max_output (default 50000 bytes): caps returned output;
excess is shown as the tail (last N bytes).
env actions
env(action="help") lists what's available. env(action="help", topic="<action>") returns full docs for a specific action.
Always available
| Action | Params | Description |
|---|
help | topic? | List actions or get docs for one. |
status | โ | Default machine, machines, shells. |
list_targets | โ | Pre-defined SSH targets from config. |
machine_list | โ | Registered machines. |
shell_list | machine? | Shells, optionally filtered. |
shell_new | machine?, purpose? | New shell session. |
shell_remove | shell_id | Terminate and remove. |
default_set | machine or shell_id | Set default machine or shell. |
Docker
| Action | Required params | Description |
|---|
docker_run | name, image, purpose | Create/start container. Reattaches on name collision. |
docker_ps | โ | List managed containers. |
docker_images | โ | List all images on daemon. |
docker_image_history | image | Layer-by-layer build history. |
docker_build | image_tag, machine | Build from a Dockerfile in /workspace. |
docker_commit | machine, image_tag | Commit container as new image. |
docker_stop | machine | Stop (state preserved). |
docker_start | machine | Start a stopped container. |
docker_remove | machine | Stop + remove container and its shells. |
docker_inspect | machine | Curated config. kind=image for images. |
docker_logs | machine | Logs with tail, since, until. |
docker_diff | machine | Filesystem changes vs image. |
docker_stats | machine | CPU/memory/network/IO snapshot. |
docker_restart | machine | Stop + start + verify. |
SSH
| Action | Required params | Description |
|---|
connect | name | Connect to a configured target. |
close | name | Disconnect and unregister. |
Available when [ssh.targets] is configured.
File operations
| Tool | Key params | Highlights |
|---|
file_read | path, offset, limit | Line-numbered. Rejects files > 50 KB with a hint. |
file_write | path, content | Atomic (temp + rename), auto-creates parent dirs, post-write verification. |
file_patch | path, old_string, new_string (replace mode) or patch (unified diff) | Fuzzy matching. Preserves BOM and line endings. |
file_search | pattern, search_type, path, file_glob, limit | Powered by ripgrep. Results sorted by modification time. |
Safety warnings are surfaced for sensitive paths (.ssh, .aws,
.env*, /etc/shadow, etc.) โ advisory only, agents still have full
access. Writes to .json, .yaml, .yml, .toml are
syntax-checked before writing (fail-closed).
Configuration
Config lives at ~/.sandbox-mcp/config.toml (copy
config/config.example.toml). Every field can be overridden with
SANDBOX_MCP_<SECTION>_<KEY> env vars.
[server]
port = 8010
auth_tokens_file = "~/.sandbox-mcp/auth_tokens"
[storage]
work_home = "/var/lib/sandbox-mcp"
[docker]
default_image = "python:3.14-slim"
auto_network = "sandbox-mcp"
admin_machine = "admin"
host = ""
[ssh]
connect_timeout = 10
[ssh.targets.win-build]
host = "192.168.1.100"
user = "builder"
os_type = "windows"
[default_machine]
enabled = true
backend = "docker"
name = "admin"
[shell]
default_max_output = 50000
[files]
max_file_size = 51200
Backends
Docker
Containers get bind mounts for workspace isolation:
work_home/<name>/ โ /workspace (rw)
work_home/<share_subdir>/ โ /share/ (ro, shared across peers)
work_home/<share_subdir>/<name>/ โ /share/<name>/ (rw overlay)
When a container's name matches admin_machine, it also gets
work_home/ โ /host (rw) โ a global view of all workspaces.
Server startup auto-reconciles with the Docker daemon: surviving
containers are re-adopted into the registry.
SSH
Connects over SSH with ControlMaster for connection reuse. Windows
targets get automatic code-page probing and encoded-command execution.
Deployment
services:
sandbox-mcp:
image: ghcr.io/hs3434/sandbox-env-mcp:latest
network_mode: host
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- /var/lib/sandbox-mcp:/var/lib/sandbox-mcp
- ./config:/root/.sandbox-mcp
HTTP mode reads bearer tokens from auth_tokens_file (hot-reload on
every request). If the file is empty or missing and
auto_generate_if_empty=true, a random token is printed to stderr at
startup.
Audit
Every tool call is recorded: timestamp, machine, action, status,
duration, and hashed parameters. Defaults to SQLite at
~/.sandbox-mcp/audit.db. Set log_path="" for JSON-line stderr
output instead.