#
tokens: 3046/50000 5/5 files
lines: off (toggle) GitHub
raw markdown copy
# 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()

```