#
tokens: 26078/50000 27/27 files
lines: on (toggle) GitHub
raw markdown copy reset
# 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:
--------------------------------------------------------------------------------

```
1 | venv/
2 | .env
3 | bin/
4 | .DS_Store
5 | # Test directory
6 | test_code/*
7 | src/code-sandbox-mcp/vendor
8 | 
```

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

```markdown
  1 | # Code Sandbox MCP 🐳
  2 | [![smithery badge](https://smithery.ai/badge/@Automata-Labs-team/code-sandbox-mcp)](https://smithery.ai/server/@Automata-Labs-team/code-sandbox-mcp)
  3 | 
  4 | 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.
  5 | 
  6 | ## 🌟 Features
  7 | 
  8 | - **Flexible Container Management**: Create and manage isolated Docker containers for code execution
  9 | - **Custom Environment Support**: Use any Docker image as your execution environment
 10 | - **File Operations**: Easy file and directory transfer between host and containers
 11 | - **Command Execution**: Run any shell commands within the containerized environment
 12 | - **Real-time Logging**: Stream container logs and command output in real-time
 13 | - **Auto-Updates**: Built-in update checking and automatic binary updates
 14 | - **Multi-Platform**: Supports Linux, macOS, and Windows
 15 | 
 16 | ## 🚀 Installation
 17 | 
 18 | ### Prerequisites
 19 | 
 20 | - Docker installed and running
 21 |   - [Install Docker for Linux](https://docs.docker.com/engine/install/)
 22 |   - [Install Docker Desktop for macOS](https://docs.docker.com/desktop/install/mac/)
 23 |   - [Install Docker Desktop for Windows](https://docs.docker.com/desktop/install/windows-install/)
 24 | 
 25 | ### Quick Install
 26 | 
 27 | #### Linux, MacOS
 28 | ```bash
 29 | curl -fsSL https://raw.githubusercontent.com/Automata-Labs-team/code-sandbox-mcp/main/install.sh | bash
 30 | ```
 31 | 
 32 | #### Windows
 33 | ```powershell
 34 | # Run in PowerShell
 35 | irm https://raw.githubusercontent.com/Automata-Labs-team/code-sandbox-mcp/main/install.ps1 | iex
 36 | ```
 37 | 
 38 | The installer will:
 39 | 1. Check for Docker installation
 40 | 2. Download the appropriate binary for your system
 41 | 3. Create necessary configuration files
 42 | 
 43 | ### Manual Installation
 44 | 
 45 | 1. Download the latest release for your platform from the [releases page](https://github.com/Automata-Labs-team/code-sandbox-mcp/releases)
 46 | 2. Place the binary in a directory in your PATH
 47 | 3. Make it executable (Unix-like systems only):
 48 |    ```bash
 49 |    chmod +x code-sandbox-mcp
 50 |    ```
 51 | 
 52 | ## 🛠️ Available Tools
 53 | 
 54 | #### `sandbox_initialize`
 55 | Initialize a new compute environment for code execution.
 56 | Creates a container based on the specified Docker image.
 57 | 
 58 | **Parameters:**
 59 | - `image` (string, optional): Docker image to use as the base environment
 60 |   - Default: 'python:3.12-slim-bookworm'
 61 | 
 62 | **Returns:**
 63 | - `container_id` that can be used with other tools to interact with this environment
 64 | 
 65 | #### `copy_project`
 66 | Copy a directory to the sandboxed filesystem.
 67 | 
 68 | **Parameters:**
 69 | - `container_id` (string, required): ID of the container returned from the initialize call
 70 | - `local_src_dir` (string, required): Path to a directory in the local file system
 71 | - `dest_dir` (string, optional): Path to save the src directory in the sandbox environment
 72 | 
 73 | #### `write_file`
 74 | Write a file to the sandboxed filesystem.
 75 | 
 76 | **Parameters:**
 77 | - `container_id` (string, required): ID of the container returned from the initialize call
 78 | - `file_name` (string, required): Name of the file to create
 79 | - `file_contents` (string, required): Contents to write to the file
 80 | - `dest_dir` (string, optional): Directory to create the file in (Default: ${WORKDIR})
 81 | 
 82 | #### `sandbox_exec`
 83 | Execute commands in the sandboxed environment.
 84 | 
 85 | **Parameters:**
 86 | - `container_id` (string, required): ID of the container returned from the initialize call
 87 | - `commands` (array, required): List of command(s) to run in the sandboxed environment
 88 |   - Example: ["apt-get update", "pip install numpy", "python script.py"]
 89 | 
 90 | #### `copy_file`
 91 | Copy a single file to the sandboxed filesystem.
 92 | 
 93 | **Parameters:**
 94 | - `container_id` (string, required): ID of the container returned from the initialize call
 95 | - `local_src_file` (string, required): Path to a file in the local file system
 96 | - `dest_path` (string, optional): Path to save the file in the sandbox environment
 97 | 
 98 | #### `sandbox_stop`
 99 | Stop and remove a running container sandbox.
100 | 
101 | **Parameters:**
102 | - `container_id` (string, required): ID of the container to stop and remove
103 | 
104 | **Description:**
105 | Gracefully stops the specified container with a 10-second timeout and removes it along with its volumes.
106 | 
107 | #### Container Logs Resource
108 | A dynamic resource that provides access to container logs.
109 | 
110 | **Resource Path:** `containers://{id}/logs`  
111 | **MIME Type:** `text/plain`  
112 | **Description:** Returns all container logs from the specified container as a single text resource.
113 | 
114 | ## 🔐 Security Features
115 | 
116 | - Isolated execution environment using Docker containers
117 | - Resource limitations through Docker container constraints
118 | - Separate stdout and stderr streams
119 | 
120 | 
121 | ## 🔧 Configuration
122 | 
123 | ### Claude Desktop
124 | 
125 | The installer automatically creates the configuration file. If you need to manually configure it:
126 | 
127 | #### Linux
128 | ```json
129 | // ~/.config/Claude/claude_desktop_config.json
130 | {
131 |     "mcpServers": {
132 |         "code-sandbox-mcp": {
133 |             "command": "/path/to/code-sandbox-mcp",
134 |             "args": [],
135 |             "env": {}
136 |         }
137 |     }
138 | }
139 | ```
140 | 
141 | #### macOS
142 | ```json
143 | // ~/Library/Application Support/Claude/claude_desktop_config.json
144 | {
145 |     "mcpServers": {
146 |         "code-sandbox-mcp": {
147 |             "command": "/path/to/code-sandbox-mcp",
148 |             "args": [],
149 |             "env": {}
150 |         }
151 |     }
152 | }
153 | ```
154 | 
155 | #### Windows
156 | ```json
157 | // %APPDATA%\Claude\claude_desktop_config.json
158 | {
159 |     "mcpServers": {
160 |         "code-sandbox-mcp": {
161 |             "command": "C:\\path\\to\\code-sandbox-mcp.exe",
162 |             "args": [],
163 |             "env": {}
164 |         }
165 |     }
166 | }
167 | ```
168 | 
169 | ### Other AI Applications
170 | 
171 | For other AI applications that support MCP servers, configure them to use the `code-sandbox-mcp` binary as their code execution backend.
172 | 
173 | ## 🛠️ Development
174 | 
175 | If you want to build the project locally or contribute to its development, see [DEVELOPMENT.md](DEVELOPMENT.md).
176 | 
177 | ## 📝 License
178 | 
179 | This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
180 | 
```

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

```
1 | numpy
```

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

```typescript
1 | export {};
2 | 
```

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

```python
1 | import numpy as np
2 | 
3 | print(np.array([1, 2, 3]))
4 | 
```

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

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

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

```go
 1 | package main
 2 | 
 3 | import (
 4 | 	"fmt"
 5 | 	"os"
 6 | 	cmd "github.com/docker/cli/cli/command"
 7 | )
 8 | 
 9 | func main() {
10 | 	fmt.Println("Hello, World!")
11 | 	os.Exit(0)
12 | 	cmd.PrettyPrint("Hello, World!")
13 | }
14 | 
```

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

```typescript
1 | // Import the @automatalabs/mcp-server-playwright package
2 | import * as mcp from "@automatalabs/mcp-server-playwright/dist/index.js";
3 | 
4 | console.log(JSON.stringify(mcp.Tools, ["name", "description"], 2));
5 | 
```

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

```json
 1 | {
 2 |   "name": "test",
 3 |   "version": "1.0.0",
 4 |   "main": "test.ts",
 5 |   "scripts": {
 6 |     "test": "test.ts",
 7 |     "start": "node test.ts"
 8 |   },
 9 |   "author": "",
10 |   "license": "ISC",
11 |   "description": "",
12 |   "dependencies": {
13 |     "@automatalabs/mcp-server-playwright": "^1.2.1"
14 |   }
15 | }
16 | 
```

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

```yaml
 1 | # Smithery configuration file: https://smithery.ai/docs/config#smitheryyaml
 2 | 
 3 | startCommand:
 4 |   type: sse
 5 |   configSchema:
 6 |     # JSON Schema defining the configuration options for the MCP.
 7 |     type: object
 8 |     required: []
 9 |     properties: {}
10 |   commandFunction:
11 |     # A function that produces the CLI command to start the MCP on stdio.
12 |     |-
13 |     (config) => ({command: '/usr/local/bin/code-sandbox-mcp', args: []})
14 | 
```

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

```dockerfile
 1 | # Generated by https://smithery.ai. See: https://smithery.ai/docs/config#dockerfile
 2 | # Use the official Go image as a build environment
 3 | FROM golang:1.23.4-alpine3.21 AS builder
 4 | 
 5 | # Install necessary packages
 6 | RUN apk add --no-cache bash git make
 7 | 
 8 | # Set the working directory
 9 | WORKDIR /app
10 | 
11 | # Copy the source code into the container
12 | COPY . .
13 | 
14 | # Build the application using the build script
15 | RUN ./build.sh --release
16 | 
17 | # Use a Docker in Docker image for running the application
18 | FROM docker:24-dind
19 | 
20 | # Set the working directory
21 | WORKDIR /app
22 | 
23 | # Copy the built binary from the builder stage
24 | COPY --from=builder /app/bin/code-sandbox-mcp /usr/local/bin/
25 | 
26 | # Expose any ports the application needs
27 | EXPOSE 9520
28 | 
29 | # Run the application
30 | ENTRYPOINT ["/bin/bash", "code-sandbox-mcp"]
31 | 
```

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

```go
 1 | package resources
 2 | 
 3 | import (
 4 | 	"context"
 5 | 	"fmt"
 6 | 	"strings"
 7 | 
 8 | 	"github.com/docker/docker/api/types/container"
 9 | 	"github.com/docker/docker/pkg/stdcopy"
10 | 
11 | 	"github.com/docker/docker/client"
12 | 	"github.com/mark3labs/mcp-go/mcp"
13 | )
14 | 
15 | func GetContainerLogs(ctx context.Context, request mcp.ReadResourceRequest) ([]mcp.ResourceContents, error) {
16 | 
17 | 	cli, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation())
18 | 	if err != nil {
19 | 		return nil, fmt.Errorf("failed to create Docker client: %w", err)
20 | 	}
21 | 	defer cli.Close()
22 | 
23 | 	containerIDPath, found := strings.CutPrefix(request.Params.URI, "containers://") // Extract ID from the full URI
24 | 	if !found {
25 | 		return nil, fmt.Errorf("invalid URI: %s", request.Params.URI)
26 | 	}
27 | 	containerID := strings.TrimSuffix(containerIDPath, "/logs")
28 | 
29 | 	// Set default ContainerLogsOptions
30 | 	logOpts := container.LogsOptions{
31 | 		ShowStdout: true,
32 | 		ShowStderr: true,
33 | 	}
34 | 
35 | 	// Actually fetch the logs
36 | 	reader, err := cli.ContainerLogs(ctx, containerID, logOpts)
37 | 	if err != nil {
38 | 		return nil, fmt.Errorf("error fetching container logs: %w", err)
39 | 	}
40 | 	defer reader.Close()
41 | 
42 | 	var b strings.Builder
43 | 	if _, err := stdcopy.StdCopy(&b, &b, reader); err != nil {
44 | 		return nil, fmt.Errorf("error copying container logs: %w", err)
45 | 	}
46 | 
47 | 	// Combine them. You could also return them separately if you prefer.
48 | 	combined := b.String()
49 | 
50 | 	return []mcp.ResourceContents{
51 | 		mcp.TextResourceContents{
52 | 			URI:      fmt.Sprintf("containers://%s/logs", containerID),
53 | 			MIMEType: "text/plain",
54 | 			Text:     combined,
55 | 		},
56 | 	}, nil
57 | }
58 | 
```

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

```go
 1 | package tools
 2 | 
 3 | import (
 4 | 	"context"
 5 | 	"fmt"
 6 | 
 7 | 	"github.com/docker/docker/api/types/container"
 8 | 	"github.com/docker/docker/client"
 9 | 	"github.com/mark3labs/mcp-go/mcp"
10 | )
11 | 
12 | // StopContainer stops and removes a container by its ID
13 | func StopContainer(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
14 | 	// Get the container ID from the request
15 | 	containerId, ok := request.Params.Arguments["container_id"].(string)
16 | 	if !ok || containerId == "" {
17 | 		return mcp.NewToolResultText("Error: container_id is required"), nil
18 | 	}
19 | 
20 | 	// Stop and remove the container
21 | 	if err := stopAndRemoveContainer(ctx, containerId); err != nil {
22 | 		return mcp.NewToolResultText(fmt.Sprintf("Error: %v", err)), nil
23 | 	}
24 | 
25 | 	return mcp.NewToolResultText(fmt.Sprintf("Successfully stopped and removed container: %s", containerId)), nil
26 | }
27 | 
28 | // stopAndRemoveContainer stops and removes a Docker container
29 | func stopAndRemoveContainer(ctx context.Context, containerId string) error {
30 | 	cli, err := client.NewClientWithOpts(
31 | 		client.FromEnv,
32 | 		client.WithAPIVersionNegotiation(),
33 | 	)
34 | 	if err != nil {
35 | 		return fmt.Errorf("failed to create Docker client: %w", err)
36 | 	}
37 | 	defer cli.Close()
38 | 
39 | 	// Stop the container with a timeout
40 | 	timeout := 10 // seconds
41 | 	if err := cli.ContainerStop(ctx, containerId, container.StopOptions{Timeout: &timeout}); err != nil {
42 | 		return fmt.Errorf("failed to stop container: %w", err)
43 | 	}
44 | 
45 | 	// Remove the container
46 | 	if err := cli.ContainerRemove(ctx, containerId, container.RemoveOptions{
47 | 		RemoveVolumes: true,
48 | 		Force:         true,
49 | 	}); err != nil {
50 | 		return fmt.Errorf("failed to remove container: %w", err)
51 | 	}
52 | 
53 | 	return nil
54 | }
55 | 
```

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

```go
 1 | package tools
 2 | 
 3 | import (
 4 | 	"context"
 5 | 	"fmt"
 6 | 
 7 | 	dockerImage "github.com/docker/docker/api/types/image"
 8 | 	"github.com/docker/docker/api/types/container"
 9 | 	"github.com/docker/docker/client"
10 | 	"github.com/mark3labs/mcp-go/mcp"
11 | )
12 | 
13 | // InitializeEnvironment creates a new container for code execution
14 | func InitializeEnvironment(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
15 | 	// Get the requested Docker image or use default
16 | 	image, ok := request.Params.Arguments["image"].(string)
17 | 	if !ok || image == "" {
18 | 		// Default to a slim debian image with Python pre-installed
19 | 		image = "python:3.12-slim-bookworm"
20 | 	}
21 | 
22 | 	// Create and start the container
23 | 	containerId, err := createContainer(ctx, image)
24 | 	if err != nil {
25 | 		return mcp.NewToolResultText(fmt.Sprintf("Error: %v", err)), nil
26 | 	}
27 | 
28 | 	return mcp.NewToolResultText(fmt.Sprintf("container_id: %s", containerId)), nil
29 | }
30 | 
31 | // createContainer creates a new Docker container and returns its ID
32 | func createContainer(ctx context.Context, image string) (string, error) {
33 | 	cli, err := client.NewClientWithOpts(
34 | 		client.FromEnv,
35 | 		client.WithAPIVersionNegotiation(),
36 | 	)
37 | 	if err != nil {
38 | 		return "", fmt.Errorf("failed to create Docker client: %w", err)
39 | 	}
40 | 	defer cli.Close()
41 | 
42 | 	// Pull the Docker image if not already available
43 | 	reader, err := cli.ImagePull(ctx, image, dockerImage.PullOptions{})
44 | 	if err != nil {
45 | 		return "", fmt.Errorf("failed to pull Docker image %s: %w", image, err)
46 | 	}
47 | 	defer reader.Close()
48 | 
49 | 	// Create container config with a working directory
50 | 	config := &container.Config{
51 | 		Image:      image,
52 | 		WorkingDir: "/app",
53 | 		Tty:        true,
54 | 		OpenStdin:  true,
55 | 		StdinOnce:  false,
56 | 	}
57 | 
58 | 	// Create host config
59 | 	hostConfig := &container.HostConfig{
60 | 		// Add any resource constraints here if needed
61 | 	}
62 | 
63 | 	// Create the container
64 | 	resp, err := cli.ContainerCreate(
65 | 		ctx,
66 | 		config,
67 | 		hostConfig,
68 | 		nil,
69 | 		nil,
70 | 		"",
71 | 	)
72 | 	if err != nil {
73 | 		return "", fmt.Errorf("failed to create container: %w", err)
74 | 	}
75 | 
76 | 	// Start the container
77 | 	if err := cli.ContainerStart(ctx, resp.ID, container.StartOptions{}); err != nil {
78 | 		return "", fmt.Errorf("failed to start container: %w", err)
79 | 	}
80 | 
81 | 	return resp.ID, nil
82 | }
83 | 
```

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

```markdown
 1 | # Development Guide 🛠️
 2 | 
 3 | This guide is for developers who want to build the project locally or contribute to its development.
 4 | 
 5 | ## Prerequisites
 6 | 
 7 | - Go 1.21 or later
 8 | - Docker installed and running
 9 | - Git (for version information)
10 | - Make (optional, for build automation)
11 | 
12 | ## Building from Source
13 | 
14 | 1. Clone the repository:
15 | ```bash
16 | git clone https://github.com/Automata-Labs-team/code-sandbox-mcp.git
17 | cd code-sandbox-mcp
18 | ```
19 | 
20 | 2. Build the project:
21 | ```bash
22 | # Development build
23 | ./build.sh
24 | 
25 | # Release build
26 | ./build.sh --release
27 | 
28 | # Release with specific version
29 | ./build.sh --release --version v1.0.0
30 | ```
31 | 
32 | The binaries will be available in the `bin` directory.
33 | 
34 | ## Build Options
35 | 
36 | The `build.sh` script supports several options:
37 | 
38 | | Option | Description |
39 | |--------|-------------|
40 | | `--release` | Build in release mode with version information |
41 | | `--version <ver>` | Specify a version number (e.g., v1.0.0) |
42 | 
43 | ## Project Structure
44 | 
45 | ```
46 | code-sandbox-mcp/
47 | ├── src/
48 | │   └── code-sandbox-mcp/
49 | │       └── main.go       # Main application code
50 | ├── bin/                  # Compiled binaries
51 | ├── build.sh             # Build script
52 | ├── install.sh           # Unix-like systems installer
53 | ├── install.ps1          # Windows installer
54 | ├── README.md            # User documentation
55 | └── DEVELOPMENT.md       # This file
56 | ```
57 | 
58 | ## API Documentation
59 | 
60 | The project implements the MCP (Machine Code Protocol) server interface for executing code in Docker containers.
61 | 
62 | ### Core Functions
63 | 
64 | - `runInDocker`: Executes single-file code in a Docker container
65 | - `runProjectInDocker`: Runs project directories in containers
66 | - `RegisterTool`: Registers new tool endpoints
67 | - `NewServer`: Creates a new MCP server instance
68 | 
69 | ### Tool Arguments
70 | 
71 | #### RunCodeArguments
72 | ```go
73 | type RunCodeArguments struct {
74 |     Code       string   `json:"code"`       // The code to run
75 |     Language   Language `json:"language"`   // Programming language
76 | }
77 | ```
78 | 
79 | #### RunProjectArguments
80 | ```go
81 | type RunProjectArguments struct {
82 |     ProjectDir string   `json:"project_dir"` // Project directory
83 |     Language   Language `json:"language"`    // Programming language
84 |     Entrypoint string   `json:"entrypoint"` // Command to run the project
85 |     Background bool     `json:"background"`  // Run in background
86 | }
87 | ```
```

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

```go
  1 | package installer
  2 | 
  3 | import (
  4 | 	"encoding/json"
  5 | 	"fmt"
  6 | 	"os"
  7 | 	"path/filepath"
  8 | 	"runtime"
  9 | )
 10 | 
 11 | // MCPConfig represents the Claude Desktop config file structure
 12 | type MCPConfig struct {
 13 | 	MCPServers map[string]MCPServer `json:"mcpServers"`
 14 | }
 15 | 
 16 | // MCPServer represents a single MCP server configuration
 17 | type MCPServer struct {
 18 | 	Command string            `json:"command"`
 19 | 	Args    []string          `json:"args"`
 20 | 	Env     map[string]string `json:"env"`
 21 | }
 22 | func InstallConfig() error {
 23 | 	configPath, err := getConfigPath()
 24 | 	if err != nil {
 25 | 		return err
 26 | 	}
 27 | 
 28 | 	// Create config directory if it doesn't exist
 29 | 	configDir := filepath.Dir(configPath)
 30 | 	if err := os.MkdirAll(configDir, 0755); err != nil {
 31 | 		return fmt.Errorf("failed to create config directory: %w", err)
 32 | 	}
 33 | 
 34 | 	// Get the absolute path of the current executable
 35 | 	execPath, err := os.Executable()
 36 | 	if err != nil {
 37 | 		return fmt.Errorf("failed to get executable path: %w", err)
 38 | 	}
 39 | 	execPath, err = filepath.Abs(execPath)
 40 | 	if err != nil {
 41 | 		return fmt.Errorf("failed to get absolute path: %w", err)
 42 | 	}
 43 | 
 44 | 	var config MCPConfig
 45 | 	if _, err := os.Stat(configPath); err == nil {
 46 | 		// Read existing config
 47 | 		configData, err := os.ReadFile(configPath)
 48 | 		if err != nil {
 49 | 			return fmt.Errorf("failed to read config file: %w", err)
 50 | 		}
 51 | 		if err := json.Unmarshal(configData, &config); err != nil {
 52 | 			return fmt.Errorf("failed to parse config file: %w", err)
 53 | 		}
 54 | 	} else {
 55 | 		// Create new config
 56 | 		config = MCPConfig{
 57 | 			MCPServers: make(map[string]MCPServer),
 58 | 		}
 59 | 	}
 60 | 
 61 | 	// Add or update our server config
 62 | 	var command string
 63 | 	if runtime.GOOS == "windows" {
 64 | 		command = "cmd"
 65 | 		config.MCPServers["code-sandbox-mcp"] = MCPServer{
 66 | 			Command: command,
 67 | 			Args:    []string{"/c", execPath},
 68 | 			Env:     map[string]string{},
 69 | 		}
 70 | 	} else {
 71 | 		config.MCPServers["code-sandbox-mcp"] = MCPServer{
 72 | 			Command: execPath,
 73 | 			Args:    []string{},
 74 | 			Env:     map[string]string{},
 75 | 		}
 76 | 	}
 77 | 
 78 | 	// Write the updated config
 79 | 	configData, err := json.MarshalIndent(config, "", "  ")
 80 | 	if err != nil {
 81 | 		return fmt.Errorf("failed to marshal config: %w", err)
 82 | 	}
 83 | 
 84 | 	if err := os.WriteFile(configPath, configData, 0644); err != nil {
 85 | 		return fmt.Errorf("failed to write config file: %w", err)
 86 | 	}
 87 | 
 88 | 	fmt.Printf("Added code-sandbox-mcp to %s\n", configPath)
 89 | 	return nil
 90 | }
 91 | 
 92 | func getConfigPath() (string, error) {
 93 | 	homeDir, err := os.UserHomeDir()
 94 | 	if err != nil {
 95 | 		return "", fmt.Errorf("failed to get user home directory: %w", err)
 96 | 	}
 97 | 
 98 | 	var configDir string
 99 | 	switch runtime.GOOS {
100 | 	case "darwin":
101 | 		configDir = filepath.Join(homeDir, "Library", "Application Support", "Claude")
102 | 	case "windows":
103 | 		configDir = filepath.Join(os.Getenv("APPDATA"), "Claude")
104 | 	default: // linux and others
105 | 		configDir = filepath.Join(homeDir, ".config", "Claude")
106 | 	}
107 | 
108 | 	return filepath.Join(configDir, "claude_desktop_config.json"), nil
109 | }
```

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

```bash
  1 | #!/bin/bash
  2 | 
  3 | # Default values
  4 | VERSION="dev"
  5 | RELEASE=false
  6 | 
  7 | # Parse command line arguments
  8 | while [[ "$#" -gt 0 ]]; do
  9 |     case $1 in
 10 |         --release) 
 11 |             RELEASE=true
 12 |             # If no version specified, use git tag or commit hash
 13 |             if [ "$VERSION" = "dev" ]; then
 14 |                 if [ -d .git ]; then
 15 |                     VERSION=$(git describe --tags 2>/dev/null || git rev-parse --short HEAD)
 16 |                 fi
 17 |             fi
 18 |             ;;
 19 |         --version) 
 20 |             VERSION="$2"
 21 |             shift 
 22 |             ;;
 23 |         *) echo "Unknown parameter: $1"; exit 1 ;;
 24 |     esac
 25 |     shift
 26 | done
 27 | 
 28 | # Create bin directory if it doesn't exist
 29 | mkdir -p bin
 30 | 
 31 | # Colors for output
 32 | GREEN='\033[0;32m'
 33 | RED='\033[0;31m'
 34 | BLUE='\033[0;34m'
 35 | NC='\033[0m' # No Color
 36 | 
 37 | # Build mode banner
 38 | if [ "$RELEASE" = true ]; then
 39 |     echo -e "${BLUE}Building in RELEASE mode (version: ${VERSION})${NC}"
 40 | else
 41 |     echo -e "${BLUE}Building in DEVELOPMENT mode${NC}"
 42 | fi
 43 | 
 44 | # Build flags for optimization
 45 | BUILDFLAGS="-trimpath"  # Remove file system paths from binary
 46 | 
 47 | # Set up ldflags
 48 | LDFLAGS="-s -w"  # Strip debug information and symbol tables
 49 | if [ "$RELEASE" = true ]; then
 50 |     # Add version information for release builds
 51 |     LDFLAGS="$LDFLAGS -X 'main.Version=$VERSION' -X 'main.BuildMode=release'"
 52 | else
 53 |     LDFLAGS="$LDFLAGS -X 'main.BuildMode=development'"
 54 | fi
 55 | 
 56 | # Function to build for a specific platform
 57 | build_for_platform() {
 58 |     local GOOS=$1
 59 |     local GOARCH=$2
 60 |     local EXTENSION=$3
 61 |     local OUTPUT="$(pwd)/bin/code-sandbox-mcp-${GOOS}-${GOARCH}${EXTENSION}"
 62 |     
 63 |     if [ "$RELEASE" = true ]; then
 64 |         OUTPUT="$(pwd)/bin/code-sandbox-mcp-${GOOS}-${GOARCH}${EXTENSION}"
 65 |     fi
 66 | 
 67 |     echo -e "${GREEN}Building for ${GOOS}/${GOARCH}...${NC}"
 68 |     pushd src/code-sandbox-mcp
 69 |     GOOS=$GOOS GOARCH=$GOARCH go build -ldflags="${LDFLAGS}" ${BUILDFLAGS} -o "$OUTPUT" .
 70 |     
 71 |     if [ $? -eq 0 ]; then
 72 |         popd
 73 |         echo -e "${GREEN}✓ Successfully built:${NC} $OUTPUT"
 74 |         # Create symlink for native platform
 75 |         if [ "$GOOS" = "$(go env GOOS)" ] && [ "$GOARCH" = "$(go env GOARCH)" ]; then
 76 |             local SYMLINK="bin/code-sandbox-mcp${EXTENSION}"
 77 |             ln -sf "$(basename $OUTPUT)" "$SYMLINK"
 78 |             echo -e "${GREEN}✓ Created symlink:${NC} $SYMLINK -> $OUTPUT"
 79 |         fi
 80 |     else
 81 |         popd
 82 |         echo -e "${RED}✗ Failed to build for ${GOOS}/${GOARCH}${NC}"
 83 |         return 1
 84 |     fi
 85 | }
 86 | 
 87 | # Clean previous builds
 88 | echo -e "${GREEN}Cleaning previous builds...${NC}"
 89 | rm -f bin/code-sandbox-mcp*
 90 | 
 91 | # Build for Linux
 92 | build_for_platform linux amd64 ""
 93 | build_for_platform linux arm64 ""
 94 | 
 95 | # Build for macOS
 96 | build_for_platform darwin amd64 ""
 97 | build_for_platform darwin arm64 ""
 98 | 
 99 | # Build for Windows
100 | build_for_platform windows amd64 ".exe"
101 | build_for_platform windows arm64 ".exe"
102 | 
103 | echo -e "\n${GREEN}Build process completed!${NC}" 
```

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

```bash
  1 | #!/bin/sh
  2 | set -e
  3 | 
  4 | # Colors for output
  5 | RED='\033[0;31m'
  6 | GREEN='\033[0;32m'
  7 | YELLOW='\033[1;33m'
  8 | NC='\033[0m' # No Color
  9 | 
 10 | # Check if we're in a terminal that supports colors
 11 | if [ -t 1 ] && command -v tput >/dev/null 2>&1 && [ "$(tput colors)" -ge 8 ]; then
 12 |     HAS_COLORS=1
 13 | else
 14 |     HAS_COLORS=0
 15 |     # Reset color variables if colors are not supported
 16 |     RED=''
 17 |     GREEN=''
 18 |     YELLOW=''
 19 |     NC=''
 20 | fi
 21 | 
 22 | # Function to print colored output
 23 | print_status() {
 24 |     local color=$1
 25 |     local message=$2
 26 |     if [ "$HAS_COLORS" = "1" ]; then
 27 |         printf "%b%s%b\n" "$color" "$message" "$NC"
 28 |     else
 29 |         printf "%s\n" "$message"
 30 |     fi
 31 | }
 32 | 
 33 | # Detect OS and architecture
 34 | OS=$(uname -s | tr '[:upper:]' '[:lower:]')
 35 | ARCH=$(uname -m)
 36 | 
 37 | # Convert architecture to our naming scheme
 38 | case "$ARCH" in
 39 |     x86_64)  ARCH="amd64" ;;
 40 |     aarch64) ARCH="arm64" ;;
 41 |     arm64)   ARCH="arm64" ;;
 42 |     *)
 43 |         print_status "$RED" "Unsupported architecture: $ARCH"
 44 |         exit 1
 45 |         ;;
 46 | esac
 47 | 
 48 | # Convert OS to our naming scheme
 49 | case "$OS" in
 50 |     linux)   OS="linux" ;;
 51 |     darwin)  OS="darwin" ;;
 52 |     *)
 53 |         print_status "$RED" "Unsupported operating system: $OS"
 54 |         exit 1
 55 |         ;;
 56 | esac
 57 | 
 58 | # Check if Docker is installed
 59 | if ! command -v docker >/dev/null 2>&1; then
 60 |     print_status "$RED" "Error: Docker is not installed"
 61 |     print_status "$YELLOW" "Please install Docker first:"
 62 |     echo "  - For Linux: https://docs.docker.com/engine/install/"
 63 |     echo "  - For macOS: https://docs.docker.com/desktop/install/mac/"
 64 |     exit 1
 65 | fi
 66 | 
 67 | # Check if Docker daemon is running
 68 | if ! docker info >/dev/null 2>&1; then
 69 |     print_status "$RED" "Error: Docker daemon is not running"
 70 |     print_status "$YELLOW" "Please start Docker and try again"
 71 |     exit 1
 72 | fi
 73 | 
 74 | print_status "$GREEN" "Downloading latest release..."
 75 | 
 76 | # Get the latest release URL
 77 | 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)
 78 | 
 79 | if [ -z "$LATEST_RELEASE_URL" ]; then
 80 |     print_status "$RED" "Error: Could not find release for $OS-$ARCH"
 81 |     exit 1
 82 | fi
 83 | 
 84 | # Create installation directory
 85 | INSTALL_DIR="$HOME/.local/share/code-sandbox-mcp"
 86 | mkdir -p "$INSTALL_DIR"
 87 | 
 88 | # Download to a temporary file first
 89 | TEMP_FILE="$INSTALL_DIR/code-sandbox-mcp.tmp"
 90 | print_status "$GREEN" "Installing to $INSTALL_DIR/code-sandbox-mcp..."
 91 | 
 92 | if ! curl -L "$LATEST_RELEASE_URL" -o "$TEMP_FILE"; then
 93 |     print_status "$RED" "Error: Failed to download the binary"
 94 |     rm -f "$TEMP_FILE"
 95 |     exit 1
 96 | fi
 97 | 
 98 | chmod +x "$TEMP_FILE"
 99 | 
100 | # Try to stop the existing process if it's running
101 | if [ -f "$INSTALL_DIR/code-sandbox-mcp" ]; then
102 |     pkill -f "$INSTALL_DIR/code-sandbox-mcp" >/dev/null 2>&1 || true
103 |     sleep 1  # Give it a moment to shut down
104 | fi
105 | 
106 | # Move the temporary file to the final location
107 | if ! mv "$TEMP_FILE" "$INSTALL_DIR/code-sandbox-mcp"; then
108 |     print_status "$RED" "Error: Failed to install the binary. Please ensure no instances are running and try again."
109 |     rm -f "$TEMP_FILE"
110 |     exit 1
111 | fi
112 | 
113 | # Add to Claude Desktop config
114 | print_status "$GREEN" "Adding to Claude Desktop configuration..."
115 | "$INSTALL_DIR/code-sandbox-mcp" --install
116 | 
117 | print_status "$GREEN" "Installation complete!"
118 | echo "You can now use code-sandbox-mcp with Claude Desktop or other AI applications." 
```

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

```go
  1 | package installer
  2 | 
  3 | import (
  4 | 	"encoding/json"
  5 | 	"fmt"
  6 | 	"io"
  7 | 	"net/http"
  8 | 	"os"
  9 | 	"os/exec"
 10 | 	"runtime"
 11 | 	"strings"
 12 | )
 13 | 
 14 | // Version information (set by build flags)
 15 | var (
 16 | 	Version   = "dev"         // Version number (from git tag or specified)
 17 | 	BuildMode = "development" // Build mode (development or release)
 18 | )
 19 | 
 20 | // checkForUpdate checks GitHub releases for a newer version
 21 | func CheckForUpdate() (bool, string, error) {
 22 | 	resp, err := http.Get("https://api.github.com/repos/Automata-Labs-team/code-sandbox-mcp/releases/latest")
 23 | 	if err != nil {
 24 | 		return false, "", fmt.Errorf("failed to check for updates: %w", err)
 25 | 	}
 26 | 	defer resp.Body.Close()
 27 | 
 28 | 	var release struct {
 29 | 		TagName string `json:"tag_name"`
 30 | 		Assets  []struct {
 31 | 			Name               string `json:"name"`
 32 | 			BrowserDownloadURL string `json:"browser_download_url"`
 33 | 		} `json:"assets"`
 34 | 	}
 35 | 
 36 | 	if err := json.NewDecoder(resp.Body).Decode(&release); err != nil {
 37 | 		return false, "", fmt.Errorf("failed to parse release info: %w", err)
 38 | 	}
 39 | 
 40 | 	// Skip update check if we're on development version
 41 | 	if Version == "dev" {
 42 | 		return false, "", nil
 43 | 	}
 44 | 
 45 | 	// Compare versions (assuming semver format v1.2.3)
 46 | 	if release.TagName > "v"+Version {
 47 | 		// Find matching asset for current OS/arch
 48 | 		suffix := fmt.Sprintf("%s-%s", runtime.GOOS, runtime.GOARCH)
 49 | 		if runtime.GOOS == "windows" {
 50 | 			suffix += ".exe"
 51 | 		}
 52 | 		for _, asset := range release.Assets {
 53 | 			if strings.HasSuffix(asset.Name, suffix) {
 54 | 				return true, asset.BrowserDownloadURL, nil
 55 | 			}
 56 | 		}
 57 | 	}
 58 | 
 59 | 	return false, "", nil
 60 | }
 61 | 
 62 | // performUpdate downloads and replaces the current binary and restarts the process
 63 | func PerformUpdate(downloadURL string) error {
 64 | 	// Get current executable path
 65 | 	execPath, err := os.Executable()
 66 | 	if err != nil {
 67 | 		return fmt.Errorf("failed to get executable path: %w", err)
 68 | 	}
 69 | 
 70 | 	// Download new version to temporary file
 71 | 	tmpFile, err := os.CreateTemp("", "code-sandbox-mcp-update-*")
 72 | 	if err != nil {
 73 | 		return fmt.Errorf("failed to create temp file: %w", err)
 74 | 	}
 75 | 	defer os.Remove(tmpFile.Name())
 76 | 
 77 | 	resp, err := http.Get(downloadURL)
 78 | 	if err != nil {
 79 | 		return fmt.Errorf("failed to download update: %w", err)
 80 | 	}
 81 | 	defer resp.Body.Close()
 82 | 
 83 | 	if _, err := io.Copy(tmpFile, resp.Body); err != nil {
 84 | 		return fmt.Errorf("failed to write update: %w", err)
 85 | 	}
 86 | 	tmpFile.Close()
 87 | 
 88 | 	// Make temporary file executable
 89 | 	if runtime.GOOS != "windows" {
 90 | 		if err := os.Chmod(tmpFile.Name(), 0755); err != nil {
 91 | 			return fmt.Errorf("failed to make update executable: %w", err)
 92 | 		}
 93 | 	}
 94 | 
 95 | 	// Replace the current executable
 96 | 	// On Windows, we need to move the current executable first
 97 | 	if runtime.GOOS == "windows" {
 98 | 		oldPath := execPath + ".old"
 99 | 		if err := os.Rename(execPath, oldPath); err != nil {
100 | 			return fmt.Errorf("failed to rename current executable: %w", err)
101 | 		}
102 | 		defer os.Remove(oldPath)
103 | 	}
104 | 
105 | 	if err := os.Rename(tmpFile.Name(), execPath); err != nil {
106 | 		return fmt.Errorf("failed to replace executable: %w", err)
107 | 	}
108 | 
109 | 	// Start the new version and exit the current process
110 | 	args := os.Args[1:] // Keep all arguments except the program name
111 | 	cmd := exec.Command(execPath, args...)
112 | 	cmd.Stdin = os.Stdin
113 | 	cmd.Stdout = os.Stdout
114 | 	cmd.Stderr = os.Stderr
115 | 	if err := cmd.Start(); err != nil {
116 | 		return fmt.Errorf("failed to start new version: %w", err)
117 | 	}
118 | 
119 | 	// Exit the current process
120 | 	os.Exit(0)
121 | 	return nil // Never reached, just for compiler
122 | }
```

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

```go
  1 | package tools
  2 | 
  3 | import (
  4 | 	"archive/tar"
  5 | 	"context"
  6 | 	"fmt"
  7 | 	"io"
  8 | 	"os"
  9 | 	"path/filepath"
 10 | 	"strings"
 11 | 
 12 | 	"github.com/docker/docker/client"
 13 | 	"github.com/mark3labs/mcp-go/mcp"
 14 | )
 15 | 
 16 | // CopyFileFromContainer copies a single file from a container's filesystem to the local filesystem
 17 | func CopyFileFromContainer(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
 18 | 	// Extract parameters
 19 | 	containerID, ok := request.Params.Arguments["container_id"].(string)
 20 | 	if !ok || containerID == "" {
 21 | 		return mcp.NewToolResultText("container_id is required"), nil
 22 | 	}
 23 | 
 24 | 	containerSrcPath, ok := request.Params.Arguments["container_src_path"].(string)
 25 | 	if !ok || containerSrcPath == "" {
 26 | 		return mcp.NewToolResultText("container_src_path is required"), nil
 27 | 	}
 28 | 
 29 | 	// If container path doesn't start with /, prepend /app/
 30 | 	if !strings.HasPrefix(containerSrcPath, "/") {
 31 | 		containerSrcPath = filepath.Join("/app", containerSrcPath)
 32 | 	}
 33 | 
 34 | 	// Get the local destination path (optional parameter)
 35 | 	localDestPath, ok := request.Params.Arguments["local_dest_path"].(string)
 36 | 	if !ok || localDestPath == "" {
 37 | 		// Default: use the name of the source file in current directory
 38 | 		localDestPath = filepath.Base(containerSrcPath)
 39 | 	}
 40 | 
 41 | 	// Clean and create the destination directory if it doesn't exist
 42 | 	localDestPath = filepath.Clean(localDestPath)
 43 | 	if err := os.MkdirAll(filepath.Dir(localDestPath), 0755); err != nil {
 44 | 		return mcp.NewToolResultText(fmt.Sprintf("Error creating destination directory: %v", err)), nil
 45 | 	}
 46 | 
 47 | 	// Copy the file from the container
 48 | 	if err := copyFileFromContainer(ctx, containerID, containerSrcPath, localDestPath); err != nil {
 49 | 		return mcp.NewToolResultText(fmt.Sprintf("Error copying file from container: %v", err)), nil
 50 | 	}
 51 | 
 52 | 	return mcp.NewToolResultText(fmt.Sprintf("Successfully copied %s from container %s to %s", containerSrcPath, containerID, localDestPath)), nil
 53 | }
 54 | 
 55 | // copyFileFromContainer copies a single file from the container to the local filesystem
 56 | func copyFileFromContainer(ctx context.Context, containerID string, srcPath string, destPath string) error {
 57 | 	cli, err := client.NewClientWithOpts(
 58 | 		client.FromEnv,
 59 | 		client.WithAPIVersionNegotiation(),
 60 | 	)
 61 | 	if err != nil {
 62 | 		return fmt.Errorf("failed to create Docker client: %w", err)
 63 | 	}
 64 | 	defer cli.Close()
 65 | 
 66 | 	// Create reader for the file from container
 67 | 	reader, stat, err := cli.CopyFromContainer(ctx, containerID, srcPath)
 68 | 	if err != nil {
 69 | 		return fmt.Errorf("failed to copy from container: %w", err)
 70 | 	}
 71 | 	defer reader.Close()
 72 | 
 73 | 	// Check if the source is a directory
 74 | 	if stat.Mode.IsDir() {
 75 | 		return fmt.Errorf("source path is a directory, only files are supported")
 76 | 	}
 77 | 
 78 | 	// Create tar reader since Docker sends files in tar format
 79 | 	tr := tar.NewReader(reader)
 80 | 
 81 | 	// Read the first (and should be only) file from the archive
 82 | 	header, err := tr.Next()
 83 | 	if err != nil {
 84 | 		return fmt.Errorf("failed to read tar header: %w", err)
 85 | 	}
 86 | 
 87 | 	// Verify it's a regular file
 88 | 	if header.Typeflag != tar.TypeReg {
 89 | 		return fmt.Errorf("source is not a regular file")
 90 | 	}
 91 | 
 92 | 	// Create the destination file
 93 | 	destFile, err := os.Create(destPath)
 94 | 	if err != nil {
 95 | 		return fmt.Errorf("failed to create destination file: %w", err)
 96 | 	}
 97 | 	defer destFile.Close()
 98 | 
 99 | 	// Copy the content
100 | 	_, err = io.Copy(destFile, tr)
101 | 	if err != nil {
102 | 		return fmt.Errorf("failed to write file content: %w", err)
103 | 	}
104 | 
105 | 	// Set file permissions from tar header
106 | 	if err := os.Chmod(destPath, os.FileMode(header.Mode)); err != nil {
107 | 		return fmt.Errorf("failed to set file permissions: %w", err)
108 | 	}
109 | 
110 | 	return nil
111 | }
112 | 
```

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

```go
  1 | package tools
  2 | 
  3 | import (
  4 | 	"context"
  5 | 	"fmt"
  6 | 	"strings"
  7 | 
  8 | 	"github.com/docker/docker/api/types/container"
  9 | 	"github.com/mark3labs/mcp-go/mcp"
 10 | 	"github.com/docker/docker/client"
 11 | 	"github.com/docker/docker/pkg/stdcopy"
 12 | )
 13 | 
 14 | // Exec executes commands in a container
 15 | func Exec(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
 16 | 	// Extract parameters
 17 | 	containerID, ok := request.Params.Arguments["container_id"].(string)
 18 | 	if !ok || containerID == "" {
 19 | 		return mcp.NewToolResultText("container_id is required"), nil
 20 | 	}
 21 | 
 22 | 	// Commands can be a single string or an array of strings
 23 | 	var commands []string
 24 | 	if cmdsArr, ok := request.Params.Arguments["commands"].([]interface{}); ok {
 25 | 		// It's an array of commands
 26 | 		for _, cmd := range cmdsArr {
 27 | 			if cmdStr, ok := cmd.(string); ok {
 28 | 				commands = append(commands, cmdStr)
 29 | 			} else {
 30 | 				return mcp.NewToolResultText("Each command must be a string"), nil
 31 | 			}
 32 | 		}
 33 | 	} else if cmdStr, ok := request.Params.Arguments["commands"].(string); ok {
 34 | 		// It's a single command string
 35 | 		commands = []string{cmdStr}
 36 | 	} else {
 37 | 		return mcp.NewToolResultText("commands must be a string or an array of strings"), nil
 38 | 	}
 39 | 
 40 | 	if len(commands) == 0 {
 41 | 		return mcp.NewToolResultText("at least one command is required"), nil
 42 | 	}
 43 | 
 44 | 	// Execute each command and collect output
 45 | 	var outputBuilder strings.Builder
 46 | 	for i, cmd := range commands {
 47 | 		// Format the command nicely in the output
 48 | 		if i > 0 {
 49 | 			outputBuilder.WriteString("\n\n")
 50 | 		}
 51 | 		outputBuilder.WriteString(fmt.Sprintf("$ %s\n", cmd))
 52 | 
 53 | 		// Execute the command
 54 | 		stdout, stderr, exitCode, err := executeCommandWithOutput(ctx, containerID, cmd)
 55 | 		if err != nil {
 56 | 			return mcp.NewToolResultText(fmt.Sprintf("Error executing command: %v", err)), nil
 57 | 		}
 58 | 
 59 | 		// Add the command output to the collector
 60 | 		if stdout != "" {
 61 | 			outputBuilder.WriteString(stdout)
 62 | 			if !strings.HasSuffix(stdout, "\n") {
 63 | 				outputBuilder.WriteString("\n")
 64 | 			}
 65 | 		}
 66 | 		if stderr != "" {
 67 | 			outputBuilder.WriteString("Error: ")
 68 | 			outputBuilder.WriteString(stderr)
 69 | 			if !strings.HasSuffix(stderr, "\n") {
 70 | 				outputBuilder.WriteString("\n")
 71 | 			}
 72 | 		}
 73 | 
 74 | 		// If the command failed, add the exit code and stop processing subsequent commands
 75 | 		if exitCode != 0 {
 76 | 			outputBuilder.WriteString(fmt.Sprintf("Command exited with code %d\n", exitCode))
 77 | 			break
 78 | 		}
 79 | 	}
 80 | 
 81 | 	return mcp.NewToolResultText(outputBuilder.String()), nil
 82 | }
 83 | 
 84 | // executeCommandWithOutput runs a command in a container and returns its stdout, stderr, exit code, and any error
 85 | func executeCommandWithOutput(ctx context.Context, containerID string, cmd string) (stdout string, stderr string, exitCode int, err error) {
 86 | 	cli, err := client.NewClientWithOpts(
 87 | 		client.FromEnv,
 88 | 		client.WithAPIVersionNegotiation(),
 89 | 	)
 90 | 	if err != nil {
 91 | 		return "", "", -1, fmt.Errorf("failed to create Docker client: %w", err)
 92 | 	}
 93 | 	defer cli.Close()
 94 | 
 95 | 	// Create the exec configuration
 96 | 	exec, err := cli.ContainerExecCreate(ctx, containerID, container.ExecOptions{
 97 | 		Cmd:          []string{"sh", "-c", cmd},
 98 | 		AttachStdout: true,
 99 | 		AttachStderr: true,
100 | 	})
101 | 	if err != nil {
102 | 		return "", "", -1, fmt.Errorf("failed to create exec: %w", err)
103 | 	}
104 | 
105 | 	// Attach to the exec instance to get output
106 | 	resp, err := cli.ContainerExecAttach(ctx, exec.ID, container.ExecAttachOptions{})
107 | 	if err != nil {
108 | 		return "", "", -1, fmt.Errorf("failed to attach to exec: %w", err)
109 | 	}
110 | 	defer resp.Close()
111 | 
112 | 	// Read the output
113 | 	var stdoutBuf, stderrBuf strings.Builder
114 | 	_, err = stdcopy.StdCopy(&stdoutBuf, &stderrBuf, resp.Reader)
115 | 	if err != nil {
116 | 		return "", "", -1, fmt.Errorf("failed to read command output: %w", err)
117 | 	}
118 | 
119 | 	// Get the exit code
120 | 	inspect, err := cli.ContainerExecInspect(ctx, exec.ID)
121 | 	if err != nil {
122 | 		return "", "", -1, fmt.Errorf("failed to inspect exec: %w", err)
123 | 	}
124 | 
125 | 	return stdoutBuf.String(), stderrBuf.String(), inspect.ExitCode, nil
126 | }
```

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

```go
  1 | package tools
  2 | 
  3 | import (
  4 | 	"context"
  5 | 	"fmt"
  6 | 	"io"
  7 | 	"path/filepath"
  8 | 	"strings"
  9 | 	"time"
 10 | 
 11 | 	"github.com/docker/docker/api/types/container"
 12 | 	"github.com/docker/docker/client"
 13 | 	"github.com/mark3labs/mcp-go/mcp"
 14 | )
 15 | 
 16 | // WriteFile writes a file to the container's filesystem
 17 | func WriteFile(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
 18 | 	// Extract parameters
 19 | 	containerID, ok := request.Params.Arguments["container_id"].(string)
 20 | 	if !ok || containerID == "" {
 21 | 		return mcp.NewToolResultText("container_id is required"), nil
 22 | 	}
 23 | 
 24 | 	fileName, ok := request.Params.Arguments["file_name"].(string)
 25 | 	if !ok || fileName == "" {
 26 | 		return mcp.NewToolResultText("file_name is required"), nil
 27 | 	}
 28 | 
 29 | 	fileContents, ok := request.Params.Arguments["file_contents"].(string)
 30 | 	if !ok {
 31 | 		return mcp.NewToolResultText("file_contents is required"), nil
 32 | 	}
 33 | 
 34 | 	// Get the destination path (optional parameter)
 35 | 	destDir, ok := request.Params.Arguments["dest_dir"].(string)
 36 | 	if !ok || destDir == "" {
 37 | 		// Default: write to the working directory
 38 | 		destDir = "/app"
 39 | 	} else {
 40 | 		// If provided but doesn't start with /, prepend /app/
 41 | 		if !strings.HasPrefix(destDir, "/") {
 42 | 			destDir = filepath.Join("/app", destDir)
 43 | 		}
 44 | 	}
 45 | 
 46 | 	// Full path to the file
 47 | 	fullPath := filepath.Join(destDir, fileName)
 48 | 
 49 | 	// Create the directory if it doesn't exist
 50 | 	if err := ensureDirectoryExists(ctx, containerID, destDir); err != nil {
 51 | 		return mcp.NewToolResultText(fmt.Sprintf("Error creating directory: %v", err)), nil
 52 | 	}
 53 | 
 54 | 	// Write the file
 55 | 	if err := writeFileToContainer(ctx, containerID, fullPath, fileContents); err != nil {
 56 | 		return mcp.NewToolResultText(fmt.Sprintf("Error writing file: %v", err)), nil
 57 | 	}
 58 | 
 59 | 	return mcp.NewToolResultText(fmt.Sprintf("Successfully wrote file %s to container %s", fullPath, containerID)), nil
 60 | }
 61 | 
 62 | // ensureDirectoryExists creates a directory in the container if it doesn't already exist
 63 | func ensureDirectoryExists(ctx context.Context, containerID, dirPath string) error {
 64 | 	cli, err := client.NewClientWithOpts(
 65 | 		client.FromEnv,
 66 | 		client.WithAPIVersionNegotiation(),
 67 | 	)
 68 | 	if err != nil {
 69 | 		return fmt.Errorf("failed to create Docker client: %w", err)
 70 | 	}
 71 | 	defer cli.Close()
 72 | 
 73 | 	// Create the directory if it doesn't exist
 74 | 	cmd := []string{"mkdir", "-p", dirPath}
 75 | 	exec, err := cli.ContainerExecCreate(ctx, containerID, container.ExecOptions{
 76 | 		Cmd: cmd,
 77 | 	})
 78 | 	if err != nil {
 79 | 		return fmt.Errorf("failed to create exec for mkdir: %w", err)
 80 | 	}
 81 | 
 82 | 	if err := cli.ContainerExecStart(ctx, exec.ID, container.ExecStartOptions{}); err != nil {
 83 | 		return fmt.Errorf("failed to start exec for mkdir: %w", err)
 84 | 	}
 85 | 
 86 | 	return nil
 87 | }
 88 | 
 89 | // writeFileToContainer writes file contents to a file in the container
 90 | func writeFileToContainer(ctx context.Context, containerID, filePath, contents string) error {
 91 | 	cli, err := client.NewClientWithOpts(
 92 | 		client.FromEnv,
 93 | 		client.WithAPIVersionNegotiation(),
 94 | 	)
 95 | 	if err != nil {
 96 | 		return fmt.Errorf("failed to create Docker client: %w", err)
 97 | 	}
 98 | 	defer cli.Close()
 99 | 
100 | 	// Command to write the content to the specified file using cat
101 | 	cmd := []string{"sh", "-c", fmt.Sprintf("cat > %s", filePath)}
102 | 
103 | 	// Create the exec configuration
104 | 	execConfig := container.ExecOptions{
105 | 		Cmd:          cmd,
106 | 		AttachStdin:  true,
107 | 		AttachStdout: true,
108 | 		AttachStderr: true,
109 | 	}
110 | 
111 | 	// Create the exec instance
112 | 	execIDResp, err := cli.ContainerExecCreate(ctx, containerID, execConfig)
113 | 	if err != nil {
114 | 		return fmt.Errorf("failed to create exec: %w", err)
115 | 	}
116 | 
117 | 	// Attach to the exec instance
118 | 	resp, err := cli.ContainerExecAttach(ctx, execIDResp.ID, container.ExecAttachOptions{})
119 | 	if err != nil {
120 | 		return fmt.Errorf("failed to attach to exec: %w", err)
121 | 	}
122 | 	defer resp.Close()
123 | 
124 | 	// Write the content to the container's stdin
125 | 	_, err = io.Copy(resp.Conn, strings.NewReader(contents))
126 | 	if err != nil {
127 | 		return fmt.Errorf("failed to write content to container: %w", err)
128 | 	}
129 | 	resp.CloseWrite()
130 | 
131 | 	// Wait for the command to complete
132 | 	for {
133 | 		inspect, err := cli.ContainerExecInspect(ctx, execIDResp.ID)
134 | 		if err != nil {
135 | 			return fmt.Errorf("failed to inspect exec: %w", err)
136 | 		}
137 | 		if !inspect.Running {
138 | 			if inspect.ExitCode != 0 {
139 | 				return fmt.Errorf("command exited with code %d", inspect.ExitCode)
140 | 			}
141 | 			break
142 | 		}
143 | 		// Small sleep to avoid hammering the Docker API
144 | 		time.Sleep(100 * time.Millisecond)
145 | 	}
146 | 
147 | 	return nil
148 | }
149 | 
```

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

```
  1 | # Function to check if running in a terminal that supports colors
  2 | function Test-ColorSupport {
  3 |     # Check if we're in a terminal that supports VirtualTerminalLevel
  4 |     $supportsVT = $false
  5 |     try {
  6 |         $supportsVT = [Console]::IsOutputRedirected -eq $false -and 
  7 |                       [Console]::IsErrorRedirected -eq $false -and
  8 |                       [Environment]::GetEnvironmentVariable("TERM") -ne $null
  9 |     } catch {
 10 |         $supportsVT = $false
 11 |     }
 12 |     return $supportsVT
 13 | }
 14 | 
 15 | # Function to write colored output
 16 | function Write-ColoredMessage {
 17 |     param(
 18 |         [string]$Message,
 19 |         [System.ConsoleColor]$Color = [System.ConsoleColor]::White
 20 |     )
 21 |     
 22 |     if (Test-ColorSupport) {
 23 |         $originalColor = [Console]::ForegroundColor
 24 |         [Console]::ForegroundColor = $Color
 25 |         Write-Host $Message
 26 |         [Console]::ForegroundColor = $originalColor
 27 |     } else {
 28 |         Write-Host $Message
 29 |     }
 30 | }
 31 | 
 32 | # Function to stop running instances
 33 | function Stop-RunningInstances {
 34 |     param(
 35 |         [string]$ProcessName
 36 |     )
 37 |     
 38 |     try {
 39 |         $processes = Get-Process -Name $ProcessName -ErrorAction SilentlyContinue
 40 |         if ($processes) {
 41 |             $processes | ForEach-Object {
 42 |                 try {
 43 |                     $_.Kill()
 44 |                     $_.WaitForExit(1000)
 45 |                 } catch {
 46 |                     # Ignore errors if process already exited
 47 |                 }
 48 |             }
 49 |             Start-Sleep -Seconds 1  # Give processes time to fully exit
 50 |         }
 51 |     } catch {
 52 |         # Ignore errors if no processes found
 53 |     }
 54 | }
 55 | 
 56 | # Check if Docker is installed
 57 | if (-not (Get-Command "docker" -ErrorAction SilentlyContinue)) {
 58 |     Write-ColoredMessage "Error: Docker is not installed" -Color Red
 59 |     Write-ColoredMessage "Please install Docker Desktop for Windows:" -Color Yellow
 60 |     Write-Host "  https://docs.docker.com/desktop/install/windows-install/"
 61 |     exit 1
 62 | }
 63 | 
 64 | # Check if Docker daemon is running
 65 | try {
 66 |     docker info | Out-Null
 67 | } catch {
 68 |     Write-ColoredMessage "Error: Docker daemon is not running" -Color Red
 69 |     Write-ColoredMessage "Please start Docker Desktop and try again" -Color Yellow
 70 |     exit 1
 71 | }
 72 | 
 73 | Write-ColoredMessage "Downloading latest release..." -Color Green
 74 | 
 75 | # Determine architecture
 76 | $arch = if ([Environment]::Is64BitOperatingSystem) { "amd64" } else { "386" }
 77 | 
 78 | # Get the latest release URL
 79 | try {
 80 |     $apiResponse = Invoke-RestMethod -Uri "https://api.github.com/repos/Automata-Labs-team/code-sandbox-mcp/releases/latest"
 81 |     $asset = $apiResponse.assets | Where-Object { $_.name -like "code-sandbox-mcp-windows-$arch.exe" }
 82 | } catch {
 83 |     Write-ColoredMessage "Error: Failed to fetch latest release information" -Color Red
 84 |     Write-Host $_.Exception.Message
 85 |     exit 1
 86 | }
 87 | 
 88 | if (-not $asset) {
 89 |     Write-ColoredMessage "Error: Could not find release for windows-$arch" -Color Red
 90 |     exit 1
 91 | }
 92 | 
 93 | # Create installation directory
 94 | $installDir = "$env:LOCALAPPDATA\code-sandbox-mcp"
 95 | New-Item -ItemType Directory -Force -Path $installDir | Out-Null
 96 | 
 97 | # Download to a temporary file first
 98 | $tempFile = "$installDir\code-sandbox-mcp.tmp"
 99 | Write-ColoredMessage "Installing to $installDir\code-sandbox-mcp.exe..." -Color Green
100 | 
101 | try {
102 |     # Download the binary to temporary file
103 |     Invoke-WebRequest -Uri $asset.browser_download_url -OutFile $tempFile
104 | 
105 |     # Stop any running instances
106 |     Stop-RunningInstances -ProcessName "code-sandbox-mcp"
107 | 
108 |     # Try to move the temporary file to the final location
109 |     try {
110 |         Move-Item -Path $tempFile -Destination "$installDir\code-sandbox-mcp.exe" -Force
111 |     } catch {
112 |         Write-ColoredMessage "Error: Failed to install the binary. Please ensure no instances are running and try again." -Color Red
113 |         Remove-Item -Path $tempFile -ErrorAction SilentlyContinue
114 |         exit 1
115 |     }
116 | } catch {
117 |     Write-ColoredMessage "Error: Failed to download or install the binary" -Color Red
118 |     Write-Host $_.Exception.Message
119 |     Remove-Item -Path $tempFile -ErrorAction SilentlyContinue
120 |     exit 1
121 | }
122 | 
123 | # Add to Claude Desktop config
124 | Write-ColoredMessage "Adding to Claude Desktop configuration..." -Color Green
125 | try {
126 |     & "$installDir\code-sandbox-mcp.exe" --install
127 | } catch {
128 |     Write-ColoredMessage "Error: Failed to configure Claude Desktop" -Color Red
129 |     Write-Host $_.Exception.Message
130 |     exit 1
131 | }
132 | 
133 | Write-ColoredMessage "Installation complete!" -Color Green
134 | 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
  1 | package tools
  2 | 
  3 | import (
  4 | 	"archive/tar"
  5 | 	"bytes"
  6 | 	"context"
  7 | 	"fmt"
  8 | 	"io"
  9 | 	"os"
 10 | 	"path/filepath"
 11 | 	"strings"
 12 | 
 13 | 	"github.com/docker/docker/api/types/container"
 14 | 	"github.com/docker/docker/client"
 15 | 	"github.com/mark3labs/mcp-go/mcp"
 16 | )
 17 | 
 18 | // CopyFile copies a single local file to a container's filesystem
 19 | func CopyFile(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
 20 | 	// Extract parameters
 21 | 	containerID, ok := request.Params.Arguments["container_id"].(string)
 22 | 	if !ok || containerID == "" {
 23 | 		return mcp.NewToolResultText("container_id is required"), nil
 24 | 	}
 25 | 
 26 | 	localSrcFile, ok := request.Params.Arguments["local_src_file"].(string)
 27 | 	if !ok || localSrcFile == "" {
 28 | 		return mcp.NewToolResultText("local_src_file is required"), nil
 29 | 	}
 30 | 
 31 | 	// Clean and validate the source path
 32 | 	localSrcFile = filepath.Clean(localSrcFile)
 33 | 	info, err := os.Stat(localSrcFile)
 34 | 	if err != nil {
 35 | 		return mcp.NewToolResultText(fmt.Sprintf("Error accessing source file: %v", err)), nil
 36 | 	}
 37 | 
 38 | 	if info.IsDir() {
 39 | 		return mcp.NewToolResultText("local_src_file must be a file, not a directory"), nil
 40 | 	}
 41 | 
 42 | 	// Get the destination path (optional parameter)
 43 | 	destPath, ok := request.Params.Arguments["dest_path"].(string)
 44 | 	if !ok || destPath == "" {
 45 | 		// Default: use the name of the source file
 46 | 		destPath = filepath.Join("/app", filepath.Base(localSrcFile))
 47 | 	} else {
 48 | 		// If provided but doesn't start with /, prepend /app/
 49 | 		if !strings.HasPrefix(destPath, "/") {
 50 | 			destPath = filepath.Join("/app", destPath)
 51 | 		}
 52 | 	}
 53 | 
 54 | 	// Create destination directory in container if it doesn't exist
 55 | 	destDir := filepath.Dir(destPath)
 56 | 	if err := createDirectoryInContainer(ctx, containerID, destDir); err != nil {
 57 | 		return mcp.NewToolResultText(fmt.Sprintf("Error creating destination directory: %v", err)), nil
 58 | 	}
 59 | 
 60 | 	// Copy the file to the container
 61 | 	if err := copyFileToContainer(ctx, containerID, localSrcFile, destPath); err != nil {
 62 | 		return mcp.NewToolResultText(fmt.Sprintf("Error copying file to container: %v", err)), nil
 63 | 	}
 64 | 
 65 | 	return mcp.NewToolResultText(fmt.Sprintf("Successfully copied %s to %s in container %s", localSrcFile, destPath, containerID)), nil
 66 | }
 67 | 
 68 | // createDirectoryInContainer creates a directory in the container if it doesn't exist
 69 | func createDirectoryInContainer(ctx context.Context, containerID string, dirPath string) error {
 70 | 	cli, err := client.NewClientWithOpts(
 71 | 		client.FromEnv,
 72 | 		client.WithAPIVersionNegotiation(),
 73 | 	)
 74 | 	if err != nil {
 75 | 		return fmt.Errorf("failed to create Docker client: %w", err)
 76 | 	}
 77 | 	defer cli.Close()
 78 | 
 79 | 	createDirCmd := []string{"mkdir", "-p", dirPath}
 80 | 	exec, err := cli.ContainerExecCreate(ctx, containerID, container.ExecOptions{
 81 | 		Cmd:          createDirCmd,
 82 | 		AttachStdout: true,
 83 | 		AttachStderr: true,
 84 | 	})
 85 | 	if err != nil {
 86 | 		return fmt.Errorf("failed to create exec: %w", err)
 87 | 	}
 88 | 
 89 | 	if err := cli.ContainerExecStart(ctx, exec.ID, container.ExecStartOptions{}); err != nil {
 90 | 		return fmt.Errorf("failed to start exec: %w", err)
 91 | 	}
 92 | 
 93 | 	return nil
 94 | }
 95 | 
 96 | // copyFileToContainer copies a single file to the container
 97 | func copyFileToContainer(ctx context.Context, containerID string, srcPath string, destPath string) error {
 98 | 	cli, err := client.NewClientWithOpts(
 99 | 		client.FromEnv,
100 | 		client.WithAPIVersionNegotiation(),
101 | 	)
102 | 	if err != nil {
103 | 		return fmt.Errorf("failed to create Docker client: %w", err)
104 | 	}
105 | 	defer cli.Close()
106 | 
107 | 	// Open and stat the source file
108 | 	srcFile, err := os.Open(srcPath)
109 | 	if err != nil {
110 | 		return fmt.Errorf("failed to open source file: %w", err)
111 | 	}
112 | 	defer srcFile.Close()
113 | 
114 | 	srcInfo, err := srcFile.Stat()
115 | 	if err != nil {
116 | 		return fmt.Errorf("failed to stat source file: %w", err)
117 | 	}
118 | 
119 | 	// Create a buffer to write our archive to
120 | 	var buf bytes.Buffer
121 | 
122 | 	// Create a new tar archive
123 | 	tw := tar.NewWriter(&buf)
124 | 
125 | 	// Create tar header
126 | 	header := &tar.Header{
127 | 		Name:    filepath.Base(destPath),
128 | 		Size:    srcInfo.Size(),
129 | 		Mode:    int64(srcInfo.Mode()),
130 | 		ModTime: srcInfo.ModTime(),
131 | 	}
132 | 
133 | 	// Write header
134 | 	if err := tw.WriteHeader(header); err != nil {
135 | 		return fmt.Errorf("failed to write tar header: %w", err)
136 | 	}
137 | 
138 | 	// Copy file content to tar archive
139 | 	if _, err := io.Copy(tw, srcFile); err != nil {
140 | 		return fmt.Errorf("failed to write file content to tar: %w", err)
141 | 	}
142 | 
143 | 	// Close tar writer
144 | 	if err := tw.Close(); err != nil {
145 | 		return fmt.Errorf("failed to close tar writer: %w", err)
146 | 	}
147 | 
148 | 	// Copy the tar archive to the container
149 | 	err = cli.CopyToContainer(ctx, containerID, filepath.Dir(destPath), &buf, container.CopyToContainerOptions{})
150 | 	if err != nil {
151 | 		return fmt.Errorf("failed to copy to container: %w", err)
152 | 	}
153 | 
154 | 	return nil
155 | }
156 | 
```

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

```go
  1 | package tools
  2 | 
  3 | import (
  4 | 	"archive/tar"
  5 | 	"bytes"
  6 | 	"context"
  7 | 	"fmt"
  8 | 	"io"
  9 | 	"os"
 10 | 	"path/filepath"
 11 | 	"strings"
 12 | 	"time"
 13 | 
 14 | 	"github.com/docker/docker/api/types/container"
 15 | 	"github.com/docker/docker/client"
 16 | 	"github.com/mark3labs/mcp-go/mcp"
 17 | )
 18 | 
 19 | // CopyProject copies a local directory to a container's filesystem
 20 | func CopyProject(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
 21 | 	// Extract parameters
 22 | 	containerID, ok := request.Params.Arguments["container_id"].(string)
 23 | 	if !ok || containerID == "" {
 24 | 		return mcp.NewToolResultText("container_id is required"), nil
 25 | 	}
 26 | 
 27 | 	localSrcDir, ok := request.Params.Arguments["local_src_dir"].(string)
 28 | 	if !ok || localSrcDir == "" {
 29 | 		return mcp.NewToolResultText("local_src_dir is required"), nil
 30 | 	}
 31 | 
 32 | 	// Clean and validate the source path
 33 | 	localSrcDir = filepath.Clean(localSrcDir)
 34 | 	info, err := os.Stat(localSrcDir)
 35 | 	if err != nil {
 36 | 		return mcp.NewToolResultText(fmt.Sprintf("Error accessing source directory: %v", err)), nil
 37 | 	}
 38 | 
 39 | 	if !info.IsDir() {
 40 | 		return mcp.NewToolResultText("local_src_dir must be a directory"), nil
 41 | 	}
 42 | 
 43 | 	// Get the destination path (optional parameter)
 44 | 	destDir, ok := request.Params.Arguments["dest_dir"].(string)
 45 | 	if !ok || destDir == "" {
 46 | 		// Default: use the name of the source directory
 47 | 		destDir = filepath.Join("/app", filepath.Base(localSrcDir))
 48 | 	} else {
 49 | 		// If provided but doesn't start with /, prepend /app/
 50 | 		if !strings.HasPrefix(destDir, "/") {
 51 | 			destDir = filepath.Join("/app", destDir)
 52 | 		}
 53 | 	}
 54 | 
 55 | 	// Create tar archive of the source directory
 56 | 	tarBuffer, err := createTarArchive(localSrcDir)
 57 | 	if err != nil {
 58 | 		return mcp.NewToolResultText(fmt.Sprintf("Error creating tar archive: %v", err)), nil
 59 | 	}
 60 | 
 61 | 	// Create a temporary file name for the tar archive in the container
 62 | 	tarFileName := filepath.Join("/tmp", fmt.Sprintf("project_%s.tar", filepath.Base(localSrcDir)))
 63 | 
 64 | 	// Copy the tar archive to the container's temp directory
 65 | 	err = copyToContainer(ctx, containerID, "/tmp", tarBuffer)
 66 | 	if err != nil {
 67 | 		return mcp.NewToolResultText(fmt.Sprintf("Error copying to container: %v", err)), nil
 68 | 	}
 69 | 
 70 | 	// Extract the tar archive in the container
 71 | 	err = extractTarInContainer(ctx, containerID, tarFileName, destDir)
 72 | 	if err != nil {
 73 | 		return mcp.NewToolResultText(fmt.Sprintf("Error extracting archive in container: %v", err)), nil
 74 | 	}
 75 | 
 76 | 	// Clean up the temporary tar file
 77 | 	cleanupCmd := []string{"rm", tarFileName}
 78 | 	if err := executeCommand(ctx, containerID, cleanupCmd); err != nil {
 79 | 		// Just log the error but don't fail the operation
 80 | 		fmt.Printf("Warning: Failed to clean up temporary tar file: %v\n", err)
 81 | 	}
 82 | 
 83 | 	return mcp.NewToolResultText(fmt.Sprintf("Successfully copied %s to %s in container %s", localSrcDir, destDir, containerID)), nil
 84 | }
 85 | 
 86 | // createTarArchive creates a tar archive of the specified source path
 87 | func createTarArchive(srcPath string) (io.Reader, error) {
 88 | 	buf := new(bytes.Buffer)
 89 | 	tw := tar.NewWriter(buf)
 90 | 	defer tw.Close()
 91 | 
 92 | 	srcPath = filepath.Clean(srcPath)
 93 | 	baseDir := filepath.Base(srcPath)
 94 | 
 95 | 	err := filepath.Walk(srcPath, func(file string, fi os.FileInfo, err error) error {
 96 | 		if err != nil {
 97 | 			return err
 98 | 		}
 99 | 
100 | 		// Create tar header
101 | 		header, err := tar.FileInfoHeader(fi, fi.Name())
102 | 		if err != nil {
103 | 			return err
104 | 		}
105 | 
106 | 		// Maintain directory structure relative to the source directory
107 | 		relPath, err := filepath.Rel(srcPath, file)
108 | 		if err != nil {
109 | 			return err
110 | 		}
111 | 
112 | 		if relPath == "." {
113 | 			// Skip the root directory itself
114 | 			return nil
115 | 		}
116 | 
117 | 		header.Name = filepath.Join(baseDir, relPath)
118 | 
119 | 		if err := tw.WriteHeader(header); err != nil {
120 | 			return err
121 | 		}
122 | 
123 | 		// If it's a regular file, write its content
124 | 		if fi.Mode().IsRegular() {
125 | 			f, err := os.Open(file)
126 | 			if err != nil {
127 | 				return err
128 | 			}
129 | 			defer f.Close()
130 | 
131 | 			if _, err := io.Copy(tw, f); err != nil {
132 | 				return err
133 | 			}
134 | 		}
135 | 		return nil
136 | 	})
137 | 
138 | 	if err != nil {
139 | 		return nil, err
140 | 	}
141 | 
142 | 	return buf, nil
143 | }
144 | 
145 | // copyToContainer copies a tar archive to a container
146 | func copyToContainer(ctx context.Context, containerID string, destPath string, tarArchive io.Reader) error {
147 | 	cli, err := client.NewClientWithOpts(
148 | 		client.FromEnv,
149 | 		client.WithAPIVersionNegotiation(),
150 | 	)
151 | 	if err != nil {
152 | 		return fmt.Errorf("failed to create Docker client: %w", err)
153 | 	}
154 | 	defer cli.Close()
155 | 
156 | 	// Make sure the container exists and is running
157 | 	_, err = cli.ContainerInspect(ctx, containerID)
158 | 	if err != nil {
159 | 		return fmt.Errorf("failed to inspect container: %w", err)
160 | 	}
161 | 
162 | 	// Create the destination directory in the container if it doesn't exist
163 | 	createDirCmd := []string{"mkdir", "-p", destPath}
164 | 	if err := executeCommand(ctx, containerID, createDirCmd); err != nil {
165 | 		return fmt.Errorf("failed to create destination directory: %w", err)
166 | 	}
167 | 
168 | 	// Copy the tar archive to the container
169 | 	err = cli.CopyToContainer(ctx, containerID, destPath, tarArchive, container.CopyToContainerOptions{})
170 | 	if err != nil {
171 | 		return fmt.Errorf("failed to copy to container: %w", err)
172 | 	}
173 | 
174 | 	return nil
175 | }
176 | 
177 | // extractTarInContainer extracts a tar archive inside the container
178 | func extractTarInContainer(ctx context.Context, containerID string, tarFilePath string, destPath string) error {
179 | 	// Create the destination directory if it doesn't exist
180 | 	mkdirCmd := []string{"mkdir", "-p", destPath}
181 | 	if err := executeCommand(ctx, containerID, mkdirCmd); err != nil {
182 | 		return fmt.Errorf("failed to create destination directory: %w", err)
183 | 	}
184 | 
185 | 	// Extract the tar archive
186 | 	extractCmd := []string{"tar", "-xf", tarFilePath, "-C", destPath}
187 | 	if err := executeCommand(ctx, containerID, extractCmd); err != nil {
188 | 		return fmt.Errorf("failed to extract tar archive: %w", err)
189 | 	}
190 | 
191 | 	return nil
192 | }
193 | 
194 | // executeCommand runs a command in a container and waits for it to complete
195 | func executeCommand(ctx context.Context, containerID string, cmd []string) error {
196 | 	cli, err := client.NewClientWithOpts(
197 | 		client.FromEnv,
198 | 		client.WithAPIVersionNegotiation(),
199 | 	)
200 | 	if err != nil {
201 | 		return fmt.Errorf("failed to create Docker client: %w", err)
202 | 	}
203 | 	defer cli.Close()
204 | 
205 | 	// Create the exec configuration
206 | 	exec, err := cli.ContainerExecCreate(ctx, containerID, container.ExecOptions{
207 | 		Cmd:          cmd,
208 | 		AttachStdout: true,
209 | 		AttachStderr: true,
210 | 	})
211 | 	if err != nil {
212 | 		return fmt.Errorf("failed to create exec: %w", err)
213 | 	}
214 | 
215 | 	// Start the exec command
216 | 	if err := cli.ContainerExecStart(ctx, exec.ID, container.ExecStartOptions{}); err != nil {
217 | 		return fmt.Errorf("failed to start exec: %w", err)
218 | 	}
219 | 
220 | 	// Wait for the command to complete
221 | 	for {
222 | 		inspect, err := cli.ContainerExecInspect(ctx, exec.ID)
223 | 		if err != nil {
224 | 			return fmt.Errorf("failed to inspect exec: %w", err)
225 | 		}
226 | 		if !inspect.Running {
227 | 			if inspect.ExitCode != 0 {
228 | 				return fmt.Errorf("command exited with code %d", inspect.ExitCode)
229 | 			}
230 | 			break
231 | 		}
232 | 		// Small sleep to avoid hammering the Docker API
233 | 		time.Sleep(100 * time.Millisecond)
234 | 	}
235 | 
236 | 	return nil
237 | }
238 | 
```

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

```go
  1 | package main
  2 | 
  3 | import (
  4 | 	"context"
  5 | 	"flag"
  6 | 	"fmt"
  7 | 	"log"
  8 | 	"os"
  9 | 
 10 | 	"github.com/Automata-Labs-team/code-sandbox-mcp/installer"
 11 | 	"github.com/Automata-Labs-team/code-sandbox-mcp/resources"
 12 | 	"github.com/Automata-Labs-team/code-sandbox-mcp/tools"
 13 | 	"github.com/mark3labs/mcp-go/mcp"
 14 | 	"github.com/mark3labs/mcp-go/server"
 15 | )
 16 | 
 17 | func init() {
 18 | 	// Check for --install flag
 19 | 	installFlag := flag.Bool("install", false, "Add this binary to Claude Desktop config")
 20 | 	noUpdateFlag := flag.Bool("no-update", false, "Disable auto-update check")
 21 | 	flag.Parse()
 22 | 
 23 | 	if *installFlag {
 24 | 		if err := installer.InstallConfig(); err != nil {
 25 | 			fmt.Fprintf(os.Stderr, "Error: %v\n", err)
 26 | 			os.Exit(1)
 27 | 		}
 28 | 		os.Exit(0)
 29 | 	}
 30 | 
 31 | 	// Check for updates unless disabled
 32 | 	if !*noUpdateFlag {
 33 | 		if hasUpdate, downloadURL, err := installer.CheckForUpdate(); err != nil {
 34 | 			fmt.Fprintf(os.Stderr, "Warning: Failed to check for updates: %v\n", err)
 35 | 			os.Exit(1)
 36 | 		} else if hasUpdate {
 37 | 			fmt.Println("Updating to new version...")
 38 | 			if err := installer.PerformUpdate(downloadURL); err != nil {
 39 | 				fmt.Fprintf(os.Stderr, "Warning: Failed to update: %v\n", err)
 40 | 			}
 41 | 			fmt.Println("Update complete. Restarting...")
 42 | 		}
 43 | 	}
 44 | }
 45 | 
 46 | func main() {
 47 | 	port := flag.String("port", "9520", "Port to listen on")
 48 | 	transport := flag.String("transport", "stdio", "Transport to use (stdio, sse)")
 49 | 	flag.Parse()
 50 | 	s := server.NewMCPServer("code-sandbox-mcp", "v1.0.0", server.WithLogging(), server.WithResourceCapabilities(true, true), server.WithPromptCapabilities(false))
 51 | 	s.AddNotificationHandler("notifications/error", handleNotification)
 52 | 	// Register tools
 53 | 	// Initialize a new compute environment for code execution
 54 | 	initializeTool := mcp.NewTool("sandbox_initialize",
 55 | 		mcp.WithDescription(
 56 | 			"Initialize a new compute environment for code execution. \n"+
 57 | 				"Creates a container based on the specified Docker image or defaults to a slim debian image with Python. \n"+
 58 | 				"Returns a container_id that can be used with other tools to interact with this environment.",
 59 | 		),
 60 | 		mcp.WithString("image",
 61 | 			mcp.Description("Docker image to use as the base environment (e.g., 'python:3.12-slim-bookworm')"),
 62 | 			mcp.DefaultString("python:3.12-slim-bookworm"),
 63 | 		),
 64 | 	)
 65 | 
 66 | 	// Copy a directory to the sandboxed filesystem
 67 | 	copyProjectTool := mcp.NewTool("copy_project",
 68 | 		mcp.WithDescription(
 69 | 			"Copy a directory to the sandboxed filesystem. \n"+
 70 | 				"Transfers a local directory and its contents to the specified container.",
 71 | 		),
 72 | 		mcp.WithString("container_id",
 73 | 			mcp.Required(),
 74 | 			mcp.Description("ID of the container returned from the initialize call"),
 75 | 		),
 76 | 		mcp.WithString("local_src_dir",
 77 | 			mcp.Required(),
 78 | 			mcp.Description("Path to a directory in the local file system"),
 79 | 		),
 80 | 		mcp.WithString("dest_dir",
 81 | 			mcp.Description("Path to save the src directory in the sandbox environment, relative to the container working dir"),
 82 | 		),
 83 | 	)
 84 | 
 85 | 	// Write a file to the sandboxed filesystem
 86 | 	writeFileTool := mcp.NewTool("write_file_sandbox",
 87 | 		mcp.WithDescription(
 88 | 			"Write a file to the sandboxed filesystem. \n"+
 89 | 				"Creates a file with the specified content in the container.",
 90 | 		),
 91 | 		mcp.WithString("container_id",
 92 | 			mcp.Required(),
 93 | 			mcp.Description("ID of the container returned from the initialize call"),
 94 | 		),
 95 | 		mcp.WithString("file_name",
 96 | 			mcp.Required(),
 97 | 			mcp.Description("Name of the file to create"),
 98 | 		),
 99 | 		mcp.WithString("file_contents",
100 | 			mcp.Required(),
101 | 			mcp.Description("Contents to write to the file"),
102 | 		),
103 | 		mcp.WithString("dest_dir",
104 | 			mcp.Description("Directory to create the file in, relative to the container working dir"),
105 | 			mcp.Description("Default: ${WORKDIR}"),
106 | 		),
107 | 	)
108 | 
109 | 	// Execute commands in the sandboxed environment
110 | 	execTool := mcp.NewTool("sandbox_exec",
111 | 		mcp.WithDescription(
112 | 			"Execute commands in the sandboxed environment. \n"+
113 | 				"Runs one or more shell commands in the specified container and returns the output.",
114 | 		),
115 | 		mcp.WithString("container_id",
116 | 			mcp.Required(),
117 | 			mcp.Description("ID of the container returned from the initialize call"),
118 | 		),
119 | 		mcp.WithArray("commands",
120 | 			mcp.Required(),
121 | 			mcp.Description("List of command(s) to run in the sandboxed environment"),
122 | 			mcp.Description("Example: [\"apt-get update\", \"pip install numpy\", \"python script.py\"]"),
123 | 		),
124 | 	)
125 | 
126 | 	// Copy a single file to the sandboxed filesystem
127 | 	copyFileTool := mcp.NewTool("copy_file",
128 | 		mcp.WithDescription(
129 | 			"Copy a single file to the sandboxed filesystem. \n"+
130 | 				"Transfers a local file to the specified container.",
131 | 		),
132 | 		mcp.WithString("container_id",
133 | 			mcp.Required(),
134 | 			mcp.Description("ID of the container returned from the initialize call"),
135 | 		),
136 | 		mcp.WithString("local_src_file",
137 | 			mcp.Required(),
138 | 			mcp.Description("Path to a file in the local file system"),
139 | 		),
140 | 		mcp.WithString("dest_path",
141 | 			mcp.Description("Path to save the file in the sandbox environment, relative to the container working dir"),
142 | 		),
143 | 	)
144 | 
145 | 	// Copy a file from container to local filesystem
146 | 	copyFileFromContainerTool := mcp.NewTool("copy_file_from_sandbox",
147 | 		mcp.WithDescription(
148 | 			"Copy a single file from the sandboxed filesystem to the local filesystem. \n"+
149 | 				"Transfers a file from the specified container to the local system.",
150 | 		),
151 | 		mcp.WithString("container_id",
152 | 			mcp.Required(),
153 | 			mcp.Description("ID of the container to copy from"),
154 | 		),
155 | 		mcp.WithString("container_src_path",
156 | 			mcp.Required(),
157 | 			mcp.Description("Path to the file in the container to copy"),
158 | 		),
159 | 		mcp.WithString("local_dest_path",
160 | 			mcp.Description("Path where to save the file in the local filesystem"),
161 | 			mcp.Description("Default: Current directory with the same filename"),
162 | 		),
163 | 	)
164 | 
165 | 	// Stop and remove a container
166 | 	stopContainerTool := mcp.NewTool("sandbox_stop",
167 | 		mcp.WithDescription(
168 | 			"Stop and remove a running container sandbox. \n"+
169 | 				"Gracefully stops the specified container and removes it along with its volumes.",
170 | 		),
171 | 		mcp.WithString("container_id",
172 | 			mcp.Required(),
173 | 			mcp.Description("ID of the container to stop and remove"),
174 | 		),
175 | 	)
176 | 
177 | 	// Register dynamic resource for container logs
178 | 	// Dynamic resource example - Container Logs by ID
179 | 	containerLogsTemplate := mcp.NewResourceTemplate(
180 | 		"containers://{id}/logs",
181 | 		"Container Logs",
182 | 		mcp.WithTemplateDescription("Returns all container logs from the specified container. Logs are returned as a single text resource."),
183 | 		mcp.WithTemplateMIMEType("text/plain"),
184 | 		mcp.WithTemplateAnnotations([]mcp.Role{mcp.RoleAssistant, mcp.RoleUser}, 0.5),
185 | 	)
186 | 
187 | 	s.AddResourceTemplate(containerLogsTemplate, resources.GetContainerLogs)
188 | 	s.AddTool(initializeTool, tools.InitializeEnvironment)
189 | 	s.AddTool(copyProjectTool, tools.CopyProject)
190 | 	s.AddTool(writeFileTool, tools.WriteFile)
191 | 	s.AddTool(execTool, tools.Exec)
192 | 	s.AddTool(copyFileTool, tools.CopyFile)
193 | 	s.AddTool(copyFileFromContainerTool, tools.CopyFileFromContainer)
194 | 	s.AddTool(stopContainerTool, tools.StopContainer)
195 | 	switch *transport {
196 | 	case "stdio":
197 | 		if err := server.ServeStdio(s); err != nil {
198 | 			s.SendNotificationToClient(context.Background(), "notifications/error", map[string]interface{}{
199 | 				"message": fmt.Sprintf("Failed to start stdio server: %v", err),
200 | 			})
201 | 		}
202 | 	case "sse":
203 | 		sseServer := server.NewSSEServer(s)
204 | 		if err := sseServer.Start(fmt.Sprintf(":%s", *port)); err != nil {
205 | 			s.SendNotificationToClient(context.Background(), "notifications/error", map[string]interface{}{
206 | 				"message": fmt.Sprintf("Failed to start SSE server: %v", err),
207 | 			})
208 | 		}
209 | 	default:
210 | 		s.SendNotificationToClient(context.Background(), "notifications/error", map[string]interface{}{
211 | 			"message": fmt.Sprintf("Invalid transport: %s", *transport),
212 | 		})
213 | 	}
214 | }
215 | 
216 | func handleNotification(
217 | 	ctx context.Context,
218 | 	notification mcp.JSONRPCNotification,
219 | ) {
220 | 	log.Printf("Received notification from client: %s", notification.Method)
221 | }
222 | 
```

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

```yaml
  1 | name: Release
  2 | 
  3 | on:
  4 |   push:
  5 |     branches:
  6 |       - main
  7 |   workflow_dispatch:
  8 |     inputs:
  9 |       version_increment:
 10 |         description: 'Version increment type'
 11 |         required: true
 12 |         default: 'patch'
 13 |         type: choice
 14 |         options:
 15 |           - patch
 16 |           - minor
 17 |           - major
 18 |       prerelease:
 19 |         description: 'Mark as prerelease'
 20 |         required: true
 21 |         default: false
 22 |         type: boolean
 23 | 
 24 | jobs:
 25 |   release:
 26 |     runs-on: ubuntu-latest
 27 |     permissions:
 28 |       contents: write
 29 |     steps:
 30 |       - name: Checkout code
 31 |         uses: actions/checkout@v4
 32 |         with:
 33 |           fetch-depth: 0
 34 | 
 35 |       - name: Set up Go
 36 |         uses: actions/setup-go@v4
 37 |         with:
 38 |           go-version: '1.21'
 39 |           cache: true
 40 | 
 41 |       - name: Get version and generate changelog
 42 |         id: get_version
 43 |         run: |
 44 |           # Get the latest tag or use v0.0.0 if no tags exist
 45 |           LATEST_TAG=$(git describe --tags --abbrev=0 2>/dev/null || echo "v0.0.0")
 46 |           
 47 |           # Remove 'v' prefix for version calculations
 48 |           VERSION=${LATEST_TAG#v}
 49 |           MAJOR=$(echo $VERSION | cut -d. -f1)
 50 |           MINOR=$(echo $VERSION | cut -d. -f2)
 51 |           PATCH=$(echo $VERSION | cut -d. -f3)
 52 |           
 53 |           # Handle version increment based on input or default to patch
 54 |           if [[ "${{ github.event_name }}" == "workflow_dispatch" ]]; then
 55 |             case "${{ inputs.version_increment }}" in
 56 |               "major")
 57 |                 MAJOR=$((MAJOR + 1))
 58 |                 MINOR=0
 59 |                 PATCH=0
 60 |                 ;;
 61 |               "minor")
 62 |                 MINOR=$((MINOR + 1))
 63 |                 PATCH=0
 64 |                 ;;
 65 |               "patch")
 66 |                 PATCH=$((PATCH + 1))
 67 |                 ;;
 68 |             esac
 69 |           else
 70 |             # Auto increment patch version for push events
 71 |             PATCH=$((PATCH + 1))
 72 |           fi
 73 |           
 74 |           NEW_VERSION="${MAJOR}.${MINOR}.${PATCH}"
 75 |           echo "version=$NEW_VERSION" >> $GITHUB_OUTPUT
 76 |           
 77 |           # Function to extract and format issue/PR references
 78 |           format_references() {
 79 |             local msg="$1"
 80 |             # Look for common issue/PR reference patterns (#123, GH-123, fixes #123, etc.)
 81 |             local refs=$(echo "$msg" | grep -o -E '(#[0-9]+|GH-[0-9]+)' || true)
 82 |             if [ ! -z "$refs" ]; then
 83 |               local formatted_refs=""
 84 |               while read -r ref; do
 85 |                 # Remove any prefix and get just the number
 86 |                 local num=$(echo "$ref" | grep -o '[0-9]\+')
 87 |                 formatted_refs="$formatted_refs [${ref}](${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/issues/${num})"
 88 |               done <<< "$refs"
 89 |               echo "$formatted_refs"
 90 |             fi
 91 |           }
 92 | 
 93 |           # Function to format commit messages by type
 94 |           format_commits() {
 95 |             local pattern=$1
 96 |             local title=$2
 97 |             # Include author, date, and full commit info
 98 |             local commits=$(git log --pretty=format:"- %s ([%h](${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/commit/%H)) - %an, %as" ${LATEST_TAG}..HEAD | grep -E "^$pattern" || true)
 99 |             if [ ! -z "$commits" ]; then
100 |               echo "### $title"
101 |               while IFS= read -r commit; do
102 |                 if [ ! -z "$commit" ]; then
103 |                   # Extract the commit message for issue/PR reference search
104 |                   local commit_msg=$(echo "$commit" | sed -E 's/^- ([^(]+).*/\1/')
105 |                   local refs=$(format_references "$commit_msg")
106 |                   if [ ! -z "$refs" ]; then
107 |                     # Add references to the end of the commit line
108 |                     echo "$commit - References: $refs"
109 |                   else
110 |                     echo "$commit"
111 |                   fi
112 |                 fi
113 |               done <<< "$commits" | sed 's/^[^:]*: //'
114 |               echo ""
115 |             fi
116 |           }
117 |           
118 |           # Generate categorized changelog
119 |           if [ "$LATEST_TAG" != "v0.0.0" ]; then
120 |             CHANGES=$(
121 |               {
122 |                 echo "## 📋 Changelog"
123 |                 echo "$(git log -1 --pretty=format:"Generated on: %ad" --date=format:"%Y-%m-%d %H:%M:%S %Z")"
124 |                 echo ""
125 |                 format_commits "feat(\w*)?:" "🚀 New Features"
126 |                 format_commits "fix(\w*)?:" "🐛 Bug Fixes"
127 |                 format_commits "perf(\w*)?:" "⚡ Performance Improvements"
128 |                 format_commits "refactor(\w*)?:" "♻️ Refactoring"
129 |                 format_commits "test(\w*)?:" "🧪 Testing"
130 |                 format_commits "docs(\w*)?:" "📚 Documentation"
131 |                 format_commits "style(\w*)?:" "💎 Styling"
132 |                 format_commits "chore(\w*)?:" "🔧 Maintenance"
133 |                 
134 |                 # Get other commits that don't match conventional commit format
135 |                 echo "### 🔍 Other Changes"
136 |                 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
137 |                   if [ ! -z "$commit" ]; then
138 |                     local commit_msg=$(echo "$commit" | sed -E 's/^- ([^(]+).*/\1/')
139 |                     local refs=$(format_references "$commit_msg")
140 |                     if [ ! -z "$refs" ]; then
141 |                       echo "$commit - References: $refs"
142 |                     else
143 |                       echo "$commit"
144 |                     fi
145 |                   fi
146 |                 done || true
147 |               } | sed '/^$/d'
148 |             )
149 |           else
150 |             # For first release, include all commits with metadata and links
151 |             CHANGES=$(
152 |               {
153 |                 echo "## 📋 Initial Release Changelog"
154 |                 echo "$(git log -1 --pretty=format:"Generated on: %ad" --date=format:"%Y-%m-%d %H:%M:%S %Z")"
155 |                 echo ""
156 |                 git log --pretty=format:"- %s ([%h](${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/commit/%H)) - %an, %as" | while IFS= read -r commit; do
157 |                   if [ ! -z "$commit" ]; then
158 |                     local commit_msg=$(echo "$commit" | sed -E 's/^- ([^(]+).*/\1/')
159 |                     local refs=$(format_references "$commit_msg")
160 |                     if [ ! -z "$refs" ]; then
161 |                       echo "$commit - References: $refs"
162 |                     else
163 |                       echo "$commit"
164 |                     fi
165 |                   fi
166 |                 done
167 |               }
168 |             )
169 |           fi
170 |           
171 |           # Save changes to output
172 |           echo "changes<<EOF" >> $GITHUB_OUTPUT
173 |           echo "$CHANGES" >> $GITHUB_OUTPUT
174 |           echo "EOF" >> $GITHUB_OUTPUT
175 | 
176 |       - name: Build binaries
177 |         run: |
178 |           chmod +x build.sh
179 |           ./build.sh --release --version ${{ steps.get_version.outputs.version }}
180 | 
181 |       - name: Generate checksums
182 |         run: |
183 |           cd bin
184 |           echo "### 🔒 SHA256 Checksums" > checksums.txt
185 |           echo '```' >> checksums.txt
186 |           sha256sum code-sandbox-mcp-* >> checksums.txt
187 |           echo '```' >> checksums.txt
188 | 
189 |       - name: Create Release
190 |         id: create_release
191 |         uses: softprops/action-gh-release@v1
192 |         with:
193 |           tag_name: v${{ steps.get_version.outputs.version }}
194 |           name: Release v${{ steps.get_version.outputs.version }}
195 |           draft: false
196 |           prerelease: ${{ github.event.inputs.prerelease == 'true' }}
197 |           files: |
198 |             bin/code-sandbox-mcp-linux-amd64
199 |             bin/code-sandbox-mcp-linux-arm64
200 |             bin/code-sandbox-mcp-darwin-amd64
201 |             bin/code-sandbox-mcp-darwin-arm64
202 |             bin/code-sandbox-mcp-windows-amd64.exe
203 |             bin/code-sandbox-mcp-windows-arm64.exe
204 |           body: |
205 |             ## 🎉 Release v${{ steps.get_version.outputs.version }}
206 |             
207 |             ${{ steps.get_version.outputs.changes }}
208 |             
209 |             ### 📦 Included Binaries
210 |             - 🐧 Linux (amd64, arm64)
211 |             - 🍎 macOS (amd64, arm64)
212 |             - 🪟 Windows (amd64, arm64)
213 |             
214 |             $(cat bin/checksums.txt) 
```