Skip to main content
Glama

workbench-sandbox

Gives agent-built apps an address, an identity and a durable home.

An MCP server for Kubernetes. An agent calls a handful of tools and gets an isolated runtime it can write files in, run commands in, start a process in, and publish an app from at its own authenticated URL. The user's files live in a durable home that outlives any runtime.

It is the runtime half of workbench, a spike about letting an agent build the frame and then step aside while people fill it in.

%%{init: {"theme":"base","themeVariables":{"fontFamily":"Poppins, ui-sans-serif, system-ui, sans-serif","fontSize":"15px","primaryColor":"#ecebf7","primaryBorderColor":"#3d3f73","primaryTextColor":"#2b2c52","lineColor":"#3d3f73","secondaryColor":"#fdeee6","tertiaryColor":"#f7f6fb","clusterBkg":"#faf9fc","clusterBorder":"#cfcbe0","actorBkg":"#ecebf7","actorBorder":"#3d3f73","actorTextColor":"#2b2c52","actorLineColor":"#9a98b8","signalColor":"#3d3f73","signalTextColor":"#2b2c52","noteBkgColor":"#fdeee6","noteBorderColor":"#e0693a","noteTextColor":"#2b2c52"}}}%%
flowchart LR
    agent(["Agent"]) -- "MCP" --> srv["workbench-sandbox<br/>MCP server"]
    srv -- "creates" --> proc
    subgraph rt["Isolated runtime · one pod per session"]
        proc["Your processes"]
        gw["App gateway · Caddy<br/>app.sandbox.domain<br/>token per request"]
        home["/home/sandbox"]
        proc --- gw
    end
    home <-- "sync" --> vol[("Durable home<br/>PVC or S3 prefix, per user")]
    srv -. "TTL reaper removes expired runtimes<br/>homes are kept" .-> rt
    classDef ctl fill:#ecebf7,stroke:#3d3f73,stroke-width:2px,color:#2b2c52;
    classDef data fill:#fdeee6,stroke:#e0693a,stroke-width:2px,color:#2b2c52;
    class agent,srv ctl;
    class proc,gw,home,vol data;

What it does

Isolation

each session gets its own pod; with gVisor (RuntimeClass: gvisor) its syscalls are intercepted. CPU, memory and a time-to-live are capped

Addressable apps

expose_port publishes a process at http://<name>.<sandbox_id>.<domain>. Several apps run side by side, each on its own host, so UIs with absolute asset paths and WebSockets work unchanged

Auth per request

Authorization: Bearer <token>, ?token= (which sets a cookie) or the cookie. Only /docs, /redoc and /openapi.json are public, GET only

Durable home

a per-user volume mounted as the home directory: a PersistentVolumeClaim, or an S3 bucket prefix synced by rclone with short-lived credentials scoped to that user's prefix

Cleanup

a background reaper deletes expired runtimes and sweeps orphaned services, ingresses and secrets

Related MCP server: paddock

Quick start (laptop)

Prerequisites: Docker, minikube, kubectl, make. About 6 GB of RAM for the cluster.

./scripts/local-up.sh        # minikube (containerd) + ingress + gVisor + both images + deploy
minikube tunnel              # separate terminal; binds port 80 so *.sandbox.localhost reaches the cluster
curl -i http://mcp.sandbox.localhost/healthz        # HTTP 200

Prefer to see each step? The script is just this:

minikube start --driver=docker --container-runtime=containerd --cpus=4 --memory=6144
minikube addons enable ingress
minikube addons enable gvisor            # optional isolation layer
make build build-sandbox                 # docker build + load into minikube
make deploy-local                        # namespace, RBAC, server, ingress

No gVisor? Run ./scripts/local-up.sh --no-gvisor, or set RUNTIME_CLASS= (empty) on the deployment to use the cluster default runtime.

The full-stack guide, including the agent and the UI, is in the workbench repository: docs/getting-started.md.

Talking to it

The server speaks MCP over Streamable HTTP at /mcp. Identity is a header on the connection, not an argument, so a model can never pick whose home it is touching:

Header

Meaning

X-User-Id

the tenant. Its home volume (user-<id>-pvc or users/<id>/ in the bucket) is mounted into every runtime it creates

X-Session-Id

optional, recorded on the runtime

Tool reference

Tool

Parameters

What it does

create_sandbox

ttl_minutes, cpu_limit, memory_limit

Starts a runtime with the user's home mounted. Returns sandbox_id, url, token

run_command

sandbox_id, command, workdir, timeout_seconds

Runs a bash command; returns stdout, stderr and the return code

start_background

sandbox_id, command, log_file, workdir

Starts a long-running process detached from the exec session; returns its pid

expose_port

sandbox_id, internal_port, name

Publishes a port at http://<name>.<sandbox_id>.<domain>. Ports 8080 and 2019 are reserved

list_apps / unexpose_port

sandbox_id [, name]

List or remove published apps

write_file / read_file

sandbox_id, path, content / max_bytes, encoding

Text or base64, size-capped (5 MiB by default), confined to the home directory

list_files / delete_file

sandbox_id, path [, recursive]

List or delete

get_sandbox_access

sandbox_id

The token and apps, for trusted gateways only (not for models)

get_sandbox_status

sandbox_id

Pod phase and metadata, with the token redacted

destroy_sandbox

sandbox_id

Deletes the pod, service and ingress. The user's home is kept

Configuration

Environment variables (a .env is read too).

Variable

Default

Meaning

NAMESPACE

sandbox-system

where runtimes and the server live

BASE_DOMAIN

sandbox.localhost

apps are served at <name>.<sandbox_id>.<BASE_DOMAIN>

RUNTIME_CLASS

gvisor

pod runtime class; empty means the cluster default

SANDBOX_IMAGE

sandbox-base:latest

image every runtime starts from

STORAGE_BACKEND

pvc

pvc or s3

DEFAULT_STORAGE_SIZE, DEFAULT_STORAGE_CLASS

10Gi, standard

for the PVC backend

DEFAULT_TTL_MINUTES

60

runtime lifetime

DEFAULT_CPU_LIMIT, DEFAULT_MEMORY_LIMIT

2, 4Gi

per-runtime caps

MAX_FILE_BYTES

5242880

write_file / read_file cap

REAPER_INTERVAL_SECONDS

60

how often expired runtimes are swept

S3_ENDPOINT, S3_BUCKET, S3_REGION, S3_PROVIDER

S3 backend: where the bucket is

S3_ACCESS_KEY_ID, S3_SECRET_ACCESS_KEY

admin credentials, used only by this server

S3_SYNC_INTERVAL_SECONDS

5

how often a runtime syncs its home

Home storage

Backend

What it is

pvc (default)

one PersistentVolumeClaim per user, mounted directly

s3

a bucket prefix per user, kept in sync with the runtime's home by an rclone sidecar

With s3, a runtime never receives the admin credentials. For each runtime the server mints short-lived (STS) credentials whose policy is limited to users/<user_id>/*, so a runtime cannot read another user's prefix. Two runtimes for the same user sync into the same prefix: newer files win and nothing is deleted when the sync has to fall back to a merge. An S3 example is in deploy/k8s/examples/ (RustFS or MinIO work).

Repository layout

src/sandbox_mcp/
  server.py, app.py        FastMCP server, lifespan, reaper startup
  config.py                settings
  tools/                   the MCP tools
  k8s/                     pods, services, ingresses, exec, annotations
  caddy/                   renders the per-runtime gateway config
  routing.py               route table (kept in a pod annotation) and hot reload
  storage/                 pvc and s3 adapters (scoped credentials, sync sidecar)
  reaper.py                TTL cleanup
docker/                    Dockerfile.mcp (server), Dockerfile.sandbox-base (the runtime image)
deploy/k8s/                namespace, RBAC, deployment, ingress; examples/ for S3
scripts/local-up.sh        laptop bootstrap
tests/                     unit tests (no cluster)

Tests

uv sync --extra dev
uv run pytest -q       # unit tests, no cluster needed

Security notes

  • The runtime image installs packages from the internet on demand and runtimes have open egress by default. Add a NetworkPolicy for anything beyond a laptop.

  • The sandbox token is returned by create_sandbox. Gateways should keep it server-side and strip it from anything a model reads.

  • This is a spike. Read the code and the RBAC before pointing it at a real cluster.

License

Apache-2.0. See LICENSE.

Related MCP Connectors

Related MCP Servers

  • A
    license
    Not graded
    quality
    B
    maintenance
    Enables AI agents to work in a persistent, isolated Linux workspace with file management, Bash execution, SSH/SFTP access, and durable storage while keeping workloads contained from the host and private networks.
    MIT
  • A
    license
    Not graded
    quality
    B
    maintenance
    Enables AI agents to safely run bounded, sandboxed tasks on remote machines with persistent state and reviewable artifacts.
    Apache 2.0
  • F
    license
    Not graded
    quality
    B
    maintenance
    Enables AI coding agents to run shell commands, spawn persistent background tasks, and read/write files inside isolated remote container sandboxes on a VPS. It also provides project workspace listing, task attach/kill controls, and a searchable knowledge graph for memory continuity across sessions.
    -