LLM Sandbox
Use the LLM Sandbox MCP server to run LLM-generated code securely in a sandbox and discover language support.
Execute code in an isolated sandbox environment with automatic visualization capture.
Run Python, JavaScript, Java, C++, Go, R, or Ruby code.
Install libraries/packages on the fly during execution.
Set a custom execution timeout.
Receive execution results including text output and generated plot/image artifacts.
Get the list of supported programming languages.
Get detailed information about a specific supported language.
Configure the sandbox backend for Docker, Podman, or Kubernetes via MCP client settings.
Provides runnable examples for integrating the sandbox with CrewAI to securely execute LLM-generated code.
Allows running LLM-generated code in isolated Docker containers, providing a secure execution environment with resource limits and network isolation.
Provides runnable examples for integrating the sandbox with Google ADK to securely execute LLM-generated code.
Allows running LLM-generated code in isolated Kubernetes pods, enabling scalable and secure execution in enterprise-grade orchestration.
Provides runnable examples for integrating the sandbox with LangChain to securely execute LLM-generated code.
Provides runnable examples for integrating the sandbox with the OpenAI Agents SDK to securely execute LLM-generated code.
Allows running LLM-generated code in isolated rootless Podman containers, providing a secure execution environment.
Provides runnable examples for integrating the sandbox with Pydantic AI to securely execute LLM-generated code.
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., "@LLM Sandboxrun this Python code in a sandbox and show me the output"
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.
LLM Sandbox
Securely Execute LLM-Generated Code with Ease
LLM Sandbox runs Large Language Model (LLM) generated code through a common API for container and local Linux and Windows sandbox backends. Choose an isolation backend for your deployment, configure its security controls, and reuse the same code execution and file APIs.
Documentation: https://vndee.github.io/llm-sandbox/
Backend benchmarks compare Docker, Bubblewrap and NsJail across startup, execution, file transfers and concurrency, with confidence intervals and reproducible evidence.

β¨ New: This project now supports the Model Context Protocol (MCP) server, which allows your MCP clients (e.g. Claude Desktop) to run code generated by LLMs in a secure sandbox environment.
Related MCP server: Shell-MCP
π Key Features
π‘οΈ Security First
Isolated Execution: Container or local OS sandboxing with explicitly configured host access
Security Policies: Define custom security policies to control code execution
Resource Limits: Set CPU, memory, and execution time limits
Network Isolation: Control network access for sandboxed code
ποΈ Flexible Sandbox Backends
Docker: Most popular and widely supported option
Kubernetes: Enterprise-grade orchestration for scalable deployments
Podman: Rootless containers for enhanced security
Bubblewrap: Local Linux namespaces and seccomp without Docker or a daemon
NsJail: Local Linux namespaces and native seccomp policies without a daemon
Windows MXC: Native Windows process isolation through Microsoft's MXC/PSEC APIs
WSL2 Bubblewrap: Linux namespaces and seccomp in an explicitly selected WSL2 distribution
Backend Registry: Register additional adapters and inspect their supported features
π Multi-Language Support
Execute code in multiple programming languages with automatic dependency management:
Python - Full ecosystem support with pip packages
JavaScript/Node.js - npm package installation
Java - Maven and Gradle dependency management
C++ - Compilation and execution
Go - Module support and compilation
R - Statistical computing and data analysis with CRAN packages
π LLM Framework Integration
Runnable examples for eleven agent frameworks β OpenAI Agents SDK, Claude Agent SDK, LangChain, DeepAgents, LlamaIndex, Google ADK, CrewAI, Pydantic AI, smolagents, Strands and AG2. See examples/agent_sdks/.
π Advanced Features
Artifact Extraction: Automatically capture plots and visualizations
Library Management: Install dependencies on-the-fly
File Operations: Copy files to/from sandbox environments
Custom Images: Use your own container images
Fast Production Mode: Skip environment setup for faster container startup
Container Pooling: Pre-warm and reuse containers for improved performance (NEW!)
π¦ Installation
Basic Installation
pip install llm-sandboxWith Specific Backend Support
# For Docker support (most common)
pip install 'llm-sandbox[docker]'
# For Kubernetes support
pip install 'llm-sandbox[k8s]'
# For Podman support
pip install 'llm-sandbox[podman]'
# All optional container SDKs
pip install 'llm-sandbox[docker,k8s,podman]'Bubblewrap and NsJail use the base Python package plus system executables; no container SDK extra is needed. See local backend requirements for Linux prerequisites.
The Windows adapters also need no container SDK. windows-mxc requires a compatible Windows host and the native helper, while wsl2-bubblewrap requires a configured WSL2 distribution with the matching Python package and Linux prerequisites. See Windows requirements; neither adapter installs an OS runtime or falls back to unsandboxed execution.
Development Installation
Dev dependencies live in the dev uv dependency group, so install them with uv (or the make install shortcut):
git clone https://github.com/vndee/llm-sandbox.git
cd llm-sandbox
make install # uv sync + pre-commit installSee CONTRIBUTING.md for the full workflow.
πββοΈ Quick Start
Basic Usage
from llm_sandbox import SandboxSession
# Create and use a sandbox session
with SandboxSession(lang="python") as session:
result = session.run("""
print("Hello from LLM Sandbox!")
print("I'm running in a secure container.")
""")
print(result.stdout)Local Linux Usage Without Docker
On Debian/Ubuntu, install the system prerequisites with sudo apt install bubblewrap libseccomp2 python3-venv. Bubblewrap must be version 0.8.0 or newer with --disable-userns support; older distributions such as Ubuntu 22.04 need a newer package or upstream build. Your host must permit unprivileged user namespaces; distribution security policy can restrict them.
from llm_sandbox import SandboxSession
with SandboxSession(backend="bubblewrap", lang="python") as session:
result = session.run("print('Hello from a local Linux sandbox!')")
print(result.stdout)The local defaults combine namespace isolation with a seccomp syscall denylist, disable network access, expose runtime paths read-only, and provide a private writable workspace and temporary directory. Python setup creates a virtual environment offline. Install the host runtime and its venv support first. Select backend="nsjail" to use an installed NsJail binary instead.
The Linux adapters support run(), execute_command(), install(), file copying, and the artifact wrapper. They do not support container images, pooling, or interactive sessions. Files persist between commands in a session; background processes do not. These backends share the host kernel and are not a VM boundary. See configuration, security, and the backend roadmap.
Windows Usage Without Docker
For native Windows Python execution, use the MXC adapter on a compatible Windows host with the helper installed:
from llm_sandbox import SandboxSession
with SandboxSession(backend="windows-mxc", lang="python") as session:
print(session.run("import platform; print(platform.system())").stdout)To run Linux tools from a Windows Python application, select a WSL2 distribution explicitly:
from llm_sandbox import SandboxSession
with SandboxSession(
backend="wsl2-bubblewrap",
distribution="Ubuntu",
lang="python",
) as session:
print(session.run("import platform; print(platform.system())").stdout)These are experimental adapters with different prerequisites and API limits. MXC initially supports Python and JavaScript with preinstalled dependencies. WSL2 reuses the Linux Bubblewrap runtime, requires seccomp, and restricts mounts to built-in Linux runtime paths to keep Windows interoperability inaccessible. Both default to disabled networking. See backend setup and limitations.
Installing Libraries
from llm_sandbox import SandboxSession
with SandboxSession(lang="python") as session:
result = session.run("""
import numpy as np
# Create an array
arr = np.array([1, 2, 3, 4, 5])
print(f"Array: {arr}")
print(f"Mean: {np.mean(arr)}")
""", libraries=["numpy"])
print(result.stdout)Multi-Language Support
JavaScript
with SandboxSession(lang="javascript") as session:
result = session.run("""
const greeting = "Hello from Node.js!";
console.log(greeting);
const axios = require('axios');
console.log("Axios loaded successfully!");
""", libraries=["axios"])Java
with SandboxSession(lang="java") as session:
result = session.run("""
public class HelloWorld {
public static void main(String[] args) {
System.out.println("Hello from Java!");
}
}
""")C++
with SandboxSession(lang="cpp") as session:
result = session.run("""
#include <iostream>
int main() {
std::cout << "Hello from C++!" << std::endl;
return 0;
}
""")Go
with SandboxSession(lang="go") as session:
result = session.run("""
package main
import "fmt"
func main() {
fmt.Println("Hello from Go!")
}
""")R
with SandboxSession(
lang="r",
image="ghcr.io/vndee/sandbox-r-451-bullseye",
verbose=True,
) as session:
result = session.run(
"""
# Basic R operations
print("=== Basic R Demo ===")
# Create some data
numbers <- c(1, 2, 3, 4, 5, 10, 15, 20)
print(paste("Numbers:", paste(numbers, collapse=", ")))
# Basic statistics
print(paste("Mean:", mean(numbers)))
print(paste("Median:", median(numbers)))
print(paste("Standard Deviation:", sd(numbers)))
# Work with data frames
df <- data.frame(
name = c("Alice", "Bob", "Charlie", "Diana"),
age = c(25, 30, 35, 28),
score = c(85, 92, 78, 96)
)
print("=== Data Frame ===")
print(df)
# Calculate average score
avg_score <- mean(df$score)
print(paste("Average Score:", avg_score))
"""
)Interactive Sessions
For notebook-style workflows you can use InteractiveSandboxSession, which keeps the Python interpreter state across multiple run calls.
from llm_sandbox import InteractiveSandboxSession
with InteractiveSandboxSession(
lang="python",
kernel_type="ipython",
history_size=200,
) as session:
session.run("value = 21 * 2")
result = session.run("print(f'Result: {value}')")
print(result.stdout) # -> Result: 42
# Use magic command to install libraries
session.run("%pip install pandas")
result = session.run("import pandas as pd; print(pd.DataFrame({'A': [1, 2, 3], 'B': [4, 5, 6]}))")
print(result.stdout)Interactive sessions support Docker, Podman, and Kubernetes backends and currently target Python language. They spin up a long-running IPython kernel inside the sandbox, so each run() behaves like a notebook cellβstate, imports, and magic commands stay alive until the context manager exits, without any extra networking or manual serialization.
Capturing Plots and Visualizations
Python Plots
from llm_sandbox import ArtifactSandboxSession
import base64
from pathlib import Path
with ArtifactSandboxSession(lang="python") as session:
result = session.run("""
import matplotlib.pyplot as plt
import numpy as np
x = np.linspace(0, 10, 100)
y = np.sin(x)
plt.figure(figsize=(10, 6))
plt.plot(x, y)
plt.title("Sine Wave")
plt.xlabel("x")
plt.ylabel("sin(x)")
plt.grid(True)
plt.savefig("sine_wave.png", dpi=150, bbox_inches="tight")
plt.show()
""", libraries=["matplotlib", "numpy"])
# Extract the generated plots
print(f"Generated {len(result.plots)} plots")
# Save plots to files
for i, plot in enumerate(result.plots):
plot_path = Path(f"plot_{i + 1}.{plot.format.value}")
with plot_path.open("wb") as f:
f.write(base64.b64decode(plot.content_base64))R Plots
from llm_sandbox import ArtifactSandboxSession
import base64
from pathlib import Path
with ArtifactSandboxSession(lang="r") as session:
result = session.run("""
library(ggplot2)
# Create sample data
data <- data.frame(
x = rnorm(100),
y = rnorm(100)
)
# Create ggplot2 visualization
p <- ggplot(data, aes(x = x, y = y)) +
geom_point(alpha = 0.6) +
geom_smooth(method = "lm", se = FALSE) +
labs(title = "Scatter Plot with Trend Line",
x = "X values", y = "Y values") +
theme_minimal()
print(p)
# Base R plot
hist(data$x, main = "Distribution of X",
xlab = "X values", col = "lightblue", breaks = 20)
""", libraries=["ggplot2"])
# Extract the generated plots
print(f"Generated {len(result.plots)} R plots")
# Save plots to files
for i, plot in enumerate(result.plots):
plot_path = Path(f"r_plot_{i + 1}.{plot.format.value}")
with plot_path.open("wb") as f:
f.write(base64.b64decode(plot.content_base64))π§ Configuration
Basic Configuration
from llm_sandbox import SandboxSession
# Create a new sandbox session
with SandboxSession(image="python:3.9.19-bullseye", keep_template=True, lang="python") as session:
result = session.run("print('Hello, World!')")
print(result)
# With custom Dockerfile
with SandboxSession(dockerfile="Dockerfile", keep_template=True, lang="python") as session:
result = session.run("print('Hello, World!')")
print(result)
# Or default image
with SandboxSession(lang="python", keep_template=True) as session:
result = session.run("print('Hello, World!')")
print(result)LLM Sandbox also supports copying files between the host and the sandbox:
from llm_sandbox import SandboxSession
with SandboxSession(lang="python", keep_template=True) as session:
# Copy a file from the host to the sandbox
session.copy_to_runtime("test.py", "/sandbox/test.py")
# Run the copied Python code in the sandbox
result = session.execute_command("python /sandbox/test.py")
print(result)
# Copy a file from the sandbox to the host
session.copy_from_runtime("/sandbox/output.txt", "output.txt")Custom runtime configs
from llm_sandbox import SandboxSession
pod_manifest = {
"apiVersion": "v1",
"kind": "Pod",
"metadata": {
"name": "test",
"namespace": "test",
"labels": {"app": "sandbox"},
},
"spec": {
"containers": [
{
"name": "sandbox-container",
"image": "test",
"tty": True,
"volumeMounts": {
"name": "tmp",
"mountPath": "/tmp",
},
}
],
"volumes": [{"name": "tmp", "emptyDir": {"sizeLimit": "5Gi"}}],
},
}
with SandboxSession(
backend="kubernetes",
image="python:3.9.19-bullseye",
dockerfile=None,
lang="python",
keep_template=False,
verbose=False,
pod_manifest=pod_manifest,
) as session:
result = session.run("print('Hello, World!')")
print(result)Remote Docker Host
import docker
from llm_sandbox import SandboxSession
tls_config = docker.tls.TLSConfig(
client_cert=("path/to/cert.pem", "path/to/key.pem"),
ca_cert="path/to/ca.pem",
verify=True
)
docker_client = docker.DockerClient(base_url="tcp://<your_host>:<port>", tls=tls_config)
with SandboxSession(
client=docker_client,
image="python:3.9.19-bullseye",
keep_template=True,
lang="python",
) as session:
result = session.run("print('Hello, World!')")
print(result)Kubernetes Support
from kubernetes import client, config
from llm_sandbox import SandboxSession
# Use local kubeconfig
config.load_kube_config()
k8s_client = client.CoreV1Api()
with SandboxSession(
client=k8s_client,
backend="kubernetes",
image="python:3.9.19-bullseye",
lang="python",
pod_manifest=pod_manifest, # None by default
) as session:
result = session.run("print('Hello from Kubernetes!')")
print(result)β οΈ Important for Custom Pod Manifests:
When using custom pod manifests, ensure your container configuration includes:
"tty": True(keeps container alive)Proper
securityContextat both pod and container levelsContainer name can be any valid name (no restrictions)
See the Configuration Guide for complete requirements.
Podman Support
from llm_sandbox import SandboxSession
with SandboxSession(
backend="podman",
lang="python",
image="python:3.9.19-bullseye"
) as session:
result = session.run("print('Hello from Podman!')")
print(result)β‘ Container Pooling (Performance Optimization)
Container pooling dramatically improves performance by reusing pre-warmed containers instead of creating new ones for each execution. This is particularly beneficial for applications that execute code frequently.
Key Benefits
Faster Execution: Eliminate container creation overhead (up to 10x faster)
Pre-warmed Environments: Containers are initialized with your dependencies
Thread-Safe: Safely handle concurrent requests
Resource Efficient: Automatic container lifecycle management
Flexible Configuration: Control pool size, timeouts, and behavior
Basic Pool Usage
from llm_sandbox import SandboxSession
from llm_sandbox.pool import PoolConfig, create_pool_manager
# Create a pool manager explicitly
pool = create_pool_manager(
backend="docker",
config=PoolConfig(
max_pool_size=10, # Maximum containers
min_pool_size=3, # Keep at least 3 warm
idle_timeout=300.0, # Recycle idle containers after 5 min
enable_prewarming=True, # Create containers on startup
),
lang="python",
)
# Use the pool in a session
with SandboxSession(
lang="python",
pool=pool,
) as session:
result = session.run("print('Hello from pool!')")
# Container is automatically returned to pool when the session closes
# Clean up the pool when done
pool.close()Sharing a Pool Across Sessions
For maximum efficiency, share a single pool across multiple sessions:
from llm_sandbox import SandboxSession
from llm_sandbox.pool import create_pool_manager, PoolConfig
# Create a shared pool manager
pool = create_pool_manager(
backend="docker",
config=PoolConfig(
max_pool_size=10,
min_pool_size=3,
),
lang="python",
libraries=["numpy", "pandas"], # Pre-install libraries in all containers
)
# Use the pool in multiple sessions
with SandboxSession(lang="python", pool=pool) as session1:
result1 = session1.run("import pandas; print(pandas.__version__)")
with SandboxSession(lang="python", pool=pool) as session2:
result2 = session2.run("import numpy; print(numpy.__version__)")
# Clean up when done
pool.close()Concurrent Execution
Container pools are thread-safe and handle concurrent requests efficiently:
from concurrent.futures import ThreadPoolExecutor
from llm_sandbox import SandboxSession
from llm_sandbox.pool import create_pool_manager, PoolConfig
# Create shared pool
pool = create_pool_manager(
backend="docker",
config=PoolConfig(max_pool_size=5),
lang="python",
)
def run_code(task_id: int):
with SandboxSession(lang="python", pool=pool) as session:
return session.run(f'print("Task {task_id}")')
try:
# Execute 20 tasks concurrently using only 5 containers
with ThreadPoolExecutor(max_workers=10) as executor:
results = list(executor.map(run_code, range(20)))
finally:
pool.close()Pool Configuration Options
from llm_sandbox.pool import PoolConfig, ExhaustionStrategy, create_pool_manager
config = PoolConfig(
# Pool size limits
max_pool_size=10, # Maximum containers in pool
min_pool_size=2, # Minimum warm containers
# Timeout configuration
idle_timeout=300.0, # Recycle idle containers (seconds)
acquisition_timeout=30.0, # Wait time for available container
# Health and lifecycle
health_check_interval=60.0, # Health check frequency
max_container_lifetime=3600.0, # Max container lifetime
max_container_uses=100, # Max uses before recycling
# Pool exhaustion behavior
exhaustion_strategy=ExhaustionStrategy.WAIT, # WAIT, FAIL_FAST, or TEMPORARY
# Pre-warming
enable_prewarming=True, # Pre-warm containers
)
pool = create_pool_manager(
backend="docker",
config=config,
lang="python",
libraries=["requests", "numpy"], # Pre-install libraries
)Pool Exhaustion Strategies
When all containers are busy, the pool can handle it in different ways:
1. WAIT (Default)
Wait for a container to become available:
config = PoolConfig(
max_pool_size=5,
exhaustion_strategy=ExhaustionStrategy.WAIT,
acquisition_timeout=30.0, # Wait up to 30 seconds
)2. FAIL_FAST
Immediately raise an error:
config = PoolConfig(
max_pool_size=5,
exhaustion_strategy=ExhaustionStrategy.FAIL_FAST,
)3. TEMPORARY
Create a temporary container outside the pool:
config = PoolConfig(
max_pool_size=5,
exhaustion_strategy=ExhaustionStrategy.TEMPORARY,
)Monitoring Pool Statistics
from llm_sandbox.pool import create_pool_manager
pool = create_pool_manager(backend="docker", lang="python")
# Get pool statistics
stats = pool.get_stats()
print(f"Total containers: {stats['total_size']}")
print(f"Idle containers: {stats['state_counts']['idle']}")
print(f"Busy containers: {stats['state_counts']['busy']}")
pool.close()Artifact Extraction with Pooling
For capturing plots and visualizations with container pooling, you can use either approach:
from llm_sandbox import ArtifactSandboxSession
from llm_sandbox.pool import create_pool_manager, PoolConfig
import base64
from pathlib import Path
# Create pool with pre-installed visualization libraries
pool = create_pool_manager(
backend="docker",
config=PoolConfig(max_pool_size=5, min_pool_size=2),
lang="python",
libraries=["matplotlib", "numpy"],
)
try:
# Option 1: Use pool parameter (recommended for API consistency)
with ArtifactSandboxSession(pool=pool, enable_plotting=True) as session:
result = session.run("""
import matplotlib.pyplot as plt
import numpy as np
x = np.linspace(0, 10, 100)
y = np.sin(x)
plt.plot(x, y)
plt.title('Pooled Execution - Sine Wave')
plt.show()
""")
# Save generated plots
for i, plot in enumerate(result.plots):
Path(f"plot_{i}.{plot.format.value}").write_bytes(
base64.b64decode(plot.content_base64)
)
print(f"Generated {len(result.plots)} plots using pooled container")
# Option 2: Use ArtifactPooledSandboxSession (explicit class)
# Both approaches work identically
from llm_sandbox.pool import ArtifactPooledSandboxSession
with ArtifactPooledSandboxSession(pool_manager=pool, enable_plotting=True) as session:
result = session.run("print('Same functionality, different API')")
finally:
pool.close()Examples
See the examples/ directory for complete demonstrations:
pool_basic_demo.py - Basic pool usage and configuration
pool_concurrent_demo.py - Concurrent execution patterns
pool_monitoring_demo.py - Health monitoring and lifecycle management
pool_artifact_demo.py - Artifact extraction with pooling (plots, CSV, mixed artifacts)
π€ LLM Framework Integration
Runnable, version-pinned examples for eleven agent frameworks live in
examples/agent_sdks/:
Each file is self-contained, names the SDK version it was verified against, and applies container hardening. They all share the same core β a sandbox call with the controls that matter once the code was written by a model:
from llm_sandbox import SandboxSession
SANDBOX_RUNTIME = {
"network_mode": "none", # no egress
"mem_limit": "512m",
"pids_limit": 128, # bounds fork bombs
"security_opt": ["no-new-privileges:true"],
}
def run_python(code: str) -> str:
with SandboxSession(
lang="python",
keep_template=True, # else the image is re-pulled each call
runtime_configs=SANDBOX_RUNTIME,
) as session:
result = session.run(code, timeout=30)
return result.stdout if result.exit_code == 0 else result.stderrSecurity policies are advisory. session.is_safe(code) returns a verdict β
it does not block execution, and run() executes code the policy flagged.
Check it yourself before calling run(). See the
security guide.
π Model Context Protocol (MCP) Server
LLM Sandbox provides a Model Context Protocol (MCP) server that enables AI assistants like Claude Desktop to execute code securely in sandboxed environments. This integration allows LLMs to run code directly with automatic visualization capture and multi-language support.
Features
Secure Code Execution: Execute code in isolated containers with your preferred backend
Multi-Language Support: Run Python, JavaScript, Java, C++, Go, R, and Ruby code
Automatic Visualization Capture: Automatically capture and return plots and visualizations
Library Management: Install packages and dependencies on-the-fly
Flexible Backend Support: Choose from Docker, Podman, or Kubernetes backends
Installation
Install LLM Sandbox with MCP support using your preferred backend:
# For Docker backend
pip install 'llm-sandbox[mcp-docker]'
# For Podman backend
pip install 'llm-sandbox[mcp-podman]'
# For Kubernetes backend
pip install 'llm-sandbox[mcp-k8s]'Configuration
Add the following configuration to your MCP client (e.g., claude_desktop_config.json for Claude Desktop):
{
"mcpServers": {
"llm-sandbox": {
"command": "python3",
"args": ["-m", "llm_sandbox.mcp_server.server"],
}
}
}Backend-Specific Configuration
For the specific backend, you need to set the BACKEND environment variable to the backend you want to use. You might need to set other environment variables depending on the backend you are using. For example, you might need to set the DOCKER_HOST environment variable to the host you want to use if the DOCKER_HOST is not automatically in your system.
Docker (default):
{
"mcpServers": {
"llm-sandbox": {
"command": "python3",
"args": ["-m", "llm_sandbox.mcp_server.server"],
"env": {
"BACKEND": "docker",
"DOCKER_HOST": "unix:///var/run/docker.sock" // change this to the actual host you are using
}
}
}
}Podman:
{
"mcpServers": {
"llm-sandbox": {
"command": "python3",
"args": ["-m", "llm_sandbox.mcp_server.server"],
"env": {
"BACKEND": "podman",
"DOCKER_HOST": "unix:///var/run/podman/podman.sock" // change this to the actual host you are using
}
}
}
}For Kubernetes, you might need to set the KUBECONFIG environment variable to the path to your kubeconfig file.
Kubernetes:
{
"mcpServers": {
"llm-sandbox": {
"command": "python3",
"args": ["-m", "llm_sandbox.mcp_server.server"],
"env": {
"BACKEND": "kubernetes",
"KUBECONFIG": "/path/to/kubeconfig" // change this to the actual path to your kubeconfig file
}
}
}
}Runtime Config Environment Variables
For Docker, Podman, and Micromamba backends, the MCP server can translate SANDBOX_* environment
variables into session runtime_configs.
{
"mcpServers": {
"llm-sandbox": {
"command": "python3",
"args": ["-m", "llm_sandbox.mcp_server.server"],
"env": {
"BACKEND": "podman",
"DOCKER_HOST": "unix:///run/podman/podman.sock",
"SANDBOX_NETWORK_MODE": "none",
"SANDBOX_READ_ONLY": "true",
"SANDBOX_CAP_DROP": "ALL",
"SANDBOX_SECURITY_OPT": "no-new-privileges:true",
"SANDBOX_MEMORY": "4g",
"SANDBOX_CPU_COUNT": "1"
}
}
}
}Supported variables: SANDBOX_NETWORK_MODE, SANDBOX_READ_ONLY, SANDBOX_MEMORY, SANDBOX_MEM_LIMIT,
SANDBOX_CPUS, SANDBOX_CPU_COUNT, SANDBOX_CAP_DROP, SANDBOX_SECURITY_OPT, and SANDBOX_PRIVILEGED.
SANDBOX_MEMORY is normalized to the backend-safe mem_limit runtime config. SANDBOX_CPUS and
SANDBOX_CPU_COUNT are normalized to Linux-compatible CPU quota settings.
Security note: prefer
SANDBOX_NETWORK_MODE=none,SANDBOX_READ_ONLY=true,SANDBOX_CAP_DROP=ALL, and restrictiveSANDBOX_SECURITY_OPTvalues for hardened sandboxes. AvoidSANDBOX_PRIVILEGED=trueunless you explicitly need it, keep CPU and memory limits minimal, and audit any combination that re-enables privileges.SANDBOX_*settings do not apply to the Kubernetes backend in this MCP server. If you need Kubernetes-specific resource or security controls, provide a custompod_manifestvia a custom MCP wrapper or use the llm-sandbox Python API directly.
Available Tools
The MCP server provides the following tools:
execute_code: Execute code in a secure sandbox with automatic visualization captureget_supported_languages: Get the list of supported programming languagesget_language_details: Get detailed information about a specific language
Usage Example
Once configured, you can ask your AI assistant to run code, and it will automatically use the LLM Sandbox MCP server:
"Create a scatter plot showing the relationship between x and y data points using matplotlib"The assistant will execute Python code in a secure sandbox and automatically capture any generated plots or visualizations.
ποΈ Architecture
graph LR
A[LLM Client] --> B[LLM Sandbox]
B --> C[Container Backend]
A1[OpenAI] --> A
A2[Anthropic] --> A
A3[Local LLMs] --> A
A4[LangChain] --> A
A5[LangGraph] --> A
A6[LlamaIndex] --> A
A7[MCP Clients] --> A
C --> C1[Docker]
C --> C2[Kubernetes]
C --> C3[Podman]
style A fill:#e1f5fe
style B fill:#f3e5f5
style C fill:#e8f5e8
style A1 fill:#fff3e0
style A2 fill:#fff3e0
style A3 fill:#fff3e0
style A4 fill:#fff3e0
style A5 fill:#fff3e0
style A6 fill:#fff3e0
style A7 fill:#fff3e0
style C1 fill:#e0f2f1
style C2 fill:#e0f2f1
style C3 fill:#e0f2f1π Documentation
Full Documentation - Complete documentation
Getting Started - Installation and basic usage
Configuration - Detailed configuration options
Security - Security policies and best practices
Backends - Container backend details
Languages - Supported programming languages
Integrations - LLM framework integrations
API Reference - Complete API documentation
Examples - Real-world usage examples
π€ Contributing
We welcome contributions! Please see our Contributing Guide for details.
Development Setup
# Clone the repository
git clone https://github.com/vndee/llm-sandbox.git
cd llm-sandbox
# Install in development mode
make install
# Run pre-commit hooks
uv run pre-commit run -a
# Run tests
make testπ Citation
If you use LLM Sandbox in your research, please cite it:
@software{huynh_llm_sandbox,
author = {Huynh, Duy V.},
title = {{LLM Sandbox: a portable runtime for securely executing LLM-generated code}},
publisher = {Zenodo},
doi = {10.5281/zenodo.21760525},
url = {https://doi.org/10.5281/zenodo.21760525}
}The DOI above resolves to the latest release. To cite a specific version, use that release's own DOI from the Zenodo record.
π License
This project is licensed under the MIT License - see the LICENSE file for details.
π Star History
If you find LLM Sandbox useful, please consider giving it a star on GitHub!
π Support & Community
GitHub Issues: Report bugs or request features
GitHub Discussions: Join the community
Documentation: vndee.github.io/llm-sandbox
Contributors
Star History
Available Tools
3 toolsexecute_codeB
Execute code in a secure sandbox environment and automatic visualization capture.
Args:
code: The code to execute
language: Programming language (python, javascript, java, cpp, go, r, ruby)
libraries: List of libraries/packages to install
timeout: Execution timeout in seconds (default: 30)
Returns:
List of content items including execution results and any generated visualizations| Name | Required | Description | Default |
|---|---|---|---|
| code | Yes | ||
| timeout | No | ||
| language | No | python | |
| libraries | No |
Output Schema
| Name | Required | Description |
|---|---|---|
| result | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
No annotations are provided, so the description carries the full behavioral burden. It does disclose that execution runs in a 'secure sandbox' and that visualizations are captured automatically, which is useful. However, it omits statefulness (does code persist between calls?), filesystem/network access, resource or rate limits, and failure behavior for any code-execution tool where these matter.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Front-loaded one-line summary followed by a compact Args/Returns block; every line earns its place. The phrase 'and automatic visualization capture' is slightly awkward but conveys real information. No wasted sentences.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Parameter semantics are fully covered and an output schema exists, so return values needn't be spelled out (though the description helpfully does). What's missing is behavioral context that annotations would normally supply β sandbox isolation details, persistence, and limits β leaving a code-execution tool under-specified on the safety/state dimension.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 0%, so the description must compensate and largely does: it documents all four params, enumerates the accepted languages (python, javascript, java, cpp, go, r, ruby), and states the timeout default of 30 seconds. It doesn't explain the format of the libraries list beyond 'list of libraries/packages', a minor gap.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb and resource: 'Execute code in a secure sandbox environment', plus a notable secondary behavior ('automatic visualization capture'). An agent can tell it apart from get_supported_languages and get_language_details, which are clearly informational siblings. No explicit sibling naming, so not a 5.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
There is no when-to-use guidance, no prerequisites, and no mention of the sibling tools (e.g. consult get_supported_languages first to validate the language argument). Usage is only implicit from the verb 'Execute code'. Comparable to the MID calibration where absence of when-to-use scored 2.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
get_language_detailsC
Get the details of a language.
Args:
language: The language to get the details of
Returns:
TextContent: The details of the language| Name | Required | Description | Default |
|---|---|---|---|
| language | Yes |
Output Schema
| Name | Required | Description |
|---|---|---|
| text | Yes | |
| type | Yes | |
| _meta | No | |
| annotations | No |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
No annotations are provided, so the description carries the full behavioral burden. It discloses only that the result is TextContent, with no information about error handling for unsupported languages, permission needs, or how the language should be identified. This is a thin disclosure for a tool with zero annotation coverage.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is short and the purpose is front-loaded, but the Args/Returns boilerplate conveys almost no information and the parameter line is a tautology. Length is fine; content density is low.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
An output schema exists, so return values need not be explained. Even so, for a tool whose only parameter is completely undocumented and which has no annotation coverage, the description is too sparse to let an agent invoke it correctly.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 0% and the single required parameter is undocumented. The description's 'the language to get the details of' merely restates the parameter name and never specifies whether it expects a language code, name, or locale, so it fails to compensate for the schema gap.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description states a specific verb and resource ('Get the details of a language'), so the basic action is identifiable. However, it offers no differentiation from the sibling get_supported_languages, and 'details' is vague about what information is actually returned. It is a minimum-viable purpose statement.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
There is no guidance on when to use this tool versus get_supported_languages or execute_code, and no prerequisites or context are mentioned. The agent is left to infer usage entirely from the name.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
get_supported_languagesB
Get the list of supported languages.
Returns:
TextContent: The list of supported languages| Name | Required | Description | Default |
|---|---|---|---|
No parameters | |||
Output Schema
| Name | Required | Description |
|---|---|---|
| text | Yes | |
| type | Yes | |
| _meta | No | |
| annotations | No |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations provided, the description carries the full behavioral burden but discloses almost nothing beyond the name. It does not state that this is a read-only/enumeration operation, whether results are cached or paginated, or any auth requirements.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The first sentence is front-loaded and efficient. The trailing 'Returns' block restates what the output schema already provides, adding mild redundancy, but the overall text is short and does not waste much space.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Because an output schema exists, the description need not explain return values, and the tool is parameterless, so little is required. Still, it offers no differentiation from sibling tools, leaving the agent to infer the boundary between this and get_language_details.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
The tool takes zero parameters, so there is no parameter semantics to document; the baseline for a parameterless tool is 4. The description adds no misleading parameter information.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description states a specific verb and resource ('Get the list of supported languages'), so an agent immediately knows what it returns. It does not, however, distinguish itself from the sibling get_language_details, which an agent must infer is the more detailed per-language lookup.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
There is no guidance on when to use this tool versus the sibling get_language_details or execute_code, and no stated prerequisites or context. Usage is only implied by the tool name.
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.
3 tool updates
v0.1.0- First observed
execute_code - First observed
get_language_details - First observed
get_supported_languages
TDQS
Scored across 3 tools
The three tools have clearly distinct purposes: execute_code runs code, get_supported_languages lists languages, and get_language_details returns metadata for a specific language. There is no overlap that would cause misselection.
All tool names follow a consistent snake_case verb_noun pattern (execute_code, get_supported_languages, get_language_details). The convention is predictable and readable.
Three tools is slightly thin but appropriate for a focused sandbox execution server. Each tool earns its place, though two are lightweight metadata helpers around a single action.
The surface covers execution and language introspection well. Minor gaps exist, such as file input/output management or execution status/cancellation, but core workflows are supported.
Maintenance
Related MCP Connectors
MCP server for Superserve sandboxes: create, exec, and manage Firecracker microVMs
An MCP server that provides an API to LLMs to manage their JumpCloud resources.
- ArcjetOAuthcom.arcjet
An MCP server for Arcjet - the runtime security platform that ships with your AI code.
Related MCP Servers
- AlicenseNot gradedqualityCmaintenanceAn MCP server to create secure code sandbox environment for executing code within Docker containers.326MIT
- AlicenseBqualityDmaintenanceA secure MCP server for executing whitelisted shell commands with resource and timeout controls, designed for integration with Claude and other MCP-compatible LLMs.20389 npm7MIT
- FlicenseNot gradedqualityDmaintenanceAn extensible Message Communication Protocol server that provides secure code execution capabilities in isolated sandbox environments, compatible with Claude for Desktop and other MCP clients.17-
- AlicenseNot gradedqualityDmaintenanceA Model Context Protocol (MCP) server that enables LLMs to run ANY code safely in isolated Docker containers.122MIT