Odel
Sandbox Env MCP

Sandbox Env MCP

Local
@hs34341PythonAGPL-3.0Updated 2w ago

Persistent Docker/SSH sandbox for AI agents: shell exec, file ops, audit log.

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 discoveryenv 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

# stdio — for Claude Desktop, Cline, Continue
sandbox-mcp

# HTTP — for remote agents
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.

Tools

All tools target the default machine unless an explicit machine parameter is passed.

ToolWhat it does
shell_execRun a command in a persistent shell. Blocks until the command finishes (wait=true, 10 s timeout) or fire-and-forget with wait=false.
shell_readRead buffered output from a running or finished command.
shell_newCreate a fresh shell on a machine. Returns a shell_id.
shell_removeTerminate and remove a shell by shell_id.
shell_listList all shells with state, machine, uptime, last command.
write_stdinWrite 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_listList all registered machines with backend, status, purpose, shell count.
default_setSet the default machine or default shell for a machine.
file_readRead a file with line numbers. Supports offset + limit pagination.
file_writeWrite content atomically. Creates parent directories automatically.
file_patchTargeted edits with fuzzy matching. mode=replace (find-and-replace) or mode=patch (unified diff).
file_searchSearch file contents (ripgrep) or find files (glob). Sorted by modification time.
envProgressive-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:

StateWhat it meansWhat the agent can do
initShell just created; booting up. Times out → terminated at 10 s.Wait — shell_exec returns an error until ready.
readyAt a prompt, accepting commands.Send commands, read output, write stdin.
waitingA command is running.Poll output with shell_read. Send Ctrl-C with write_stdin.
terminatedShell 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

ActionParamsDescription
helptopic?List actions or get docs for one.
statusDefault machine, machines, shells.
list_targetsPre-defined SSH targets from config.
machine_listRegistered machines.
shell_listmachine?Shells, optionally filtered.
shell_newmachine?, purpose?New shell session.
shell_removeshell_idTerminate and remove.
default_setmachine or shell_idSet default machine or shell.

Docker

ActionRequired paramsDescription
docker_runname, image, purposeCreate/start container. Reattaches on name collision.
docker_psList managed containers.
docker_imagesList all images on daemon.
docker_image_historyimageLayer-by-layer build history.
docker_buildimage_tag, machineBuild from a Dockerfile in /workspace.
docker_commitmachine, image_tagCommit container as new image.
docker_stopmachineStop (state preserved).
docker_startmachineStart a stopped container.
docker_removemachineStop + remove container and its shells.
docker_inspectmachineCurated config. kind=image for images.
docker_logsmachineLogs with tail, since, until.
docker_diffmachineFilesystem changes vs image.
docker_statsmachineCPU/memory/network/IO snapshot.
docker_restartmachineStop + start + verify.

SSH

ActionRequired paramsDescription
connectnameConnect to a configured target.
closenameDisconnect and unregister.

Available when [ssh.targets] is configured.

File operations

ToolKey paramsHighlights
file_readpath, offset, limitLine-numbered. Rejects files > 50 KB with a hint.
file_writepath, contentAtomic (temp + rename), auto-creates parent dirs, post-write verification.
file_patchpath, old_string, new_string (replace mode) or patch (unified diff)Fuzzy matching. Preserves BOM and line endings.
file_searchpattern, search_type, path, file_glob, limitPowered 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"      # "" = none
admin_machine = "admin"           # "" = no /host mount
host = ""                         # "" = from Docker environment

[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

# docker-compose.yml
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.