Sandbox MCP
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@Sandbox MCP Run the tests and show me the results"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
Sandbox MCP
AI coding agents are powerful because they can execute code. That is also their biggest risk: experimentation can modify the developer's environment.
An agent that is genuinely useful has to be able to run npm install, apply a
migration, try a build, or run code it has not read. Every one of those actions,
performed on your machine, can install something you did not want, overwrite
work in progress, burn your CPU, read your credentials, or leave behind an
environment that no longer builds.
The usual answers are both bad. Deny the agent those tools and it can only guess. Allow them and you are trusting a probabilistic system with your laptop.
Sandbox MCP is the third answer: give the agent somewhere real to work that is not your machine.
"Experiment with this project, but don't touch my actual environment."
Without a sandbox
flowchart TD
CC["Claude Code"] --> HOST["Your machine"]
HOST --> A["npm install"]
HOST --> B["scripts"]
HOST --> C["migrations"]
HOST --> D["file modifications"]
HOST --> E["arbitrary execution"]
classDef danger fill:#fdeceb,stroke:#d1453b,color:#7a1f18
classDef actor fill:#eef2f7,stroke:#5b6b7f,color:#22303f
class HOST,A,B,C,D,E danger
class CC actorRelated MCP server: Secure Grid Genesis Execution
With Sandbox MCP
flowchart TD
CC["Claude Code"] --> MCP["FastMCP"]
MCP --> POL["Experiment policy"]
POL --> SBX["Docker sandbox"]
SBX --> WORK["execute · modify · test<br/>build · experiment"]
WORK --> RES["Results"]
RES --> CC
classDef safe fill:#e7f4ea,stroke:#2f7d4f,color:#14432a
classDef actor fill:#eef2f7,stroke:#5b6b7f,color:#22303f
class SBX,WORK safe
class CC,MCP,POL,RES actorWhat makes this different from a Docker MCP server
Existing Docker MCP implementations expose Docker operations to AI agents. Sandbox MCP exposes an experimentation abstraction.
That is not a wording preference; it changes what the agent can do.
A Docker MCP server | Sandbox MCP | |
Tools |
|
|
The agent decides | flags, mounts, network mode, privileges | what it wants to find out |
Isolation | whatever the agent passed | enforced by policy before Docker is called |
Host filesystem | reachable via | not reachable; the project is snapshot-copied |
"What changed?" | ask the agent to diff it |
|
Failure mode | an agent that mounts | a request the policy engine rejects |
The agent never receives Docker API access. It states an intent; the server decides how that intent is realised.
flowchart TB
CC["Claude Code"] -- "MCP tool call" --> SERVER
subgraph SERVER["Sandbox MCP · the only component holding the socket"]
direction LR
POL["Policy / Security<br/><i>validates · clamps · refuses</i>"]
EXP["Experiment Manager<br/><i>lifecycle · state machine</i>"]
JOB["Job Manager<br/><i>async · timeouts · cancellation</i>"]
COL["Result Collector<br/><i>diffs · artifacts · reports</i>"]
POL --> EXP --> JOB --> COL
end
SERVER --> API["Docker Engine API<br/><i>/var/run/docker.sock — SDK only, no docker CLI</i>"]
API --> SBX["Disposable sandbox<br/><i>no socket · no host filesystem · no network</i>"]
SBX --> OUT["Structured result<br/><i>back to Claude Code</i>"]
classDef actor fill:#eef2f7,stroke:#5b6b7f,color:#22303f
classDef infra fill:#eaf1fb,stroke:#3a6ea8,color:#173352
classDef safe fill:#e7f4ea,stroke:#2f7d4f,color:#14432a
class CC,POL,EXP,JOB,COL,OUT actor
class API infra
class SBX safe
style SERVER fill:#fbfcfd,stroke:#9aa7b4,color:#22303fDocker is the isolation mechanism. MCP is the agent interface. Sandbox MCP is the experimentation layer.
There is deliberately no orchestrator here. The agent already is one: Claude Code plans, reads output, forms a hypothesis and retries better than any loop this server could ship. Putting a second, dumber loop underneath it would only compete with the caller. This project's job is the surface the agent drives — and making that surface one it cannot hurt you with.
The central abstraction: an experiment
Experiment: exp_2859b8eb8656
Project: payments-service
Base image: node:22-slim
Network: disabled
CPU: 2 cores
Memory: 1GB
Timeout: 300s
Status: READY
Commands: 1. npm install --no-audit --no-fund (exit 1, EBADENGINE)
2. npm install --no-audit --no-fund (exit 0)
3. npm test (37 passed, 3 failed)
4. npm test (40 passed, 0 failed)
Changes: 3 files modified, +9 -4Experiments move through an explicit, validated state machine. An illegal
transition is an error, not a silently corrupted record — which matters,
because DESTROYED is what tells the server a container no longer exists.
stateDiagram-v2
direction TB
[*] --> CREATING
CREATING --> READY: sandbox provisioned
READY --> RUNNING: command submitted
RUNNING --> READY: exit 0
state "terminal, but the sandbox is still alive" as OUTCOME {
direction LR
COMPLETED
FAILED
TIMEOUT
CANCELLED
}
CREATING --> FAILED: could not start
RUNNING --> OUTCOME
OUTCOME --> RUNNING: more work
OUTCOME --> DESTROYED: destroy_experiment
READY --> DESTROYED: destroy_experiment
DESTROYED --> [*]Security model
The Docker socket is a highly privileged interface — access to it is effectively root on the host. It is held by the server and by nothing else. The agent talks to the server; the server talks to Docker.
Filesystem
The project is copied, not mounted:
flowchart LR
HOST["HOST PROJECT<br/><i>read once, never written</i>"]
SNAP["SANDBOX COPY<br/><i>also the baseline every diff uses</i>"]
CON["DOCKER CONTAINER"]
HOST -- "snapshot: filtered, read-only pass" --> SNAP
SNAP -- "put_archive" --> CON
CON -. "no path back" .-x HOST
classDef host fill:#fdeceb,stroke:#d1453b,color:#7a1f18
classDef safe fill:#e7f4ea,stroke:#2f7d4f,color:#14432a
class HOST host
class SNAP,CON safeDefault strategy is
COPY_TO_SANDBOX.READ_ONLY_BIND_MOUNTexists for large repositories you only need to read. Writable host bind mounts are not implemented — there is no configuration that produces one..envfiles,*.pem,*.key,id_rsa*,.netrc,.ssh/,.aws/,.gnupg/,.kube/and similar are never copied, regardless of configuration. Build output (node_modules,dist,target, …) is skipped by default and that list is configurable.Project paths are resolved before they are checked, so a symlink cannot launder a denied location. The home directory and filesystem root are refused outright.
Paths inside the sandbox are confined to the workspace:
../../etc/passwdand/etc/shadoware rejected by every tool that takes a path.
One deliberate exception worth knowing: .npmrc is copied, because the
real behaviour of npm install often depends on it. If yours holds an
_authToken, add it to SANDBOX_MCP_SNAPSHOT_EXCLUDES.
Network
Disabled by default. network_mode is an explicit decision at creation time:
Mode | Behaviour |
| No interfaces at all. The default. |
| A private bridge network of its own — egress works, no reach to other sandboxes |
| The daemon's default bridge. Full egress. |
restricted prevents lateral movement between sandboxes. It does not firewall
egress; per-experiment network allowlists are listed under future extensions.
Environment variables
The host environment is never inherited. Variables cross the boundary only when
named: "CI" forwards the host's value, "NODE_ENV=test" injects a literal.
Credential-shaped names (*_TOKEN, *_SECRET, *API_KEY*, AWS_*,
DATABASE_URL, …) are refused even when explicitly allowlisted, because an
agent asking for AWS_SECRET_ACCESS_KEY inside a throwaway container is never
right. Set SANDBOX_MCP_STRICT_ENV_DENYLIST=false if you disagree.
Values are never written to the database or the logs — only names.
Container privileges
privileged=False, always. There is no setting that changes it.cap_drop: ALL, then a small set added back:CHOWN,DAC_OVERRIDE,FOWNER,FSETID,SETUID,SETGID,KILL. That is what package managers actually need (they chown caches and drop privileges for lifecycle scripts). It excludesNET_RAW,MKNOD,SYS_ADMIN,SYS_CHROOT,SETPCAPandSETFCAP— the ones that matter for escape.no-new-privileges:true.The Docker socket is never mounted into a sandbox. There is no code path that does it, and an integration test asserts its absence.
SANDBOX_MCP_SANDBOX_USERruns containers as a non-root user where the toolchain tolerates it.
Resource limits
Every sandbox is capped, and a request may only ask for less than the configured ceiling. Asking for more is clamped, and the clamp is returned to the agent as a warning rather than failing silently.
Limit | Default | Ceiling | Enforced by |
CPU | 2 cores | 4 cores |
|
Memory | 2GB | 8GB |
|
Processes | 512 | 2048 |
|
Wall clock | 120s | 1800s | the job manager, which kills the process |
| 512MB | — |
|
Captured output | 1MB/stream | — | truncated, and flagged as truncated |
Installation
Requires Python 3.12+ and a running Docker engine (Docker Desktop, OrbStack, Colima or Rancher Desktop).
git clone https://github.com/Dhananjay-JSR/sandbox-mcp.git
cd sandbox-mcp
uv sync # exact versions from uv.lockConfirm the server can reach Docker before wiring it to anything:
uv run sandbox-mcp --check{
"docker_host": "unix:///var/run/docker.sock",
"state_dir": "/Users/you/.sandbox-mcp",
"defaults": { "base_image": "debian:bookworm-slim", "network_mode": "none", ... },
"docker": { "server_version": "29.4.0", "api_version": "1.54" },
"ok": true
}ok: false with DOCKER_UNAVAILABLE means the daemon is not running or the
socket is somewhere unusual. The server probes /var/run/docker.sock,
~/.docker/run/docker.sock, ~/.orbstack/run/docker.sock,
~/.colima/default/docker.sock and ~/.rd/docker.sock in that order — the
Docker CLI's context is not consulted, so set DOCKER_HOST explicitly if
yours is elsewhere.
Optional extras:
uv sync --extra dev # pytest, ruff, mypyConnecting Claude Code
Local (stdio) — recommended
claude mcp add sandbox -- uv --directory /absolute/path/to/sandbox-mcp run sandbox-mcpOr, if you installed it into an environment already on your PATH:
claude mcp add sandbox -- sandbox-mcpThen, inside Claude Code:
/mcp # should list "sandbox" as connectedEquivalent .mcp.json, if you prefer to commit the configuration:
{
"mcpServers": {
"sandbox": {
"command": "uv",
"args": ["--directory", "/absolute/path/to/sandbox-mcp", "run", "sandbox-mcp"],
"env": {
"SANDBOX_MCP_LOG_LEVEL": "INFO"
}
}
}
}Remote (Streamable HTTP)
uv run sandbox-mcp --transport http --host 127.0.0.1 --port 8000
claude mcp add --transport http sandbox http://127.0.0.1:8000/mcpOr run the server itself in a container:
docker compose up -d
claude mcp add --transport http sandbox http://127.0.0.1:8000/mcpThe compose file mounts the Docker socket into the server — read the warning at the top of it first, and do not expose port 8000 beyond localhost without authentication in front of it.
flowchart TD
CC["Claude Code"] --> SRV["Sandbox MCP<br/><i>holds the socket</i>"]
SRV --> ENG["Docker Desktop / OrbStack"]
ENG --> SBX["Sandbox<br/><i>no socket · no host filesystem · no network</i>"]
classDef actor fill:#eef2f7,stroke:#5b6b7f,color:#22303f
classDef infra fill:#eaf1fb,stroke:#3a6ea8,color:#173352
classDef safe fill:#e7f4ea,stroke:#2f7d4f,color:#14432a
class CC,SRV actor
class ENG infra
class SBX safeTools
Tool | Purpose |
| Create a disposable environment and copy a project into it |
| Run a command inside the sandbox (sync or background) |
| Detect the test runner, run it, parse the results |
| Read a file from the sandbox to investigate a failure |
| Apply a candidate fix without shell-quoting hazards |
| What changed, against the pre-container baseline |
| Lift build output, reports or logs out before teardown |
| Full state and a summary of what happened |
| Find earlier experiments, including ones left running |
| Poll a background command |
| Fetch a completed background result |
| Stop a running command and kill its process |
| Tear down; idempotent; returns the final report |
| Rank several approaches and recommend one |
| Confirm Docker is reachable and show the active policy |
Notably absent: docker_run, docker_exec, docker_ps, docker_build,
docker_pull. A test asserts that no tool name begins with docker_.
Resources
sandbox://experiments all experiments
sandbox://experiments/{experiment_id} metadata + state history
sandbox://experiments/{experiment_id}/logs every command, with output
sandbox://experiments/{experiment_id}/diff unified diff
sandbox://experiments/{experiment_id}/artifacts collected filesThe primary demo: Node 20 → Node 22
examples/node-upgrade/ is a real, dependency-free Node library pinned to
Node 20. It fails on Node 22 for two independent and entirely realistic
reasons:
package.jsondeclaresengines.node: ">=18 <21"and.npmrcsetsengine-strict=true, sonpm installfails withEBADENGINEbefore a single test runs.src/crypto.jscallscrypto.createCipher, deprecated since Node 10 and removed in Node 22.0.0.
Ask Claude Code:
Determine whether
examples/node-upgradecan be upgraded from Node 20 to Node 22. You may install dependencies, modify files, run tests and experiment freely, but do not modify my actual working tree.
What happens — every figure below is from an actual run, and the whole thing works with the network disabled:
sequenceDiagram
autonumber
participant CC as Claude Code
participant S as Sandbox MCP
participant D as Disposable sandbox
CC->>S: create_experiment(node:22-slim, network none)
S->>D: snapshot 13 files, start container
S-->>CC: exp_2859b8eb8656 · READY
CC->>S: execute_experiment("npm install")
S-->>CC: exit 1 · EBADENGINE
CC->>S: read_sandbox_file("package.json")
S-->>CC: engines.node ">=18 <21"
CC->>S: write_sandbox_file("package.json", ">=18")
CC->>S: execute_experiment("npm install")
S-->>CC: exit 0
CC->>S: run_tests()
S-->>CC: 37 passed · 3 failed<br/>seal produces hex output<br/>seal and open round-trip<br/>seal round-trips unicode
CC->>S: read_sandbox_file("src/crypto.js")
S-->>CC: crypto.createCipher(...)
CC->>S: write_sandbox_file("src/crypto.js", createCipheriv + scrypt key + IV)
CC->>S: run_tests()
S-->>CC: 40 passed · 0 failed
CC->>S: inspect_changes()
S-->>CC: 3 modified · +9 -4
CC->>S: collect_artifacts(["src/crypto.js"])
S-->>CC: kept for the developer
CC->>S: destroy_experiment()
S->>D: remove container and snapshot
S-->>CC: report · host working tree UNCHANGEDExperiment: Node 20 → Node 22
Result: COMPATIBLE (with 2 source changes)
Tests: 40 passed, 0 failed
Changes: 3 files modified (+9 -4)
Host working tree: UNCHANGED
Sandbox: DESTROYEDThat last pair of lines is the product. This exact sequence runs as a test:
uv run pytest tests/integration/test_node_upgrade_demo.py -m integrationOther things this makes possible
Dependency upgrade — "try upgrading React to the latest compatible version; don't modify my working tree." Needs
network_mode="restricted"to reach the registry.Database migration — create an experiment on
postgres:16, run the migration, report whether it applied, destroy it.Build debugging — "work out why this build fails; experiment freely."
Multi-approach debugging —
examples/failing-project/is a Python project whose money arithmetic is done in floats; 3 of its 14 tests fail. Three fixes are plausible (round(),Decimal, truncation) and they are not equivalent. Run each in its own experiment, then callcompare_experiments, which ranks them on failures, exit code, change size and duration, and recommends one.
Why there is no orchestration layer
An earlier cut of this server shipped a LangGraph loop — plan, test, diagnose,
fix, retest — behind a run_autonomous_experiment tool. It was removed, on
purpose.
The caller is already an agent. Claude Code reads a failing test, forms a hypothesis, edits a file and retries; it does that better than a graph of hardcoded repair strategies ever will, because it has judgement and the strategies had a lookup table. Shipping a second, weaker loop underneath a strong one does not add a capability — it competes with the caller for the same decision, and it is the one more likely to be wrong.
What is left is the part the agent genuinely cannot do for itself: the isolation boundary, the policy that enforces it, the state machine, the baseline diff, and the audit trail. Those are the product.
Concretely, the loop lives in the transcript instead of inside the server:
flowchart LR
DEC["Decide the next step<br/><i>Claude Code — the loop lives here</i>"]
ACT["Call a tool"]
SRV["Sandbox MCP<br/><i>enforces · refuses · records</i>"]
OBS["Read the full output"]
DEC --> ACT --> SRV --> OBS --> DEC
classDef actor fill:#eef2f7,stroke:#5b6b7f,color:#22303f
classDef safe fill:#e7f4ea,stroke:#2f7d4f,color:#14432a
class DEC,ACT,OBS actor
class SRV safeThat is the primary demo, and it is what the integration suite runs.
Observability
Every operation emits a structured JSON event to stderr (stdout carries the MCP protocol under stdio transport):
{"event": "command_completed", "experiment_id": "exp_2859b8eb8656",
"job_id": "job_4f1c8a2b90de", "operation": "job.run", "status": "COMPLETED",
"exit_code": 0, "duration_ms": 1432, "timestamp": "2026-09-07T10:29:41Z"}Credential-shaped keys are scrubbed at the processor level, so no individual call site can leak by forgetting.
Everything needed to answer "what exactly did the agent do?" is persisted in
SQLite (~/.sandbox-mcp/sandbox.db): experiments, jobs with their commands,
exit codes and captured output, state transitions with timestamps and reasons,
artifacts with checksums, the pre-container baseline, and the change statistics
— which are written before teardown, so a destroyed experiment is still
comparable against a live one.
The database stores no secrets: environment variable names, never values.
Project layout
src/sandbox_mcp/
├── server.py MCP tools and resources — the agent-facing surface
├── app.py composition root; the only place the graph is wired
├── config.py every tunable, in one auditable place
├── models.py domain models (experiments, jobs, changes, reports)
├── errors.py structured errors; no traceback ever reaches a client
├── logging.py structured logging to stderr, with redaction
├── sandbox/
│ ├── interface.py SandboxBackend — the seam other runtimes target
│ └── docker.py Docker Engine API via the SDK; all hardening lives here
├── experiments/
│ ├── manager.py the domain core
│ ├── state.py the validated state machine
│ ├── repository.py ExperimentRepository + the SQLite implementation
│ ├── changes.py diffing the sandbox against its baseline
│ └── testing.py test-runner detection and output parsing
├── execution/
│ ├── manager.py job lifecycle: timeouts, cancellation, persistence
│ ├── jobs.py live task registry and concurrency limits
│ └── executor.py JobExecutor interface + the sandbox implementation
├── artifacts/manager.py pulling files out before teardown
└── security/
├── policy.py validates and clamps every request
├── filesystem.py path confinement and snapshotting
└── resources.py limit parsingSandboxBackend, ExperimentRepository and JobExecutor are abstract. Docker
implements the first; the MCP tool surface does not know it exists. Supporting
Firecracker, Kubernetes Jobs or remote workers means one new implementation and
no change to the agent-facing API — the in-memory FakeSandboxBackend the unit
tests run against is the proof that the seam is real.
Three deviations from a literal reading of the brief, all deliberate:
experiments/changes.py and experiments/testing.py exist as focused modules
rather than being folded into manager.py; commands/results are columns on
the jobs table rather than separate tables, since a job is a command and its
result; and there is no orchestration/ package, for the reason given above.
Development
uv sync --extra dev
uv run pytest -m "not integration" # 225 unit tests, no Docker needed
uv run pytest -m integration # 23 tests against a live daemon
uv run pytest # everything
uv run ruff check . && uv run ruff format --check .
uv run mypyThe unit suite runs against FakeSandboxBackend and needs no daemon. The
integration suite is the one that proves the product claim: the nine-step
isolation checklist, the host tree unchanged byte-for-byte, the network
genuinely off, secrets genuinely absent, the memory cap actually biting,
timeouts killing the process, cancellation killing the process, hardening flags
present on the real container, orphan containers swept at startup, and the full
Node 20 → 22 demo.
MVP boundaries
Not built, on purpose: a web dashboard, authentication, cloud infrastructure, Kubernetes support, distributed scheduling, billing, multi-tenant production infrastructure. This is a polished local-first MVP.
Known limits, stated plainly:
Killing a timed-out command signals the command's own process. Descendants it spawned may survive until the sandbox is destroyed;
pids_limitbounds the damage in the meantime.restrictednetworking isolates sandboxes from each other but does not filter egress.Change detection covers regular files. Symlinks are copied but not tracked in diffs.
Per-sandbox disk quotas need a storage driver that supports them (overlay2 on XFS with
pquota);/tmpis capped via tmpfs, the workspace is not.
Future extensions
Remote sandbox execution
Firecracker isolation
Kubernetes sandbox workers
Persistent base-image cache
Snapshot / restore
Parallel experiments
Experiment replay
Resource usage analytics
Network allowlists
Human approval before applying changes
Agent experiment benchmarking
Licence
MIT.
Available Tools
15 toolscancel_jobCancel JobA
Stop a running command and kill its process inside the sandbox.
USE THIS when a command is clearly stuck or no longer needed. Safe on a job that already finished -- it returns the final state untouched.
Inputs: job_id (required). Returns: Outcome of one command. Identical shape whether it ran sync or async.
check_sandbox_runtimeCheck Sandbox RuntimeA
Confirm the sandbox runtime is available and report the active defaults.
USE THIS first if create_experiment fails, to tell a stopped Docker daemon apart from a rejected request. Returns the Docker version the server is talking to and the isolation defaults every experiment starts from.
Inputs: no named parameters. Returns: object
collect_artifactsCollect ArtifactsA
Copy selected files out of a sandbox before it is destroyed.
USE THIS for build output, test reports, coverage, benchmark results or logs you want to keep or quote.
Patterns are workspace-relative shell globs ('dist/*.js'), or '/name' for a recursive search ('/junit.xml'). Files land in the server's own state directory; this tool cannot write anywhere on the developer's machine.
Inputs: patterns (required), experiment_id (required). Returns: skipped, artifacts, total_bytes, experiment_id
compare_experimentsCompare ExperimentsA
Compare two or more experiments side by side.
USE THIS when you tried several approaches -- three candidate fixes, two runtime versions, a couple of dependency upgrades -- and have to recommend one. Run each approach in its own experiment, then compare.
RETURNS per-experiment test results, failing test names, exit codes, change size, duration and artifact counts, plus a recommendation when the evidence supports one. Destroyed experiments are still comparable: their findings were recorded before teardown.
Inputs: experiment_ids (required), labels. Returns: notes, dimensions, experiments, recommendation
create_experimentCreate ExperimentA
Create a disposable, isolated environment and copy a project into it.
USE THIS when you are about to do something you should not do on the developer's machine: install dependencies, run a build or a migration, try an upgrade, run unfamiliar code, or explore a fix you are not sure about. Reach for it before the risky step, not after.
The project is SNAPSHOT-COPIED into the sandbox. Files you change inside never propagate back; the developer's working tree is untouched by construction. Secrets (.env files, keys, credential directories) are withheld from the copy, as are node_modules and other build output.
RETURNS an experiment_id plus the isolation actually applied -- read the warnings field, which tells you where your request was clamped.
SAFETY: network is disabled unless you ask for it; no host environment variable is passed unless you name it, and credential-shaped names are refused even then; CPU, memory, PIDs and wall-clock are capped.
Inputs: timeout, cpu_limit, objective, base_image, +6 more. Returns: status, warnings, base_image, setup_jobs, +8 more
destroy_experimentDestroy ExperimentA
Destroy a sandbox and everything in it, returning a final report.
USE THIS as soon as an experiment has told you what you needed. Always call it -- a sandbox left running keeps consuming the developer's CPU and memory.
IDEMPOTENT: calling it on an already-destroyed experiment is safe and returns the stored report rather than an error.
RETURNS the final report, including what changed inside the sandbox, so you can summarise the experiment after it is gone.
Inputs: experiment_id (required). Returns: report, status, experiment_id, jobs_cancelled, +3 more
execute_experimentExecute ExperimentA
Run a shell command inside a sandbox.
USE THIS for anything you would otherwise run in the developer's terminal: installs, builds, scripts, migrations, one-off exploration. The command runs in the container, never on the host.
RETURNS exit code, stdout, stderr and duration. A non-zero exit is a normal result, not an error -- read it and decide what to try next.
Set background=true for something long-running, then poll get_job_status and fetch get_job_result when it finishes. Otherwise this waits, bounded by the timeout, so it cannot hang your session.
Inputs: command (required), experiment_id (required), timeout, workdir, +1 more. Returns: Outcome of one command. Identical shape whether it ran sync or async.
get_experimentGet ExperimentA
Fetch an experiment's full state and a summary of what happened in it.
USE THIS to re-orient -- after a long gap, or to check whether a sandbox is still alive before sending more commands.
RETURNS status, base image, isolation settings, resource limits, how many commands ran and how many failed, the last test summary, change statistics and artifact count.
Inputs: experiment_id (required). Returns: The concise summary an agent should quote back to the developer.
get_job_resultGet Job ResultA
Fetch the completed result of a background command.
RETURNS the same shape as execute_experiment: exit code, stdout, stderr, duration. If the job is still running this waits for it, bounded by the job's own timeout.
Inputs: job_id (required). Returns: Outcome of one command. Identical shape whether it ran sync or async.
get_job_statusGet Job StatusA
Check on a command started with background=true.
RETURNS status and elapsed milliseconds. Poll this, then call get_job_result once finished is true.
Inputs: job_id (required). Returns: job_id, status, command, finished, +3 more
inspect_changesInspect ChangesA
Show what the experiment changed, against the project as it was copied in.
USE THIS before destroying a sandbox, and before telling the developer what you found. It is the evidence for your conclusion.
RETURNS created / modified / deleted file lists plus insertion and deletion counts, and optionally the unified diff. Build output and dependency directories are excluded, so a 30,000-file node_modules will not bury the two lines that matter.
Inputs: experiment_id (required), include_diff, max_files_with_diff. Returns: What the sandbox looks like now versus the untouched baseline.
list_experimentsList ExperimentsA
List experiments, most recent first.
USE THIS to find a sandbox you created earlier, or to check for ones you forgot to destroy before creating another.
Inputs: limit, status. Returns: result
read_sandbox_fileRead Sandbox FileA
Read a file from inside the sandbox as text.
USE THIS to investigate a failure -- read the source, the config, a log -- without guessing from stack traces. Paths are workspace-relative and cannot escape it; this tool cannot read the developer's machine.
Inputs: path (required), experiment_id (required). Returns: result
run_testsRun TestsA
Run the project's test suite inside a sandbox and parse the results.
USE THIS instead of execute_experiment when you want to know whether the project still works -- it detects the runner (npm, pytest, cargo, go, make) by looking at what is actually in the sandbox, and parses counts out of the output.
RETURNS exit code, stdout/stderr, duration and, when parseable, a summary with passed/failed/total and the names of failing tests. If test_summary.detected is false, trust the exit code, not the zeros.
Pass command to override detection.
Inputs: experiment_id (required), command, timeout. Returns: error, job_id, status, stderr, +9 more
write_sandbox_fileWrite Sandbox FileA
Write a file inside the sandbox, creating parent directories as needed.
USE THIS to apply a candidate fix. Prefer it over shell heredocs: no quoting to get wrong.
SAFETY: writes land in the sandbox copy only. There is no tool here that writes to the developer's project -- if a change is worth keeping, show them the diff from inspect_changes and let them apply it.
Inputs: path (required), content (required), experiment_id (required). Returns: result
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
Loading tool schema changelog…
TDQS
Scored across 15 tools
Each tool maps cleanly to a distinct lifecycle phase: create, execute, test, file operations, change inspection, artifact collection, job management, and teardown. Even execute_experiment versus run_tests is explicitly delineated, so there is no real ambiguity in choosing between tools.
All names follow a consistent snake_case verb_noun pattern with predictable verbs like create, destroy, execute, get, list, read, write, cancel, and compare. The object nouns are similarly stable, making the toolset easy to navigate and predict.
Fifteen tools sit at the upper edge of a well-scoped server, but every tool has a distinct purpose and none feels redundant. The background job helpers, runtime check, and comparison tool all earn their place in the sandbox workflow.
The toolset covers the full sandbox experiment lifecycle: create, run commands and tests, read and write files, inspect changes, collect artifacts, compare experiments, retrieve state, and destroy. It also includes job polling and a runtime health check, leaving no obvious dead ends for agent workflows.
Maintenance
Related MCP Connectors
Run AI agent tasks in isolated sandboxes with a cost cap, saved workflows and scoped connections.
Persistent cloud workspaces for AI agents: run commands, edit files, use git and a browser.
Security gateway for AI agents: policy, approval, and audited execution, no secrets shared.
Fail-closed policy guardrails for AI agents running kubectl, terraform, helm, and argocd.
Related MCP Servers
- AlicenseNot gradedqualityNot gradedmaintenanceProvides AI coding agents with a secure, sandboxed environment for executing coding tasks including file operations, command execution, and testing. Features session management, policy enforcement, and Docker-based sandboxing for safe code execution and development workflows.MIT
- FlicenseNot gradedqualityDmaintenanceEnables AI agents to safely execute Python, JavaScript, and Bash code in an isolated Docker sandbox with strict security constraints.1-
- AlicenseNot gradedqualityCmaintenanceProvides a restricted Docker-based sandbox for LLM agents, enabling file operations, command execution, and local Git within an isolated runtime.9 npmMIT
- FlicenseAqualityCmaintenanceEnables AI coding agents to run Kubernetes inspection and Terraform plan/apply operations inside ephemeral gVisor-sandboxed jobs with short-lived, narrowly-scoped credentials, while routing destructive changes through a human approval gate.3-