# Directory Structure
```
├── .gitignore
├── .python-version
├── pyproject.toml
├── README.md
├── sandbox_server.py
└── uv.lock
```
# Files
--------------------------------------------------------------------------------
/.python-version:
--------------------------------------------------------------------------------
```
3.11
```
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
```
# Python-generated files
__pycache__/
*.py[oc]
build/
dist/
wheels/
*.egg-info
# Virtual environments
.venv
```
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
```markdown
# Sandbox MCP Server
An MCP server that provides isolated Docker environments for code execution. This server allows you to:
- Create containers with any Docker image
- Write and execute code in multiple programming languages
- Install packages and set up development environments
- Run commands in isolated containers
## Prerequisites
- Python 3.9 or higher
- Docker installed and running
- uv package manager (recommended)
- Docker MCP server (recommended)
## Installation
1. Clone this repository:
```bash
git clone <your-repo-url>
cd sandbox_server
```
2. Create and activate a virtual environment with uv:
```bash
uv venv
source .venv/bin/activate # On Unix/MacOS
# Or on Windows:
# .venv\Scripts\activate
```
3. Install dependencies:
```bash
uv pip install .
```
## Integration with Claude Desktop
1. Open Claude Desktop's configuration file:
- macOS: `~/Library/Application Support/Claude/claude_desktop_config.json`
- Windows: `%APPDATA%\Claude\claude_desktop_config.json`
2. Add the sandbox server configuration:
```json
{
"mcpServers": {
"sandbox": {
"command": "uv",
"args": [
"--directory",
"/absolute/path/to/sandbox_server",
"run",
"sandbox_server.py"
],
"env": {
"PYTHONPATH": "/absolute/path/to/sandbox_server"
}
}
}
}
```
Replace `/absolute/path/to/sandbox_server` with the actual path to your project directory.
3. Restart Claude Desktop
## Usage Examples
### Basic Usage
Once connected to Claude Desktop, you can:
1. Create a Python container:
```
Could you create a Python container and write a simple hello world program?
```
2. Run code in different languages:
```
Could you create a C program that calculates the fibonacci sequence and run it?
```
3. Install packages and use them:
```
Could you create a Python script that uses numpy to generate and plot some random data?
```
### Saving and Reproducing Environments
The server provides several ways to save and reproduce your development environments:
#### Creating Persistent Containers
When creating a container, you can make it persistent:
```
Could you create a persistent Python container with numpy and pandas installed?
```
This will create a container that:
- Stays running after Claude Desktop closes
- Can be accessed directly through Docker
- Preserves all installed packages and files
The server will provide instructions for:
- Accessing the container directly (`docker exec`)
- Stopping and starting the container
- Removing it when no longer needed
#### Saving Container State
After setting up your environment, you can save it as a Docker image:
```
Could you save the current container state as an image named 'my-ds-env:v1'?
```
This will:
1. Create a new Docker image with all your:
- Installed packages
- Created files
- Configuration changes
2. Provide instructions for reusing the environment
You can then share this image or use it as a starting point for new containers:
```
Could you create a new container using the my-ds-env:v1 image?
```
#### Generating Dockerfiles
To make your environment fully reproducible, you can generate a Dockerfile:
```
Could you export a Dockerfile that recreates this environment?
```
The generated Dockerfile will include:
- Base image specification
- Created files
- Template for additional setup steps
You can use this Dockerfile to:
1. Share your environment setup with others
2. Version control your development environment
3. Modify and customize the build process
4. Deploy to different systems
#### Recommended Workflow
For reproducible development environments:
1. Create a persistent container:
```
Create a persistent Python container for data science work
```
2. Install needed packages and set up the environment:
```
Install numpy, pandas, and scikit-learn in the container
```
3. Test your setup:
```
Create and run a test script to verify the environment
```
4. Save the state:
```
Save this container as 'ds-workspace:v1'
```
5. Export a Dockerfile:
```
Generate a Dockerfile for this environment
```
This gives you multiple options for recreating your environment:
- Use the saved Docker image directly
- Build from the Dockerfile with modifications
- Access the original container if needed
## Security Notes
- All code executes in isolated Docker containers
- Containers are automatically removed after use
- File systems are isolated between containers
- Host system access is restricted
## Project Structure
```
sandbox_server/
├── sandbox_server.py # Main server implementation
├── pyproject.toml # Project configuration
└── README.md # This file
```
## Available Tools
The server provides three main tools:
1. `create_container_environment`: Creates a new Docker container with specified image
2. `create_file_in_container`: Creates a file in a container
3. `execute_command_in_container`: Runs commands in a container
4. `save_container_state`: Saves the container state to a persistent container
5. `export_dockerfile`: exports a docker file to create a persistant environment
6. `exit_container`: closes a container to cleanup environment when finished
```
--------------------------------------------------------------------------------
/pyproject.toml:
--------------------------------------------------------------------------------
```toml
[project]
name = "sandbox-server"
version = "0.1.0"
description = "MCP sandbox server for running code in containers"
dependencies = [
"mcp>=1.2.0",
"docker>=6.1.0",
]
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
```
--------------------------------------------------------------------------------
/sandbox_server.py:
--------------------------------------------------------------------------------
```python
from mcp.server.fastmcp import FastMCP
import docker
import tempfile
import os
from pathlib import Path
from typing import Dict, List, Optional
class SandboxServer:
def __init__(self):
self.mcp = FastMCP("sandbox-server")
self.docker_client = docker.from_env()
# Map of container IDs to their info
self.containers: Dict[str, dict] = {}
# Register all tools
self._register_tools()
def _register_tools(self):
@self.mcp.tool()
async def create_container_environment(image: str, persist: bool) -> str:
"""Create a new container with the specified base image.
Args:
image: Docker image to use (e.g., python:3.9-slim, ubuntu:latest)
Returns:
Container ID of the new container
"""
try:
# Create a temporary directory for file mounting
temp_dir = tempfile.mkdtemp()
# Create container with the temp directory mounted
container = self.docker_client.containers.run(
image=image,
command="tail -f /dev/null", # Keep container running
volumes={
temp_dir: {
'bind': '/workspace',
'mode': 'rw'
}
},
working_dir='/workspace',
detach=True,
remove=not persist
)
# Store container info
self.containers[container.id] = {
'temp_dir': temp_dir,
'files': {}
}
return f"""Container created with ID: {container.id}
Working directory is /workspace
Container is ready for commands"""
except docker.errors.ImageNotFound:
return f"Error: Image {image} not found. Please verify the image name."
except Exception as e:
return f"Error creating container: {str(e)}"
@self.mcp.tool()
async def create_file_in_container(container_id: str, filename: str, content: str) -> str:
"""Create a file in the specified container.
Args:
container_id: ID of the container
filename: Name of the file to create
content: Content of the file
Returns:
Status message
"""
if container_id not in self.containers:
return f"Container {container_id} not found"
try:
container_info = self.containers[container_id]
temp_dir = container_info['temp_dir']
# Create file in the mounted directory
file_path = Path(temp_dir) / filename
with open(file_path, 'w') as f:
f.write(content)
# Update container info
container_info['files'][filename] = content
return f"File {filename} has been created in /workspace"
except Exception as e:
return f"Error creating file: {str(e)}"
@self.mcp.tool()
async def execute_command_in_container(container_id: str, command: str) -> str:
"""Execute a command in the specified container.
Args:
container_id: ID of the container
command: Command to execute
Returns:
Command output
"""
try:
container = self.docker_client.containers.get(container_id)
result = container.exec_run(
command,
workdir='/workspace',
environment={
"DEBIAN_FRONTEND": "noninteractive" # For apt-get
}
)
return result.output.decode('utf-8')
except docker.errors.NotFound:
return f"Container {container_id} not found"
except Exception as e:
return f"Error executing command: {str(e)}"
@self.mcp.tool()
async def save_container_state(container_id: str, name: str) -> str:
"""Save the current state of a container as a new image.
Args:
container_id: ID of the container to save
name: Name for the saved image (e.g., 'my-python-env:v1')
Returns:
Instructions for using the saved image
"""
try:
container = self.docker_client.containers.get(container_id)
repository, tag = name.split(':') if ':' in name else (name, 'latest')
container.commit(repository=repository, tag=tag)
return f"""Environment saved as image: {name}
To use this environment later:
1. Create new container: docker run -it {name}
2. Or use with this MCP server: create_container("{name}")
The image contains all installed packages and configurations."""
except docker.errors.NotFound:
return f"Container {container_id} not found"
except Exception as e:
return f"Error saving container: {str(e)}"
@self.mcp.tool()
async def export_dockerfile(container_id: str) -> str:
"""Generate a Dockerfile that recreates the current container state.
Args:
container_id: ID of the container to export
Returns:
Dockerfile content and instructions
"""
try:
container = self.docker_client.containers.get(container_id)
# Get container info
info = container_info = self.containers.get(container_id, {})
image = container.attrs['Config']['Image']
# Get history of commands (if available)
history = []
if 'files' in info:
history.extend([f"COPY {file} /workspace/{file}" for file in info['files'].keys()])
# Create Dockerfile content
dockerfile = [
f"FROM {image}",
"WORKDIR /workspace",
*history,
'\n# Add any additional steps needed:',
'# RUN pip install <packages>',
'# COPY <src> <dest>',
'# etc.'
]
return f"""Here's a Dockerfile to recreate this environment:
{chr(10).join(dockerfile)}
To use this Dockerfile:
1. Save it to a file named 'Dockerfile'
2. Build: docker build -t your-image-name .
3. Run: docker run -it your-image-name"""
except docker.errors.NotFound:
return f"Container {container_id} not found"
except Exception as e:
return f"Error generating Dockerfile: {str(e)}"
@self.mcp.tool()
async def exit_container(container_id: str, force: bool = False) -> str:
"""Stop and remove a running container.
Args:
container_id: ID of the container to stop and remove
force: Force remove the container even if it's running
Returns:
Status message about container cleanup
"""
try:
container = self.docker_client.containers.get(container_id)
container_info = self.containers.get(container_id, {})
# Try to stop container gracefully first
if not force:
try:
container.stop(timeout=10)
except Exception as e:
return f"Failed to stop container gracefully: {str(e)}. Try using force=True if needed."
# Remove container and cleanup
container.remove(force=force)
# Clean up temp directory if it exists
if 'temp_dir' in container_info:
try:
import shutil
shutil.rmtree(container_info['temp_dir'])
except Exception as e:
print(f"Warning: Failed to remove temp directory: {str(e)}")
# Remove from our tracking
if container_id in self.containers:
del self.containers[container_id]
return f"Container {container_id} has been stopped and removed."
except docker.errors.NotFound:
return f"Container {container_id} not found"
except Exception as e:
return f"Error cleaning up container: {str(e)}"
def run(self):
"""Start the MCP server."""
self.mcp.run()
def main():
server = SandboxServer()
server.run()
if __name__ == "__main__":
main()
```