Skip to main content
Glama

SandboxRunner ๐Ÿณ

SandboxRunner is a custom Model Context Protocol (MCP) server that enables AI assistants and MCP-compatible clients to securely execute Python and C++ code snippets inside disposable, isolated Docker containers โ€” and receive back structured results (stdout, stderr, exit code, and timing) in real time.

Built to understand MCP server design, Docker-based process isolation, and security-boundary thinking from the ground up.


Table of Contents


Related MCP server: Docker MCP Server

Features

  • โœ… Python execution โ€” runs snippets inside python:3.12-slim

  • โœ… C++ execution โ€” two-stage compile (g++ -std=c++17 -O2) + run, using gcc:14

  • โœ… Strong isolation per run:

    • No network access (--network none)

    • Hard memory cap (default 256m)

    • Hard CPU cap (default 0.5 cores)

    • Read-only root filesystem with a temporary scratch-only mount

    • noexec tmpfs for /tmp

    • Containers are ephemeral โ€” deleted after every run

  • โœ… Independent timeout enforcement โ€” a hung snippet cannot hang the MCP server; the container is force-killed on timeout

  • โœ… Output size capping โ€” stdout/stderr truncated at 100 KB with a clear marker

  • โœ… Distinct compile vs. runtime errors for C++ โ€” compiler errors are surfaced separately from runtime crashes

  • โœ… Local execution history โ€” every run is logged to a local SQLite database and queryable via MCP

  • โœ… Actionable Docker errors โ€” clear error message if Docker is not running, instead of a silent hang

  • โœ… All tunables in one place โ€” config.py is the single source of truth for limits, images, and settings


Architecture

MCP Client โ”€โ”€(stdio / sse)โ”€โ”€โ–ถ  SandboxRunner (FastMCP)
                                       โ”‚
                                       โ–ผ
                             execution.py (orchestration)
                                       โ”‚
                         โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
                         โ–ผ                           โ–ผ
              Docker (Python)              Docker (C++)
           python:3.12-slim             gcc:14 container
           --network none               --network none
           --memory 256m                --memory 256m
           --cpus 0.5                   --cpus 0.5
           --read-only                  Stage 1: compile (rw /scratch)
                                        Stage 2: run     (ro /scratch)
                                       โ”‚
                                       โ–ผ
                          database.py โ†’ sandbox_history.db (SQLite)

Per-execution flow (run_code)

  1. Validates input โ€” language, code size, timeout ceiling

  2. Writes code to a temporary host directory

  3. Mounts that directory into a fresh Docker container as /scratch

  4. Python: runs python /scratch/snippet.py directly

  5. C++: compiles to /scratch/a.out (read-write mount), then runs the binary (read-only mount) in a second container โ€” compile errors are returned distinctly from runtime errors

  6. Enforces timeout at the server level; kills the container if exceeded

  7. Captures and truncates stdout/stderr

  8. Logs run metadata to SQLite

  9. Returns a structured result to the MCP client


Requirements

  • Python โ‰ฅ 3.12

  • uv โ€” package manager (pip install uv or see uv docs)

  • Docker Desktop or Docker Engine โ€” must be installed and running

  • Docker images (pre-pull recommended):

    • python:3.12-slim

    • gcc:14

Python Dependencies

Package

Purpose

mcp[cli] >= 1.28.1

Official MCP SDK (FastMCP server + CLI)

docker >= 7.1.0

Docker SDK โ€” container orchestration

Dev only:

Package

Purpose

pytest >= 9.1.1

Test runner

pytest-asyncio >= 1.4.0

Async test support


Installation

# 1. Clone the repository
git clone https://github.com/huzayfaSiddique/sandbox_runner.git
cd sandbox_runner

# 2. Ensure Docker is running
docker ps

# 3. Install dependencies and create the virtual environment
uv sync

# 4. Pre-pull the Docker images (avoids slow cold start on first run)
docker pull python:3.12-slim
docker pull gcc:14

The sandbox-runner console script is installed automatically via [project.scripts] in pyproject.toml.


Configuration

All tunables live in src/sandbox_runner/config.py. No environment variables are required beyond the optional transport flag.

Setting

Default

Description

DEFAULT_TIMEOUT_SECONDS

10

Default execution timeout

MAX_TIMEOUT_SECONDS

30

Hard ceiling โ€” cannot be exceeded by callers

MEMORY_LIMIT

"256m"

Per-container memory cap

CPU_LIMIT

"0.5"

Per-container CPU share (in cores)

MAX_CODE_SIZE_BYTES

51200 (50 KB)

Maximum allowed snippet size

MAX_OUTPUT_SIZE_BYTES

102400 (100 KB)

Output truncation threshold

DB_FILE

"sandbox_history.db"

SQLite file for execution history

DEFAULT_HISTORY_LIMIT

20

Default rows returned by history tool

To add a new language, add an entry to SUPPORTED_LANGUAGES in config.py with an image, run_cmd, and optionally compile_cmd / source_file for compiled languages.


MCP Tools

Tool

Description

Inputs

Outputs

run_code

Execute a code snippet in an isolated container

language (python|cpp), code, timeout_seconds (opt, default 10, max 30)

status, exit_code, stdout, stderr, duration_ms, language

list_supported_languages

List available languages and Docker images

โ€”

[{language, image, description}]

get_execution_history

Retrieve recent run records

limit (opt, default 20)

[{id, timestamp, language, code_snippet, status, exit_code, duration_ms, stdout_size, stderr_size}]

status values: success ยท error ยท timeout ยท compile_error (C++ only)

Example run_code response:

{
  "status": "success",
  "exit_code": 0,
  "stdout": "The sum of elements is: 15\n",
  "stderr": "",
  "duration_ms": 1823.47,
  "language": "cpp"
}

Isolation & Resource Limits

Each run is sandboxed with the following Docker constraints:

Constraint

Value

Effect

--network none

Enforced

No outbound or inbound network access

--memory 256m

Configurable

Hard memory ceiling per container

--cpus 0.5

Configurable

CPU share cap

--read-only

Always on

Root filesystem is immutable

tmpfs /tmp

size=64m,noexec

Small, non-executable temp space

--rm

Always on

Container is deleted after each run

Timeout kill

Server-level

Server kills container if it exceeds timeout

โš ๏ธ This uses Docker-level isolation โ€” suitable for personal/local use. It is not a hardened multi-tenant sandbox (e.g. gVisor, Firecracker) and is not intended for running untrusted third-party code.


Execution History

Every run is stored in a local SQLite database (sandbox_history.db):

CREATE TABLE IF NOT EXISTS runs (
    id           INTEGER PRIMARY KEY AUTOINCREMENT,
    timestamp    REAL    NOT NULL,
    language     TEXT    NOT NULL,
    code_snippet TEXT    NOT NULL,   -- first 500 chars only
    status       TEXT    NOT NULL,
    exit_code    INTEGER,
    duration_ms  REAL    NOT NULL,
    stdout_size  INTEGER NOT NULL DEFAULT 0,
    stderr_size  INTEGER NOT NULL DEFAULT 0
);
  • Only the first 500 characters of each snippet are persisted as a preview.

  • stdout_size / stderr_size store byte counts, not the full content.

  • Timestamps are returned as ISO 8601 UTC strings via the get_execution_history tool.


Usage

Registering with an MCP Client

Add SandboxRunner to your MCP client's configuration. Example for Claude Desktop (claude_desktop_config.json):

{
  "mcpServers": {
    "sandbox-runner": {
      "command": "uv",
      "args": [
        "--directory",
        "/absolute/path/to/sandbox-runner",
        "run",
        "sandbox-runner"
      ]
    }
  }
}

Replace /absolute/path/to/sandbox-runner with your actual cloned directory path.
On Windows: C:\\Users\\YourName\\path\\to\\sandbox-runner

Restart the MCP client after saving the config. The three tools will become available immediately.

Running Standalone

# Start with stdio transport (default โ€” for MCP clients like Claude Desktop)
uv run sandbox-runner

# Start with SSE transport (for web-based or HTTP MCP clients)
uv run sandbox-runner --transport sse

# View CLI help
uv run sandbox-runner --help

Project Structure

sandbox-runner/
โ”œโ”€โ”€ src/
โ”‚   โ””โ”€โ”€ sandbox_runner/
โ”‚       โ”œโ”€โ”€ __init__.py       # Package version
โ”‚       โ”œโ”€โ”€ config.py         # All tunables โ€” limits, images, DB path
โ”‚       โ”œโ”€โ”€ database.py       # SQLite connection, record_run, fetch_history
โ”‚       โ”œโ”€โ”€ execution.py      # Docker orchestration, validation, truncation
โ”‚       โ”œโ”€โ”€ main.py           # CLI entrypoint (argparse + transport)
โ”‚       โ””โ”€โ”€ server.py         # FastMCP server + tool definitions
โ”œโ”€โ”€ tests/
โ”‚   โ”œโ”€โ”€ test_execution.py     # Input validation + mocked Docker execution tests
โ”‚   โ””โ”€โ”€ test_server.py        # Mocked MCP tool-level tests
โ”œโ”€โ”€ pyproject.toml            # Project metadata, deps, build config
โ”œโ”€โ”€ uv.lock                   # Locked dependency tree
โ”œโ”€โ”€ .python-version           # Pinned Python version
โ”œโ”€โ”€ sandbox_history.db        # SQLite history (auto-created at runtime)
โ””โ”€โ”€ README.md

Testing

The test suite uses mocked Docker calls โ€” Docker does not need to be running to run the tests.

uv run pytest

Current coverage:

  • test_execution.py

    • Valid input passes without errors

    • Unsupported language raises ValueError

    • Empty code raises ValueError

    • Oversized code raises ValueError

    • Timeout exceeding max raises ValueError

    • Mocked end-to-end Python execution โ€” asserts status == "success" and correct stdout

  • test_server.py

    • run_code success path โ€” verifies result format and DB logging call

    • run_code invalid language โ€” verifies error response and that DB is not written

    • list_supported_languages โ€” returns entries for both python and cpp

    • get_execution_history โ€” timestamps formatted as ISO 8601 strings


Known Limitations

  • Docker must be running. The server returns a clear error if the Docker daemon is unreachable โ€” no silent hangs.

  • Docker-level isolation only. Not a hardened cloud-grade sandbox. Designed for single-user, local use.

  • No multi-tenancy. No auth, no rate limiting โ€” assumes a single trusted operator.

  • Fixed resource limits by default. 256 MB / 0.5 CPU / 10s default timeout may be restrictive for heavy compute workloads.

  • History stores only snippet previews. Full code content is not persisted; only the first 500 characters are stored.


Contributing

Contributions are welcome!

  1. Fork the repo and create a feature branch

  2. Run uv run pytest and ensure all tests pass

  3. Keep new tunables in config.py โ€” avoid hardcoding values elsewhere

  4. Open a Pull Request with a clear description of the change and its motivation


Available Tools

3 tools
get_execution_historyA

Retrieve a log of recent code executions performed by run_code, most recent first.

Useful for reviewing what code was run previously, checking whether a
past run succeeded, or auditing recent sandbox activity. Only a preview
(first 500 characters) of each snippet's code is stored, not the full
source, and stdout/stderr are recorded as byte sizes only, not content.

Returns a list of dicts, each with:
- id: unique run identifier
- timestamp: ISO 8601 UTC timestamp of when the run occurred
- language: language that was executed
- code_snippet: first 500 characters of the executed code
- status: "success", "error", "timeout", or "compile_error"
- exit_code: process exit code, or null
- duration_ms: how long the run took, in milliseconds
- stdout_size / stderr_size: byte counts of captured output
ParametersJSON Schema
NameRequiredDescriptionDefault
limitNoMaximum number of past runs to return, most recent first. Default 20. Values below 1 are treated as 1.

Output Schema

ParametersJSON Schema
NameRequiredDescription
resultYes

TDQS

A4.1/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Discloses that only first 500 characters of code are stored, stdout/stderr recorded as byte sizes only, and order is most recent first. With no annotations, this provides useful behavioral context.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Front-loaded with purpose and clear structure. The description is well-organized with paragraphs for use cases and return fields, though the return field list is somewhat redundant given the output schema.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given one parameter and existence of output schema, the description covers key aspects: what data is stored, what is omitted (full code, output content), and use cases. Minor omissions like pagination but overall complete.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, so the schema already documents the limit parameter adequately. The description does not add extra semantics beyond what the schema provides.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the tool retrieves a log of recent code executions by run_code, most recent first. It distinguishes itself from siblings run_code (which executes code) and list_supported_languages.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Provides concrete use cases: reviewing past code runs, checking success, auditing. No explicit when-not-to-use, but context makes it clear this is for history retrieval.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

list_supported_languagesA

List every programming language this sandbox can execute, along with the Docker image used to run it.

Call this first if you're unsure what values are valid for the
`language` parameter of `run_code`. Takes no arguments.

Returns a list of dicts, each with:
- language: the identifier to pass to run_code (e.g. "python", "cpp")
- image: the Docker image used to execute code in this language
- description: a short human-readable description of the runtime
ParametersJSON Schema
NameRequiredDescriptionDefault

No parameters

Output Schema

ParametersJSON Schema
NameRequiredDescription
resultYes

TDQS

A4.5/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

No annotations are provided, so the description carries full burden. It explains the return format (list of dicts with three fields) and that it has no side effects. It doesn't mention auth or rate limits, but for a read-only listing tool that is acceptable.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Extremely concise: three short paragraphs. Front-loads the action and result, then usage advice, then return schema. Every sentence earns its place with no redundancy.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given zero parameters and the availability of an output schema (presumably documenting the return fields), the description is complete. It describes the output structure clearly, compensating for any potential gaps in the output schema.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

There are zero parameters. The description adds no parameter info, but the baseline for 0 params is 4. No need for further detail.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states it lists every programming language and Docker image for the sandbox. It distinguishes itself from sibling tools (run_code, get_execution_history) by focusing on discovery of supported languages.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Explicitly says 'Call this first if you're unsure what values are valid for the `language` parameter of `run_code`.' This provides clear context for when to use it. It also notes 'Takes no arguments,' which is helpful. Lacks explicit when-not-to-use or alternative suggestions, but sufficient.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

run_codeA

Execute a Python or C++ code snippet inside an isolated, network-disabled Docker container and return its output.

Each run happens in a fresh, ephemeral container with no network access, a
256 MB memory cap, a 0.5 CPU cap, and a read-only root filesystem. The
container is destroyed immediately after the run. Use this tool to run
untrusted or exploratory code snippets, verify that code works, or inspect
program output, without touching the host machine.

Returns a dict with:
- status: one of "success", "error", "timeout", or "compile_error" (C++ only)
- exit_code: the process exit code, or null if it never ran
- stdout / stderr: captured output, truncated at 100 KB
- duration_ms: wall-clock execution time in milliseconds
- language: the normalized language that was actually run

Note: this sandbox provides Docker-level isolation suitable for personal/
local use. It is not a hardened multi-tenant sandbox and should not be
used to run code from untrusted third parties in a production setting.
ParametersJSON Schema
NameRequiredDescriptionDefault
codeYesThe full source code to execute, as a single string. Max size 50 KB. For C++, this must be a complete, compilable program including a main() function and any necessary #include directives. For Python, this is executed directly as a standalone script.
languageYesProgramming language of the snippet. Supported values: 'python' (runs in python:3.12-slim) and 'cpp' (compiled with g++ -std=c++17 -O2 then run in gcc:14). Case-insensitive.
timeout_secondsNoMaximum time in seconds to allow the code to run before it is force-killed. Default 10, hard ceiling 30 (requests above this are rejected). For C++, this timeout applies to the run stage only, not the compile stage.

Output Schema

ParametersJSON Schema
NameRequiredDescription

No output parameters

TDQS

A4.6/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description fully discloses isolation level, resource caps (256 MB, 0.5 CPU), ephemeral nature, return structure, and limitations. No contradictions.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Description is thorough but well-organized; each sentence adds value. Could be slightly shorter, but effective.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Covers purpose, behavior, parameters, return values, and limitations comprehensively. Output schema is described, so no gaps.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema descriptions are already detailed, but the description adds context like max code size 50KB, case-insensitive language, and timeout ceiling 30s, which are not in schema.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states it executes Python or C++ code in an isolated container and returns output. It distinguishes itself from siblings like list_supported_languages and get_execution_history by focusing on execution.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Provides explicit use cases (exploratory code, verification) and a warning about not being a hardened sandbox. Does not explicitly compare to siblings, but context makes it clear.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections. Dates show when Glama detected each change.

  1. 2 tool updatesv0.1.1
    • Changedget_execution_history1 field changed
      • addedInput schema / properties / limit / description
        Added value: +"Maximum number of past runs to return, most recent first. Default 20. Values below 1 are treated as 1."
    • Changedrun_code3 fields changed
      • addedInput schema / properties / code / description
        Added value: +"The full source code to execute, as a single string. Max size 50 KB. For C++, this must be a complete, compilable program including a main() function and any necessary #include directives. For Python, this is executed directly as a standalone script."
      • addedInput schema / properties / language / description
        Added value: +"Programming language of the snippet. Supported values: 'python' (runs in python:3.12-slim) and 'cpp' (compiled with g++ -std=c++17 -O2 then run in gcc:14). Case-insensitive."
      • addedInput schema / properties / timeout_seconds / description
        Added value: +"Maximum time in seconds to allow the code to run before it is force-killed. Default 10, hard ceiling 30 (requests above this are rejected). For C++, this timeout applies to the run stage only, not the compile stage."
  2. 3 tool updatesv0.1.0
    • First observedget_execution_history
    • First observedlist_supported_languages
    • First observedrun_code

TDQS

A4.5/5.0
Disambiguation5/5

Each tool has a clearly distinct purpose: run_code executes code snippets, list_supported_languages returns available runtimes, and get_execution_history provides a log of past runs. There is no functional overlap.

Naming Consistency5/5

All tool names follow the verb_noun pattern (run_code, list_supported_languages, get_execution_history) with consistent snake_case style.

Tool Count5/5

Three tools is a well-scoped set for a code sandbox server. It covers the essential operations: running code, querying supported languages, and reviewing execution history, without unnecessary extras.

Completeness5/5

The tool surface is complete for the stated purpose of running code in an isolated sandbox. It includes language discovery, code execution, and history auditing, with no obvious missing functionality.

Maintenance

ActivitySlowing
ResponsivenessSyncing

Resources

Unclaimed servers have limited discoverability.

Looking for Admin?

If you are the server author, to access and configure the admin panel.

Related MCP Connectors

Related MCP Servers

  • F
    license
    Not graded
    quality
    C
    maintenance
    Enables AI assistants to execute Python, JavaScript, Bash, and Go code in blazing-fast (~0.1ms startup), isolated cloud containers with secure, ephemeral environments that auto-destroy after use.
    155
    -
  • A
    license
    Not graded
    quality
    D
    maintenance
    Enables LLMs to safely execute code in isolated Docker containers with resource limits and security controls, supporting session management and automatic dependency installation.
    MIT
  • A
    license
    Not graded
    quality
    C
    maintenance
    Enables LLMs to execute Python code securely in a sandboxed environment. Supports configurable restrictions like no network access and returns results including files.
    MIT

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/huzayfaSiddique/sandbox_runner'

If you have feedback or need assistance with the MCP directory API, please join our Discord server