#
tokens: 18881/50000 27/27 files
lines: off (toggle) GitHub
raw markdown copy
# Directory Structure

```
├── .github
│   └── workflows
│       └── release.yml
├── .gitignore
├── build.sh
├── DEVELOPMENT.md
├── Dockerfile
├── install.ps1
├── install.sh
├── LICENSE
├── README.md
├── smithery.yaml
├── src
│   └── code-sandbox-mcp
│       ├── go.mod
│       ├── go.sum
│       ├── installer
│       │   ├── install.go
│       │   └── update.go
│       ├── main.go
│       ├── resources
│       │   └── container_logs.go
│       └── tools
│           ├── copy-file-from-container.go
│           ├── copy-file.go
│           ├── copy-project.go
│           ├── exec.go
│           ├── initialize.go
│           ├── stop-container.go
│           └── write-file.go
└── test
    ├── go
    │   ├── go.mod
    │   ├── go.sum
    │   └── test.go
    ├── python
    │   ├── main.py
    │   └── requirements.txt
    └── typescript
        ├── index.d.ts
        ├── package-lock.json
        ├── package.json
        ├── test.ts
        └── types
            └── test.d.ts
```

# Files

--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------

```
venv/
.env
bin/
.DS_Store
# Test directory
test_code/*
src/code-sandbox-mcp/vendor

```

--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------

```markdown
# Code Sandbox MCP 🐳
[![smithery badge](https://smithery.ai/badge/@Automata-Labs-team/code-sandbox-mcp)](https://smithery.ai/server/@Automata-Labs-team/code-sandbox-mcp)

A secure sandbox environment for executing code within Docker containers. This MCP server provides AI applications with a safe and isolated environment for running code while maintaining security through containerization.

## 🌟 Features

- **Flexible Container Management**: Create and manage isolated Docker containers for code execution
- **Custom Environment Support**: Use any Docker image as your execution environment
- **File Operations**: Easy file and directory transfer between host and containers
- **Command Execution**: Run any shell commands within the containerized environment
- **Real-time Logging**: Stream container logs and command output in real-time
- **Auto-Updates**: Built-in update checking and automatic binary updates
- **Multi-Platform**: Supports Linux, macOS, and Windows

## 🚀 Installation

### Prerequisites

- Docker installed and running
  - [Install Docker for Linux](https://docs.docker.com/engine/install/)
  - [Install Docker Desktop for macOS](https://docs.docker.com/desktop/install/mac/)
  - [Install Docker Desktop for Windows](https://docs.docker.com/desktop/install/windows-install/)

### Quick Install

#### Linux, MacOS
```bash
curl -fsSL https://raw.githubusercontent.com/Automata-Labs-team/code-sandbox-mcp/main/install.sh | bash
```

#### Windows
```powershell
# Run in PowerShell
irm https://raw.githubusercontent.com/Automata-Labs-team/code-sandbox-mcp/main/install.ps1 | iex
```

The installer will:
1. Check for Docker installation
2. Download the appropriate binary for your system
3. Create necessary configuration files

### Manual Installation

1. Download the latest release for your platform from the [releases page](https://github.com/Automata-Labs-team/code-sandbox-mcp/releases)
2. Place the binary in a directory in your PATH
3. Make it executable (Unix-like systems only):
   ```bash
   chmod +x code-sandbox-mcp
   ```

## 🛠️ Available Tools

#### `sandbox_initialize`
Initialize a new compute environment for code execution.
Creates a container based on the specified Docker image.

**Parameters:**
- `image` (string, optional): Docker image to use as the base environment
  - Default: 'python:3.12-slim-bookworm'

**Returns:**
- `container_id` that can be used with other tools to interact with this environment

#### `copy_project`
Copy a directory to the sandboxed filesystem.

**Parameters:**
- `container_id` (string, required): ID of the container returned from the initialize call
- `local_src_dir` (string, required): Path to a directory in the local file system
- `dest_dir` (string, optional): Path to save the src directory in the sandbox environment

#### `write_file`
Write a file to the sandboxed filesystem.

**Parameters:**
- `container_id` (string, required): ID of the container returned from the initialize call
- `file_name` (string, required): Name of the file to create
- `file_contents` (string, required): Contents to write to the file
- `dest_dir` (string, optional): Directory to create the file in (Default: ${WORKDIR})

#### `sandbox_exec`
Execute commands in the sandboxed environment.

**Parameters:**
- `container_id` (string, required): ID of the container returned from the initialize call
- `commands` (array, required): List of command(s) to run in the sandboxed environment
  - Example: ["apt-get update", "pip install numpy", "python script.py"]

#### `copy_file`
Copy a single file to the sandboxed filesystem.

**Parameters:**
- `container_id` (string, required): ID of the container returned from the initialize call
- `local_src_file` (string, required): Path to a file in the local file system
- `dest_path` (string, optional): Path to save the file in the sandbox environment

#### `sandbox_stop`
Stop and remove a running container sandbox.

**Parameters:**
- `container_id` (string, required): ID of the container to stop and remove

**Description:**
Gracefully stops the specified container with a 10-second timeout and removes it along with its volumes.

#### Container Logs Resource
A dynamic resource that provides access to container logs.

**Resource Path:** `containers://{id}/logs`  
**MIME Type:** `text/plain`  
**Description:** Returns all container logs from the specified container as a single text resource.

## 🔐 Security Features

- Isolated execution environment using Docker containers
- Resource limitations through Docker container constraints
- Separate stdout and stderr streams


## 🔧 Configuration

### Claude Desktop

The installer automatically creates the configuration file. If you need to manually configure it:

#### Linux
```json
// ~/.config/Claude/claude_desktop_config.json
{
    "mcpServers": {
        "code-sandbox-mcp": {
            "command": "/path/to/code-sandbox-mcp",
            "args": [],
            "env": {}
        }
    }
}
```

#### macOS
```json
// ~/Library/Application Support/Claude/claude_desktop_config.json
{
    "mcpServers": {
        "code-sandbox-mcp": {
            "command": "/path/to/code-sandbox-mcp",
            "args": [],
            "env": {}
        }
    }
}
```

#### Windows
```json
// %APPDATA%\Claude\claude_desktop_config.json
{
    "mcpServers": {
        "code-sandbox-mcp": {
            "command": "C:\\path\\to\\code-sandbox-mcp.exe",
            "args": [],
            "env": {}
        }
    }
}
```

### Other AI Applications

For other AI applications that support MCP servers, configure them to use the `code-sandbox-mcp` binary as their code execution backend.

## 🛠️ Development

If you want to build the project locally or contribute to its development, see [DEVELOPMENT.md](DEVELOPMENT.md).

## 📝 License

This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.

```

--------------------------------------------------------------------------------
/test/python/requirements.txt:
--------------------------------------------------------------------------------

```
numpy
```

--------------------------------------------------------------------------------
/test/typescript/types/test.d.ts:
--------------------------------------------------------------------------------

```typescript
export {};

```

--------------------------------------------------------------------------------
/test/python/main.py:
--------------------------------------------------------------------------------

```python
import numpy as np

print(np.array([1, 2, 3]))

```

--------------------------------------------------------------------------------
/test/typescript/index.d.ts:
--------------------------------------------------------------------------------

```typescript
declare module '@automatalabs/mcp-server-playwright/dist/index.js' {
    export const Tools: any[];
}
```

--------------------------------------------------------------------------------
/test/go/test.go:
--------------------------------------------------------------------------------

```go
package main

import (
	"fmt"
	"os"
	cmd "github.com/docker/cli/cli/command"
)

func main() {
	fmt.Println("Hello, World!")
	os.Exit(0)
	cmd.PrettyPrint("Hello, World!")
}

```

--------------------------------------------------------------------------------
/test/typescript/test.ts:
--------------------------------------------------------------------------------

```typescript
// Import the @automatalabs/mcp-server-playwright package
import * as mcp from "@automatalabs/mcp-server-playwright/dist/index.js";

console.log(JSON.stringify(mcp.Tools, ["name", "description"], 2));

```

--------------------------------------------------------------------------------
/test/typescript/package.json:
--------------------------------------------------------------------------------

```json
{
  "name": "test",
  "version": "1.0.0",
  "main": "test.ts",
  "scripts": {
    "test": "test.ts",
    "start": "node test.ts"
  },
  "author": "",
  "license": "ISC",
  "description": "",
  "dependencies": {
    "@automatalabs/mcp-server-playwright": "^1.2.1"
  }
}

```

--------------------------------------------------------------------------------
/smithery.yaml:
--------------------------------------------------------------------------------

```yaml
# Smithery configuration file: https://smithery.ai/docs/config#smitheryyaml

startCommand:
  type: sse
  configSchema:
    # JSON Schema defining the configuration options for the MCP.
    type: object
    required: []
    properties: {}
  commandFunction:
    # A function that produces the CLI command to start the MCP on stdio.
    |-
    (config) => ({command: '/usr/local/bin/code-sandbox-mcp', args: []})

```

--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------

```dockerfile
# Generated by https://smithery.ai. See: https://smithery.ai/docs/config#dockerfile
# Use the official Go image as a build environment
FROM golang:1.23.4-alpine3.21 AS builder

# Install necessary packages
RUN apk add --no-cache bash git make

# Set the working directory
WORKDIR /app

# Copy the source code into the container
COPY . .

# Build the application using the build script
RUN ./build.sh --release

# Use a Docker in Docker image for running the application
FROM docker:24-dind

# Set the working directory
WORKDIR /app

# Copy the built binary from the builder stage
COPY --from=builder /app/bin/code-sandbox-mcp /usr/local/bin/

# Expose any ports the application needs
EXPOSE 9520

# Run the application
ENTRYPOINT ["/bin/bash", "code-sandbox-mcp"]

```

--------------------------------------------------------------------------------
/src/code-sandbox-mcp/resources/container_logs.go:
--------------------------------------------------------------------------------

```go
package resources

import (
	"context"
	"fmt"
	"strings"

	"github.com/docker/docker/api/types/container"
	"github.com/docker/docker/pkg/stdcopy"

	"github.com/docker/docker/client"
	"github.com/mark3labs/mcp-go/mcp"
)

func GetContainerLogs(ctx context.Context, request mcp.ReadResourceRequest) ([]mcp.ResourceContents, error) {

	cli, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation())
	if err != nil {
		return nil, fmt.Errorf("failed to create Docker client: %w", err)
	}
	defer cli.Close()

	containerIDPath, found := strings.CutPrefix(request.Params.URI, "containers://") // Extract ID from the full URI
	if !found {
		return nil, fmt.Errorf("invalid URI: %s", request.Params.URI)
	}
	containerID := strings.TrimSuffix(containerIDPath, "/logs")

	// Set default ContainerLogsOptions
	logOpts := container.LogsOptions{
		ShowStdout: true,
		ShowStderr: true,
	}

	// Actually fetch the logs
	reader, err := cli.ContainerLogs(ctx, containerID, logOpts)
	if err != nil {
		return nil, fmt.Errorf("error fetching container logs: %w", err)
	}
	defer reader.Close()

	var b strings.Builder
	if _, err := stdcopy.StdCopy(&b, &b, reader); err != nil {
		return nil, fmt.Errorf("error copying container logs: %w", err)
	}

	// Combine them. You could also return them separately if you prefer.
	combined := b.String()

	return []mcp.ResourceContents{
		mcp.TextResourceContents{
			URI:      fmt.Sprintf("containers://%s/logs", containerID),
			MIMEType: "text/plain",
			Text:     combined,
		},
	}, nil
}

```

--------------------------------------------------------------------------------
/src/code-sandbox-mcp/tools/stop-container.go:
--------------------------------------------------------------------------------

```go
package tools

import (
	"context"
	"fmt"

	"github.com/docker/docker/api/types/container"
	"github.com/docker/docker/client"
	"github.com/mark3labs/mcp-go/mcp"
)

// StopContainer stops and removes a container by its ID
func StopContainer(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
	// Get the container ID from the request
	containerId, ok := request.Params.Arguments["container_id"].(string)
	if !ok || containerId == "" {
		return mcp.NewToolResultText("Error: container_id is required"), nil
	}

	// Stop and remove the container
	if err := stopAndRemoveContainer(ctx, containerId); err != nil {
		return mcp.NewToolResultText(fmt.Sprintf("Error: %v", err)), nil
	}

	return mcp.NewToolResultText(fmt.Sprintf("Successfully stopped and removed container: %s", containerId)), nil
}

// stopAndRemoveContainer stops and removes a Docker container
func stopAndRemoveContainer(ctx context.Context, containerId string) error {
	cli, err := client.NewClientWithOpts(
		client.FromEnv,
		client.WithAPIVersionNegotiation(),
	)
	if err != nil {
		return fmt.Errorf("failed to create Docker client: %w", err)
	}
	defer cli.Close()

	// Stop the container with a timeout
	timeout := 10 // seconds
	if err := cli.ContainerStop(ctx, containerId, container.StopOptions{Timeout: &timeout}); err != nil {
		return fmt.Errorf("failed to stop container: %w", err)
	}

	// Remove the container
	if err := cli.ContainerRemove(ctx, containerId, container.RemoveOptions{
		RemoveVolumes: true,
		Force:         true,
	}); err != nil {
		return fmt.Errorf("failed to remove container: %w", err)
	}

	return nil
}

```

--------------------------------------------------------------------------------
/src/code-sandbox-mcp/tools/initialize.go:
--------------------------------------------------------------------------------

```go
package tools

import (
	"context"
	"fmt"

	dockerImage "github.com/docker/docker/api/types/image"
	"github.com/docker/docker/api/types/container"
	"github.com/docker/docker/client"
	"github.com/mark3labs/mcp-go/mcp"
)

// InitializeEnvironment creates a new container for code execution
func InitializeEnvironment(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
	// Get the requested Docker image or use default
	image, ok := request.Params.Arguments["image"].(string)
	if !ok || image == "" {
		// Default to a slim debian image with Python pre-installed
		image = "python:3.12-slim-bookworm"
	}

	// Create and start the container
	containerId, err := createContainer(ctx, image)
	if err != nil {
		return mcp.NewToolResultText(fmt.Sprintf("Error: %v", err)), nil
	}

	return mcp.NewToolResultText(fmt.Sprintf("container_id: %s", containerId)), nil
}

// createContainer creates a new Docker container and returns its ID
func createContainer(ctx context.Context, image string) (string, error) {
	cli, err := client.NewClientWithOpts(
		client.FromEnv,
		client.WithAPIVersionNegotiation(),
	)
	if err != nil {
		return "", fmt.Errorf("failed to create Docker client: %w", err)
	}
	defer cli.Close()

	// Pull the Docker image if not already available
	reader, err := cli.ImagePull(ctx, image, dockerImage.PullOptions{})
	if err != nil {
		return "", fmt.Errorf("failed to pull Docker image %s: %w", image, err)
	}
	defer reader.Close()

	// Create container config with a working directory
	config := &container.Config{
		Image:      image,
		WorkingDir: "/app",
		Tty:        true,
		OpenStdin:  true,
		StdinOnce:  false,
	}

	// Create host config
	hostConfig := &container.HostConfig{
		// Add any resource constraints here if needed
	}

	// Create the container
	resp, err := cli.ContainerCreate(
		ctx,
		config,
		hostConfig,
		nil,
		nil,
		"",
	)
	if err != nil {
		return "", fmt.Errorf("failed to create container: %w", err)
	}

	// Start the container
	if err := cli.ContainerStart(ctx, resp.ID, container.StartOptions{}); err != nil {
		return "", fmt.Errorf("failed to start container: %w", err)
	}

	return resp.ID, nil
}

```

--------------------------------------------------------------------------------
/DEVELOPMENT.md:
--------------------------------------------------------------------------------

```markdown
# Development Guide 🛠️

This guide is for developers who want to build the project locally or contribute to its development.

## Prerequisites

- Go 1.21 or later
- Docker installed and running
- Git (for version information)
- Make (optional, for build automation)

## Building from Source

1. Clone the repository:
```bash
git clone https://github.com/Automata-Labs-team/code-sandbox-mcp.git
cd code-sandbox-mcp
```

2. Build the project:
```bash
# Development build
./build.sh

# Release build
./build.sh --release

# Release with specific version
./build.sh --release --version v1.0.0
```

The binaries will be available in the `bin` directory.

## Build Options

The `build.sh` script supports several options:

| Option | Description |
|--------|-------------|
| `--release` | Build in release mode with version information |
| `--version <ver>` | Specify a version number (e.g., v1.0.0) |

## Project Structure

```
code-sandbox-mcp/
├── src/
│   └── code-sandbox-mcp/
│       └── main.go       # Main application code
├── bin/                  # Compiled binaries
├── build.sh             # Build script
├── install.sh           # Unix-like systems installer
├── install.ps1          # Windows installer
├── README.md            # User documentation
└── DEVELOPMENT.md       # This file
```

## API Documentation

The project implements the MCP (Machine Code Protocol) server interface for executing code in Docker containers.

### Core Functions

- `runInDocker`: Executes single-file code in a Docker container
- `runProjectInDocker`: Runs project directories in containers
- `RegisterTool`: Registers new tool endpoints
- `NewServer`: Creates a new MCP server instance

### Tool Arguments

#### RunCodeArguments
```go
type RunCodeArguments struct {
    Code       string   `json:"code"`       // The code to run
    Language   Language `json:"language"`   // Programming language
}
```

#### RunProjectArguments
```go
type RunProjectArguments struct {
    ProjectDir string   `json:"project_dir"` // Project directory
    Language   Language `json:"language"`    // Programming language
    Entrypoint string   `json:"entrypoint"` // Command to run the project
    Background bool     `json:"background"`  // Run in background
}
```
```

--------------------------------------------------------------------------------
/src/code-sandbox-mcp/installer/install.go:
--------------------------------------------------------------------------------

```go
package installer

import (
	"encoding/json"
	"fmt"
	"os"
	"path/filepath"
	"runtime"
)

// MCPConfig represents the Claude Desktop config file structure
type MCPConfig struct {
	MCPServers map[string]MCPServer `json:"mcpServers"`
}

// MCPServer represents a single MCP server configuration
type MCPServer struct {
	Command string            `json:"command"`
	Args    []string          `json:"args"`
	Env     map[string]string `json:"env"`
}
func InstallConfig() error {
	configPath, err := getConfigPath()
	if err != nil {
		return err
	}

	// Create config directory if it doesn't exist
	configDir := filepath.Dir(configPath)
	if err := os.MkdirAll(configDir, 0755); err != nil {
		return fmt.Errorf("failed to create config directory: %w", err)
	}

	// Get the absolute path of the current executable
	execPath, err := os.Executable()
	if err != nil {
		return fmt.Errorf("failed to get executable path: %w", err)
	}
	execPath, err = filepath.Abs(execPath)
	if err != nil {
		return fmt.Errorf("failed to get absolute path: %w", err)
	}

	var config MCPConfig
	if _, err := os.Stat(configPath); err == nil {
		// Read existing config
		configData, err := os.ReadFile(configPath)
		if err != nil {
			return fmt.Errorf("failed to read config file: %w", err)
		}
		if err := json.Unmarshal(configData, &config); err != nil {
			return fmt.Errorf("failed to parse config file: %w", err)
		}
	} else {
		// Create new config
		config = MCPConfig{
			MCPServers: make(map[string]MCPServer),
		}
	}

	// Add or update our server config
	var command string
	if runtime.GOOS == "windows" {
		command = "cmd"
		config.MCPServers["code-sandbox-mcp"] = MCPServer{
			Command: command,
			Args:    []string{"/c", execPath},
			Env:     map[string]string{},
		}
	} else {
		config.MCPServers["code-sandbox-mcp"] = MCPServer{
			Command: execPath,
			Args:    []string{},
			Env:     map[string]string{},
		}
	}

	// Write the updated config
	configData, err := json.MarshalIndent(config, "", "  ")
	if err != nil {
		return fmt.Errorf("failed to marshal config: %w", err)
	}

	if err := os.WriteFile(configPath, configData, 0644); err != nil {
		return fmt.Errorf("failed to write config file: %w", err)
	}

	fmt.Printf("Added code-sandbox-mcp to %s\n", configPath)
	return nil
}

func getConfigPath() (string, error) {
	homeDir, err := os.UserHomeDir()
	if err != nil {
		return "", fmt.Errorf("failed to get user home directory: %w", err)
	}

	var configDir string
	switch runtime.GOOS {
	case "darwin":
		configDir = filepath.Join(homeDir, "Library", "Application Support", "Claude")
	case "windows":
		configDir = filepath.Join(os.Getenv("APPDATA"), "Claude")
	default: // linux and others
		configDir = filepath.Join(homeDir, ".config", "Claude")
	}

	return filepath.Join(configDir, "claude_desktop_config.json"), nil
}
```

--------------------------------------------------------------------------------
/build.sh:
--------------------------------------------------------------------------------

```bash
#!/bin/bash

# Default values
VERSION="dev"
RELEASE=false

# Parse command line arguments
while [[ "$#" -gt 0 ]]; do
    case $1 in
        --release) 
            RELEASE=true
            # If no version specified, use git tag or commit hash
            if [ "$VERSION" = "dev" ]; then
                if [ -d .git ]; then
                    VERSION=$(git describe --tags 2>/dev/null || git rev-parse --short HEAD)
                fi
            fi
            ;;
        --version) 
            VERSION="$2"
            shift 
            ;;
        *) echo "Unknown parameter: $1"; exit 1 ;;
    esac
    shift
done

# Create bin directory if it doesn't exist
mkdir -p bin

# Colors for output
GREEN='\033[0;32m'
RED='\033[0;31m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color

# Build mode banner
if [ "$RELEASE" = true ]; then
    echo -e "${BLUE}Building in RELEASE mode (version: ${VERSION})${NC}"
else
    echo -e "${BLUE}Building in DEVELOPMENT mode${NC}"
fi

# Build flags for optimization
BUILDFLAGS="-trimpath"  # Remove file system paths from binary

# Set up ldflags
LDFLAGS="-s -w"  # Strip debug information and symbol tables
if [ "$RELEASE" = true ]; then
    # Add version information for release builds
    LDFLAGS="$LDFLAGS -X 'main.Version=$VERSION' -X 'main.BuildMode=release'"
else
    LDFLAGS="$LDFLAGS -X 'main.BuildMode=development'"
fi

# Function to build for a specific platform
build_for_platform() {
    local GOOS=$1
    local GOARCH=$2
    local EXTENSION=$3
    local OUTPUT="$(pwd)/bin/code-sandbox-mcp-${GOOS}-${GOARCH}${EXTENSION}"
    
    if [ "$RELEASE" = true ]; then
        OUTPUT="$(pwd)/bin/code-sandbox-mcp-${GOOS}-${GOARCH}${EXTENSION}"
    fi

    echo -e "${GREEN}Building for ${GOOS}/${GOARCH}...${NC}"
    pushd src/code-sandbox-mcp
    GOOS=$GOOS GOARCH=$GOARCH go build -ldflags="${LDFLAGS}" ${BUILDFLAGS} -o "$OUTPUT" .
    
    if [ $? -eq 0 ]; then
        popd
        echo -e "${GREEN}✓ Successfully built:${NC} $OUTPUT"
        # Create symlink for native platform
        if [ "$GOOS" = "$(go env GOOS)" ] && [ "$GOARCH" = "$(go env GOARCH)" ]; then
            local SYMLINK="bin/code-sandbox-mcp${EXTENSION}"
            ln -sf "$(basename $OUTPUT)" "$SYMLINK"
            echo -e "${GREEN}✓ Created symlink:${NC} $SYMLINK -> $OUTPUT"
        fi
    else
        popd
        echo -e "${RED}✗ Failed to build for ${GOOS}/${GOARCH}${NC}"
        return 1
    fi
}

# Clean previous builds
echo -e "${GREEN}Cleaning previous builds...${NC}"
rm -f bin/code-sandbox-mcp*

# Build for Linux
build_for_platform linux amd64 ""
build_for_platform linux arm64 ""

# Build for macOS
build_for_platform darwin amd64 ""
build_for_platform darwin arm64 ""

# Build for Windows
build_for_platform windows amd64 ".exe"
build_for_platform windows arm64 ".exe"

echo -e "\n${GREEN}Build process completed!${NC}" 
```

--------------------------------------------------------------------------------
/install.sh:
--------------------------------------------------------------------------------

```bash
#!/bin/sh
set -e

# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m' # No Color

# Check if we're in a terminal that supports colors
if [ -t 1 ] && command -v tput >/dev/null 2>&1 && [ "$(tput colors)" -ge 8 ]; then
    HAS_COLORS=1
else
    HAS_COLORS=0
    # Reset color variables if colors are not supported
    RED=''
    GREEN=''
    YELLOW=''
    NC=''
fi

# Function to print colored output
print_status() {
    local color=$1
    local message=$2
    if [ "$HAS_COLORS" = "1" ]; then
        printf "%b%s%b\n" "$color" "$message" "$NC"
    else
        printf "%s\n" "$message"
    fi
}

# Detect OS and architecture
OS=$(uname -s | tr '[:upper:]' '[:lower:]')
ARCH=$(uname -m)

# Convert architecture to our naming scheme
case "$ARCH" in
    x86_64)  ARCH="amd64" ;;
    aarch64) ARCH="arm64" ;;
    arm64)   ARCH="arm64" ;;
    *)
        print_status "$RED" "Unsupported architecture: $ARCH"
        exit 1
        ;;
esac

# Convert OS to our naming scheme
case "$OS" in
    linux)   OS="linux" ;;
    darwin)  OS="darwin" ;;
    *)
        print_status "$RED" "Unsupported operating system: $OS"
        exit 1
        ;;
esac

# Check if Docker is installed
if ! command -v docker >/dev/null 2>&1; then
    print_status "$RED" "Error: Docker is not installed"
    print_status "$YELLOW" "Please install Docker first:"
    echo "  - For Linux: https://docs.docker.com/engine/install/"
    echo "  - For macOS: https://docs.docker.com/desktop/install/mac/"
    exit 1
fi

# Check if Docker daemon is running
if ! docker info >/dev/null 2>&1; then
    print_status "$RED" "Error: Docker daemon is not running"
    print_status "$YELLOW" "Please start Docker and try again"
    exit 1
fi

print_status "$GREEN" "Downloading latest release..."

# Get the latest release URL
LATEST_RELEASE_URL=$(curl -s https://api.github.com/repos/Automata-Labs-team/code-sandbox-mcp/releases/latest | grep "browser_download_url.*code-sandbox-mcp-$OS-$ARCH" | cut -d '"' -f 4)

if [ -z "$LATEST_RELEASE_URL" ]; then
    print_status "$RED" "Error: Could not find release for $OS-$ARCH"
    exit 1
fi

# Create installation directory
INSTALL_DIR="$HOME/.local/share/code-sandbox-mcp"
mkdir -p "$INSTALL_DIR"

# Download to a temporary file first
TEMP_FILE="$INSTALL_DIR/code-sandbox-mcp.tmp"
print_status "$GREEN" "Installing to $INSTALL_DIR/code-sandbox-mcp..."

if ! curl -L "$LATEST_RELEASE_URL" -o "$TEMP_FILE"; then
    print_status "$RED" "Error: Failed to download the binary"
    rm -f "$TEMP_FILE"
    exit 1
fi

chmod +x "$TEMP_FILE"

# Try to stop the existing process if it's running
if [ -f "$INSTALL_DIR/code-sandbox-mcp" ]; then
    pkill -f "$INSTALL_DIR/code-sandbox-mcp" >/dev/null 2>&1 || true
    sleep 1  # Give it a moment to shut down
fi

# Move the temporary file to the final location
if ! mv "$TEMP_FILE" "$INSTALL_DIR/code-sandbox-mcp"; then
    print_status "$RED" "Error: Failed to install the binary. Please ensure no instances are running and try again."
    rm -f "$TEMP_FILE"
    exit 1
fi

# Add to Claude Desktop config
print_status "$GREEN" "Adding to Claude Desktop configuration..."
"$INSTALL_DIR/code-sandbox-mcp" --install

print_status "$GREEN" "Installation complete!"
echo "You can now use code-sandbox-mcp with Claude Desktop or other AI applications." 
```

--------------------------------------------------------------------------------
/src/code-sandbox-mcp/installer/update.go:
--------------------------------------------------------------------------------

```go
package installer

import (
	"encoding/json"
	"fmt"
	"io"
	"net/http"
	"os"
	"os/exec"
	"runtime"
	"strings"
)

// Version information (set by build flags)
var (
	Version   = "dev"         // Version number (from git tag or specified)
	BuildMode = "development" // Build mode (development or release)
)

// checkForUpdate checks GitHub releases for a newer version
func CheckForUpdate() (bool, string, error) {
	resp, err := http.Get("https://api.github.com/repos/Automata-Labs-team/code-sandbox-mcp/releases/latest")
	if err != nil {
		return false, "", fmt.Errorf("failed to check for updates: %w", err)
	}
	defer resp.Body.Close()

	var release struct {
		TagName string `json:"tag_name"`
		Assets  []struct {
			Name               string `json:"name"`
			BrowserDownloadURL string `json:"browser_download_url"`
		} `json:"assets"`
	}

	if err := json.NewDecoder(resp.Body).Decode(&release); err != nil {
		return false, "", fmt.Errorf("failed to parse release info: %w", err)
	}

	// Skip update check if we're on development version
	if Version == "dev" {
		return false, "", nil
	}

	// Compare versions (assuming semver format v1.2.3)
	if release.TagName > "v"+Version {
		// Find matching asset for current OS/arch
		suffix := fmt.Sprintf("%s-%s", runtime.GOOS, runtime.GOARCH)
		if runtime.GOOS == "windows" {
			suffix += ".exe"
		}
		for _, asset := range release.Assets {
			if strings.HasSuffix(asset.Name, suffix) {
				return true, asset.BrowserDownloadURL, nil
			}
		}
	}

	return false, "", nil
}

// performUpdate downloads and replaces the current binary and restarts the process
func PerformUpdate(downloadURL string) error {
	// Get current executable path
	execPath, err := os.Executable()
	if err != nil {
		return fmt.Errorf("failed to get executable path: %w", err)
	}

	// Download new version to temporary file
	tmpFile, err := os.CreateTemp("", "code-sandbox-mcp-update-*")
	if err != nil {
		return fmt.Errorf("failed to create temp file: %w", err)
	}
	defer os.Remove(tmpFile.Name())

	resp, err := http.Get(downloadURL)
	if err != nil {
		return fmt.Errorf("failed to download update: %w", err)
	}
	defer resp.Body.Close()

	if _, err := io.Copy(tmpFile, resp.Body); err != nil {
		return fmt.Errorf("failed to write update: %w", err)
	}
	tmpFile.Close()

	// Make temporary file executable
	if runtime.GOOS != "windows" {
		if err := os.Chmod(tmpFile.Name(), 0755); err != nil {
			return fmt.Errorf("failed to make update executable: %w", err)
		}
	}

	// Replace the current executable
	// On Windows, we need to move the current executable first
	if runtime.GOOS == "windows" {
		oldPath := execPath + ".old"
		if err := os.Rename(execPath, oldPath); err != nil {
			return fmt.Errorf("failed to rename current executable: %w", err)
		}
		defer os.Remove(oldPath)
	}

	if err := os.Rename(tmpFile.Name(), execPath); err != nil {
		return fmt.Errorf("failed to replace executable: %w", err)
	}

	// Start the new version and exit the current process
	args := os.Args[1:] // Keep all arguments except the program name
	cmd := exec.Command(execPath, args...)
	cmd.Stdin = os.Stdin
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr
	if err := cmd.Start(); err != nil {
		return fmt.Errorf("failed to start new version: %w", err)
	}

	// Exit the current process
	os.Exit(0)
	return nil // Never reached, just for compiler
}
```

--------------------------------------------------------------------------------
/src/code-sandbox-mcp/tools/copy-file-from-container.go:
--------------------------------------------------------------------------------

```go
package tools

import (
	"archive/tar"
	"context"
	"fmt"
	"io"
	"os"
	"path/filepath"
	"strings"

	"github.com/docker/docker/client"
	"github.com/mark3labs/mcp-go/mcp"
)

// CopyFileFromContainer copies a single file from a container's filesystem to the local filesystem
func CopyFileFromContainer(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
	// Extract parameters
	containerID, ok := request.Params.Arguments["container_id"].(string)
	if !ok || containerID == "" {
		return mcp.NewToolResultText("container_id is required"), nil
	}

	containerSrcPath, ok := request.Params.Arguments["container_src_path"].(string)
	if !ok || containerSrcPath == "" {
		return mcp.NewToolResultText("container_src_path is required"), nil
	}

	// If container path doesn't start with /, prepend /app/
	if !strings.HasPrefix(containerSrcPath, "/") {
		containerSrcPath = filepath.Join("/app", containerSrcPath)
	}

	// Get the local destination path (optional parameter)
	localDestPath, ok := request.Params.Arguments["local_dest_path"].(string)
	if !ok || localDestPath == "" {
		// Default: use the name of the source file in current directory
		localDestPath = filepath.Base(containerSrcPath)
	}

	// Clean and create the destination directory if it doesn't exist
	localDestPath = filepath.Clean(localDestPath)
	if err := os.MkdirAll(filepath.Dir(localDestPath), 0755); err != nil {
		return mcp.NewToolResultText(fmt.Sprintf("Error creating destination directory: %v", err)), nil
	}

	// Copy the file from the container
	if err := copyFileFromContainer(ctx, containerID, containerSrcPath, localDestPath); err != nil {
		return mcp.NewToolResultText(fmt.Sprintf("Error copying file from container: %v", err)), nil
	}

	return mcp.NewToolResultText(fmt.Sprintf("Successfully copied %s from container %s to %s", containerSrcPath, containerID, localDestPath)), nil
}

// copyFileFromContainer copies a single file from the container to the local filesystem
func copyFileFromContainer(ctx context.Context, containerID string, srcPath string, destPath string) error {
	cli, err := client.NewClientWithOpts(
		client.FromEnv,
		client.WithAPIVersionNegotiation(),
	)
	if err != nil {
		return fmt.Errorf("failed to create Docker client: %w", err)
	}
	defer cli.Close()

	// Create reader for the file from container
	reader, stat, err := cli.CopyFromContainer(ctx, containerID, srcPath)
	if err != nil {
		return fmt.Errorf("failed to copy from container: %w", err)
	}
	defer reader.Close()

	// Check if the source is a directory
	if stat.Mode.IsDir() {
		return fmt.Errorf("source path is a directory, only files are supported")
	}

	// Create tar reader since Docker sends files in tar format
	tr := tar.NewReader(reader)

	// Read the first (and should be only) file from the archive
	header, err := tr.Next()
	if err != nil {
		return fmt.Errorf("failed to read tar header: %w", err)
	}

	// Verify it's a regular file
	if header.Typeflag != tar.TypeReg {
		return fmt.Errorf("source is not a regular file")
	}

	// Create the destination file
	destFile, err := os.Create(destPath)
	if err != nil {
		return fmt.Errorf("failed to create destination file: %w", err)
	}
	defer destFile.Close()

	// Copy the content
	_, err = io.Copy(destFile, tr)
	if err != nil {
		return fmt.Errorf("failed to write file content: %w", err)
	}

	// Set file permissions from tar header
	if err := os.Chmod(destPath, os.FileMode(header.Mode)); err != nil {
		return fmt.Errorf("failed to set file permissions: %w", err)
	}

	return nil
}

```

--------------------------------------------------------------------------------
/src/code-sandbox-mcp/tools/exec.go:
--------------------------------------------------------------------------------

```go
package tools

import (
	"context"
	"fmt"
	"strings"

	"github.com/docker/docker/api/types/container"
	"github.com/mark3labs/mcp-go/mcp"
	"github.com/docker/docker/client"
	"github.com/docker/docker/pkg/stdcopy"
)

// Exec executes commands in a container
func Exec(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
	// Extract parameters
	containerID, ok := request.Params.Arguments["container_id"].(string)
	if !ok || containerID == "" {
		return mcp.NewToolResultText("container_id is required"), nil
	}

	// Commands can be a single string or an array of strings
	var commands []string
	if cmdsArr, ok := request.Params.Arguments["commands"].([]interface{}); ok {
		// It's an array of commands
		for _, cmd := range cmdsArr {
			if cmdStr, ok := cmd.(string); ok {
				commands = append(commands, cmdStr)
			} else {
				return mcp.NewToolResultText("Each command must be a string"), nil
			}
		}
	} else if cmdStr, ok := request.Params.Arguments["commands"].(string); ok {
		// It's a single command string
		commands = []string{cmdStr}
	} else {
		return mcp.NewToolResultText("commands must be a string or an array of strings"), nil
	}

	if len(commands) == 0 {
		return mcp.NewToolResultText("at least one command is required"), nil
	}

	// Execute each command and collect output
	var outputBuilder strings.Builder
	for i, cmd := range commands {
		// Format the command nicely in the output
		if i > 0 {
			outputBuilder.WriteString("\n\n")
		}
		outputBuilder.WriteString(fmt.Sprintf("$ %s\n", cmd))

		// Execute the command
		stdout, stderr, exitCode, err := executeCommandWithOutput(ctx, containerID, cmd)
		if err != nil {
			return mcp.NewToolResultText(fmt.Sprintf("Error executing command: %v", err)), nil
		}

		// Add the command output to the collector
		if stdout != "" {
			outputBuilder.WriteString(stdout)
			if !strings.HasSuffix(stdout, "\n") {
				outputBuilder.WriteString("\n")
			}
		}
		if stderr != "" {
			outputBuilder.WriteString("Error: ")
			outputBuilder.WriteString(stderr)
			if !strings.HasSuffix(stderr, "\n") {
				outputBuilder.WriteString("\n")
			}
		}

		// If the command failed, add the exit code and stop processing subsequent commands
		if exitCode != 0 {
			outputBuilder.WriteString(fmt.Sprintf("Command exited with code %d\n", exitCode))
			break
		}
	}

	return mcp.NewToolResultText(outputBuilder.String()), nil
}

// executeCommandWithOutput runs a command in a container and returns its stdout, stderr, exit code, and any error
func executeCommandWithOutput(ctx context.Context, containerID string, cmd string) (stdout string, stderr string, exitCode int, err error) {
	cli, err := client.NewClientWithOpts(
		client.FromEnv,
		client.WithAPIVersionNegotiation(),
	)
	if err != nil {
		return "", "", -1, fmt.Errorf("failed to create Docker client: %w", err)
	}
	defer cli.Close()

	// Create the exec configuration
	exec, err := cli.ContainerExecCreate(ctx, containerID, container.ExecOptions{
		Cmd:          []string{"sh", "-c", cmd},
		AttachStdout: true,
		AttachStderr: true,
	})
	if err != nil {
		return "", "", -1, fmt.Errorf("failed to create exec: %w", err)
	}

	// Attach to the exec instance to get output
	resp, err := cli.ContainerExecAttach(ctx, exec.ID, container.ExecAttachOptions{})
	if err != nil {
		return "", "", -1, fmt.Errorf("failed to attach to exec: %w", err)
	}
	defer resp.Close()

	// Read the output
	var stdoutBuf, stderrBuf strings.Builder
	_, err = stdcopy.StdCopy(&stdoutBuf, &stderrBuf, resp.Reader)
	if err != nil {
		return "", "", -1, fmt.Errorf("failed to read command output: %w", err)
	}

	// Get the exit code
	inspect, err := cli.ContainerExecInspect(ctx, exec.ID)
	if err != nil {
		return "", "", -1, fmt.Errorf("failed to inspect exec: %w", err)
	}

	return stdoutBuf.String(), stderrBuf.String(), inspect.ExitCode, nil
}
```

--------------------------------------------------------------------------------
/src/code-sandbox-mcp/tools/write-file.go:
--------------------------------------------------------------------------------

```go
package tools

import (
	"context"
	"fmt"
	"io"
	"path/filepath"
	"strings"
	"time"

	"github.com/docker/docker/api/types/container"
	"github.com/docker/docker/client"
	"github.com/mark3labs/mcp-go/mcp"
)

// WriteFile writes a file to the container's filesystem
func WriteFile(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
	// Extract parameters
	containerID, ok := request.Params.Arguments["container_id"].(string)
	if !ok || containerID == "" {
		return mcp.NewToolResultText("container_id is required"), nil
	}

	fileName, ok := request.Params.Arguments["file_name"].(string)
	if !ok || fileName == "" {
		return mcp.NewToolResultText("file_name is required"), nil
	}

	fileContents, ok := request.Params.Arguments["file_contents"].(string)
	if !ok {
		return mcp.NewToolResultText("file_contents is required"), nil
	}

	// Get the destination path (optional parameter)
	destDir, ok := request.Params.Arguments["dest_dir"].(string)
	if !ok || destDir == "" {
		// Default: write to the working directory
		destDir = "/app"
	} else {
		// If provided but doesn't start with /, prepend /app/
		if !strings.HasPrefix(destDir, "/") {
			destDir = filepath.Join("/app", destDir)
		}
	}

	// Full path to the file
	fullPath := filepath.Join(destDir, fileName)

	// Create the directory if it doesn't exist
	if err := ensureDirectoryExists(ctx, containerID, destDir); err != nil {
		return mcp.NewToolResultText(fmt.Sprintf("Error creating directory: %v", err)), nil
	}

	// Write the file
	if err := writeFileToContainer(ctx, containerID, fullPath, fileContents); err != nil {
		return mcp.NewToolResultText(fmt.Sprintf("Error writing file: %v", err)), nil
	}

	return mcp.NewToolResultText(fmt.Sprintf("Successfully wrote file %s to container %s", fullPath, containerID)), nil
}

// ensureDirectoryExists creates a directory in the container if it doesn't already exist
func ensureDirectoryExists(ctx context.Context, containerID, dirPath string) error {
	cli, err := client.NewClientWithOpts(
		client.FromEnv,
		client.WithAPIVersionNegotiation(),
	)
	if err != nil {
		return fmt.Errorf("failed to create Docker client: %w", err)
	}
	defer cli.Close()

	// Create the directory if it doesn't exist
	cmd := []string{"mkdir", "-p", dirPath}
	exec, err := cli.ContainerExecCreate(ctx, containerID, container.ExecOptions{
		Cmd: cmd,
	})
	if err != nil {
		return fmt.Errorf("failed to create exec for mkdir: %w", err)
	}

	if err := cli.ContainerExecStart(ctx, exec.ID, container.ExecStartOptions{}); err != nil {
		return fmt.Errorf("failed to start exec for mkdir: %w", err)
	}

	return nil
}

// writeFileToContainer writes file contents to a file in the container
func writeFileToContainer(ctx context.Context, containerID, filePath, contents string) error {
	cli, err := client.NewClientWithOpts(
		client.FromEnv,
		client.WithAPIVersionNegotiation(),
	)
	if err != nil {
		return fmt.Errorf("failed to create Docker client: %w", err)
	}
	defer cli.Close()

	// Command to write the content to the specified file using cat
	cmd := []string{"sh", "-c", fmt.Sprintf("cat > %s", filePath)}

	// Create the exec configuration
	execConfig := container.ExecOptions{
		Cmd:          cmd,
		AttachStdin:  true,
		AttachStdout: true,
		AttachStderr: true,
	}

	// Create the exec instance
	execIDResp, err := cli.ContainerExecCreate(ctx, containerID, execConfig)
	if err != nil {
		return fmt.Errorf("failed to create exec: %w", err)
	}

	// Attach to the exec instance
	resp, err := cli.ContainerExecAttach(ctx, execIDResp.ID, container.ExecAttachOptions{})
	if err != nil {
		return fmt.Errorf("failed to attach to exec: %w", err)
	}
	defer resp.Close()

	// Write the content to the container's stdin
	_, err = io.Copy(resp.Conn, strings.NewReader(contents))
	if err != nil {
		return fmt.Errorf("failed to write content to container: %w", err)
	}
	resp.CloseWrite()

	// Wait for the command to complete
	for {
		inspect, err := cli.ContainerExecInspect(ctx, execIDResp.ID)
		if err != nil {
			return fmt.Errorf("failed to inspect exec: %w", err)
		}
		if !inspect.Running {
			if inspect.ExitCode != 0 {
				return fmt.Errorf("command exited with code %d", inspect.ExitCode)
			}
			break
		}
		// Small sleep to avoid hammering the Docker API
		time.Sleep(100 * time.Millisecond)
	}

	return nil
}

```

--------------------------------------------------------------------------------
/install.ps1:
--------------------------------------------------------------------------------

```
# Function to check if running in a terminal that supports colors
function Test-ColorSupport {
    # Check if we're in a terminal that supports VirtualTerminalLevel
    $supportsVT = $false
    try {
        $supportsVT = [Console]::IsOutputRedirected -eq $false -and 
                      [Console]::IsErrorRedirected -eq $false -and
                      [Environment]::GetEnvironmentVariable("TERM") -ne $null
    } catch {
        $supportsVT = $false
    }
    return $supportsVT
}

# Function to write colored output
function Write-ColoredMessage {
    param(
        [string]$Message,
        [System.ConsoleColor]$Color = [System.ConsoleColor]::White
    )
    
    if (Test-ColorSupport) {
        $originalColor = [Console]::ForegroundColor
        [Console]::ForegroundColor = $Color
        Write-Host $Message
        [Console]::ForegroundColor = $originalColor
    } else {
        Write-Host $Message
    }
}

# Function to stop running instances
function Stop-RunningInstances {
    param(
        [string]$ProcessName
    )
    
    try {
        $processes = Get-Process -Name $ProcessName -ErrorAction SilentlyContinue
        if ($processes) {
            $processes | ForEach-Object {
                try {
                    $_.Kill()
                    $_.WaitForExit(1000)
                } catch {
                    # Ignore errors if process already exited
                }
            }
            Start-Sleep -Seconds 1  # Give processes time to fully exit
        }
    } catch {
        # Ignore errors if no processes found
    }
}

# Check if Docker is installed
if (-not (Get-Command "docker" -ErrorAction SilentlyContinue)) {
    Write-ColoredMessage "Error: Docker is not installed" -Color Red
    Write-ColoredMessage "Please install Docker Desktop for Windows:" -Color Yellow
    Write-Host "  https://docs.docker.com/desktop/install/windows-install/"
    exit 1
}

# Check if Docker daemon is running
try {
    docker info | Out-Null
} catch {
    Write-ColoredMessage "Error: Docker daemon is not running" -Color Red
    Write-ColoredMessage "Please start Docker Desktop and try again" -Color Yellow
    exit 1
}

Write-ColoredMessage "Downloading latest release..." -Color Green

# Determine architecture
$arch = if ([Environment]::Is64BitOperatingSystem) { "amd64" } else { "386" }

# Get the latest release URL
try {
    $apiResponse = Invoke-RestMethod -Uri "https://api.github.com/repos/Automata-Labs-team/code-sandbox-mcp/releases/latest"
    $asset = $apiResponse.assets | Where-Object { $_.name -like "code-sandbox-mcp-windows-$arch.exe" }
} catch {
    Write-ColoredMessage "Error: Failed to fetch latest release information" -Color Red
    Write-Host $_.Exception.Message
    exit 1
}

if (-not $asset) {
    Write-ColoredMessage "Error: Could not find release for windows-$arch" -Color Red
    exit 1
}

# Create installation directory
$installDir = "$env:LOCALAPPDATA\code-sandbox-mcp"
New-Item -ItemType Directory -Force -Path $installDir | Out-Null

# Download to a temporary file first
$tempFile = "$installDir\code-sandbox-mcp.tmp"
Write-ColoredMessage "Installing to $installDir\code-sandbox-mcp.exe..." -Color Green

try {
    # Download the binary to temporary file
    Invoke-WebRequest -Uri $asset.browser_download_url -OutFile $tempFile

    # Stop any running instances
    Stop-RunningInstances -ProcessName "code-sandbox-mcp"

    # Try to move the temporary file to the final location
    try {
        Move-Item -Path $tempFile -Destination "$installDir\code-sandbox-mcp.exe" -Force
    } catch {
        Write-ColoredMessage "Error: Failed to install the binary. Please ensure no instances are running and try again." -Color Red
        Remove-Item -Path $tempFile -ErrorAction SilentlyContinue
        exit 1
    }
} catch {
    Write-ColoredMessage "Error: Failed to download or install the binary" -Color Red
    Write-Host $_.Exception.Message
    Remove-Item -Path $tempFile -ErrorAction SilentlyContinue
    exit 1
}

# Add to Claude Desktop config
Write-ColoredMessage "Adding to Claude Desktop configuration..." -Color Green
try {
    & "$installDir\code-sandbox-mcp.exe" --install
} catch {
    Write-ColoredMessage "Error: Failed to configure Claude Desktop" -Color Red
    Write-Host $_.Exception.Message
    exit 1
}

Write-ColoredMessage "Installation complete!" -Color Green
Write-Host "You can now use code-sandbox-mcp with Claude Desktop or other AI applications." 
```

--------------------------------------------------------------------------------
/src/code-sandbox-mcp/tools/copy-file.go:
--------------------------------------------------------------------------------

```go
package tools

import (
	"archive/tar"
	"bytes"
	"context"
	"fmt"
	"io"
	"os"
	"path/filepath"
	"strings"

	"github.com/docker/docker/api/types/container"
	"github.com/docker/docker/client"
	"github.com/mark3labs/mcp-go/mcp"
)

// CopyFile copies a single local file to a container's filesystem
func CopyFile(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
	// Extract parameters
	containerID, ok := request.Params.Arguments["container_id"].(string)
	if !ok || containerID == "" {
		return mcp.NewToolResultText("container_id is required"), nil
	}

	localSrcFile, ok := request.Params.Arguments["local_src_file"].(string)
	if !ok || localSrcFile == "" {
		return mcp.NewToolResultText("local_src_file is required"), nil
	}

	// Clean and validate the source path
	localSrcFile = filepath.Clean(localSrcFile)
	info, err := os.Stat(localSrcFile)
	if err != nil {
		return mcp.NewToolResultText(fmt.Sprintf("Error accessing source file: %v", err)), nil
	}

	if info.IsDir() {
		return mcp.NewToolResultText("local_src_file must be a file, not a directory"), nil
	}

	// Get the destination path (optional parameter)
	destPath, ok := request.Params.Arguments["dest_path"].(string)
	if !ok || destPath == "" {
		// Default: use the name of the source file
		destPath = filepath.Join("/app", filepath.Base(localSrcFile))
	} else {
		// If provided but doesn't start with /, prepend /app/
		if !strings.HasPrefix(destPath, "/") {
			destPath = filepath.Join("/app", destPath)
		}
	}

	// Create destination directory in container if it doesn't exist
	destDir := filepath.Dir(destPath)
	if err := createDirectoryInContainer(ctx, containerID, destDir); err != nil {
		return mcp.NewToolResultText(fmt.Sprintf("Error creating destination directory: %v", err)), nil
	}

	// Copy the file to the container
	if err := copyFileToContainer(ctx, containerID, localSrcFile, destPath); err != nil {
		return mcp.NewToolResultText(fmt.Sprintf("Error copying file to container: %v", err)), nil
	}

	return mcp.NewToolResultText(fmt.Sprintf("Successfully copied %s to %s in container %s", localSrcFile, destPath, containerID)), nil
}

// createDirectoryInContainer creates a directory in the container if it doesn't exist
func createDirectoryInContainer(ctx context.Context, containerID string, dirPath string) error {
	cli, err := client.NewClientWithOpts(
		client.FromEnv,
		client.WithAPIVersionNegotiation(),
	)
	if err != nil {
		return fmt.Errorf("failed to create Docker client: %w", err)
	}
	defer cli.Close()

	createDirCmd := []string{"mkdir", "-p", dirPath}
	exec, err := cli.ContainerExecCreate(ctx, containerID, container.ExecOptions{
		Cmd:          createDirCmd,
		AttachStdout: true,
		AttachStderr: true,
	})
	if err != nil {
		return fmt.Errorf("failed to create exec: %w", err)
	}

	if err := cli.ContainerExecStart(ctx, exec.ID, container.ExecStartOptions{}); err != nil {
		return fmt.Errorf("failed to start exec: %w", err)
	}

	return nil
}

// copyFileToContainer copies a single file to the container
func copyFileToContainer(ctx context.Context, containerID string, srcPath string, destPath string) error {
	cli, err := client.NewClientWithOpts(
		client.FromEnv,
		client.WithAPIVersionNegotiation(),
	)
	if err != nil {
		return fmt.Errorf("failed to create Docker client: %w", err)
	}
	defer cli.Close()

	// Open and stat the source file
	srcFile, err := os.Open(srcPath)
	if err != nil {
		return fmt.Errorf("failed to open source file: %w", err)
	}
	defer srcFile.Close()

	srcInfo, err := srcFile.Stat()
	if err != nil {
		return fmt.Errorf("failed to stat source file: %w", err)
	}

	// Create a buffer to write our archive to
	var buf bytes.Buffer

	// Create a new tar archive
	tw := tar.NewWriter(&buf)

	// Create tar header
	header := &tar.Header{
		Name:    filepath.Base(destPath),
		Size:    srcInfo.Size(),
		Mode:    int64(srcInfo.Mode()),
		ModTime: srcInfo.ModTime(),
	}

	// Write header
	if err := tw.WriteHeader(header); err != nil {
		return fmt.Errorf("failed to write tar header: %w", err)
	}

	// Copy file content to tar archive
	if _, err := io.Copy(tw, srcFile); err != nil {
		return fmt.Errorf("failed to write file content to tar: %w", err)
	}

	// Close tar writer
	if err := tw.Close(); err != nil {
		return fmt.Errorf("failed to close tar writer: %w", err)
	}

	// Copy the tar archive to the container
	err = cli.CopyToContainer(ctx, containerID, filepath.Dir(destPath), &buf, container.CopyToContainerOptions{})
	if err != nil {
		return fmt.Errorf("failed to copy to container: %w", err)
	}

	return nil
}

```

--------------------------------------------------------------------------------
/src/code-sandbox-mcp/tools/copy-project.go:
--------------------------------------------------------------------------------

```go
package tools

import (
	"archive/tar"
	"bytes"
	"context"
	"fmt"
	"io"
	"os"
	"path/filepath"
	"strings"
	"time"

	"github.com/docker/docker/api/types/container"
	"github.com/docker/docker/client"
	"github.com/mark3labs/mcp-go/mcp"
)

// CopyProject copies a local directory to a container's filesystem
func CopyProject(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
	// Extract parameters
	containerID, ok := request.Params.Arguments["container_id"].(string)
	if !ok || containerID == "" {
		return mcp.NewToolResultText("container_id is required"), nil
	}

	localSrcDir, ok := request.Params.Arguments["local_src_dir"].(string)
	if !ok || localSrcDir == "" {
		return mcp.NewToolResultText("local_src_dir is required"), nil
	}

	// Clean and validate the source path
	localSrcDir = filepath.Clean(localSrcDir)
	info, err := os.Stat(localSrcDir)
	if err != nil {
		return mcp.NewToolResultText(fmt.Sprintf("Error accessing source directory: %v", err)), nil
	}

	if !info.IsDir() {
		return mcp.NewToolResultText("local_src_dir must be a directory"), nil
	}

	// Get the destination path (optional parameter)
	destDir, ok := request.Params.Arguments["dest_dir"].(string)
	if !ok || destDir == "" {
		// Default: use the name of the source directory
		destDir = filepath.Join("/app", filepath.Base(localSrcDir))
	} else {
		// If provided but doesn't start with /, prepend /app/
		if !strings.HasPrefix(destDir, "/") {
			destDir = filepath.Join("/app", destDir)
		}
	}

	// Create tar archive of the source directory
	tarBuffer, err := createTarArchive(localSrcDir)
	if err != nil {
		return mcp.NewToolResultText(fmt.Sprintf("Error creating tar archive: %v", err)), nil
	}

	// Create a temporary file name for the tar archive in the container
	tarFileName := filepath.Join("/tmp", fmt.Sprintf("project_%s.tar", filepath.Base(localSrcDir)))

	// Copy the tar archive to the container's temp directory
	err = copyToContainer(ctx, containerID, "/tmp", tarBuffer)
	if err != nil {
		return mcp.NewToolResultText(fmt.Sprintf("Error copying to container: %v", err)), nil
	}

	// Extract the tar archive in the container
	err = extractTarInContainer(ctx, containerID, tarFileName, destDir)
	if err != nil {
		return mcp.NewToolResultText(fmt.Sprintf("Error extracting archive in container: %v", err)), nil
	}

	// Clean up the temporary tar file
	cleanupCmd := []string{"rm", tarFileName}
	if err := executeCommand(ctx, containerID, cleanupCmd); err != nil {
		// Just log the error but don't fail the operation
		fmt.Printf("Warning: Failed to clean up temporary tar file: %v\n", err)
	}

	return mcp.NewToolResultText(fmt.Sprintf("Successfully copied %s to %s in container %s", localSrcDir, destDir, containerID)), nil
}

// createTarArchive creates a tar archive of the specified source path
func createTarArchive(srcPath string) (io.Reader, error) {
	buf := new(bytes.Buffer)
	tw := tar.NewWriter(buf)
	defer tw.Close()

	srcPath = filepath.Clean(srcPath)
	baseDir := filepath.Base(srcPath)

	err := filepath.Walk(srcPath, func(file string, fi os.FileInfo, err error) error {
		if err != nil {
			return err
		}

		// Create tar header
		header, err := tar.FileInfoHeader(fi, fi.Name())
		if err != nil {
			return err
		}

		// Maintain directory structure relative to the source directory
		relPath, err := filepath.Rel(srcPath, file)
		if err != nil {
			return err
		}

		if relPath == "." {
			// Skip the root directory itself
			return nil
		}

		header.Name = filepath.Join(baseDir, relPath)

		if err := tw.WriteHeader(header); err != nil {
			return err
		}

		// If it's a regular file, write its content
		if fi.Mode().IsRegular() {
			f, err := os.Open(file)
			if err != nil {
				return err
			}
			defer f.Close()

			if _, err := io.Copy(tw, f); err != nil {
				return err
			}
		}
		return nil
	})

	if err != nil {
		return nil, err
	}

	return buf, nil
}

// copyToContainer copies a tar archive to a container
func copyToContainer(ctx context.Context, containerID string, destPath string, tarArchive io.Reader) error {
	cli, err := client.NewClientWithOpts(
		client.FromEnv,
		client.WithAPIVersionNegotiation(),
	)
	if err != nil {
		return fmt.Errorf("failed to create Docker client: %w", err)
	}
	defer cli.Close()

	// Make sure the container exists and is running
	_, err = cli.ContainerInspect(ctx, containerID)
	if err != nil {
		return fmt.Errorf("failed to inspect container: %w", err)
	}

	// Create the destination directory in the container if it doesn't exist
	createDirCmd := []string{"mkdir", "-p", destPath}
	if err := executeCommand(ctx, containerID, createDirCmd); err != nil {
		return fmt.Errorf("failed to create destination directory: %w", err)
	}

	// Copy the tar archive to the container
	err = cli.CopyToContainer(ctx, containerID, destPath, tarArchive, container.CopyToContainerOptions{})
	if err != nil {
		return fmt.Errorf("failed to copy to container: %w", err)
	}

	return nil
}

// extractTarInContainer extracts a tar archive inside the container
func extractTarInContainer(ctx context.Context, containerID string, tarFilePath string, destPath string) error {
	// Create the destination directory if it doesn't exist
	mkdirCmd := []string{"mkdir", "-p", destPath}
	if err := executeCommand(ctx, containerID, mkdirCmd); err != nil {
		return fmt.Errorf("failed to create destination directory: %w", err)
	}

	// Extract the tar archive
	extractCmd := []string{"tar", "-xf", tarFilePath, "-C", destPath}
	if err := executeCommand(ctx, containerID, extractCmd); err != nil {
		return fmt.Errorf("failed to extract tar archive: %w", err)
	}

	return nil
}

// executeCommand runs a command in a container and waits for it to complete
func executeCommand(ctx context.Context, containerID string, cmd []string) error {
	cli, err := client.NewClientWithOpts(
		client.FromEnv,
		client.WithAPIVersionNegotiation(),
	)
	if err != nil {
		return fmt.Errorf("failed to create Docker client: %w", err)
	}
	defer cli.Close()

	// Create the exec configuration
	exec, err := cli.ContainerExecCreate(ctx, containerID, container.ExecOptions{
		Cmd:          cmd,
		AttachStdout: true,
		AttachStderr: true,
	})
	if err != nil {
		return fmt.Errorf("failed to create exec: %w", err)
	}

	// Start the exec command
	if err := cli.ContainerExecStart(ctx, exec.ID, container.ExecStartOptions{}); err != nil {
		return fmt.Errorf("failed to start exec: %w", err)
	}

	// Wait for the command to complete
	for {
		inspect, err := cli.ContainerExecInspect(ctx, exec.ID)
		if err != nil {
			return fmt.Errorf("failed to inspect exec: %w", err)
		}
		if !inspect.Running {
			if inspect.ExitCode != 0 {
				return fmt.Errorf("command exited with code %d", inspect.ExitCode)
			}
			break
		}
		// Small sleep to avoid hammering the Docker API
		time.Sleep(100 * time.Millisecond)
	}

	return nil
}

```

--------------------------------------------------------------------------------
/src/code-sandbox-mcp/main.go:
--------------------------------------------------------------------------------

```go
package main

import (
	"context"
	"flag"
	"fmt"
	"log"
	"os"

	"github.com/Automata-Labs-team/code-sandbox-mcp/installer"
	"github.com/Automata-Labs-team/code-sandbox-mcp/resources"
	"github.com/Automata-Labs-team/code-sandbox-mcp/tools"
	"github.com/mark3labs/mcp-go/mcp"
	"github.com/mark3labs/mcp-go/server"
)

func init() {
	// Check for --install flag
	installFlag := flag.Bool("install", false, "Add this binary to Claude Desktop config")
	noUpdateFlag := flag.Bool("no-update", false, "Disable auto-update check")
	flag.Parse()

	if *installFlag {
		if err := installer.InstallConfig(); err != nil {
			fmt.Fprintf(os.Stderr, "Error: %v\n", err)
			os.Exit(1)
		}
		os.Exit(0)
	}

	// Check for updates unless disabled
	if !*noUpdateFlag {
		if hasUpdate, downloadURL, err := installer.CheckForUpdate(); err != nil {
			fmt.Fprintf(os.Stderr, "Warning: Failed to check for updates: %v\n", err)
			os.Exit(1)
		} else if hasUpdate {
			fmt.Println("Updating to new version...")
			if err := installer.PerformUpdate(downloadURL); err != nil {
				fmt.Fprintf(os.Stderr, "Warning: Failed to update: %v\n", err)
			}
			fmt.Println("Update complete. Restarting...")
		}
	}
}

func main() {
	port := flag.String("port", "9520", "Port to listen on")
	transport := flag.String("transport", "stdio", "Transport to use (stdio, sse)")
	flag.Parse()
	s := server.NewMCPServer("code-sandbox-mcp", "v1.0.0", server.WithLogging(), server.WithResourceCapabilities(true, true), server.WithPromptCapabilities(false))
	s.AddNotificationHandler("notifications/error", handleNotification)
	// Register tools
	// Initialize a new compute environment for code execution
	initializeTool := mcp.NewTool("sandbox_initialize",
		mcp.WithDescription(
			"Initialize a new compute environment for code execution. \n"+
				"Creates a container based on the specified Docker image or defaults to a slim debian image with Python. \n"+
				"Returns a container_id that can be used with other tools to interact with this environment.",
		),
		mcp.WithString("image",
			mcp.Description("Docker image to use as the base environment (e.g., 'python:3.12-slim-bookworm')"),
			mcp.DefaultString("python:3.12-slim-bookworm"),
		),
	)

	// Copy a directory to the sandboxed filesystem
	copyProjectTool := mcp.NewTool("copy_project",
		mcp.WithDescription(
			"Copy a directory to the sandboxed filesystem. \n"+
				"Transfers a local directory and its contents to the specified container.",
		),
		mcp.WithString("container_id",
			mcp.Required(),
			mcp.Description("ID of the container returned from the initialize call"),
		),
		mcp.WithString("local_src_dir",
			mcp.Required(),
			mcp.Description("Path to a directory in the local file system"),
		),
		mcp.WithString("dest_dir",
			mcp.Description("Path to save the src directory in the sandbox environment, relative to the container working dir"),
		),
	)

	// Write a file to the sandboxed filesystem
	writeFileTool := mcp.NewTool("write_file_sandbox",
		mcp.WithDescription(
			"Write a file to the sandboxed filesystem. \n"+
				"Creates a file with the specified content in the container.",
		),
		mcp.WithString("container_id",
			mcp.Required(),
			mcp.Description("ID of the container returned from the initialize call"),
		),
		mcp.WithString("file_name",
			mcp.Required(),
			mcp.Description("Name of the file to create"),
		),
		mcp.WithString("file_contents",
			mcp.Required(),
			mcp.Description("Contents to write to the file"),
		),
		mcp.WithString("dest_dir",
			mcp.Description("Directory to create the file in, relative to the container working dir"),
			mcp.Description("Default: ${WORKDIR}"),
		),
	)

	// Execute commands in the sandboxed environment
	execTool := mcp.NewTool("sandbox_exec",
		mcp.WithDescription(
			"Execute commands in the sandboxed environment. \n"+
				"Runs one or more shell commands in the specified container and returns the output.",
		),
		mcp.WithString("container_id",
			mcp.Required(),
			mcp.Description("ID of the container returned from the initialize call"),
		),
		mcp.WithArray("commands",
			mcp.Required(),
			mcp.Description("List of command(s) to run in the sandboxed environment"),
			mcp.Description("Example: [\"apt-get update\", \"pip install numpy\", \"python script.py\"]"),
		),
	)

	// Copy a single file to the sandboxed filesystem
	copyFileTool := mcp.NewTool("copy_file",
		mcp.WithDescription(
			"Copy a single file to the sandboxed filesystem. \n"+
				"Transfers a local file to the specified container.",
		),
		mcp.WithString("container_id",
			mcp.Required(),
			mcp.Description("ID of the container returned from the initialize call"),
		),
		mcp.WithString("local_src_file",
			mcp.Required(),
			mcp.Description("Path to a file in the local file system"),
		),
		mcp.WithString("dest_path",
			mcp.Description("Path to save the file in the sandbox environment, relative to the container working dir"),
		),
	)

	// Copy a file from container to local filesystem
	copyFileFromContainerTool := mcp.NewTool("copy_file_from_sandbox",
		mcp.WithDescription(
			"Copy a single file from the sandboxed filesystem to the local filesystem. \n"+
				"Transfers a file from the specified container to the local system.",
		),
		mcp.WithString("container_id",
			mcp.Required(),
			mcp.Description("ID of the container to copy from"),
		),
		mcp.WithString("container_src_path",
			mcp.Required(),
			mcp.Description("Path to the file in the container to copy"),
		),
		mcp.WithString("local_dest_path",
			mcp.Description("Path where to save the file in the local filesystem"),
			mcp.Description("Default: Current directory with the same filename"),
		),
	)

	// Stop and remove a container
	stopContainerTool := mcp.NewTool("sandbox_stop",
		mcp.WithDescription(
			"Stop and remove a running container sandbox. \n"+
				"Gracefully stops the specified container and removes it along with its volumes.",
		),
		mcp.WithString("container_id",
			mcp.Required(),
			mcp.Description("ID of the container to stop and remove"),
		),
	)

	// Register dynamic resource for container logs
	// Dynamic resource example - Container Logs by ID
	containerLogsTemplate := mcp.NewResourceTemplate(
		"containers://{id}/logs",
		"Container Logs",
		mcp.WithTemplateDescription("Returns all container logs from the specified container. Logs are returned as a single text resource."),
		mcp.WithTemplateMIMEType("text/plain"),
		mcp.WithTemplateAnnotations([]mcp.Role{mcp.RoleAssistant, mcp.RoleUser}, 0.5),
	)

	s.AddResourceTemplate(containerLogsTemplate, resources.GetContainerLogs)
	s.AddTool(initializeTool, tools.InitializeEnvironment)
	s.AddTool(copyProjectTool, tools.CopyProject)
	s.AddTool(writeFileTool, tools.WriteFile)
	s.AddTool(execTool, tools.Exec)
	s.AddTool(copyFileTool, tools.CopyFile)
	s.AddTool(copyFileFromContainerTool, tools.CopyFileFromContainer)
	s.AddTool(stopContainerTool, tools.StopContainer)
	switch *transport {
	case "stdio":
		if err := server.ServeStdio(s); err != nil {
			s.SendNotificationToClient(context.Background(), "notifications/error", map[string]interface{}{
				"message": fmt.Sprintf("Failed to start stdio server: %v", err),
			})
		}
	case "sse":
		sseServer := server.NewSSEServer(s)
		if err := sseServer.Start(fmt.Sprintf(":%s", *port)); err != nil {
			s.SendNotificationToClient(context.Background(), "notifications/error", map[string]interface{}{
				"message": fmt.Sprintf("Failed to start SSE server: %v", err),
			})
		}
	default:
		s.SendNotificationToClient(context.Background(), "notifications/error", map[string]interface{}{
			"message": fmt.Sprintf("Invalid transport: %s", *transport),
		})
	}
}

func handleNotification(
	ctx context.Context,
	notification mcp.JSONRPCNotification,
) {
	log.Printf("Received notification from client: %s", notification.Method)
}

```

--------------------------------------------------------------------------------
/.github/workflows/release.yml:
--------------------------------------------------------------------------------

```yaml
name: Release

on:
  push:
    branches:
      - main
  workflow_dispatch:
    inputs:
      version_increment:
        description: 'Version increment type'
        required: true
        default: 'patch'
        type: choice
        options:
          - patch
          - minor
          - major
      prerelease:
        description: 'Mark as prerelease'
        required: true
        default: false
        type: boolean

jobs:
  release:
    runs-on: ubuntu-latest
    permissions:
      contents: write
    steps:
      - name: Checkout code
        uses: actions/checkout@v4
        with:
          fetch-depth: 0

      - name: Set up Go
        uses: actions/setup-go@v4
        with:
          go-version: '1.21'
          cache: true

      - name: Get version and generate changelog
        id: get_version
        run: |
          # Get the latest tag or use v0.0.0 if no tags exist
          LATEST_TAG=$(git describe --tags --abbrev=0 2>/dev/null || echo "v0.0.0")
          
          # Remove 'v' prefix for version calculations
          VERSION=${LATEST_TAG#v}
          MAJOR=$(echo $VERSION | cut -d. -f1)
          MINOR=$(echo $VERSION | cut -d. -f2)
          PATCH=$(echo $VERSION | cut -d. -f3)
          
          # Handle version increment based on input or default to patch
          if [[ "${{ github.event_name }}" == "workflow_dispatch" ]]; then
            case "${{ inputs.version_increment }}" in
              "major")
                MAJOR=$((MAJOR + 1))
                MINOR=0
                PATCH=0
                ;;
              "minor")
                MINOR=$((MINOR + 1))
                PATCH=0
                ;;
              "patch")
                PATCH=$((PATCH + 1))
                ;;
            esac
          else
            # Auto increment patch version for push events
            PATCH=$((PATCH + 1))
          fi
          
          NEW_VERSION="${MAJOR}.${MINOR}.${PATCH}"
          echo "version=$NEW_VERSION" >> $GITHUB_OUTPUT
          
          # Function to extract and format issue/PR references
          format_references() {
            local msg="$1"
            # Look for common issue/PR reference patterns (#123, GH-123, fixes #123, etc.)
            local refs=$(echo "$msg" | grep -o -E '(#[0-9]+|GH-[0-9]+)' || true)
            if [ ! -z "$refs" ]; then
              local formatted_refs=""
              while read -r ref; do
                # Remove any prefix and get just the number
                local num=$(echo "$ref" | grep -o '[0-9]\+')
                formatted_refs="$formatted_refs [${ref}](${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/issues/${num})"
              done <<< "$refs"
              echo "$formatted_refs"
            fi
          }

          # Function to format commit messages by type
          format_commits() {
            local pattern=$1
            local title=$2
            # Include author, date, and full commit info
            local commits=$(git log --pretty=format:"- %s ([%h](${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/commit/%H)) - %an, %as" ${LATEST_TAG}..HEAD | grep -E "^$pattern" || true)
            if [ ! -z "$commits" ]; then
              echo "### $title"
              while IFS= read -r commit; do
                if [ ! -z "$commit" ]; then
                  # Extract the commit message for issue/PR reference search
                  local commit_msg=$(echo "$commit" | sed -E 's/^- ([^(]+).*/\1/')
                  local refs=$(format_references "$commit_msg")
                  if [ ! -z "$refs" ]; then
                    # Add references to the end of the commit line
                    echo "$commit - References: $refs"
                  else
                    echo "$commit"
                  fi
                fi
              done <<< "$commits" | sed 's/^[^:]*: //'
              echo ""
            fi
          }
          
          # Generate categorized changelog
          if [ "$LATEST_TAG" != "v0.0.0" ]; then
            CHANGES=$(
              {
                echo "## 📋 Changelog"
                echo "$(git log -1 --pretty=format:"Generated on: %ad" --date=format:"%Y-%m-%d %H:%M:%S %Z")"
                echo ""
                format_commits "feat(\w*)?:" "🚀 New Features"
                format_commits "fix(\w*)?:" "🐛 Bug Fixes"
                format_commits "perf(\w*)?:" "⚡ Performance Improvements"
                format_commits "refactor(\w*)?:" "♻️ Refactoring"
                format_commits "test(\w*)?:" "🧪 Testing"
                format_commits "docs(\w*)?:" "📚 Documentation"
                format_commits "style(\w*)?:" "💎 Styling"
                format_commits "chore(\w*)?:" "🔧 Maintenance"
                
                # Get other commits that don't match conventional commit format
                echo "### 🔍 Other Changes"
                git log --pretty=format:"- %s ([%h](${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/commit/%H)) - %an, %as" ${LATEST_TAG}..HEAD | grep -vE "^(feat|fix|perf|refactor|test|docs|style|chore)(\w*)?:" | while IFS= read -r commit; do
                  if [ ! -z "$commit" ]; then
                    local commit_msg=$(echo "$commit" | sed -E 's/^- ([^(]+).*/\1/')
                    local refs=$(format_references "$commit_msg")
                    if [ ! -z "$refs" ]; then
                      echo "$commit - References: $refs"
                    else
                      echo "$commit"
                    fi
                  fi
                done || true
              } | sed '/^$/d'
            )
          else
            # For first release, include all commits with metadata and links
            CHANGES=$(
              {
                echo "## 📋 Initial Release Changelog"
                echo "$(git log -1 --pretty=format:"Generated on: %ad" --date=format:"%Y-%m-%d %H:%M:%S %Z")"
                echo ""
                git log --pretty=format:"- %s ([%h](${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/commit/%H)) - %an, %as" | while IFS= read -r commit; do
                  if [ ! -z "$commit" ]; then
                    local commit_msg=$(echo "$commit" | sed -E 's/^- ([^(]+).*/\1/')
                    local refs=$(format_references "$commit_msg")
                    if [ ! -z "$refs" ]; then
                      echo "$commit - References: $refs"
                    else
                      echo "$commit"
                    fi
                  fi
                done
              }
            )
          fi
          
          # Save changes to output
          echo "changes<<EOF" >> $GITHUB_OUTPUT
          echo "$CHANGES" >> $GITHUB_OUTPUT
          echo "EOF" >> $GITHUB_OUTPUT

      - name: Build binaries
        run: |
          chmod +x build.sh
          ./build.sh --release --version ${{ steps.get_version.outputs.version }}

      - name: Generate checksums
        run: |
          cd bin
          echo "### 🔒 SHA256 Checksums" > checksums.txt
          echo '```' >> checksums.txt
          sha256sum code-sandbox-mcp-* >> checksums.txt
          echo '```' >> checksums.txt

      - name: Create Release
        id: create_release
        uses: softprops/action-gh-release@v1
        with:
          tag_name: v${{ steps.get_version.outputs.version }}
          name: Release v${{ steps.get_version.outputs.version }}
          draft: false
          prerelease: ${{ github.event.inputs.prerelease == 'true' }}
          files: |
            bin/code-sandbox-mcp-linux-amd64
            bin/code-sandbox-mcp-linux-arm64
            bin/code-sandbox-mcp-darwin-amd64
            bin/code-sandbox-mcp-darwin-arm64
            bin/code-sandbox-mcp-windows-amd64.exe
            bin/code-sandbox-mcp-windows-arm64.exe
          body: |
            ## 🎉 Release v${{ steps.get_version.outputs.version }}
            
            ${{ steps.get_version.outputs.changes }}
            
            ### 📦 Included Binaries
            - 🐧 Linux (amd64, arm64)
            - 🍎 macOS (amd64, arm64)
            - 🪟 Windows (amd64, arm64)
            
            $(cat bin/checksums.txt) 
```