├── .github └── workflows │ └── release.yml ├── .gitignore ├── DEVELOPMENT.md ├── Dockerfile ├── LICENSE ├── README.md ├── build.sh ├── install.ps1 ├── install.sh ├── 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 /.github/workflows/release.yml: -------------------------------------------------------------------------------- 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<> $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) -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | venv/ 2 | .env 3 | bin/ 4 | .DS_Store 5 | # Test directory 6 | test_code/* 7 | src/code-sandbox-mcp/vendor 8 | -------------------------------------------------------------------------------- /DEVELOPMENT.md: -------------------------------------------------------------------------------- 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 ` | 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 | ``` -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Automata Labs 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 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 | -------------------------------------------------------------------------------- /build.sh: -------------------------------------------------------------------------------- 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.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." -------------------------------------------------------------------------------- /install.sh: -------------------------------------------------------------------------------- 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." -------------------------------------------------------------------------------- /smithery.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 | -------------------------------------------------------------------------------- /src/code-sandbox-mcp/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/Automata-Labs-team/code-sandbox-mcp 2 | 3 | go 1.24.0 4 | 5 | require ( 6 | github.com/docker/docker v28.0.2+incompatible 7 | github.com/mark3labs/mcp-go v0.15.0 8 | ) 9 | 10 | require ( 11 | github.com/containerd/log v0.1.0 // indirect 12 | github.com/moby/term v0.5.2 // indirect 13 | github.com/morikuni/aec v1.0.0 // indirect 14 | github.com/yosida95/uritemplate/v3 v3.0.2 // indirect 15 | go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.35.0 // indirect 16 | golang.org/x/time v0.11.0 // indirect 17 | gotest.tools/v3 v3.5.2 // indirect 18 | ) 19 | 20 | require ( 21 | github.com/Microsoft/go-winio v0.6.2 // indirect 22 | github.com/distribution/reference v0.6.0 // indirect 23 | github.com/docker/go-connections v0.5.0 // indirect 24 | github.com/docker/go-units v0.5.0 // indirect 25 | github.com/felixge/httpsnoop v1.0.4 // indirect 26 | github.com/go-logr/logr v1.4.2 // indirect 27 | github.com/go-logr/stdr v1.2.2 // indirect 28 | github.com/gogo/protobuf v1.3.2 // indirect 29 | github.com/google/uuid v1.6.0 // indirect 30 | github.com/moby/docker-image-spec v1.3.1 // indirect 31 | github.com/opencontainers/go-digest v1.0.0 // indirect 32 | github.com/opencontainers/image-spec v1.1.1 // indirect 33 | github.com/pkg/errors v0.9.1 // indirect 34 | go.opentelemetry.io/auto/sdk v1.1.0 // indirect 35 | go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0 // indirect 36 | go.opentelemetry.io/otel v1.35.0 // indirect 37 | go.opentelemetry.io/otel/metric v1.35.0 // indirect 38 | go.opentelemetry.io/otel/trace v1.35.0 // indirect 39 | golang.org/x/sys v0.31.0 // indirect 40 | ) 41 | -------------------------------------------------------------------------------- /src/code-sandbox-mcp/go.sum: -------------------------------------------------------------------------------- 1 | github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c h1:udKWzYgxTojEKWjV8V+WSxDXJ4NFATAsZjh8iIbsQIg= 2 | github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= 3 | github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= 4 | github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= 5 | github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= 6 | github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= 7 | github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= 8 | github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo= 9 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 10 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 11 | github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk= 12 | github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= 13 | github.com/docker/docker v28.0.2+incompatible h1:9BILleFwug5FSSqWBgVevgL3ewDJfWWWyZVqlDMttE8= 14 | github.com/docker/docker v28.0.2+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= 15 | github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c= 16 | github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc= 17 | github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= 18 | github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= 19 | github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= 20 | github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= 21 | github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= 22 | github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= 23 | github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= 24 | github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= 25 | github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= 26 | github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= 27 | github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= 28 | github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= 29 | github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= 30 | github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= 31 | github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 32 | github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.1 h1:e9Rjr40Z98/clHv5Yg79Is0NtosR5LXRvdr7o/6NwbA= 33 | github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.1/go.mod h1:tIxuGz/9mpox++sgp9fJjHO0+q1X9/UOWd798aAm22M= 34 | github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= 35 | github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= 36 | github.com/mark3labs/mcp-go v0.15.0 h1:lViiC4dk6chJHZccezaTzZLMOQVUXJDGNQPtzExr5NQ= 37 | github.com/mark3labs/mcp-go v0.15.0/go.mod h1:xBB350hekQsJAK7gJAii8bcEoWemboLm2mRm5/+KBaU= 38 | github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0= 39 | github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo= 40 | github.com/moby/term v0.5.2 h1:6qk3FJAFDs6i/q3W/pQ97SX192qKfZgGjCQqfCJkgzQ= 41 | github.com/moby/term v0.5.2/go.mod h1:d3djjFCrjnB+fl8NJux+EJzu0msscUP+f8it8hPkFLc= 42 | github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= 43 | github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= 44 | github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= 45 | github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= 46 | github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040= 47 | github.com/opencontainers/image-spec v1.1.1/go.mod h1:qpqAh3Dmcf36wStyyWU+kCeDgrGnAve2nCC8+7h8Q0M= 48 | github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= 49 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 50 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 51 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 52 | github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= 53 | github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= 54 | github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= 55 | github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= 56 | github.com/yosida95/uritemplate/v3 v3.0.2 h1:Ed3Oyj9yrmi9087+NczuL5BwkIc4wvTb5zIM+UJPGz4= 57 | github.com/yosida95/uritemplate/v3 v3.0.2/go.mod h1:ILOh0sOhIJR3+L/8afwt/kE++YT040gmv5BQTMR2HP4= 58 | github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 59 | github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 60 | go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= 61 | go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= 62 | go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0 h1:sbiXRNDSWJOTobXh5HyQKjq6wUC5tNybqjIqDpAY4CU= 63 | go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0/go.mod h1:69uWxva0WgAA/4bu2Yy70SLDBwZXuQ6PbBpbsa5iZrQ= 64 | go.opentelemetry.io/otel v1.35.0 h1:xKWKPxrxB6OtMCbmMY021CqC45J+3Onta9MqjhnusiQ= 65 | go.opentelemetry.io/otel v1.35.0/go.mod h1:UEqy8Zp11hpkUrL73gSlELM0DupHoiq72dR+Zqel/+Y= 66 | go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.35.0 h1:1fTNlAIJZGWLP5FVu0fikVry1IsiUnXjf7QFvoNN3Xw= 67 | go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.35.0/go.mod h1:zjPK58DtkqQFn+YUMbx0M2XV3QgKU0gS9LeGohREyK4= 68 | go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.35.0 h1:xJ2qHD0C1BeYVTLLR9sX12+Qb95kfeD/byKj6Ky1pXg= 69 | go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.35.0/go.mod h1:u5BF1xyjstDowA1R5QAO9JHzqK+ublenEW/dyqTjBVk= 70 | go.opentelemetry.io/otel/metric v1.35.0 h1:0znxYu2SNyuMSQT4Y9WDWej0VpcsxkuklLa4/siN90M= 71 | go.opentelemetry.io/otel/metric v1.35.0/go.mod h1:nKVFgxBZ2fReX6IlyW28MgZojkoAkJGaE8CpgeAU3oE= 72 | go.opentelemetry.io/otel/sdk v1.35.0 h1:iPctf8iprVySXSKJffSS79eOjl9pvxV9ZqOWT0QejKY= 73 | go.opentelemetry.io/otel/sdk v1.35.0/go.mod h1:+ga1bZliga3DxJ3CQGg3updiaAJoNECOgJREo9KHGQg= 74 | go.opentelemetry.io/otel/sdk/metric v1.35.0 h1:1RriWBmCKgkeHEhM7a2uMjMUfP7MsOF5JpUCaEqEI9o= 75 | go.opentelemetry.io/otel/sdk/metric v1.35.0/go.mod h1:is6XYCUMpcKi+ZsOvfluY5YstFnhW0BidkR+gL+qN+w= 76 | go.opentelemetry.io/otel/trace v1.35.0 h1:dPpEfJu1sDIqruz7BHFG3c7528f6ddfSWfFDVt/xgMs= 77 | go.opentelemetry.io/otel/trace v1.35.0/go.mod h1:WUk7DtFp1Aw2MkvqGdwiXYDZZNvA/1J8o6xRXLrIkyc= 78 | go.opentelemetry.io/proto/otlp v1.5.0 h1:xJvq7gMzB31/d406fB8U5CBdyQGw4P399D1aQWU/3i4= 79 | go.opentelemetry.io/proto/otlp v1.5.0/go.mod h1:keN8WnHxOy8PG0rQZjJJ5A2ebUoafqWp0eVQ4yIXvJ4= 80 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 81 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 82 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 83 | golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 84 | golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 85 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 86 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 87 | golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 88 | golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= 89 | golang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8= 90 | golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk= 91 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 92 | golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 93 | golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 94 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 95 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 96 | golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 97 | golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= 98 | golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= 99 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 100 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 101 | golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM= 102 | golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY= 103 | golang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0= 104 | golang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= 105 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 106 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 107 | golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= 108 | golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= 109 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 110 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 111 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 112 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 113 | google.golang.org/genproto/googleapis/api v0.0.0-20250218202821-56aae31c358a h1:nwKuGPlUAt+aR+pcrkfFRrTU1BVrSmYyYMxYbUIVHr0= 114 | google.golang.org/genproto/googleapis/api v0.0.0-20250218202821-56aae31c358a/go.mod h1:3kWAYMk1I75K4vykHtKt2ycnOgpA6974V7bREqbsenU= 115 | google.golang.org/genproto/googleapis/rpc v0.0.0-20250218202821-56aae31c358a h1:51aaUVRocpvUOSQKM6Q7VuoaktNIaMCLuhZB6DKksq4= 116 | google.golang.org/genproto/googleapis/rpc v0.0.0-20250218202821-56aae31c358a/go.mod h1:uRxBH1mhmO8PGhU89cMcHaXKZqO+OfakD8QQO0oYwlQ= 117 | google.golang.org/grpc v1.71.0 h1:kF77BGdPTQ4/JZWMlb9VpJ5pa25aqvVqogsxNHHdeBg= 118 | google.golang.org/grpc v1.71.0/go.mod h1:H0GRtasmQOh9LkFoCPDu3ZrwUtD1YGE+b2vYBYd/8Ec= 119 | google.golang.org/protobuf v1.36.5 h1:tPhr+woSbjfYvY6/GPufUoYizxw1cF/yFoxJ2fmpwlM= 120 | google.golang.org/protobuf v1.36.5/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= 121 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 122 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 123 | gotest.tools/v3 v3.5.2 h1:7koQfIKdy+I8UTetycgUqXWSDwpgv193Ka+qRsmBY8Q= 124 | gotest.tools/v3 v3.5.2/go.mod h1:LtdLGcnqToBH83WByAAi/wiwSFCArdFIUV/xxN4pcjA= 125 | -------------------------------------------------------------------------------- /src/code-sandbox-mcp/installer/install.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 | } -------------------------------------------------------------------------------- /src/code-sandbox-mcp/installer/update.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/main.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 | -------------------------------------------------------------------------------- /src/code-sandbox-mcp/resources/container_logs.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/copy-file-from-container.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/copy-file.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: -------------------------------------------------------------------------------- 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/tools/exec.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/initialize.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 | -------------------------------------------------------------------------------- /src/code-sandbox-mcp/tools/stop-container.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/write-file.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 | -------------------------------------------------------------------------------- /test/go/go.mod: -------------------------------------------------------------------------------- 1 | module test 2 | 3 | go 1.23.3 4 | 5 | require github.com/docker/cli v27.5.1+incompatible 6 | 7 | require ( 8 | github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c // indirect 9 | github.com/Microsoft/go-winio v0.4.14 // indirect 10 | github.com/beorn7/perks v0.0.0-20150223135152-b965b613227f // indirect 11 | github.com/cenkalti/backoff/v4 v4.3.0 // indirect 12 | github.com/containerd/log v0.1.0 // indirect 13 | github.com/distribution/reference v0.6.0 // indirect 14 | github.com/docker/distribution v2.8.3+incompatible // indirect 15 | github.com/docker/docker v27.5.1+incompatible // indirect 16 | github.com/docker/docker-credential-helpers v0.8.2 // indirect 17 | github.com/docker/go v1.5.1-1.0.20160303222718-d30aec9fd63c // indirect 18 | github.com/docker/go-connections v0.5.0 // indirect 19 | github.com/docker/go-metrics v0.0.0-20180209012529-399ea8c73916 // indirect 20 | github.com/docker/go-units v0.5.0 // indirect 21 | github.com/felixge/httpsnoop v1.0.4 // indirect 22 | github.com/fvbommel/sortorder v1.1.0 // indirect 23 | github.com/go-logr/logr v1.4.2 // indirect 24 | github.com/go-logr/stdr v1.2.2 // indirect 25 | github.com/gogo/protobuf v1.0.0 // indirect 26 | github.com/golang/protobuf v1.5.4 // indirect 27 | github.com/google/uuid v1.6.0 // indirect 28 | github.com/gorilla/mux v1.7.0 // indirect 29 | github.com/grpc-ecosystem/grpc-gateway/v2 v2.25.1 // indirect 30 | github.com/inconshreveable/mousetrap v1.1.0 // indirect 31 | github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect 32 | github.com/miekg/pkcs11 v1.0.2 // indirect 33 | github.com/moby/docker-image-spec v1.3.1 // indirect 34 | github.com/moby/sys/sequential v0.6.0 // indirect 35 | github.com/moby/term v0.5.2 // indirect 36 | github.com/morikuni/aec v1.0.0 // indirect 37 | github.com/opencontainers/go-digest v1.0.0 // indirect 38 | github.com/opencontainers/image-spec v1.1.0 // indirect 39 | github.com/pkg/errors v0.9.1 // indirect 40 | github.com/prometheus/client_golang v0.9.0-pre1.0.20180209125602-c332b6f63c06 // indirect 41 | github.com/prometheus/client_model v0.0.0-20171117100541-99fa1f4be8e5 // indirect 42 | github.com/prometheus/common v0.0.0-20180110214958-89604d197083 // indirect 43 | github.com/prometheus/procfs v0.0.0-20180125133057-cb4147076ac7 // indirect 44 | github.com/sirupsen/logrus v1.9.3 // indirect 45 | github.com/spf13/cobra v1.8.1 // indirect 46 | github.com/spf13/pflag v1.0.6 // indirect 47 | github.com/theupdateframework/notary v0.7.0 // indirect 48 | go.opentelemetry.io/auto/sdk v1.1.0 // indirect 49 | go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.59.0 // indirect 50 | go.opentelemetry.io/otel v1.34.0 // indirect 51 | go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.34.0 // indirect 52 | go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.34.0 // indirect 53 | go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.34.0 // indirect 54 | go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.34.0 // indirect 55 | go.opentelemetry.io/otel/metric v1.34.0 // indirect 56 | go.opentelemetry.io/otel/sdk v1.34.0 // indirect 57 | go.opentelemetry.io/otel/sdk/metric v1.34.0 // indirect 58 | go.opentelemetry.io/otel/trace v1.34.0 // indirect 59 | go.opentelemetry.io/proto/otlp v1.5.0 // indirect 60 | golang.org/x/crypto v0.32.0 // indirect 61 | golang.org/x/net v0.34.0 // indirect 62 | golang.org/x/sys v0.29.0 // indirect 63 | golang.org/x/term v0.28.0 // indirect 64 | golang.org/x/text v0.21.0 // indirect 65 | golang.org/x/time v0.10.0 // indirect 66 | google.golang.org/genproto/googleapis/api v0.0.0-20250115164207-1a7da9e5054f // indirect 67 | google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f // indirect 68 | google.golang.org/grpc v1.69.4 // indirect 69 | google.golang.org/protobuf v1.36.3 // indirect 70 | gotest.tools/v3 v3.5.2 // indirect 71 | ) 72 | -------------------------------------------------------------------------------- /test/go/go.sum: -------------------------------------------------------------------------------- 1 | github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c h1:udKWzYgxTojEKWjV8V+WSxDXJ4NFATAsZjh8iIbsQIg= 2 | github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= 3 | github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= 4 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= 5 | github.com/Microsoft/go-winio v0.4.14 h1:+hMXMk01us9KgxGb7ftKQt2Xpf5hH/yky+TDA+qxleU= 6 | github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA= 7 | github.com/Shopify/logrus-bugsnag v0.0.0-20170309145241-6dbc35f2c30d h1:hi6J4K6DKrR4/ljxn6SF6nURyu785wKMuQcjt7H3VCQ= 8 | github.com/Shopify/logrus-bugsnag v0.0.0-20170309145241-6dbc35f2c30d/go.mod h1:HI8ITrYtUY+O+ZhtlqUnD8+KwNPOyugEhfP9fdUIaEQ= 9 | github.com/beorn7/perks v0.0.0-20150223135152-b965b613227f h1:L/FlB1krOjojJSmUaiAiOMiIdRWylhc9QcHg0vHBuzA= 10 | github.com/beorn7/perks v0.0.0-20150223135152-b965b613227f/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= 11 | github.com/bitly/go-hostpool v0.1.0/go.mod h1:4gOCgp6+NZnVqlKyZ/iBZFTAJKembaVENUpMkpg42fw= 12 | github.com/bitly/go-simplejson v0.5.0/go.mod h1:cXHtHw4XUPsvGaxgjIAn8PhEWG9NfngEKAMDJEczWVA= 13 | github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4= 14 | github.com/bugsnag/bugsnag-go v1.0.5-0.20150529004307-13fd6b8acda0 h1:s7+5BfS4WFJoVF9pnB8kBk03S7pZXRdKamnV0FOl5Sc= 15 | github.com/bugsnag/bugsnag-go v1.0.5-0.20150529004307-13fd6b8acda0/go.mod h1:2oa8nejYd4cQ/b0hMIopN0lCRxU0bueqREvZLWFrtK8= 16 | github.com/bugsnag/osext v0.0.0-20130617224835-0dd3f918b21b h1:otBG+dV+YK+Soembjv71DPz3uX/V/6MMlSyD9JBQ6kQ= 17 | github.com/bugsnag/osext v0.0.0-20130617224835-0dd3f918b21b/go.mod h1:obH5gd0BsqsP2LwDJ9aOkm/6J86V6lyAXCoQWGw3K50= 18 | github.com/bugsnag/panicwrap v0.0.0-20151223152923-e2c28503fcd0 h1:nvj0OLI3YqYXer/kZD8Ri1aaunCxIEsOst1BVJswV0o= 19 | github.com/bugsnag/panicwrap v0.0.0-20151223152923-e2c28503fcd0/go.mod h1:D/8v3kj0zr8ZAKg1AQ6crr+5VwKN5eIywRkfhyM/+dE= 20 | github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= 21 | github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= 22 | github.com/cloudflare/cfssl v0.0.0-20180223231731-4e2dcbde5004 h1:lkAMpLVBDaj17e85keuznYcH5rqI438v41pKcBl4ZxQ= 23 | github.com/cloudflare/cfssl v0.0.0-20180223231731-4e2dcbde5004/go.mod h1:yMWuSON2oQp+43nFtAV/uvKQIFpSPerB57DCt9t8sSA= 24 | github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= 25 | github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo= 26 | github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= 27 | github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= 28 | github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY= 29 | github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= 30 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 31 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 32 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 33 | github.com/denisenkom/go-mssqldb v0.0.0-20191128021309-1d7a30a10f73/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU= 34 | github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk= 35 | github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= 36 | github.com/docker/cli v27.5.1+incompatible h1:JB9cieUT9YNiMITtIsguaN55PLOHhBSz3LKVc6cqWaY= 37 | github.com/docker/cli v27.5.1+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= 38 | github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= 39 | github.com/docker/distribution v2.8.3+incompatible h1:AtKxIZ36LoNK51+Z6RpzLpddBirtxJnzDrHLEKxTAYk= 40 | github.com/docker/distribution v2.8.3+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= 41 | github.com/docker/docker v27.5.1+incompatible h1:4PYU5dnBYqRQi0294d1FBECqT9ECWeQAIfE8q4YnPY8= 42 | github.com/docker/docker v27.5.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= 43 | github.com/docker/docker-credential-helpers v0.8.2 h1:bX3YxiGzFP5sOXWc3bTPEXdEaZSeVMrFgOr3T+zrFAo= 44 | github.com/docker/docker-credential-helpers v0.8.2/go.mod h1:P3ci7E3lwkZg6XiHdRKft1KckHiO9a2rNtyFbZ/ry9M= 45 | github.com/docker/go v1.5.1-1.0.20160303222718-d30aec9fd63c h1:lzqkGL9b3znc+ZUgi7FlLnqjQhcXxkNM/quxIjBVMD0= 46 | github.com/docker/go v1.5.1-1.0.20160303222718-d30aec9fd63c/go.mod h1:CADgU4DSXK5QUlFslkQu2yW2TKzFZcXq/leZfM0UH5Q= 47 | github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= 48 | github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c= 49 | github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc= 50 | github.com/docker/go-metrics v0.0.0-20180209012529-399ea8c73916 h1:yWHOI+vFjEsAakUTSrtqc/SAHrhSkmn48pqjidZX3QA= 51 | github.com/docker/go-metrics v0.0.0-20180209012529-399ea8c73916/go.mod h1:/u0gXw0Gay3ceNrsHubL3BtdOL2fHf93USgMTe0W5dI= 52 | github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= 53 | github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= 54 | github.com/docker/libtrust v0.0.0-20160708172513-aabc10ec26b7 h1:UhxFibDNY/bfvqU5CAUmr9zpesgbU6SWc8/B4mflAE4= 55 | github.com/docker/libtrust v0.0.0-20160708172513-aabc10ec26b7/go.mod h1:cyGadeNEkKy96OOhEzfZl+yxihPEzKnqJwvfuSUqbZE= 56 | github.com/dvsekhvalnov/jose2go v0.0.0-20170216131308-f21a8cedbbae/go.mod h1:7BvyPhdbLxMXIYTFPLsyJRFMsKmOZnQmzh6Gb+uquuM= 57 | github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5/go.mod h1:a2zkGnVExMxdzMo3M0Hi/3sEU+cWnZpSni0O6/Yb/P0= 58 | github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= 59 | github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= 60 | github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= 61 | github.com/fvbommel/sortorder v1.1.0 h1:fUmoe+HLsBTctBDoaBwpQo5N+nrCp8g/BjKb/6ZQmYw= 62 | github.com/fvbommel/sortorder v1.1.0/go.mod h1:uk88iVf1ovNn1iLfgUVU2F9o5eO30ui720w+kxuqRs0= 63 | github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= 64 | github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= 65 | github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= 66 | github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= 67 | github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= 68 | github.com/go-sql-driver/mysql v1.3.0 h1:pgwjLi/dvffoP9aabwkT3AKpXQM93QARkjFhDDqC1UE= 69 | github.com/go-sql-driver/mysql v1.3.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= 70 | github.com/gogo/protobuf v1.0.0 h1:2jyBKDKU/8v3v2xVR2PtiWQviFUyiaGk2rpfyFT8rTM= 71 | github.com/gogo/protobuf v1.0.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= 72 | github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0= 73 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 74 | github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= 75 | github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= 76 | github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= 77 | github.com/google/certificate-transparency-go v1.0.10-0.20180222191210-5ab67e519c93 h1:jc2UWq7CbdszqeH6qu1ougXMIUBfSy8Pbh/anURYbGI= 78 | github.com/google/certificate-transparency-go v1.0.10-0.20180222191210-5ab67e519c93/go.mod h1:QeJfpSbVSfYc7RgB3gJFj9cbuQMMchQxrWXz8Ruopmg= 79 | github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= 80 | github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 81 | github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= 82 | github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 83 | github.com/gorilla/mux v1.7.0 h1:tOSd0UKHQd6urX6ApfOn4XdBMY6Sh1MfxV3kmaazO+U= 84 | github.com/gorilla/mux v1.7.0/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= 85 | github.com/grpc-ecosystem/grpc-gateway/v2 v2.25.1 h1:VNqngBF40hVlDloBruUehVYC3ArSgIyScOAyMRqBxRg= 86 | github.com/grpc-ecosystem/grpc-gateway/v2 v2.25.1/go.mod h1:RBRO7fro65R6tjKzYgLAFo0t1QEXY1Dp+i/bvpRiqiQ= 87 | github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed h1:5upAirOpQc1Q53c0bnx2ufif5kANL7bfZWcc6VJWJd8= 88 | github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed/go.mod h1:tMWxXQ9wFIaZeTI9F+hmhFiGpFmhOHzyShyFUhRm0H4= 89 | github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= 90 | github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= 91 | github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= 92 | github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= 93 | github.com/jinzhu/gorm v0.0.0-20170222002820-5409931a1bb8 h1:CZkYfurY6KGhVtlalI4QwQ6T0Cu6iuY3e0x5RLu96WE= 94 | github.com/jinzhu/gorm v0.0.0-20170222002820-5409931a1bb8/go.mod h1:Vla75njaFJ8clLU1W44h34PjIkijhjHIYnZxMqCdxqo= 95 | github.com/jinzhu/inflection v0.0.0-20170102125226-1c35d901db3d h1:jRQLvyVGL+iVtDElaEIDdKwpPqUIZJfzkNLV34htpEc= 96 | github.com/jinzhu/inflection v0.0.0-20170102125226-1c35d901db3d/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= 97 | github.com/jinzhu/now v1.1.1/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= 98 | github.com/juju/loggo v0.0.0-20190526231331-6e530bcce5d8/go.mod h1:vgyd7OREkbtVEN/8IXZe5Ooef3LQePvuBm9UWj6ZL8U= 99 | github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= 100 | github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= 101 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 102 | github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= 103 | github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= 104 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 105 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 106 | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= 107 | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 108 | github.com/lib/pq v0.0.0-20150723085316-0dad96c0b94f/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= 109 | github.com/magiconair/properties v1.5.3 h1:C8fxWnhYyME3n0klPOhVM7PtYUB3eV1W3DeFmN3j53Y= 110 | github.com/magiconair/properties v1.5.3/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= 111 | github.com/mattn/go-sqlite3 v1.6.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= 112 | github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= 113 | github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= 114 | github.com/miekg/pkcs11 v1.0.2 h1:CIBkOawOtzJNE0B+EpRiUBzuVW7JEQAwdwhSS6YhIeg= 115 | github.com/miekg/pkcs11 v1.0.2/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs= 116 | github.com/mitchellh/mapstructure v0.0.0-20150613213606-2caf8efc9366 h1:1ypTpKUfEOyX1YsJru6lLq7hrmK+QGECpJQ1PHUHuGo= 117 | github.com/mitchellh/mapstructure v0.0.0-20150613213606-2caf8efc9366/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= 118 | github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0= 119 | github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo= 120 | github.com/moby/sys/sequential v0.6.0 h1:qrx7XFUd/5DxtqcoH1h438hF5TmOvzC/lspjy7zgvCU= 121 | github.com/moby/sys/sequential v0.6.0/go.mod h1:uyv8EUTrca5PnDsdMGXhZe6CCe8U/UiTWd+lL+7b/Ko= 122 | github.com/moby/term v0.5.2 h1:6qk3FJAFDs6i/q3W/pQ97SX192qKfZgGjCQqfCJkgzQ= 123 | github.com/moby/term v0.5.2/go.mod h1:d3djjFCrjnB+fl8NJux+EJzu0msscUP+f8it8hPkFLc= 124 | github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= 125 | github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= 126 | github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= 127 | github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= 128 | github.com/onsi/ginkgo v1.12.0/go.mod h1:oUhWkIvk5aDxtKvDDuw8gItl8pKl42LzjC9KZE0HfGg= 129 | github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= 130 | github.com/onsi/gomega v1.9.0/go.mod h1:Ho0h+IUsWyvy1OpqCwxlQ/21gkhVunqlU8fDGcoTdcA= 131 | github.com/opencontainers/go-digest v0.0.0-20170106003457-a6d0ee40d420/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= 132 | github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= 133 | github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= 134 | github.com/opencontainers/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= 135 | github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug= 136 | github.com/opencontainers/image-spec v1.1.0/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM= 137 | github.com/opentracing/opentracing-go v1.1.0 h1:pWlfV3Bxv7k65HYwkikxat0+s3pV4bsqf19k25Ur8rU= 138 | github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= 139 | github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 140 | github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= 141 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 142 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 143 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 144 | github.com/prometheus/client_golang v0.9.0-pre1.0.20180209125602-c332b6f63c06 h1:HfhRu7DulhCtYuCwmHYHdZ0pR/qYrCde5uhuemqD8rI= 145 | github.com/prometheus/client_golang v0.9.0-pre1.0.20180209125602-c332b6f63c06/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= 146 | github.com/prometheus/client_model v0.0.0-20171117100541-99fa1f4be8e5 h1:cLL6NowurKLMfCeQy4tIeph12XNQWgANCNvdyrOYKV4= 147 | github.com/prometheus/client_model v0.0.0-20171117100541-99fa1f4be8e5/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= 148 | github.com/prometheus/common v0.0.0-20180110214958-89604d197083 h1:BVsJT8+ZbyuL3hypz/HmEiM8h2P6hBQGig4el9/MdjA= 149 | github.com/prometheus/common v0.0.0-20180110214958-89604d197083/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= 150 | github.com/prometheus/procfs v0.0.0-20180125133057-cb4147076ac7 h1:hhvfGDVThBnd4kYisSFmYuHYeUhglxcwag7FhVPH9zM= 151 | github.com/prometheus/procfs v0.0.0-20180125133057-cb4147076ac7/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= 152 | github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= 153 | github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= 154 | github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= 155 | github.com/sirupsen/logrus v1.0.6/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc= 156 | github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= 157 | github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= 158 | github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= 159 | github.com/spf13/cast v0.0.0-20150508191742-4d07383ffe94 h1:JmfC365KywYwHB946TTiQWEb8kqPY+pybPLoGE9GgVk= 160 | github.com/spf13/cast v0.0.0-20150508191742-4d07383ffe94/go.mod h1:r2rcYCSwa1IExKTDiTfzaxqT2FNHs8hODu4LnUfgKEg= 161 | github.com/spf13/cobra v0.0.1/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= 162 | github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM= 163 | github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y= 164 | github.com/spf13/jwalterweatherman v0.0.0-20141219030609-3d60171a6431 h1:XTHrT015sxHyJ5FnQ0AeemSspZWaDq7DoTRW0EVsDCE= 165 | github.com/spf13/jwalterweatherman v0.0.0-20141219030609-3d60171a6431/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= 166 | github.com/spf13/pflag v1.0.0/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= 167 | github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= 168 | github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o= 169 | github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= 170 | github.com/spf13/viper v0.0.0-20150530192845-be5ff3e4840c h1:2EejZtjFjKJGk71ANb+wtFK5EjUzUkEM3R0xnp559xg= 171 | github.com/spf13/viper v0.0.0-20150530192845-be5ff3e4840c/go.mod h1:A8kyI5cUJhb8N+3pkfONlcEcZbueH6nhAm0Fq7SrnBM= 172 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 173 | github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 174 | github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= 175 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 176 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 177 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= 178 | github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= 179 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 180 | github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= 181 | github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= 182 | github.com/theupdateframework/notary v0.7.0 h1:QyagRZ7wlSpjT5N2qQAh/pN+DVqgekv4DzbAiAiEL3c= 183 | github.com/theupdateframework/notary v0.7.0/go.mod h1:c9DRxcmhHmVLDay4/2fUYdISnHqbFDGRSlXPO0AhYWw= 184 | go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= 185 | go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= 186 | go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.59.0 h1:CV7UdSGJt/Ao6Gp4CXckLxVRRsRgDHoI8XjbL3PDl8s= 187 | go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.59.0/go.mod h1:FRmFuRJfag1IZ2dPkHnEoSFVgTVPUd2qf5Vi69hLb8I= 188 | go.opentelemetry.io/otel v1.34.0 h1:zRLXxLCgL1WyKsPVrgbSdMN4c0FMkDAskSTQP+0hdUY= 189 | go.opentelemetry.io/otel v1.34.0/go.mod h1:OWFPOQ+h4G8xpyjgqo4SxJYdDQ/qmRH+wivy7zzx9oI= 190 | go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.34.0 h1:ajl4QczuJVA2TU9W9AGw++86Xga/RKt//16z/yxPgdk= 191 | go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.34.0/go.mod h1:Vn3/rlOJ3ntf/Q3zAI0V5lDnTbHGaUsNUeF6nZmm7pA= 192 | go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.34.0 h1:OeNbIYk/2C15ckl7glBlOBp5+WlYsOElzTNmiPW/x60= 193 | go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.34.0/go.mod h1:7Bept48yIeqxP2OZ9/AqIpYS94h2or0aB4FypJTc8ZM= 194 | go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.34.0 h1:tgJ0uaNS4c98WRNUEx5U3aDlrDOI5Rs+1Vifcw4DJ8U= 195 | go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.34.0/go.mod h1:U7HYyW0zt/a9x5J1Kjs+r1f/d4ZHnYFclhYY2+YbeoE= 196 | go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.34.0 h1:BEj3SPM81McUZHYjRS5pEgNgnmzGJ5tRpU5krWnV8Bs= 197 | go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.34.0/go.mod h1:9cKLGBDzI/F3NoHLQGm4ZrYdIHsvGt6ej6hUowxY0J4= 198 | go.opentelemetry.io/otel/metric v1.34.0 h1:+eTR3U0MyfWjRDhmFMxe2SsW64QrZ84AOhvqS7Y+PoQ= 199 | go.opentelemetry.io/otel/metric v1.34.0/go.mod h1:CEDrp0fy2D0MvkXE+dPV7cMi8tWZwX3dmaIhwPOaqHE= 200 | go.opentelemetry.io/otel/sdk v1.34.0 h1:95zS4k/2GOy069d321O8jWgYsW3MzVV+KuSPKp7Wr1A= 201 | go.opentelemetry.io/otel/sdk v1.34.0/go.mod h1:0e/pNiaMAqaykJGKbi+tSjWfNNHMTxoC9qANsCzbyxU= 202 | go.opentelemetry.io/otel/sdk/metric v1.34.0 h1:5CeK9ujjbFVL5c1PhLuStg1wxA7vQv7ce1EK0Gyvahk= 203 | go.opentelemetry.io/otel/sdk/metric v1.34.0/go.mod h1:jQ/r8Ze28zRKoNRdkjCZxfs6YvBTG1+YIqyFVFYec5w= 204 | go.opentelemetry.io/otel/trace v1.34.0 h1:+ouXS2V8Rd4hp4580a8q23bg0azF2nI8cqLYnC8mh/k= 205 | go.opentelemetry.io/otel/trace v1.34.0/go.mod h1:Svm7lSjQD7kG7KJ/MUHPVXSDGz2OX4h0M2jHBhmSfRE= 206 | go.opentelemetry.io/proto/otlp v1.5.0 h1:xJvq7gMzB31/d406fB8U5CBdyQGw4P399D1aQWU/3i4= 207 | go.opentelemetry.io/proto/otlp v1.5.0/go.mod h1:keN8WnHxOy8PG0rQZjJJ5A2ebUoafqWp0eVQ4yIXvJ4= 208 | go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= 209 | go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= 210 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 211 | golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 212 | golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 213 | golang.org/x/crypto v0.0.0-20201117144127-c1f2f97bffc9/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= 214 | golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc= 215 | golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc= 216 | golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 217 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 218 | golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0= 219 | golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k= 220 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 221 | golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 222 | golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 223 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 224 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 225 | golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 226 | golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 227 | golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 228 | golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 229 | golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 230 | golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU= 231 | golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 232 | golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= 233 | golang.org/x/term v0.28.0 h1:/Ts8HFuMR2E6IP/jlo7QVLZHggjKQbhu/7H0LJFr3Gg= 234 | golang.org/x/term v0.28.0/go.mod h1:Sw/lC2IAUZ92udQNf3WodGtn4k/XoLyZoh8v/8uiwek= 235 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 236 | golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= 237 | golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= 238 | golang.org/x/time v0.10.0 h1:3usCWA8tQn0L8+hFJQNgzpWbd89begxN66o1Ojdn5L4= 239 | golang.org/x/time v0.10.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= 240 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 241 | google.golang.org/genproto/googleapis/api v0.0.0-20250115164207-1a7da9e5054f h1:gap6+3Gk41EItBuyi4XX/bp4oqJ3UwuIMl25yGinuAA= 242 | google.golang.org/genproto/googleapis/api v0.0.0-20250115164207-1a7da9e5054f/go.mod h1:Ic02D47M+zbarjYYUlK57y316f2MoN0gjAwI3f2S95o= 243 | google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f h1:OxYkA3wjPsZyBylwymxSHa7ViiW1Sml4ToBrncvFehI= 244 | google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f/go.mod h1:+2Yz8+CLJbIfL9z73EW45avw8Lmge3xVElCP9zEKi50= 245 | google.golang.org/grpc v1.0.5/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= 246 | google.golang.org/grpc v1.69.4 h1:MF5TftSMkd8GLw/m0KM6V8CMOCY6NZ1NQDPGFgbTt4A= 247 | google.golang.org/grpc v1.69.4/go.mod h1:vyjdE6jLBI76dgpDojsFGNaHlxdjXN9ghpnd2o7JGZ4= 248 | google.golang.org/protobuf v1.36.3 h1:82DV7MYdb8anAVi3qge1wSnMDrnKK7ebr+I0hHRN1BU= 249 | google.golang.org/protobuf v1.36.3/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= 250 | gopkg.in/airbrake/gobrake.v2 v2.0.9/go.mod h1:/h5ZAUhDkGaJfjzjKLSjv6zCL6O0LLBxU4K+aSYdM/U= 251 | gopkg.in/cenkalti/backoff.v2 v2.2.1 h1:eJ9UAg01/HIHG987TwxvnzK2MgxXq97YY6rYDpY9aII= 252 | gopkg.in/cenkalti/backoff.v2 v2.2.1/go.mod h1:S0QdOvT2AlerfSBkp0O+dk+bbIMaNbEmVk876gPCthU= 253 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 254 | gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 255 | gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= 256 | gopkg.in/gemnasium/logrus-airbrake-hook.v2 v2.1.2/go.mod h1:Xk6kEKp8OKb+X14hQBKWaSkCsqBpgog8nAV2xsGOxlo= 257 | gopkg.in/rethinkdb/rethinkdb-go.v6 v6.2.1 h1:d4KQkxAaAiRY2h5Zqis161Pv91A37uZyJOx73duwUwM= 258 | gopkg.in/rethinkdb/rethinkdb-go.v6 v6.2.1/go.mod h1:WbjuEoo1oadwzQ4apSDU+JTvmllEHtsNHS6y7vFc7iw= 259 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= 260 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 261 | gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 262 | gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= 263 | gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 264 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 265 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 266 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 267 | gotest.tools/v3 v3.5.2 h1:7koQfIKdy+I8UTetycgUqXWSDwpgv193Ka+qRsmBY8Q= 268 | gotest.tools/v3 v3.5.2/go.mod h1:LtdLGcnqToBH83WByAAi/wiwSFCArdFIUV/xxN4pcjA= 269 | -------------------------------------------------------------------------------- /test/go/test.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/python/main.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | print(np.array([1, 2, 3])) 4 | -------------------------------------------------------------------------------- /test/python/requirements.txt: -------------------------------------------------------------------------------- 1 | numpy -------------------------------------------------------------------------------- /test/typescript/index.d.ts: -------------------------------------------------------------------------------- 1 | declare module '@automatalabs/mcp-server-playwright/dist/index.js' { 2 | export const Tools: any[]; 3 | } -------------------------------------------------------------------------------- /test/typescript/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "test", 3 | "version": "1.0.0", 4 | "lockfileVersion": 3, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "test", 9 | "version": "1.0.0", 10 | "license": "ISC", 11 | "dependencies": { 12 | "@automatalabs/mcp-server-playwright": "^1.2.1" 13 | } 14 | }, 15 | "node_modules/@automatalabs/mcp-server-playwright": { 16 | "version": "1.2.1", 17 | "resolved": "https://registry.npmjs.org/@automatalabs/mcp-server-playwright/-/mcp-server-playwright-1.2.1.tgz", 18 | "integrity": "sha512-RDp/phcuBInHpaYOPuo6f/6CM9rtHfFHT6B5jLqwM1Z3oLzQFaw+Bu4X4z4OMR+F5W3CpJu9ltQL7upYqv6IKQ==", 19 | "license": "MIT", 20 | "dependencies": { 21 | "@modelcontextprotocol/sdk": "0.5.0", 22 | "playwright": "^1.48.0", 23 | "yargs": "^17.7.2" 24 | }, 25 | "bin": { 26 | "mcp-server-playwright": "dist/index.js" 27 | } 28 | }, 29 | "node_modules/@modelcontextprotocol/sdk": { 30 | "version": "0.5.0", 31 | "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-0.5.0.tgz", 32 | "integrity": "sha512-RXgulUX6ewvxjAG0kOpLMEdXXWkzWgaoCGaA2CwNW7cQCIphjpJhjpHSiaPdVCnisjRF/0Cm9KWHUuIoeiAblQ==", 33 | "license": "MIT", 34 | "dependencies": { 35 | "content-type": "^1.0.5", 36 | "raw-body": "^3.0.0", 37 | "zod": "^3.23.8" 38 | } 39 | }, 40 | "node_modules/ansi-regex": { 41 | "version": "5.0.1", 42 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", 43 | "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", 44 | "license": "MIT", 45 | "engines": { 46 | "node": ">=8" 47 | } 48 | }, 49 | "node_modules/ansi-styles": { 50 | "version": "4.3.0", 51 | "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", 52 | "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", 53 | "license": "MIT", 54 | "dependencies": { 55 | "color-convert": "^2.0.1" 56 | }, 57 | "engines": { 58 | "node": ">=8" 59 | }, 60 | "funding": { 61 | "url": "https://github.com/chalk/ansi-styles?sponsor=1" 62 | } 63 | }, 64 | "node_modules/bytes": { 65 | "version": "3.1.2", 66 | "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", 67 | "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", 68 | "license": "MIT", 69 | "engines": { 70 | "node": ">= 0.8" 71 | } 72 | }, 73 | "node_modules/cliui": { 74 | "version": "8.0.1", 75 | "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", 76 | "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", 77 | "license": "ISC", 78 | "dependencies": { 79 | "string-width": "^4.2.0", 80 | "strip-ansi": "^6.0.1", 81 | "wrap-ansi": "^7.0.0" 82 | }, 83 | "engines": { 84 | "node": ">=12" 85 | } 86 | }, 87 | "node_modules/color-convert": { 88 | "version": "2.0.1", 89 | "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", 90 | "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", 91 | "license": "MIT", 92 | "dependencies": { 93 | "color-name": "~1.1.4" 94 | }, 95 | "engines": { 96 | "node": ">=7.0.0" 97 | } 98 | }, 99 | "node_modules/color-name": { 100 | "version": "1.1.4", 101 | "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", 102 | "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", 103 | "license": "MIT" 104 | }, 105 | "node_modules/content-type": { 106 | "version": "1.0.5", 107 | "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", 108 | "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", 109 | "license": "MIT", 110 | "engines": { 111 | "node": ">= 0.6" 112 | } 113 | }, 114 | "node_modules/depd": { 115 | "version": "2.0.0", 116 | "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", 117 | "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", 118 | "license": "MIT", 119 | "engines": { 120 | "node": ">= 0.8" 121 | } 122 | }, 123 | "node_modules/emoji-regex": { 124 | "version": "8.0.0", 125 | "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", 126 | "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", 127 | "license": "MIT" 128 | }, 129 | "node_modules/escalade": { 130 | "version": "3.2.0", 131 | "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", 132 | "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", 133 | "license": "MIT", 134 | "engines": { 135 | "node": ">=6" 136 | } 137 | }, 138 | "node_modules/fsevents": { 139 | "version": "2.3.2", 140 | "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", 141 | "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", 142 | "hasInstallScript": true, 143 | "license": "MIT", 144 | "optional": true, 145 | "os": [ 146 | "darwin" 147 | ], 148 | "engines": { 149 | "node": "^8.16.0 || ^10.6.0 || >=11.0.0" 150 | } 151 | }, 152 | "node_modules/get-caller-file": { 153 | "version": "2.0.5", 154 | "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", 155 | "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", 156 | "license": "ISC", 157 | "engines": { 158 | "node": "6.* || 8.* || >= 10.*" 159 | } 160 | }, 161 | "node_modules/http-errors": { 162 | "version": "2.0.0", 163 | "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", 164 | "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", 165 | "license": "MIT", 166 | "dependencies": { 167 | "depd": "2.0.0", 168 | "inherits": "2.0.4", 169 | "setprototypeof": "1.2.0", 170 | "statuses": "2.0.1", 171 | "toidentifier": "1.0.1" 172 | }, 173 | "engines": { 174 | "node": ">= 0.8" 175 | } 176 | }, 177 | "node_modules/iconv-lite": { 178 | "version": "0.6.3", 179 | "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", 180 | "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", 181 | "license": "MIT", 182 | "dependencies": { 183 | "safer-buffer": ">= 2.1.2 < 3.0.0" 184 | }, 185 | "engines": { 186 | "node": ">=0.10.0" 187 | } 188 | }, 189 | "node_modules/inherits": { 190 | "version": "2.0.4", 191 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", 192 | "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", 193 | "license": "ISC" 194 | }, 195 | "node_modules/is-fullwidth-code-point": { 196 | "version": "3.0.0", 197 | "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", 198 | "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", 199 | "license": "MIT", 200 | "engines": { 201 | "node": ">=8" 202 | } 203 | }, 204 | "node_modules/playwright": { 205 | "version": "1.50.1", 206 | "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.50.1.tgz", 207 | "integrity": "sha512-G8rwsOQJ63XG6BbKj2w5rHeavFjy5zynBA9zsJMMtBoe/Uf757oG12NXz6e6OirF7RCrTVAKFXbLmn1RbL7Qaw==", 208 | "license": "Apache-2.0", 209 | "dependencies": { 210 | "playwright-core": "1.50.1" 211 | }, 212 | "bin": { 213 | "playwright": "cli.js" 214 | }, 215 | "engines": { 216 | "node": ">=18" 217 | }, 218 | "optionalDependencies": { 219 | "fsevents": "2.3.2" 220 | } 221 | }, 222 | "node_modules/playwright-core": { 223 | "version": "1.50.1", 224 | "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.50.1.tgz", 225 | "integrity": "sha512-ra9fsNWayuYumt+NiM069M6OkcRb1FZSK8bgi66AtpFoWkg2+y0bJSNmkFrWhMbEBbVKC/EruAHH3g0zmtwGmQ==", 226 | "license": "Apache-2.0", 227 | "bin": { 228 | "playwright-core": "cli.js" 229 | }, 230 | "engines": { 231 | "node": ">=18" 232 | } 233 | }, 234 | "node_modules/raw-body": { 235 | "version": "3.0.0", 236 | "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.0.tgz", 237 | "integrity": "sha512-RmkhL8CAyCRPXCE28MMH0z2PNWQBNk2Q09ZdxM9IOOXwxwZbN+qbWaatPkdkWIKL2ZVDImrN/pK5HTRz2PcS4g==", 238 | "license": "MIT", 239 | "dependencies": { 240 | "bytes": "3.1.2", 241 | "http-errors": "2.0.0", 242 | "iconv-lite": "0.6.3", 243 | "unpipe": "1.0.0" 244 | }, 245 | "engines": { 246 | "node": ">= 0.8" 247 | } 248 | }, 249 | "node_modules/require-directory": { 250 | "version": "2.1.1", 251 | "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", 252 | "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", 253 | "license": "MIT", 254 | "engines": { 255 | "node": ">=0.10.0" 256 | } 257 | }, 258 | "node_modules/safer-buffer": { 259 | "version": "2.1.2", 260 | "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", 261 | "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", 262 | "license": "MIT" 263 | }, 264 | "node_modules/setprototypeof": { 265 | "version": "1.2.0", 266 | "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", 267 | "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", 268 | "license": "ISC" 269 | }, 270 | "node_modules/statuses": { 271 | "version": "2.0.1", 272 | "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", 273 | "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", 274 | "license": "MIT", 275 | "engines": { 276 | "node": ">= 0.8" 277 | } 278 | }, 279 | "node_modules/string-width": { 280 | "version": "4.2.3", 281 | "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", 282 | "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", 283 | "license": "MIT", 284 | "dependencies": { 285 | "emoji-regex": "^8.0.0", 286 | "is-fullwidth-code-point": "^3.0.0", 287 | "strip-ansi": "^6.0.1" 288 | }, 289 | "engines": { 290 | "node": ">=8" 291 | } 292 | }, 293 | "node_modules/strip-ansi": { 294 | "version": "6.0.1", 295 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", 296 | "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", 297 | "license": "MIT", 298 | "dependencies": { 299 | "ansi-regex": "^5.0.1" 300 | }, 301 | "engines": { 302 | "node": ">=8" 303 | } 304 | }, 305 | "node_modules/toidentifier": { 306 | "version": "1.0.1", 307 | "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", 308 | "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", 309 | "license": "MIT", 310 | "engines": { 311 | "node": ">=0.6" 312 | } 313 | }, 314 | "node_modules/unpipe": { 315 | "version": "1.0.0", 316 | "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", 317 | "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", 318 | "license": "MIT", 319 | "engines": { 320 | "node": ">= 0.8" 321 | } 322 | }, 323 | "node_modules/wrap-ansi": { 324 | "version": "7.0.0", 325 | "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", 326 | "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", 327 | "license": "MIT", 328 | "dependencies": { 329 | "ansi-styles": "^4.0.0", 330 | "string-width": "^4.1.0", 331 | "strip-ansi": "^6.0.0" 332 | }, 333 | "engines": { 334 | "node": ">=10" 335 | }, 336 | "funding": { 337 | "url": "https://github.com/chalk/wrap-ansi?sponsor=1" 338 | } 339 | }, 340 | "node_modules/y18n": { 341 | "version": "5.0.8", 342 | "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", 343 | "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", 344 | "license": "ISC", 345 | "engines": { 346 | "node": ">=10" 347 | } 348 | }, 349 | "node_modules/yargs": { 350 | "version": "17.7.2", 351 | "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", 352 | "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", 353 | "license": "MIT", 354 | "dependencies": { 355 | "cliui": "^8.0.1", 356 | "escalade": "^3.1.1", 357 | "get-caller-file": "^2.0.5", 358 | "require-directory": "^2.1.1", 359 | "string-width": "^4.2.3", 360 | "y18n": "^5.0.5", 361 | "yargs-parser": "^21.1.1" 362 | }, 363 | "engines": { 364 | "node": ">=12" 365 | } 366 | }, 367 | "node_modules/yargs-parser": { 368 | "version": "21.1.1", 369 | "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", 370 | "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", 371 | "license": "ISC", 372 | "engines": { 373 | "node": ">=12" 374 | } 375 | }, 376 | "node_modules/zod": { 377 | "version": "3.24.2", 378 | "resolved": "https://registry.npmjs.org/zod/-/zod-3.24.2.tgz", 379 | "integrity": "sha512-lY7CDW43ECgW9u1TcT3IoXHflywfVqDYze4waEz812jR/bZ8FHDsl7pFQoSZTz5N+2NqRXs8GBwnAwo3ZNxqhQ==", 380 | "license": "MIT", 381 | "funding": { 382 | "url": "https://github.com/sponsors/colinhacks" 383 | } 384 | } 385 | } 386 | } 387 | -------------------------------------------------------------------------------- /test/typescript/package.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 | -------------------------------------------------------------------------------- /test/typescript/test.ts: -------------------------------------------------------------------------------- 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/types/test.d.ts: -------------------------------------------------------------------------------- 1 | export {}; 2 | --------------------------------------------------------------------------------