├── .gitignore ├── Dockerfile ├── LICENSE ├── README.md ├── build └── index.js ├── package-lock.json ├── package.json ├── smithery.yaml ├── src └── index.ts └── tsconfig.json /.gitignore: -------------------------------------------------------------------------------- 1 | # .gitignore 2 | node_modules/ 3 | generated_code/ 4 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # Start with a Node.js base image 2 | FROM node:18-alpine AS builder 3 | # Create a directory for the app 4 | WORKDIR /app 5 | # Copy package.json and package-lock.json for installing dependencies 6 | COPY package.json package-lock.json ./ 7 | # Install dependencies 8 | RUN npm install --ignore-scripts 9 | # Copy the rest of the application source code 10 | COPY . . 11 | # Build the project 12 | RUN npm run build 13 | 14 | # Use the same Node.js base image for the final container 15 | FROM node:18-alpine 16 | # Set the working directory 17 | WORKDIR /app 18 | # Copy the build output and necessary files from the builder stage 19 | COPY --from=builder /app/build /app/build 20 | COPY --from=builder /app/package.json /app/package.json 21 | COPY --from=builder /app/package-lock.json /app/package-lock.json 22 | COPY --from=builder /app/node_modules /app/node_modules 23 | 24 | # Install Python and required tools 25 | RUN apk add --no-cache python3 py3-pip curl bash 26 | 27 | # Download and install uv using the official installer 28 | ADD https://astral.sh/uv/install.sh /uv-installer.sh 29 | RUN sh /uv-installer.sh && rm /uv-installer.sh 30 | 31 | # Ensure the installed binary is on the PATH 32 | ENV PATH="/root/.local/bin:$PATH" 33 | 34 | # Create required directories 35 | RUN mkdir -p /app/generated_code 36 | RUN mkdir -p /app/.venvs/ai 37 | 38 | # Create a virtual environment 39 | RUN uv venv /app/.venvs/ai 40 | 41 | # Set the environment variables 42 | ENV CODE_STORAGE_DIR=/app/generated_code 43 | ENV ENV_TYPE=venv-uv 44 | ENV UV_VENV_PATH=/app/.venvs/ai 45 | ENV PATH="/app/.venvs/ai/bin:$PATH" 46 | 47 | # Specify the command to run the MCP Code Executor server 48 | ENTRYPOINT ["node", "build/index.js"] 49 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 bazinga012 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 | # MCP Code Executor 2 | [![smithery badge](https://smithery.ai/badge/@bazinga012/mcp_code_executor)](https://smithery.ai/server/@bazinga012/mcp_code_executor) 3 | 4 | The MCP Code Executor is an MCP server that allows LLMs to execute Python code within a specified Python environment. This enables LLMs to run code with access to libraries and dependencies defined in the environment. It also supports incremental code generation for handling large code blocks that may exceed token limits. 5 | 6 | Code Executor MCP server 7 | 8 | ## Features 9 | 10 | - Execute Python code from LLM prompts 11 | - Support for incremental code generation to overcome token limitations 12 | - Run code within a specified environment (Conda, virtualenv, or UV virtualenv) 13 | - Install dependencies when needed 14 | - Check if packages are already installed 15 | - Dynamically configure the environment at runtime 16 | - Configurable code storage directory 17 | 18 | ## Prerequisites 19 | 20 | - Node.js installed 21 | - One of the following: 22 | - Conda installed with desired Conda environment created 23 | - Python virtualenv 24 | - UV virtualenv 25 | 26 | ## Setup 27 | 28 | 1. Clone this repository: 29 | 30 | ```bash 31 | git clone https://github.com/bazinga012/mcp_code_executor.git 32 | ``` 33 | 34 | 2. Navigate to the project directory: 35 | 36 | ```bash 37 | cd mcp_code_executor 38 | ``` 39 | 40 | 3. Install the Node.js dependencies: 41 | 42 | ```bash 43 | npm install 44 | ``` 45 | 46 | 4. Build the project: 47 | 48 | ```bash 49 | npm run build 50 | ``` 51 | 52 | ## Configuration 53 | 54 | To configure the MCP Code Executor server, add the following to your MCP servers configuration file: 55 | 56 | ### Using Node.js 57 | 58 | ```json 59 | { 60 | "mcpServers": { 61 | "mcp-code-executor": { 62 | "command": "node", 63 | "args": [ 64 | "/path/to/mcp_code_executor/build/index.js" 65 | ], 66 | "env": { 67 | "CODE_STORAGE_DIR": "/path/to/code/storage", 68 | "ENV_TYPE": "conda", 69 | "CONDA_ENV_NAME": "your-conda-env" 70 | } 71 | } 72 | } 73 | } 74 | ``` 75 | 76 | ### Using Docker 77 | 78 | ```json 79 | { 80 | "mcpServers": { 81 | "mcp-code-executor": { 82 | "command": "docker", 83 | "args": [ 84 | "run", 85 | "-i", 86 | "--rm", 87 | "mcp-code-executor" 88 | ] 89 | } 90 | } 91 | } 92 | ``` 93 | 94 | > **Note:** The Dockerfile has been tested with the venv-uv environment type only. Other environment types may require additional configuration. 95 | 96 | ### Environment Variables 97 | 98 | #### Required Variables 99 | - `CODE_STORAGE_DIR`: Directory where the generated code will be stored 100 | 101 | #### Environment Type (choose one setup) 102 | - **For Conda:** 103 | - `ENV_TYPE`: Set to `conda` 104 | - `CONDA_ENV_NAME`: Name of the Conda environment to use 105 | 106 | - **For Standard Virtualenv:** 107 | - `ENV_TYPE`: Set to `venv` 108 | - `VENV_PATH`: Path to the virtualenv directory 109 | 110 | - **For UV Virtualenv:** 111 | - `ENV_TYPE`: Set to `venv-uv` 112 | - `UV_VENV_PATH`: Path to the UV virtualenv directory 113 | 114 | ## Available Tools 115 | 116 | The MCP Code Executor provides the following tools to LLMs: 117 | 118 | ### 1. `execute_code` 119 | Executes Python code in the configured environment. Best for short code snippets. 120 | ```json 121 | { 122 | "name": "execute_code", 123 | "arguments": { 124 | "code": "import numpy as np\nprint(np.random.rand(3,3))", 125 | "filename": "matrix_gen" 126 | } 127 | } 128 | ``` 129 | 130 | ### 2. `install_dependencies` 131 | Installs Python packages in the environment. 132 | ```json 133 | { 134 | "name": "install_dependencies", 135 | "arguments": { 136 | "packages": ["numpy", "pandas", "matplotlib"] 137 | } 138 | } 139 | ``` 140 | 141 | ### 3. `check_installed_packages` 142 | Checks if packages are already installed in the environment. 143 | ```json 144 | { 145 | "name": "check_installed_packages", 146 | "arguments": { 147 | "packages": ["numpy", "pandas", "non_existent_package"] 148 | } 149 | } 150 | ``` 151 | 152 | ### 4. `configure_environment` 153 | Dynamically changes the environment configuration. 154 | ```json 155 | { 156 | "name": "configure_environment", 157 | "arguments": { 158 | "type": "conda", 159 | "conda_name": "new_env_name" 160 | } 161 | } 162 | ``` 163 | 164 | ### 5. `get_environment_config` 165 | Gets the current environment configuration. 166 | ```json 167 | { 168 | "name": "get_environment_config", 169 | "arguments": {} 170 | } 171 | ``` 172 | 173 | ### 6. `initialize_code_file` 174 | Creates a new Python file with initial content. Use this as the first step for longer code that may exceed token limits. 175 | ```json 176 | { 177 | "name": "initialize_code_file", 178 | "arguments": { 179 | "content": "def main():\n print('Hello, world!')\n\nif __name__ == '__main__':\n main()", 180 | "filename": "my_script" 181 | } 182 | } 183 | ``` 184 | 185 | ### 7. `append_to_code_file` 186 | Appends content to an existing Python code file. Use this to add more code to a file created with initialize_code_file. 187 | ```json 188 | { 189 | "name": "append_to_code_file", 190 | "arguments": { 191 | "file_path": "/path/to/code/storage/my_script_abc123.py", 192 | "content": "\ndef another_function():\n print('This was appended to the file')\n" 193 | } 194 | } 195 | ``` 196 | 197 | ### 8. `execute_code_file` 198 | Executes an existing Python file. Use this as the final step after building up code with initialize_code_file and append_to_code_file. 199 | ```json 200 | { 201 | "name": "execute_code_file", 202 | "arguments": { 203 | "file_path": "/path/to/code/storage/my_script_abc123.py" 204 | } 205 | } 206 | ``` 207 | 208 | ### 9. `read_code_file` 209 | Reads the content of an existing Python code file. Use this to verify the current state of a file before appending more content or executing it. 210 | ```json 211 | { 212 | "name": "read_code_file", 213 | "arguments": { 214 | "file_path": "/path/to/code/storage/my_script_abc123.py" 215 | } 216 | } 217 | ``` 218 | 219 | ## Usage 220 | 221 | Once configured, the MCP Code Executor will allow LLMs to execute Python code by generating a file in the specified `CODE_STORAGE_DIR` and running it within the configured environment. 222 | 223 | LLMs can generate and execute code by referencing this MCP server in their prompts. 224 | 225 | ### Handling Large Code Blocks 226 | 227 | For larger code blocks that might exceed LLM token limits, use the incremental code generation approach: 228 | 229 | 1. **Initialize a file** with the basic structure using `initialize_code_file` 230 | 2. **Add more code** in subsequent calls using `append_to_code_file` 231 | 3. **Verify the file content** if needed using `read_code_file` 232 | 4. **Execute the complete code** using `execute_code_file` 233 | 234 | This approach allows LLMs to write complex, multi-part code without running into token limitations. 235 | 236 | ## Backward Compatibility 237 | 238 | This package maintains backward compatibility with earlier versions. Users of previous versions who only specified a Conda environment will continue to work without any changes to their configuration. 239 | 240 | ## Contributing 241 | 242 | Contributions are welcome! Please open an issue or submit a pull request. 243 | 244 | ## License 245 | 246 | This project is licensed under the MIT License. 247 | -------------------------------------------------------------------------------- /build/index.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | import { Server } from "@modelcontextprotocol/sdk/server/index.js"; 3 | import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; 4 | import { CallToolRequestSchema, ListToolsRequestSchema, } from "@modelcontextprotocol/sdk/types.js"; 5 | import { randomBytes } from 'crypto'; 6 | import { join } from 'path'; 7 | import { mkdir, writeFile, appendFile, readFile, access } from 'fs/promises'; 8 | import { exec } from 'child_process'; 9 | import { promisify } from 'util'; 10 | import { platform } from 'os'; 11 | // Environment variables 12 | const CODE_STORAGE_DIR = process.env.CODE_STORAGE_DIR || ''; 13 | // Default environment settings 14 | let ENV_CONFIG = { 15 | // Default environment (conda, venv, or venv-uv) 16 | type: (process.env.ENV_TYPE || 'conda'), 17 | // Name of the conda environment 18 | conda_name: process.env.CONDA_ENV_NAME, 19 | // Path to virtualenv 20 | venv_path: process.env.VENV_PATH, 21 | // Path to uv virtualenv 22 | uv_venv_path: process.env.UV_VENV_PATH 23 | }; 24 | if (!CODE_STORAGE_DIR) { 25 | throw new Error('Missing required environment variable: CODE_STORAGE_DIR'); 26 | } 27 | // Validate environment settings based on the selected type 28 | if (ENV_CONFIG.type === 'conda' && !ENV_CONFIG.conda_name) { 29 | throw new Error('Missing required environment variable: CONDA_ENV_NAME (required for conda environment)'); 30 | } 31 | else if (ENV_CONFIG.type === 'venv' && !ENV_CONFIG.venv_path) { 32 | throw new Error('Missing required environment variable: VENV_PATH (required for virtualenv)'); 33 | } 34 | else if (ENV_CONFIG.type === 'venv-uv' && !ENV_CONFIG.uv_venv_path) { 35 | throw new Error('Missing required environment variable: UV_VENV_PATH (required for uv virtualenv)'); 36 | } 37 | // Ensure storage directory exists 38 | await mkdir(CODE_STORAGE_DIR, { recursive: true }); 39 | const execAsync = promisify(exec); 40 | /** 41 | * Get platform-specific command for environment activation and execution 42 | */ 43 | function getPlatformSpecificCommand(pythonCommand) { 44 | const isWindows = platform() === 'win32'; 45 | let command = ''; 46 | let options = {}; 47 | switch (ENV_CONFIG.type) { 48 | case 'conda': 49 | if (!ENV_CONFIG.conda_name) { 50 | throw new Error("conda_name is required for conda environment"); 51 | } 52 | if (isWindows) { 53 | command = `conda run -n ${ENV_CONFIG.conda_name} ${pythonCommand}`; 54 | options = { shell: 'cmd.exe' }; 55 | } 56 | else { 57 | command = `source $(conda info --base)/etc/profile.d/conda.sh && conda activate ${ENV_CONFIG.conda_name} && ${pythonCommand}`; 58 | options = { shell: '/bin/bash' }; 59 | } 60 | break; 61 | case 'venv': 62 | if (!ENV_CONFIG.venv_path) { 63 | throw new Error("venv_path is required for virtualenv"); 64 | } 65 | if (isWindows) { 66 | command = `${join(ENV_CONFIG.venv_path, 'Scripts', 'activate')} && ${pythonCommand}`; 67 | options = { shell: 'cmd.exe' }; 68 | } 69 | else { 70 | command = `source ${join(ENV_CONFIG.venv_path, 'bin', 'activate')} && ${pythonCommand}`; 71 | options = { shell: '/bin/bash' }; 72 | } 73 | break; 74 | case 'venv-uv': 75 | if (!ENV_CONFIG.uv_venv_path) { 76 | throw new Error("uv_venv_path is required for uv virtualenv"); 77 | } 78 | if (isWindows) { 79 | command = `${join(ENV_CONFIG.uv_venv_path, 'Scripts', 'activate')} && ${pythonCommand}`; 80 | options = { shell: 'cmd.exe' }; 81 | } 82 | else { 83 | command = `source ${join(ENV_CONFIG.uv_venv_path, 'bin', 'activate')} && ${pythonCommand}`; 84 | options = { shell: '/bin/bash' }; 85 | } 86 | break; 87 | default: 88 | throw new Error(`Unsupported environment type: ${ENV_CONFIG.type}`); 89 | } 90 | return { command, options }; 91 | } 92 | /** 93 | * Execute Python code and return the result 94 | */ 95 | async function executeCode(code, filePath) { 96 | try { 97 | // Write code to file 98 | await writeFile(filePath, code, 'utf-8'); 99 | // Get platform-specific command with unbuffered output 100 | const pythonCmd = platform() === 'win32' ? `python -u "${filePath}"` : `python3 -u "${filePath}"`; 101 | const { command, options } = getPlatformSpecificCommand(pythonCmd); 102 | // Execute code 103 | const { stdout, stderr } = await execAsync(command, { 104 | cwd: CODE_STORAGE_DIR, 105 | env: { ...process.env, PYTHONUNBUFFERED: '1' }, 106 | ...options 107 | }); 108 | const response = { 109 | status: stderr ? 'error' : 'success', 110 | output: stderr || stdout, 111 | file_path: filePath 112 | }; 113 | return { 114 | type: 'text', 115 | text: JSON.stringify(response), 116 | isError: !!stderr 117 | }; 118 | } 119 | catch (error) { 120 | const response = { 121 | status: 'error', 122 | error: error instanceof Error ? error.message : String(error), 123 | file_path: filePath 124 | }; 125 | return { 126 | type: 'text', 127 | text: JSON.stringify(response), 128 | isError: true 129 | }; 130 | } 131 | } 132 | /** 133 | * Execute Python code from an existing file and return the result 134 | */ 135 | async function executeCodeFromFile(filePath) { 136 | try { 137 | // Ensure file exists 138 | await access(filePath); 139 | // Get platform-specific command with unbuffered output 140 | const pythonCmd = platform() === 'win32' ? `python -u "${filePath}"` : `python3 -u "${filePath}"`; 141 | const { command, options } = getPlatformSpecificCommand(pythonCmd); 142 | // Execute code with unbuffered Python 143 | const { stdout, stderr } = await execAsync(command, { 144 | cwd: CODE_STORAGE_DIR, 145 | env: { ...process.env, PYTHONUNBUFFERED: '1' }, 146 | ...options 147 | }); 148 | const response = { 149 | status: stderr ? 'error' : 'success', 150 | output: stderr || stdout, 151 | file_path: filePath 152 | }; 153 | return { 154 | type: 'text', 155 | text: JSON.stringify(response), 156 | isError: !!stderr 157 | }; 158 | } 159 | catch (error) { 160 | const response = { 161 | status: 'error', 162 | error: error instanceof Error ? error.message : String(error), 163 | file_path: filePath 164 | }; 165 | return { 166 | type: 'text', 167 | text: JSON.stringify(response), 168 | isError: true 169 | }; 170 | } 171 | } 172 | /** 173 | * Create or initialize a new file with content 174 | */ 175 | async function initializeCodeFile(content, filename) { 176 | try { 177 | // Generate a filename if not provided 178 | let actualFilename; 179 | if (filename && typeof filename === 'string') { 180 | // Extract base name without extension 181 | const baseName = filename.replace(/\.py$/, ''); 182 | // Add a random suffix to ensure uniqueness 183 | actualFilename = `${baseName}_${randomBytes(4).toString('hex')}.py`; 184 | } 185 | else { 186 | // Default filename if none provided 187 | actualFilename = `code_${randomBytes(4).toString('hex')}.py`; 188 | } 189 | const filePath = join(CODE_STORAGE_DIR, actualFilename); 190 | // Write initial content to file 191 | await writeFile(filePath, content, 'utf-8'); 192 | return { 193 | type: 'text', 194 | text: JSON.stringify({ 195 | status: 'success', 196 | message: 'File initialized successfully', 197 | file_path: filePath, 198 | filename: actualFilename 199 | }), 200 | isError: false 201 | }; 202 | } 203 | catch (error) { 204 | return { 205 | type: 'text', 206 | text: JSON.stringify({ 207 | status: 'error', 208 | error: error instanceof Error ? error.message : String(error) 209 | }), 210 | isError: true 211 | }; 212 | } 213 | } 214 | /** 215 | * Append content to an existing file 216 | */ 217 | async function appendToCodeFile(filePath, content) { 218 | try { 219 | // Ensure file exists 220 | await access(filePath); 221 | // Append content to file 222 | await appendFile(filePath, content, 'utf-8'); 223 | return { 224 | type: 'text', 225 | text: JSON.stringify({ 226 | status: 'success', 227 | message: 'Content appended successfully', 228 | file_path: filePath 229 | }), 230 | isError: false 231 | }; 232 | } 233 | catch (error) { 234 | return { 235 | type: 'text', 236 | text: JSON.stringify({ 237 | status: 'error', 238 | error: error instanceof Error ? error.message : String(error), 239 | file_path: filePath 240 | }), 241 | isError: true 242 | }; 243 | } 244 | } 245 | /** 246 | * Read the content of a code file 247 | */ 248 | async function readCodeFile(filePath) { 249 | try { 250 | // Ensure file exists 251 | await access(filePath); 252 | // Read file content 253 | const content = await readFile(filePath, 'utf-8'); 254 | return { 255 | type: 'text', 256 | text: JSON.stringify({ 257 | status: 'success', 258 | content: content, 259 | file_path: filePath 260 | }), 261 | isError: false 262 | }; 263 | } 264 | catch (error) { 265 | return { 266 | type: 'text', 267 | text: JSON.stringify({ 268 | status: 'error', 269 | error: error instanceof Error ? error.message : String(error), 270 | file_path: filePath 271 | }), 272 | isError: true 273 | }; 274 | } 275 | } 276 | /** 277 | * Install dependencies using the appropriate package manager 278 | */ 279 | async function installDependencies(packages) { 280 | try { 281 | if (!packages || packages.length === 0) { 282 | return { 283 | type: 'text', 284 | text: JSON.stringify({ 285 | status: 'error', 286 | error: 'No packages specified' 287 | }), 288 | isError: true 289 | }; 290 | } 291 | // Build the install command based on environment type 292 | let installCmd = ''; 293 | const packageList = packages.join(' '); 294 | switch (ENV_CONFIG.type) { 295 | case 'conda': 296 | if (!ENV_CONFIG.conda_name) { 297 | throw new Error("conda_name is required for conda environment"); 298 | } 299 | installCmd = `conda install -y -n ${ENV_CONFIG.conda_name} ${packageList}`; 300 | break; 301 | case 'venv': 302 | installCmd = `pip install ${packageList}`; 303 | break; 304 | case 'venv-uv': 305 | installCmd = `uv pip install ${packageList}`; 306 | break; 307 | default: 308 | throw new Error(`Unsupported environment type: ${ENV_CONFIG.type}`); 309 | } 310 | // Get platform-specific command 311 | const { command, options } = getPlatformSpecificCommand(installCmd); 312 | // Execute installation with unbuffered Python 313 | const { stdout, stderr } = await execAsync(command, { 314 | cwd: CODE_STORAGE_DIR, 315 | env: { ...process.env, PYTHONUNBUFFERED: '1' }, 316 | ...options 317 | }); 318 | const response = { 319 | status: 'success', 320 | env_type: ENV_CONFIG.type, 321 | installed_packages: packages, 322 | output: stdout, 323 | warnings: stderr || undefined 324 | }; 325 | return { 326 | type: 'text', 327 | text: JSON.stringify(response), 328 | isError: false 329 | }; 330 | } 331 | catch (error) { 332 | const response = { 333 | status: 'error', 334 | env_type: ENV_CONFIG.type, 335 | error: error instanceof Error ? error.message : String(error) 336 | }; 337 | return { 338 | type: 'text', 339 | text: JSON.stringify(response), 340 | isError: true 341 | }; 342 | } 343 | } 344 | /** 345 | * Check if packages are installed in the current environment 346 | */ 347 | async function checkPackageInstallation(packages) { 348 | try { 349 | if (!packages || packages.length === 0) { 350 | return { 351 | type: 'text', 352 | text: JSON.stringify({ 353 | status: 'error', 354 | error: 'No packages specified' 355 | }), 356 | isError: true 357 | }; 358 | } 359 | // Create a temporary Python script to check packages 360 | const tempId = randomBytes(4).toString('hex'); 361 | // CODE_STORAGE_DIR is validated at the start of the program, so it's safe to use here 362 | const checkScriptPath = join(CODE_STORAGE_DIR, `check_packages_${tempId}.py`); 363 | // This script will attempt to import each package and return the results 364 | const checkScript = ` 365 | import importlib.util 366 | import json 367 | import sys 368 | 369 | results = {} 370 | 371 | for package in ${JSON.stringify(packages)}: 372 | try: 373 | # Try to find the spec 374 | spec = importlib.util.find_spec(package) 375 | if spec is None: 376 | # Package not found 377 | results[package] = { 378 | "installed": False, 379 | "error": "Package not found" 380 | } 381 | continue 382 | 383 | # Try to import the package 384 | module = importlib.import_module(package) 385 | 386 | # Get version if available 387 | version = getattr(module, "__version__", None) 388 | if version is None: 389 | version = getattr(module, "version", None) 390 | 391 | results[package] = { 392 | "installed": True, 393 | "version": version, 394 | "location": getattr(module, "__file__", None) 395 | } 396 | except ImportError as e: 397 | results[package] = { 398 | "installed": False, 399 | "error": str(e) 400 | } 401 | except Exception as e: 402 | results[package] = { 403 | "installed": False, 404 | "error": f"Unexpected error: {str(e)}" 405 | } 406 | 407 | print(json.dumps(results)) 408 | `; 409 | await writeFile(checkScriptPath, checkScript, 'utf-8'); 410 | // Execute the check script with unbuffered output 411 | const pythonCmd = platform() === 'win32' ? `python -u "${checkScriptPath}"` : `python3 -u "${checkScriptPath}"`; 412 | const { command, options } = getPlatformSpecificCommand(pythonCmd); 413 | const { stdout, stderr } = await execAsync(command, { 414 | cwd: CODE_STORAGE_DIR, 415 | env: { ...process.env, PYTHONUNBUFFERED: '1' }, 416 | ...options 417 | }); 418 | if (stderr) { 419 | return { 420 | type: 'text', 421 | text: JSON.stringify({ 422 | status: 'error', 423 | error: stderr 424 | }), 425 | isError: true 426 | }; 427 | } 428 | // Parse the package information 429 | const packageInfo = JSON.parse(stdout.trim()); 430 | // Add summary information to make it easier to use 431 | const allInstalled = Object.values(packageInfo).every((info) => info.installed); 432 | const notInstalled = Object.entries(packageInfo) 433 | .filter(([_, info]) => !info.installed) 434 | .map(([name, _]) => name); 435 | return { 436 | type: 'text', 437 | text: JSON.stringify({ 438 | status: 'success', 439 | env_type: ENV_CONFIG.type, 440 | all_installed: allInstalled, 441 | not_installed: notInstalled, 442 | package_details: packageInfo 443 | }), 444 | isError: false 445 | }; 446 | } 447 | catch (error) { 448 | return { 449 | type: 'text', 450 | text: JSON.stringify({ 451 | status: 'error', 452 | env_type: ENV_CONFIG.type, 453 | error: error instanceof Error ? error.message : String(error) 454 | }), 455 | isError: true 456 | }; 457 | } 458 | } 459 | /** 460 | * Create an MCP server to handle code execution and dependency management 461 | */ 462 | const server = new Server({ 463 | name: "code-executor", 464 | version: "0.3.0", 465 | }, { 466 | capabilities: { 467 | tools: {}, 468 | }, 469 | }); 470 | /** 471 | * Handler for listing available tools. 472 | */ 473 | server.setRequestHandler(ListToolsRequestSchema, async () => { 474 | return { 475 | tools: [ 476 | { 477 | name: "execute_code", 478 | description: `Execute Python code in the ${ENV_CONFIG.type} environment. For short code snippets only. For longer code, use initialize_code_file and append_to_code_file instead.`, 479 | inputSchema: { 480 | type: "object", 481 | properties: { 482 | code: { 483 | type: "string", 484 | description: "Python code to execute" 485 | }, 486 | filename: { 487 | type: "string", 488 | description: "Optional: Name of the file to save the code (default: generated UUID)" 489 | } 490 | }, 491 | required: ["code"] 492 | } 493 | }, 494 | { 495 | name: "initialize_code_file", 496 | description: "Create a new Python file with initial content. Use this as the first step for longer code that may exceed token limits. Follow with append_to_code_file for additional code.", 497 | inputSchema: { 498 | type: "object", 499 | properties: { 500 | content: { 501 | type: "string", 502 | description: "Initial content to write to the file" 503 | }, 504 | filename: { 505 | type: "string", 506 | description: "Optional: Name of the file (default: generated UUID)" 507 | } 508 | }, 509 | required: ["content"] 510 | } 511 | }, 512 | { 513 | name: "append_to_code_file", 514 | description: "Append content to an existing Python code file. Use this to add more code to a file created with initialize_code_file, allowing you to build up larger code bases in parts.", 515 | inputSchema: { 516 | type: "object", 517 | properties: { 518 | file_path: { 519 | type: "string", 520 | description: "Full path to the file" 521 | }, 522 | content: { 523 | type: "string", 524 | description: "Content to append to the file" 525 | } 526 | }, 527 | required: ["file_path", "content"] 528 | } 529 | }, 530 | { 531 | name: "execute_code_file", 532 | description: "Execute an existing Python file. Use this as the final step after building up code with initialize_code_file and append_to_code_file.", 533 | inputSchema: { 534 | type: "object", 535 | properties: { 536 | file_path: { 537 | type: "string", 538 | description: "Full path to the Python file to execute" 539 | } 540 | }, 541 | required: ["file_path"] 542 | } 543 | }, 544 | { 545 | name: "read_code_file", 546 | description: "Read the content of an existing Python code file. Use this to verify the current state of a file before appending more content or executing it.", 547 | inputSchema: { 548 | type: "object", 549 | properties: { 550 | file_path: { 551 | type: "string", 552 | description: "Full path to the file to read" 553 | } 554 | }, 555 | required: ["file_path"] 556 | } 557 | }, 558 | { 559 | name: "install_dependencies", 560 | description: `Install Python dependencies in the ${ENV_CONFIG.type} environment`, 561 | inputSchema: { 562 | type: "object", 563 | properties: { 564 | packages: { 565 | type: "array", 566 | items: { 567 | type: "string" 568 | }, 569 | description: "List of packages to install" 570 | } 571 | }, 572 | required: ["packages"] 573 | } 574 | }, 575 | { 576 | name: "check_installed_packages", 577 | description: `Check if packages are installed in the ${ENV_CONFIG.type} environment`, 578 | inputSchema: { 579 | type: "object", 580 | properties: { 581 | packages: { 582 | type: "array", 583 | items: { 584 | type: "string" 585 | }, 586 | description: "List of packages to check" 587 | } 588 | }, 589 | required: ["packages"] 590 | } 591 | }, 592 | { 593 | name: "configure_environment", 594 | description: "Change the environment configuration settings", 595 | inputSchema: { 596 | type: "object", 597 | properties: { 598 | type: { 599 | type: "string", 600 | enum: ["conda", "venv", "venv-uv"], 601 | description: "Type of Python environment" 602 | }, 603 | conda_name: { 604 | type: "string", 605 | description: "Name of the conda environment (required if type is 'conda')" 606 | }, 607 | venv_path: { 608 | type: "string", 609 | description: "Path to the virtualenv (required if type is 'venv')" 610 | }, 611 | uv_venv_path: { 612 | type: "string", 613 | description: "Path to the UV virtualenv (required if type is 'venv-uv')" 614 | } 615 | }, 616 | required: ["type"] 617 | } 618 | }, 619 | { 620 | name: "get_environment_config", 621 | description: "Get the current environment configuration", 622 | inputSchema: { 623 | type: "object", 624 | properties: {} 625 | } 626 | } 627 | ] 628 | }; 629 | }); 630 | /** 631 | * Validate the environment configuration 632 | */ 633 | function validateEnvironmentConfig(config) { 634 | if (config.type === 'conda' && !config.conda_name) { 635 | return "conda_name is required when type is 'conda'"; 636 | } 637 | else if (config.type === 'venv' && !config.venv_path) { 638 | return "venv_path is required when type is 'venv'"; 639 | } 640 | else if (config.type === 'venv-uv' && !config.uv_venv_path) { 641 | return "uv_venv_path is required when type is 'venv-uv'"; 642 | } 643 | return null; 644 | } 645 | /** 646 | * Handler for tool execution. 647 | */ 648 | server.setRequestHandler(CallToolRequestSchema, async (request) => { 649 | switch (request.params.name) { 650 | case "execute_code": { 651 | const args = request.params.arguments; 652 | if (!args?.code) { 653 | throw new Error("Code is required"); 654 | } 655 | // Generate a filename with both user-provided name and a random component for uniqueness 656 | let filename; 657 | if (args.filename && typeof args.filename === 'string') { 658 | // Extract base name without extension 659 | const baseName = args.filename.replace(/\.py$/, ''); 660 | // Add a random suffix to ensure uniqueness 661 | filename = `${baseName}_${randomBytes(4).toString('hex')}.py`; 662 | } 663 | else { 664 | // Default filename if none provided 665 | filename = `code_${randomBytes(4).toString('hex')}.py`; 666 | } 667 | const filePath = join(CODE_STORAGE_DIR, filename); 668 | // Execute the code and include the generated filename in the response 669 | const result = await executeCode(args.code, filePath); 670 | // Parse the result to add the filename info if it's a success response 671 | try { 672 | const resultData = JSON.parse(result.text); 673 | resultData.generated_filename = filename; 674 | result.text = JSON.stringify(resultData); 675 | } 676 | catch (e) { 677 | // In case of parsing error, continue with original result 678 | console.error("Error adding filename to result:", e); 679 | } 680 | return { 681 | content: [{ 682 | type: "text", 683 | text: result.text, 684 | isError: result.isError 685 | }] 686 | }; 687 | } 688 | case "initialize_code_file": { 689 | const args = request.params.arguments; 690 | if (!args?.content) { 691 | throw new Error("Content is required"); 692 | } 693 | const result = await initializeCodeFile(args.content, args.filename); 694 | return { 695 | content: [{ 696 | type: "text", 697 | text: result.text, 698 | isError: result.isError 699 | }] 700 | }; 701 | } 702 | case "append_to_code_file": { 703 | const args = request.params.arguments; 704 | if (!args?.file_path) { 705 | throw new Error("File path is required"); 706 | } 707 | if (!args?.content) { 708 | throw new Error("Content is required"); 709 | } 710 | const result = await appendToCodeFile(args.file_path, args.content); 711 | return { 712 | content: [{ 713 | type: "text", 714 | text: result.text, 715 | isError: result.isError 716 | }] 717 | }; 718 | } 719 | case "execute_code_file": { 720 | const args = request.params.arguments; 721 | if (!args?.file_path) { 722 | throw new Error("File path is required"); 723 | } 724 | const result = await executeCodeFromFile(args.file_path); 725 | return { 726 | content: [{ 727 | type: "text", 728 | text: result.text, 729 | isError: result.isError 730 | }] 731 | }; 732 | } 733 | case "read_code_file": { 734 | const args = request.params.arguments; 735 | if (!args?.file_path) { 736 | throw new Error("File path is required"); 737 | } 738 | const result = await readCodeFile(args.file_path); 739 | return { 740 | content: [{ 741 | type: "text", 742 | text: result.text, 743 | isError: result.isError 744 | }] 745 | }; 746 | } 747 | case "install_dependencies": { 748 | const args = request.params.arguments; 749 | if (!args?.packages || !Array.isArray(args.packages)) { 750 | throw new Error("Valid packages array is required"); 751 | } 752 | const result = await installDependencies(args.packages); 753 | return { 754 | content: [{ 755 | type: "text", 756 | text: result.text, 757 | isError: result.isError 758 | }] 759 | }; 760 | } 761 | case "check_installed_packages": { 762 | const args = request.params.arguments; 763 | if (!args?.packages || !Array.isArray(args.packages)) { 764 | throw new Error("Valid packages array is required"); 765 | } 766 | const result = await checkPackageInstallation(args.packages); 767 | return { 768 | content: [{ 769 | type: "text", 770 | text: result.text, 771 | isError: result.isError 772 | }] 773 | }; 774 | } 775 | case "configure_environment": { 776 | // Safely access and validate arguments 777 | const rawArgs = request.params.arguments || {}; 778 | // Check if type exists and is one of the allowed values 779 | if (!rawArgs || typeof rawArgs !== 'object' || !('type' in rawArgs) || 780 | !['conda', 'venv', 'venv-uv'].includes(String(rawArgs.type))) { 781 | return { 782 | content: [{ 783 | type: "text", 784 | text: JSON.stringify({ 785 | status: 'error', 786 | error: "Invalid arguments: 'type' is required and must be one of 'conda', 'venv', or 'venv-uv'" 787 | }), 788 | isError: true 789 | }] 790 | }; 791 | } 792 | // Now we can safely create a properly typed object 793 | const args = { 794 | type: String(rawArgs.type), 795 | conda_name: 'conda_name' in rawArgs ? String(rawArgs.conda_name) : undefined, 796 | venv_path: 'venv_path' in rawArgs ? String(rawArgs.venv_path) : undefined, 797 | uv_venv_path: 'uv_venv_path' in rawArgs ? String(rawArgs.uv_venv_path) : undefined, 798 | }; 799 | // Validate configuration 800 | const validationError = validateEnvironmentConfig(args); 801 | if (validationError) { 802 | return { 803 | content: [{ 804 | type: "text", 805 | text: JSON.stringify({ 806 | status: 'error', 807 | error: validationError 808 | }), 809 | isError: true 810 | }] 811 | }; 812 | } 813 | // Update configuration 814 | const previousConfig = { ...ENV_CONFIG }; 815 | ENV_CONFIG = { 816 | ...ENV_CONFIG, 817 | type: args.type, 818 | ...(args.conda_name && { conda_name: args.conda_name }), 819 | ...(args.venv_path && { venv_path: args.venv_path }), 820 | ...(args.uv_venv_path && { uv_venv_path: args.uv_venv_path }) 821 | }; 822 | return { 823 | content: [{ 824 | type: "text", 825 | text: JSON.stringify({ 826 | status: 'success', 827 | message: 'Environment configuration updated', 828 | previous: previousConfig, 829 | current: ENV_CONFIG 830 | }), 831 | isError: false 832 | }] 833 | }; 834 | } 835 | case "get_environment_config": { 836 | return { 837 | content: [{ 838 | type: "text", 839 | text: JSON.stringify({ 840 | status: 'success', 841 | config: ENV_CONFIG 842 | }), 843 | isError: false 844 | }] 845 | }; 846 | } 847 | default: 848 | throw new Error("Unknown tool"); 849 | } 850 | }); 851 | /** 852 | * Start the server using stdio transport. 853 | */ 854 | async function main() { 855 | console.error(` Info: Starting MCP Server with ${ENV_CONFIG.type} environment`); 856 | console.error(`Info: Code storage directory: ${CODE_STORAGE_DIR}`); 857 | const transport = new StdioServerTransport(); 858 | await server.connect(transport); 859 | } 860 | main().catch((error) => { 861 | console.error("Server error:", error); 862 | process.exit(1); 863 | }); 864 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "code_execution_server", 3 | "version": "0.2.0", 4 | "lockfileVersion": 3, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "code_execution_server", 9 | "version": "0.2.0", 10 | "dependencies": { 11 | "@modelcontextprotocol/sdk": "0.6.0", 12 | "mcp-framework": "^0.1.12" 13 | }, 14 | "bin": { 15 | "code execution server": "build/index.js" 16 | }, 17 | "devDependencies": { 18 | "@types/node": "^20.11.24", 19 | "typescript": "^5.3.3" 20 | } 21 | }, 22 | "node_modules/@modelcontextprotocol/sdk": { 23 | "version": "0.6.0", 24 | "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-0.6.0.tgz", 25 | "integrity": "sha512-9rsDudGhDtMbvxohPoMMyAUOmEzQsOK+XFchh6gZGqo8sx9sBuZQs+CUttXqa8RZXKDaJRCN2tUtgGof7jRkkw==", 26 | "license": "MIT", 27 | "dependencies": { 28 | "content-type": "^1.0.5", 29 | "raw-body": "^3.0.0", 30 | "zod": "^3.23.8" 31 | } 32 | }, 33 | "node_modules/@sec-ant/readable-stream": { 34 | "version": "0.4.1", 35 | "resolved": "https://registry.npmjs.org/@sec-ant/readable-stream/-/readable-stream-0.4.1.tgz", 36 | "integrity": "sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg==", 37 | "license": "MIT" 38 | }, 39 | "node_modules/@sindresorhus/merge-streams": { 40 | "version": "4.0.0", 41 | "resolved": "https://registry.npmjs.org/@sindresorhus/merge-streams/-/merge-streams-4.0.0.tgz", 42 | "integrity": "sha512-tlqY9xq5ukxTUZBmoOp+m61cqwQD5pHJtFY3Mn8CA8ps6yghLH/Hw8UPdqg4OLmFW3IFlcXnQNmo/dh8HzXYIQ==", 43 | "license": "MIT", 44 | "engines": { 45 | "node": ">=18" 46 | }, 47 | "funding": { 48 | "url": "https://github.com/sponsors/sindresorhus" 49 | } 50 | }, 51 | "node_modules/@types/node": { 52 | "version": "20.17.17", 53 | "resolved": "https://registry.npmjs.org/@types/node/-/node-20.17.17.tgz", 54 | "integrity": "sha512-/WndGO4kIfMicEQLTi/mDANUu/iVUhT7KboZPdEqqHQ4aTS+3qT3U5gIqWDFV+XouorjfgGqvKILJeHhuQgFYg==", 55 | "license": "MIT", 56 | "dependencies": { 57 | "undici-types": "~6.19.2" 58 | } 59 | }, 60 | "node_modules/@types/prompts": { 61 | "version": "2.4.9", 62 | "resolved": "https://registry.npmjs.org/@types/prompts/-/prompts-2.4.9.tgz", 63 | "integrity": "sha512-qTxFi6Buiu8+50/+3DGIWLHM6QuWsEKugJnnP6iv2Mc4ncxE4A/OJkjuVOA+5X0X1S/nq5VJRa8Lu+nwcvbrKA==", 64 | "license": "MIT", 65 | "dependencies": { 66 | "@types/node": "*", 67 | "kleur": "^3.0.3" 68 | } 69 | }, 70 | "node_modules/bytes": { 71 | "version": "3.1.2", 72 | "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", 73 | "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", 74 | "license": "MIT", 75 | "engines": { 76 | "node": ">= 0.8" 77 | } 78 | }, 79 | "node_modules/commander": { 80 | "version": "12.1.0", 81 | "resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz", 82 | "integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==", 83 | "license": "MIT", 84 | "engines": { 85 | "node": ">=18" 86 | } 87 | }, 88 | "node_modules/content-type": { 89 | "version": "1.0.5", 90 | "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", 91 | "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", 92 | "license": "MIT", 93 | "engines": { 94 | "node": ">= 0.6" 95 | } 96 | }, 97 | "node_modules/cross-spawn": { 98 | "version": "7.0.6", 99 | "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", 100 | "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", 101 | "license": "MIT", 102 | "dependencies": { 103 | "path-key": "^3.1.0", 104 | "shebang-command": "^2.0.0", 105 | "which": "^2.0.1" 106 | }, 107 | "engines": { 108 | "node": ">= 8" 109 | } 110 | }, 111 | "node_modules/depd": { 112 | "version": "2.0.0", 113 | "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", 114 | "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", 115 | "license": "MIT", 116 | "engines": { 117 | "node": ">= 0.8" 118 | } 119 | }, 120 | "node_modules/execa": { 121 | "version": "9.5.2", 122 | "resolved": "https://registry.npmjs.org/execa/-/execa-9.5.2.tgz", 123 | "integrity": "sha512-EHlpxMCpHWSAh1dgS6bVeoLAXGnJNdR93aabr4QCGbzOM73o5XmRfM/e5FUqsw3aagP8S8XEWUWFAxnRBnAF0Q==", 124 | "license": "MIT", 125 | "dependencies": { 126 | "@sindresorhus/merge-streams": "^4.0.0", 127 | "cross-spawn": "^7.0.3", 128 | "figures": "^6.1.0", 129 | "get-stream": "^9.0.0", 130 | "human-signals": "^8.0.0", 131 | "is-plain-obj": "^4.1.0", 132 | "is-stream": "^4.0.1", 133 | "npm-run-path": "^6.0.0", 134 | "pretty-ms": "^9.0.0", 135 | "signal-exit": "^4.1.0", 136 | "strip-final-newline": "^4.0.0", 137 | "yoctocolors": "^2.0.0" 138 | }, 139 | "engines": { 140 | "node": "^18.19.0 || >=20.5.0" 141 | }, 142 | "funding": { 143 | "url": "https://github.com/sindresorhus/execa?sponsor=1" 144 | } 145 | }, 146 | "node_modules/figures": { 147 | "version": "6.1.0", 148 | "resolved": "https://registry.npmjs.org/figures/-/figures-6.1.0.tgz", 149 | "integrity": "sha512-d+l3qxjSesT4V7v2fh+QnmFnUWv9lSpjarhShNTgBOfA0ttejbQUAlHLitbjkoRiDulW0OPoQPYIGhIC8ohejg==", 150 | "license": "MIT", 151 | "dependencies": { 152 | "is-unicode-supported": "^2.0.0" 153 | }, 154 | "engines": { 155 | "node": ">=18" 156 | }, 157 | "funding": { 158 | "url": "https://github.com/sponsors/sindresorhus" 159 | } 160 | }, 161 | "node_modules/find-up": { 162 | "version": "7.0.0", 163 | "resolved": "https://registry.npmjs.org/find-up/-/find-up-7.0.0.tgz", 164 | "integrity": "sha512-YyZM99iHrqLKjmt4LJDj58KI+fYyufRLBSYcqycxf//KpBk9FoewoGX0450m9nB44qrZnovzC2oeP5hUibxc/g==", 165 | "license": "MIT", 166 | "dependencies": { 167 | "locate-path": "^7.2.0", 168 | "path-exists": "^5.0.0", 169 | "unicorn-magic": "^0.1.0" 170 | }, 171 | "engines": { 172 | "node": ">=18" 173 | }, 174 | "funding": { 175 | "url": "https://github.com/sponsors/sindresorhus" 176 | } 177 | }, 178 | "node_modules/get-stream": { 179 | "version": "9.0.1", 180 | "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-9.0.1.tgz", 181 | "integrity": "sha512-kVCxPF3vQM/N0B1PmoqVUqgHP+EeVjmZSQn+1oCRPxd2P21P2F19lIgbR3HBosbB1PUhOAoctJnfEn2GbN2eZA==", 182 | "license": "MIT", 183 | "dependencies": { 184 | "@sec-ant/readable-stream": "^0.4.1", 185 | "is-stream": "^4.0.1" 186 | }, 187 | "engines": { 188 | "node": ">=18" 189 | }, 190 | "funding": { 191 | "url": "https://github.com/sponsors/sindresorhus" 192 | } 193 | }, 194 | "node_modules/http-errors": { 195 | "version": "2.0.0", 196 | "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", 197 | "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", 198 | "license": "MIT", 199 | "dependencies": { 200 | "depd": "2.0.0", 201 | "inherits": "2.0.4", 202 | "setprototypeof": "1.2.0", 203 | "statuses": "2.0.1", 204 | "toidentifier": "1.0.1" 205 | }, 206 | "engines": { 207 | "node": ">= 0.8" 208 | } 209 | }, 210 | "node_modules/human-signals": { 211 | "version": "8.0.0", 212 | "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-8.0.0.tgz", 213 | "integrity": "sha512-/1/GPCpDUCCYwlERiYjxoczfP0zfvZMU/OWgQPMya9AbAE24vseigFdhAMObpc8Q4lc/kjutPfUddDYyAmejnA==", 214 | "license": "Apache-2.0", 215 | "engines": { 216 | "node": ">=18.18.0" 217 | } 218 | }, 219 | "node_modules/iconv-lite": { 220 | "version": "0.6.3", 221 | "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", 222 | "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", 223 | "license": "MIT", 224 | "dependencies": { 225 | "safer-buffer": ">= 2.1.2 < 3.0.0" 226 | }, 227 | "engines": { 228 | "node": ">=0.10.0" 229 | } 230 | }, 231 | "node_modules/inherits": { 232 | "version": "2.0.4", 233 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", 234 | "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", 235 | "license": "ISC" 236 | }, 237 | "node_modules/is-plain-obj": { 238 | "version": "4.1.0", 239 | "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz", 240 | "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==", 241 | "license": "MIT", 242 | "engines": { 243 | "node": ">=12" 244 | }, 245 | "funding": { 246 | "url": "https://github.com/sponsors/sindresorhus" 247 | } 248 | }, 249 | "node_modules/is-stream": { 250 | "version": "4.0.1", 251 | "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-4.0.1.tgz", 252 | "integrity": "sha512-Dnz92NInDqYckGEUJv689RbRiTSEHCQ7wOVeALbkOz999YpqT46yMRIGtSNl2iCL1waAZSx40+h59NV/EwzV/A==", 253 | "license": "MIT", 254 | "engines": { 255 | "node": ">=18" 256 | }, 257 | "funding": { 258 | "url": "https://github.com/sponsors/sindresorhus" 259 | } 260 | }, 261 | "node_modules/is-unicode-supported": { 262 | "version": "2.1.0", 263 | "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-2.1.0.tgz", 264 | "integrity": "sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ==", 265 | "license": "MIT", 266 | "engines": { 267 | "node": ">=18" 268 | }, 269 | "funding": { 270 | "url": "https://github.com/sponsors/sindresorhus" 271 | } 272 | }, 273 | "node_modules/isexe": { 274 | "version": "2.0.0", 275 | "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", 276 | "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", 277 | "license": "ISC" 278 | }, 279 | "node_modules/kleur": { 280 | "version": "3.0.3", 281 | "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", 282 | "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", 283 | "license": "MIT", 284 | "engines": { 285 | "node": ">=6" 286 | } 287 | }, 288 | "node_modules/locate-path": { 289 | "version": "7.2.0", 290 | "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-7.2.0.tgz", 291 | "integrity": "sha512-gvVijfZvn7R+2qyPX8mAuKcFGDf6Nc61GdvGafQsHL0sBIxfKzA+usWn4GFC/bk+QdwPUD4kWFJLhElipq+0VA==", 292 | "license": "MIT", 293 | "dependencies": { 294 | "p-locate": "^6.0.0" 295 | }, 296 | "engines": { 297 | "node": "^12.20.0 || ^14.13.1 || >=16.0.0" 298 | }, 299 | "funding": { 300 | "url": "https://github.com/sponsors/sindresorhus" 301 | } 302 | }, 303 | "node_modules/mcp-framework": { 304 | "version": "0.1.21", 305 | "resolved": "https://registry.npmjs.org/mcp-framework/-/mcp-framework-0.1.21.tgz", 306 | "integrity": "sha512-y4yNGKT5Xbv6KpqrFSMvXFX5g649/LkziAmPRZu6u2PDTicKMdVnIRLEp4XgOL0gQTWHvHRo9Ztf9EAH7SUCrA==", 307 | "dependencies": { 308 | "@types/prompts": "^2.4.9", 309 | "commander": "^12.1.0", 310 | "execa": "^9.5.2", 311 | "find-up": "^7.0.0", 312 | "prompts": "^2.4.2", 313 | "typescript": "^5.3.3", 314 | "zod": "^3.23.8" 315 | }, 316 | "bin": { 317 | "mcp": "dist/cli/index.js", 318 | "mcp-build": "dist/cli/framework/build.js" 319 | }, 320 | "peerDependencies": { 321 | "@modelcontextprotocol/sdk": "^0.6.0" 322 | } 323 | }, 324 | "node_modules/npm-run-path": { 325 | "version": "6.0.0", 326 | "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-6.0.0.tgz", 327 | "integrity": "sha512-9qny7Z9DsQU8Ou39ERsPU4OZQlSTP47ShQzuKZ6PRXpYLtIFgl/DEBYEXKlvcEa+9tHVcK8CF81Y2V72qaZhWA==", 328 | "license": "MIT", 329 | "dependencies": { 330 | "path-key": "^4.0.0", 331 | "unicorn-magic": "^0.3.0" 332 | }, 333 | "engines": { 334 | "node": ">=18" 335 | }, 336 | "funding": { 337 | "url": "https://github.com/sponsors/sindresorhus" 338 | } 339 | }, 340 | "node_modules/npm-run-path/node_modules/path-key": { 341 | "version": "4.0.0", 342 | "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", 343 | "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", 344 | "license": "MIT", 345 | "engines": { 346 | "node": ">=12" 347 | }, 348 | "funding": { 349 | "url": "https://github.com/sponsors/sindresorhus" 350 | } 351 | }, 352 | "node_modules/npm-run-path/node_modules/unicorn-magic": { 353 | "version": "0.3.0", 354 | "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.3.0.tgz", 355 | "integrity": "sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA==", 356 | "license": "MIT", 357 | "engines": { 358 | "node": ">=18" 359 | }, 360 | "funding": { 361 | "url": "https://github.com/sponsors/sindresorhus" 362 | } 363 | }, 364 | "node_modules/p-limit": { 365 | "version": "4.0.0", 366 | "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-4.0.0.tgz", 367 | "integrity": "sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ==", 368 | "license": "MIT", 369 | "dependencies": { 370 | "yocto-queue": "^1.0.0" 371 | }, 372 | "engines": { 373 | "node": "^12.20.0 || ^14.13.1 || >=16.0.0" 374 | }, 375 | "funding": { 376 | "url": "https://github.com/sponsors/sindresorhus" 377 | } 378 | }, 379 | "node_modules/p-locate": { 380 | "version": "6.0.0", 381 | "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-6.0.0.tgz", 382 | "integrity": "sha512-wPrq66Llhl7/4AGC6I+cqxT07LhXvWL08LNXz1fENOw0Ap4sRZZ/gZpTTJ5jpurzzzfS2W/Ge9BY3LgLjCShcw==", 383 | "license": "MIT", 384 | "dependencies": { 385 | "p-limit": "^4.0.0" 386 | }, 387 | "engines": { 388 | "node": "^12.20.0 || ^14.13.1 || >=16.0.0" 389 | }, 390 | "funding": { 391 | "url": "https://github.com/sponsors/sindresorhus" 392 | } 393 | }, 394 | "node_modules/parse-ms": { 395 | "version": "4.0.0", 396 | "resolved": "https://registry.npmjs.org/parse-ms/-/parse-ms-4.0.0.tgz", 397 | "integrity": "sha512-TXfryirbmq34y8QBwgqCVLi+8oA3oWx2eAnSn62ITyEhEYaWRlVZ2DvMM9eZbMs/RfxPu/PK/aBLyGj4IrqMHw==", 398 | "license": "MIT", 399 | "engines": { 400 | "node": ">=18" 401 | }, 402 | "funding": { 403 | "url": "https://github.com/sponsors/sindresorhus" 404 | } 405 | }, 406 | "node_modules/path-exists": { 407 | "version": "5.0.0", 408 | "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-5.0.0.tgz", 409 | "integrity": "sha512-RjhtfwJOxzcFmNOi6ltcbcu4Iu+FL3zEj83dk4kAS+fVpTxXLO1b38RvJgT/0QwvV/L3aY9TAnyv0EOqW4GoMQ==", 410 | "license": "MIT", 411 | "engines": { 412 | "node": "^12.20.0 || ^14.13.1 || >=16.0.0" 413 | } 414 | }, 415 | "node_modules/path-key": { 416 | "version": "3.1.1", 417 | "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", 418 | "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", 419 | "license": "MIT", 420 | "engines": { 421 | "node": ">=8" 422 | } 423 | }, 424 | "node_modules/pretty-ms": { 425 | "version": "9.2.0", 426 | "resolved": "https://registry.npmjs.org/pretty-ms/-/pretty-ms-9.2.0.tgz", 427 | "integrity": "sha512-4yf0QO/sllf/1zbZWYnvWw3NxCQwLXKzIj0G849LSufP15BXKM0rbD2Z3wVnkMfjdn/CB0Dpp444gYAACdsplg==", 428 | "license": "MIT", 429 | "dependencies": { 430 | "parse-ms": "^4.0.0" 431 | }, 432 | "engines": { 433 | "node": ">=18" 434 | }, 435 | "funding": { 436 | "url": "https://github.com/sponsors/sindresorhus" 437 | } 438 | }, 439 | "node_modules/prompts": { 440 | "version": "2.4.2", 441 | "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", 442 | "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", 443 | "license": "MIT", 444 | "dependencies": { 445 | "kleur": "^3.0.3", 446 | "sisteransi": "^1.0.5" 447 | }, 448 | "engines": { 449 | "node": ">= 6" 450 | } 451 | }, 452 | "node_modules/raw-body": { 453 | "version": "3.0.0", 454 | "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.0.tgz", 455 | "integrity": "sha512-RmkhL8CAyCRPXCE28MMH0z2PNWQBNk2Q09ZdxM9IOOXwxwZbN+qbWaatPkdkWIKL2ZVDImrN/pK5HTRz2PcS4g==", 456 | "license": "MIT", 457 | "dependencies": { 458 | "bytes": "3.1.2", 459 | "http-errors": "2.0.0", 460 | "iconv-lite": "0.6.3", 461 | "unpipe": "1.0.0" 462 | }, 463 | "engines": { 464 | "node": ">= 0.8" 465 | } 466 | }, 467 | "node_modules/safer-buffer": { 468 | "version": "2.1.2", 469 | "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", 470 | "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", 471 | "license": "MIT" 472 | }, 473 | "node_modules/setprototypeof": { 474 | "version": "1.2.0", 475 | "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", 476 | "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", 477 | "license": "ISC" 478 | }, 479 | "node_modules/shebang-command": { 480 | "version": "2.0.0", 481 | "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", 482 | "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", 483 | "license": "MIT", 484 | "dependencies": { 485 | "shebang-regex": "^3.0.0" 486 | }, 487 | "engines": { 488 | "node": ">=8" 489 | } 490 | }, 491 | "node_modules/shebang-regex": { 492 | "version": "3.0.0", 493 | "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", 494 | "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", 495 | "license": "MIT", 496 | "engines": { 497 | "node": ">=8" 498 | } 499 | }, 500 | "node_modules/signal-exit": { 501 | "version": "4.1.0", 502 | "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", 503 | "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", 504 | "license": "ISC", 505 | "engines": { 506 | "node": ">=14" 507 | }, 508 | "funding": { 509 | "url": "https://github.com/sponsors/isaacs" 510 | } 511 | }, 512 | "node_modules/sisteransi": { 513 | "version": "1.0.5", 514 | "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", 515 | "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", 516 | "license": "MIT" 517 | }, 518 | "node_modules/statuses": { 519 | "version": "2.0.1", 520 | "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", 521 | "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", 522 | "license": "MIT", 523 | "engines": { 524 | "node": ">= 0.8" 525 | } 526 | }, 527 | "node_modules/strip-final-newline": { 528 | "version": "4.0.0", 529 | "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-4.0.0.tgz", 530 | "integrity": "sha512-aulFJcD6YK8V1G7iRB5tigAP4TsHBZZrOV8pjV++zdUwmeV8uzbY7yn6h9MswN62adStNZFuCIx4haBnRuMDaw==", 531 | "license": "MIT", 532 | "engines": { 533 | "node": ">=18" 534 | }, 535 | "funding": { 536 | "url": "https://github.com/sponsors/sindresorhus" 537 | } 538 | }, 539 | "node_modules/toidentifier": { 540 | "version": "1.0.1", 541 | "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", 542 | "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", 543 | "license": "MIT", 544 | "engines": { 545 | "node": ">=0.6" 546 | } 547 | }, 548 | "node_modules/typescript": { 549 | "version": "5.7.3", 550 | "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.7.3.tgz", 551 | "integrity": "sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw==", 552 | "license": "Apache-2.0", 553 | "bin": { 554 | "tsc": "bin/tsc", 555 | "tsserver": "bin/tsserver" 556 | }, 557 | "engines": { 558 | "node": ">=14.17" 559 | } 560 | }, 561 | "node_modules/undici-types": { 562 | "version": "6.19.8", 563 | "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", 564 | "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==", 565 | "license": "MIT" 566 | }, 567 | "node_modules/unicorn-magic": { 568 | "version": "0.1.0", 569 | "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.1.0.tgz", 570 | "integrity": "sha512-lRfVq8fE8gz6QMBuDM6a+LO3IAzTi05H6gCVaUpir2E1Rwpo4ZUog45KpNXKC/Mn3Yb9UDuHumeFTo9iV/D9FQ==", 571 | "license": "MIT", 572 | "engines": { 573 | "node": ">=18" 574 | }, 575 | "funding": { 576 | "url": "https://github.com/sponsors/sindresorhus" 577 | } 578 | }, 579 | "node_modules/unpipe": { 580 | "version": "1.0.0", 581 | "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", 582 | "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", 583 | "license": "MIT", 584 | "engines": { 585 | "node": ">= 0.8" 586 | } 587 | }, 588 | "node_modules/which": { 589 | "version": "2.0.2", 590 | "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", 591 | "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", 592 | "license": "ISC", 593 | "dependencies": { 594 | "isexe": "^2.0.0" 595 | }, 596 | "bin": { 597 | "node-which": "bin/node-which" 598 | }, 599 | "engines": { 600 | "node": ">= 8" 601 | } 602 | }, 603 | "node_modules/yocto-queue": { 604 | "version": "1.1.1", 605 | "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.1.1.tgz", 606 | "integrity": "sha512-b4JR1PFR10y1mKjhHY9LaGo6tmrgjit7hxVIeAmyMw3jegXR4dhYqLaQF5zMXZxY7tLpMyJeLjr1C4rLmkVe8g==", 607 | "license": "MIT", 608 | "engines": { 609 | "node": ">=12.20" 610 | }, 611 | "funding": { 612 | "url": "https://github.com/sponsors/sindresorhus" 613 | } 614 | }, 615 | "node_modules/yoctocolors": { 616 | "version": "2.1.1", 617 | "resolved": "https://registry.npmjs.org/yoctocolors/-/yoctocolors-2.1.1.tgz", 618 | "integrity": "sha512-GQHQqAopRhwU8Kt1DDM8NjibDXHC8eoh1erhGAJPEyveY9qqVeXvVikNKrDz69sHowPMorbPUrH/mx8c50eiBQ==", 619 | "license": "MIT", 620 | "engines": { 621 | "node": ">=18" 622 | }, 623 | "funding": { 624 | "url": "https://github.com/sponsors/sindresorhus" 625 | } 626 | }, 627 | "node_modules/zod": { 628 | "version": "3.24.1", 629 | "resolved": "https://registry.npmjs.org/zod/-/zod-3.24.1.tgz", 630 | "integrity": "sha512-muH7gBL9sI1nciMZV67X5fTKKBLtwpZ5VBp1vsOQzj1MhrBZ4wlVCm3gedKZWLp0Oyel8sIGfeiz54Su+OVT+A==", 631 | "license": "MIT", 632 | "funding": { 633 | "url": "https://github.com/sponsors/colinhacks" 634 | } 635 | } 636 | } 637 | } 638 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "code_execution_server", 3 | "version": "0.2.0", 4 | "description": "execute code", 5 | "private": true, 6 | "type": "module", 7 | "bin": { 8 | "code execution server": "./build/index.js" 9 | }, 10 | "files": [ 11 | "build" 12 | ], 13 | "scripts": { 14 | "build": "tsc && node -e \"require('fs').chmodSync('build/index.js', '755')\"", 15 | "prepare": "npm run build", 16 | "watch": "tsc --watch", 17 | "inspector": "npx @modelcontextprotocol/inspector build/index.js" 18 | }, 19 | "dependencies": { 20 | "@modelcontextprotocol/sdk": "0.6.0", 21 | "mcp-framework": "^0.1.12" 22 | }, 23 | "devDependencies": { 24 | "@types/node": "^20.11.24", 25 | "typescript": "^5.3.3" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /smithery.yaml: -------------------------------------------------------------------------------- 1 | # Smithery configuration file: https://smithery.ai/docs/config#smitheryyaml 2 | 3 | startCommand: 4 | type: stdio 5 | configSchema: 6 | # JSON Schema defining the configuration options for the MCP. 7 | type: object 8 | required: 9 | - codeStorageDir 10 | - condaEnvName 11 | properties: 12 | codeStorageDir: 13 | type: string 14 | description: Directory where generated code files will be stored. 15 | condaEnvName: 16 | type: string 17 | description: Name of the Conda environment for code execution. 18 | commandFunction: 19 | # A function that produces the CLI command to start the MCP on stdio. 20 | |- 21 | (config) => ({command:'node',args:['build/index.js'],env:{CODE_STORAGE_DIR:config.codeStorageDir, CONDA_ENV_NAME:config.condaEnvName}}) 22 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | import { Server } from "@modelcontextprotocol/sdk/server/index.js"; 4 | import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; 5 | import { 6 | CallToolRequestSchema, 7 | ListToolsRequestSchema, 8 | } from "@modelcontextprotocol/sdk/types.js"; 9 | import { randomBytes } from 'crypto'; 10 | import { join } from 'path'; 11 | import { mkdir, writeFile, appendFile, readFile, access } from 'fs/promises'; 12 | import { exec, ExecOptions } from 'child_process'; 13 | import { promisify } from 'util'; 14 | import { platform } from 'os'; 15 | 16 | // Define environment config interface for type safety 17 | interface EnvironmentConfig { 18 | type: 'conda' | 'venv' | 'venv-uv'; 19 | conda_name?: string; 20 | venv_path?: string; 21 | uv_venv_path?: string; 22 | } 23 | 24 | // Environment variables 25 | const CODE_STORAGE_DIR = process.env.CODE_STORAGE_DIR || ''; 26 | // Default environment settings 27 | let ENV_CONFIG: EnvironmentConfig = { 28 | // Default environment (conda, venv, or venv-uv) 29 | type: (process.env.ENV_TYPE || 'conda') as 'conda' | 'venv' | 'venv-uv', 30 | // Name of the conda environment 31 | conda_name: process.env.CONDA_ENV_NAME, 32 | // Path to virtualenv 33 | venv_path: process.env.VENV_PATH, 34 | // Path to uv virtualenv 35 | uv_venv_path: process.env.UV_VENV_PATH 36 | }; 37 | 38 | if (!CODE_STORAGE_DIR) { 39 | throw new Error('Missing required environment variable: CODE_STORAGE_DIR'); 40 | } 41 | 42 | // Validate environment settings based on the selected type 43 | if (ENV_CONFIG.type === 'conda' && !ENV_CONFIG.conda_name) { 44 | throw new Error('Missing required environment variable: CONDA_ENV_NAME (required for conda environment)'); 45 | } else if (ENV_CONFIG.type === 'venv' && !ENV_CONFIG.venv_path) { 46 | throw new Error('Missing required environment variable: VENV_PATH (required for virtualenv)'); 47 | } else if (ENV_CONFIG.type === 'venv-uv' && !ENV_CONFIG.uv_venv_path) { 48 | throw new Error('Missing required environment variable: UV_VENV_PATH (required for uv virtualenv)'); 49 | } 50 | 51 | // Ensure storage directory exists 52 | await mkdir(CODE_STORAGE_DIR, { recursive: true }); 53 | 54 | const execAsync = promisify(exec); 55 | 56 | /** 57 | * Get platform-specific command for environment activation and execution 58 | */ 59 | function getPlatformSpecificCommand(pythonCommand: string): { command: string, options: ExecOptions } { 60 | const isWindows = platform() === 'win32'; 61 | let command = ''; 62 | let options: ExecOptions = {}; 63 | 64 | switch (ENV_CONFIG.type) { 65 | case 'conda': 66 | if (!ENV_CONFIG.conda_name) { 67 | throw new Error("conda_name is required for conda environment"); 68 | } 69 | if (isWindows) { 70 | command = `conda run -n ${ENV_CONFIG.conda_name} ${pythonCommand}`; 71 | options = { shell: 'cmd.exe' }; 72 | } else { 73 | command = `source $(conda info --base)/etc/profile.d/conda.sh && conda activate ${ENV_CONFIG.conda_name} && ${pythonCommand}`; 74 | options = { shell: '/bin/bash' }; 75 | } 76 | break; 77 | 78 | case 'venv': 79 | if (!ENV_CONFIG.venv_path) { 80 | throw new Error("venv_path is required for virtualenv"); 81 | } 82 | if (isWindows) { 83 | command = `${join(ENV_CONFIG.venv_path, 'Scripts', 'activate')} && ${pythonCommand}`; 84 | options = { shell: 'cmd.exe' }; 85 | } else { 86 | command = `source ${join(ENV_CONFIG.venv_path, 'bin', 'activate')} && ${pythonCommand}`; 87 | options = { shell: '/bin/bash' }; 88 | } 89 | break; 90 | 91 | case 'venv-uv': 92 | if (!ENV_CONFIG.uv_venv_path) { 93 | throw new Error("uv_venv_path is required for uv virtualenv"); 94 | } 95 | if (isWindows) { 96 | command = `${join(ENV_CONFIG.uv_venv_path, 'Scripts', 'activate')} && ${pythonCommand}`; 97 | options = { shell: 'cmd.exe' }; 98 | } else { 99 | command = `source ${join(ENV_CONFIG.uv_venv_path, 'bin', 'activate')} && ${pythonCommand}`; 100 | options = { shell: '/bin/bash' }; 101 | } 102 | break; 103 | 104 | default: 105 | throw new Error(`Unsupported environment type: ${ENV_CONFIG.type}`); 106 | } 107 | 108 | return { command, options }; 109 | } 110 | 111 | /** 112 | * Execute Python code and return the result 113 | */ 114 | async function executeCode(code: string, filePath: string) { 115 | try { 116 | // Write code to file 117 | await writeFile(filePath, code, 'utf-8'); 118 | 119 | // Get platform-specific command with unbuffered output 120 | const pythonCmd = platform() === 'win32' ? `python -u "${filePath}"` : `python3 -u "${filePath}"`; 121 | const { command, options } = getPlatformSpecificCommand(pythonCmd); 122 | 123 | // Execute code 124 | const { stdout, stderr } = await execAsync(command, { 125 | cwd: CODE_STORAGE_DIR, 126 | env: { ...process.env, PYTHONUNBUFFERED: '1' }, 127 | ...options 128 | }); 129 | 130 | const response = { 131 | status: stderr ? 'error' : 'success', 132 | output: stderr || stdout, 133 | file_path: filePath 134 | }; 135 | 136 | return { 137 | type: 'text', 138 | text: JSON.stringify(response), 139 | isError: !!stderr 140 | }; 141 | } catch (error) { 142 | const response = { 143 | status: 'error', 144 | error: error instanceof Error ? error.message : String(error), 145 | file_path: filePath 146 | }; 147 | 148 | return { 149 | type: 'text', 150 | text: JSON.stringify(response), 151 | isError: true 152 | }; 153 | } 154 | } 155 | 156 | /** 157 | * Execute Python code from an existing file and return the result 158 | */ 159 | async function executeCodeFromFile(filePath: string) { 160 | try { 161 | // Ensure file exists 162 | await access(filePath); 163 | 164 | // Get platform-specific command with unbuffered output 165 | const pythonCmd = platform() === 'win32' ? `python -u "${filePath}"` : `python3 -u "${filePath}"`; 166 | const { command, options } = getPlatformSpecificCommand(pythonCmd); 167 | 168 | // Execute code with unbuffered Python 169 | const { stdout, stderr } = await execAsync(command, { 170 | cwd: CODE_STORAGE_DIR, 171 | env: { ...process.env, PYTHONUNBUFFERED: '1' }, 172 | ...options 173 | }); 174 | 175 | const response = { 176 | status: stderr ? 'error' : 'success', 177 | output: stderr || stdout, 178 | file_path: filePath 179 | }; 180 | 181 | return { 182 | type: 'text', 183 | text: JSON.stringify(response), 184 | isError: !!stderr 185 | }; 186 | } catch (error) { 187 | const response = { 188 | status: 'error', 189 | error: error instanceof Error ? error.message : String(error), 190 | file_path: filePath 191 | }; 192 | 193 | return { 194 | type: 'text', 195 | text: JSON.stringify(response), 196 | isError: true 197 | }; 198 | } 199 | } 200 | 201 | /** 202 | * Create or initialize a new file with content 203 | */ 204 | async function initializeCodeFile(content: string, filename?: string) { 205 | try { 206 | // Generate a filename if not provided 207 | let actualFilename; 208 | if (filename && typeof filename === 'string') { 209 | // Extract base name without extension 210 | const baseName = filename.replace(/\.py$/, ''); 211 | // Add a random suffix to ensure uniqueness 212 | actualFilename = `${baseName}_${randomBytes(4).toString('hex')}.py`; 213 | } else { 214 | // Default filename if none provided 215 | actualFilename = `code_${randomBytes(4).toString('hex')}.py`; 216 | } 217 | 218 | const filePath = join(CODE_STORAGE_DIR, actualFilename); 219 | 220 | // Write initial content to file 221 | await writeFile(filePath, content, 'utf-8'); 222 | 223 | return { 224 | type: 'text', 225 | text: JSON.stringify({ 226 | status: 'success', 227 | message: 'File initialized successfully', 228 | file_path: filePath, 229 | filename: actualFilename 230 | }), 231 | isError: false 232 | }; 233 | } catch (error) { 234 | return { 235 | type: 'text', 236 | text: JSON.stringify({ 237 | status: 'error', 238 | error: error instanceof Error ? error.message : String(error) 239 | }), 240 | isError: true 241 | }; 242 | } 243 | } 244 | 245 | /** 246 | * Append content to an existing file 247 | */ 248 | async function appendToCodeFile(filePath: string, content: string) { 249 | try { 250 | // Ensure file exists 251 | await access(filePath); 252 | 253 | // Append content to file 254 | await appendFile(filePath, content, 'utf-8'); 255 | 256 | return { 257 | type: 'text', 258 | text: JSON.stringify({ 259 | status: 'success', 260 | message: 'Content appended successfully', 261 | file_path: filePath 262 | }), 263 | isError: false 264 | }; 265 | } catch (error) { 266 | return { 267 | type: 'text', 268 | text: JSON.stringify({ 269 | status: 'error', 270 | error: error instanceof Error ? error.message : String(error), 271 | file_path: filePath 272 | }), 273 | isError: true 274 | }; 275 | } 276 | } 277 | 278 | /** 279 | * Read the content of a code file 280 | */ 281 | async function readCodeFile(filePath: string) { 282 | try { 283 | // Ensure file exists 284 | await access(filePath); 285 | 286 | // Read file content 287 | const content = await readFile(filePath, 'utf-8'); 288 | 289 | return { 290 | type: 'text', 291 | text: JSON.stringify({ 292 | status: 'success', 293 | content: content, 294 | file_path: filePath 295 | }), 296 | isError: false 297 | }; 298 | } catch (error) { 299 | return { 300 | type: 'text', 301 | text: JSON.stringify({ 302 | status: 'error', 303 | error: error instanceof Error ? error.message : String(error), 304 | file_path: filePath 305 | }), 306 | isError: true 307 | }; 308 | } 309 | } 310 | 311 | /** 312 | * Install dependencies using the appropriate package manager 313 | */ 314 | async function installDependencies(packages: string[]) { 315 | try { 316 | if (!packages || packages.length === 0) { 317 | return { 318 | type: 'text', 319 | text: JSON.stringify({ 320 | status: 'error', 321 | error: 'No packages specified' 322 | }), 323 | isError: true 324 | }; 325 | } 326 | 327 | // Build the install command based on environment type 328 | let installCmd = ''; 329 | const packageList = packages.join(' '); 330 | 331 | switch (ENV_CONFIG.type) { 332 | case 'conda': 333 | if (!ENV_CONFIG.conda_name) { 334 | throw new Error("conda_name is required for conda environment"); 335 | } 336 | installCmd = `conda install -y -n ${ENV_CONFIG.conda_name} ${packageList}`; 337 | break; 338 | 339 | case 'venv': 340 | installCmd = `pip install ${packageList}`; 341 | break; 342 | 343 | case 'venv-uv': 344 | installCmd = `uv pip install ${packageList}`; 345 | break; 346 | 347 | default: 348 | throw new Error(`Unsupported environment type: ${ENV_CONFIG.type}`); 349 | } 350 | 351 | // Get platform-specific command 352 | const { command, options } = getPlatformSpecificCommand(installCmd); 353 | 354 | // Execute installation with unbuffered Python 355 | const { stdout, stderr } = await execAsync(command, { 356 | cwd: CODE_STORAGE_DIR, 357 | env: { ...process.env, PYTHONUNBUFFERED: '1' }, 358 | ...options 359 | }); 360 | 361 | const response = { 362 | status: 'success', 363 | env_type: ENV_CONFIG.type, 364 | installed_packages: packages, 365 | output: stdout, 366 | warnings: stderr || undefined 367 | }; 368 | 369 | return { 370 | type: 'text', 371 | text: JSON.stringify(response), 372 | isError: false 373 | }; 374 | } catch (error) { 375 | const response = { 376 | status: 'error', 377 | env_type: ENV_CONFIG.type, 378 | error: error instanceof Error ? error.message : String(error) 379 | }; 380 | 381 | return { 382 | type: 'text', 383 | text: JSON.stringify(response), 384 | isError: true 385 | }; 386 | } 387 | } 388 | 389 | /** 390 | * Check if packages are installed in the current environment 391 | */ 392 | async function checkPackageInstallation(packages: string[]) { 393 | try { 394 | if (!packages || packages.length === 0) { 395 | return { 396 | type: 'text', 397 | text: JSON.stringify({ 398 | status: 'error', 399 | error: 'No packages specified' 400 | }), 401 | isError: true 402 | }; 403 | } 404 | 405 | // Create a temporary Python script to check packages 406 | const tempId = randomBytes(4).toString('hex'); 407 | // CODE_STORAGE_DIR is validated at the start of the program, so it's safe to use here 408 | const checkScriptPath = join(CODE_STORAGE_DIR, `check_packages_${tempId}.py`); 409 | 410 | // This script will attempt to import each package and return the results 411 | const checkScript = ` 412 | import importlib.util 413 | import json 414 | import sys 415 | 416 | results = {} 417 | 418 | for package in ${JSON.stringify(packages)}: 419 | try: 420 | # Try to find the spec 421 | spec = importlib.util.find_spec(package) 422 | if spec is None: 423 | # Package not found 424 | results[package] = { 425 | "installed": False, 426 | "error": "Package not found" 427 | } 428 | continue 429 | 430 | # Try to import the package 431 | module = importlib.import_module(package) 432 | 433 | # Get version if available 434 | version = getattr(module, "__version__", None) 435 | if version is None: 436 | version = getattr(module, "version", None) 437 | 438 | results[package] = { 439 | "installed": True, 440 | "version": version, 441 | "location": getattr(module, "__file__", None) 442 | } 443 | except ImportError as e: 444 | results[package] = { 445 | "installed": False, 446 | "error": str(e) 447 | } 448 | except Exception as e: 449 | results[package] = { 450 | "installed": False, 451 | "error": f"Unexpected error: {str(e)}" 452 | } 453 | 454 | print(json.dumps(results)) 455 | `; 456 | 457 | await writeFile(checkScriptPath, checkScript, 'utf-8'); 458 | 459 | // Execute the check script with unbuffered output 460 | const pythonCmd = platform() === 'win32' ? `python -u "${checkScriptPath}"` : `python3 -u "${checkScriptPath}"`; 461 | const { command, options } = getPlatformSpecificCommand(pythonCmd); 462 | 463 | const { stdout, stderr } = await execAsync(command, { 464 | cwd: CODE_STORAGE_DIR, 465 | env: { ...process.env, PYTHONUNBUFFERED: '1' }, 466 | ...options 467 | }); 468 | 469 | if (stderr) { 470 | return { 471 | type: 'text', 472 | text: JSON.stringify({ 473 | status: 'error', 474 | error: stderr 475 | }), 476 | isError: true 477 | }; 478 | } 479 | 480 | // Parse the package information 481 | const packageInfo = JSON.parse(stdout.trim()); 482 | 483 | // Add summary information to make it easier to use 484 | const allInstalled = Object.values(packageInfo).every((info: any) => info.installed); 485 | const notInstalled = Object.entries(packageInfo) 486 | .filter(([_, info]: [string, any]) => !info.installed) 487 | .map(([name, _]: [string, any]) => name); 488 | 489 | return { 490 | type: 'text', 491 | text: JSON.stringify({ 492 | status: 'success', 493 | env_type: ENV_CONFIG.type, 494 | all_installed: allInstalled, 495 | not_installed: notInstalled, 496 | package_details: packageInfo 497 | }), 498 | isError: false 499 | }; 500 | } catch (error) { 501 | return { 502 | type: 'text', 503 | text: JSON.stringify({ 504 | status: 'error', 505 | env_type: ENV_CONFIG.type, 506 | error: error instanceof Error ? error.message : String(error) 507 | }), 508 | isError: true 509 | }; 510 | } 511 | } 512 | 513 | /** 514 | * Create an MCP server to handle code execution and dependency management 515 | */ 516 | const server = new Server( 517 | { 518 | name: "code-executor", 519 | version: "0.3.0", 520 | }, 521 | { 522 | capabilities: { 523 | tools: {}, 524 | }, 525 | } 526 | ); 527 | 528 | /** 529 | * Handler for listing available tools. 530 | */ 531 | server.setRequestHandler(ListToolsRequestSchema, async () => { 532 | return { 533 | tools: [ 534 | { 535 | name: "execute_code", 536 | description: `Execute Python code in the ${ENV_CONFIG.type} environment. For short code snippets only. For longer code, use initialize_code_file and append_to_code_file instead.`, 537 | inputSchema: { 538 | type: "object", 539 | properties: { 540 | code: { 541 | type: "string", 542 | description: "Python code to execute" 543 | }, 544 | filename: { 545 | type: "string", 546 | description: "Optional: Name of the file to save the code (default: generated UUID)" 547 | } 548 | }, 549 | required: ["code"] 550 | } 551 | }, 552 | { 553 | name: "initialize_code_file", 554 | description: "Create a new Python file with initial content. Use this as the first step for longer code that may exceed token limits. Follow with append_to_code_file for additional code.", 555 | inputSchema: { 556 | type: "object", 557 | properties: { 558 | content: { 559 | type: "string", 560 | description: "Initial content to write to the file" 561 | }, 562 | filename: { 563 | type: "string", 564 | description: "Optional: Name of the file (default: generated UUID)" 565 | } 566 | }, 567 | required: ["content"] 568 | } 569 | }, 570 | { 571 | name: "append_to_code_file", 572 | description: "Append content to an existing Python code file. Use this to add more code to a file created with initialize_code_file, allowing you to build up larger code bases in parts.", 573 | inputSchema: { 574 | type: "object", 575 | properties: { 576 | file_path: { 577 | type: "string", 578 | description: "Full path to the file" 579 | }, 580 | content: { 581 | type: "string", 582 | description: "Content to append to the file" 583 | } 584 | }, 585 | required: ["file_path", "content"] 586 | } 587 | }, 588 | { 589 | name: "execute_code_file", 590 | description: "Execute an existing Python file. Use this as the final step after building up code with initialize_code_file and append_to_code_file.", 591 | inputSchema: { 592 | type: "object", 593 | properties: { 594 | file_path: { 595 | type: "string", 596 | description: "Full path to the Python file to execute" 597 | } 598 | }, 599 | required: ["file_path"] 600 | } 601 | }, 602 | { 603 | name: "read_code_file", 604 | description: "Read the content of an existing Python code file. Use this to verify the current state of a file before appending more content or executing it.", 605 | inputSchema: { 606 | type: "object", 607 | properties: { 608 | file_path: { 609 | type: "string", 610 | description: "Full path to the file to read" 611 | } 612 | }, 613 | required: ["file_path"] 614 | } 615 | }, 616 | 617 | { 618 | name: "install_dependencies", 619 | description: `Install Python dependencies in the ${ENV_CONFIG.type} environment`, 620 | inputSchema: { 621 | type: "object", 622 | properties: { 623 | packages: { 624 | type: "array", 625 | items: { 626 | type: "string" 627 | }, 628 | description: "List of packages to install" 629 | } 630 | }, 631 | required: ["packages"] 632 | } 633 | }, 634 | { 635 | name: "check_installed_packages", 636 | description: `Check if packages are installed in the ${ENV_CONFIG.type} environment`, 637 | inputSchema: { 638 | type: "object", 639 | properties: { 640 | packages: { 641 | type: "array", 642 | items: { 643 | type: "string" 644 | }, 645 | description: "List of packages to check" 646 | } 647 | }, 648 | required: ["packages"] 649 | } 650 | }, 651 | { 652 | name: "configure_environment", 653 | description: "Change the environment configuration settings", 654 | inputSchema: { 655 | type: "object", 656 | properties: { 657 | type: { 658 | type: "string", 659 | enum: ["conda", "venv", "venv-uv"], 660 | description: "Type of Python environment" 661 | }, 662 | conda_name: { 663 | type: "string", 664 | description: "Name of the conda environment (required if type is 'conda')" 665 | }, 666 | venv_path: { 667 | type: "string", 668 | description: "Path to the virtualenv (required if type is 'venv')" 669 | }, 670 | uv_venv_path: { 671 | type: "string", 672 | description: "Path to the UV virtualenv (required if type is 'venv-uv')" 673 | } 674 | }, 675 | required: ["type"] 676 | } 677 | }, 678 | { 679 | name: "get_environment_config", 680 | description: "Get the current environment configuration", 681 | inputSchema: { 682 | type: "object", 683 | properties: {} 684 | } 685 | } 686 | ] 687 | }; 688 | }); 689 | 690 | interface ExecuteCodeArgs { 691 | code?: string; 692 | filename?: string; 693 | } 694 | 695 | interface InitializeCodeFileArgs { 696 | content?: string; 697 | filename?: string; 698 | } 699 | 700 | interface AppendToCodeFileArgs { 701 | file_path?: string; 702 | content?: string; 703 | } 704 | 705 | interface ExecuteCodeFileArgs { 706 | file_path?: string; 707 | } 708 | 709 | interface ReadCodeFileArgs { 710 | file_path?: string; 711 | } 712 | 713 | interface InstallDependenciesArgs { 714 | packages?: string[]; 715 | } 716 | 717 | interface CheckInstalledPackagesArgs { 718 | packages?: string[]; 719 | } 720 | 721 | interface ConfigureEnvironmentArgs { 722 | type: 'conda' | 'venv' | 'venv-uv'; 723 | conda_name?: string; 724 | venv_path?: string; 725 | uv_venv_path?: string; 726 | } 727 | 728 | /** 729 | * Validate the environment configuration 730 | */ 731 | function validateEnvironmentConfig(config: ConfigureEnvironmentArgs): string | null { 732 | if (config.type === 'conda' && !config.conda_name) { 733 | return "conda_name is required when type is 'conda'"; 734 | } else if (config.type === 'venv' && !config.venv_path) { 735 | return "venv_path is required when type is 'venv'"; 736 | } else if (config.type === 'venv-uv' && !config.uv_venv_path) { 737 | return "uv_venv_path is required when type is 'venv-uv'"; 738 | } 739 | return null; 740 | } 741 | 742 | /** 743 | * Handler for tool execution. 744 | */ 745 | server.setRequestHandler(CallToolRequestSchema, async (request) => { 746 | switch (request.params.name) { 747 | case "execute_code": { 748 | const args = request.params.arguments as ExecuteCodeArgs; 749 | if (!args?.code) { 750 | throw new Error("Code is required"); 751 | } 752 | 753 | // Generate a filename with both user-provided name and a random component for uniqueness 754 | let filename; 755 | if (args.filename && typeof args.filename === 'string') { 756 | // Extract base name without extension 757 | const baseName = args.filename.replace(/\.py$/, ''); 758 | // Add a random suffix to ensure uniqueness 759 | filename = `${baseName}_${randomBytes(4).toString('hex')}.py`; 760 | } else { 761 | // Default filename if none provided 762 | filename = `code_${randomBytes(4).toString('hex')}.py`; 763 | } 764 | 765 | const filePath = join(CODE_STORAGE_DIR, filename); 766 | 767 | // Execute the code and include the generated filename in the response 768 | const result = await executeCode(args.code, filePath); 769 | 770 | // Parse the result to add the filename info if it's a success response 771 | try { 772 | const resultData = JSON.parse(result.text); 773 | resultData.generated_filename = filename; 774 | result.text = JSON.stringify(resultData); 775 | } catch (e) { 776 | // In case of parsing error, continue with original result 777 | console.error("Error adding filename to result:", e); 778 | } 779 | 780 | return { 781 | content: [{ 782 | type: "text", 783 | text: result.text, 784 | isError: result.isError 785 | }] 786 | }; 787 | } 788 | 789 | case "initialize_code_file": { 790 | const args = request.params.arguments as InitializeCodeFileArgs; 791 | if (!args?.content) { 792 | throw new Error("Content is required"); 793 | } 794 | 795 | const result = await initializeCodeFile(args.content, args.filename); 796 | 797 | return { 798 | content: [{ 799 | type: "text", 800 | text: result.text, 801 | isError: result.isError 802 | }] 803 | }; 804 | } 805 | 806 | case "append_to_code_file": { 807 | const args = request.params.arguments as AppendToCodeFileArgs; 808 | if (!args?.file_path) { 809 | throw new Error("File path is required"); 810 | } 811 | if (!args?.content) { 812 | throw new Error("Content is required"); 813 | } 814 | 815 | const result = await appendToCodeFile(args.file_path, args.content); 816 | 817 | return { 818 | content: [{ 819 | type: "text", 820 | text: result.text, 821 | isError: result.isError 822 | }] 823 | }; 824 | } 825 | 826 | case "execute_code_file": { 827 | const args = request.params.arguments as ExecuteCodeFileArgs; 828 | if (!args?.file_path) { 829 | throw new Error("File path is required"); 830 | } 831 | 832 | const result = await executeCodeFromFile(args.file_path); 833 | 834 | return { 835 | content: [{ 836 | type: "text", 837 | text: result.text, 838 | isError: result.isError 839 | }] 840 | }; 841 | } 842 | 843 | case "read_code_file": { 844 | const args = request.params.arguments as ReadCodeFileArgs; 845 | if (!args?.file_path) { 846 | throw new Error("File path is required"); 847 | } 848 | 849 | const result = await readCodeFile(args.file_path); 850 | 851 | return { 852 | content: [{ 853 | type: "text", 854 | text: result.text, 855 | isError: result.isError 856 | }] 857 | }; 858 | } 859 | 860 | case "install_dependencies": { 861 | const args = request.params.arguments as InstallDependenciesArgs; 862 | if (!args?.packages || !Array.isArray(args.packages)) { 863 | throw new Error("Valid packages array is required"); 864 | } 865 | 866 | const result = await installDependencies(args.packages); 867 | 868 | return { 869 | content: [{ 870 | type: "text", 871 | text: result.text, 872 | isError: result.isError 873 | }] 874 | }; 875 | } 876 | 877 | case "check_installed_packages": { 878 | const args = request.params.arguments as CheckInstalledPackagesArgs; 879 | if (!args?.packages || !Array.isArray(args.packages)) { 880 | throw new Error("Valid packages array is required"); 881 | } 882 | 883 | const result = await checkPackageInstallation(args.packages); 884 | 885 | return { 886 | content: [{ 887 | type: "text", 888 | text: result.text, 889 | isError: result.isError 890 | }] 891 | }; 892 | } 893 | 894 | case "configure_environment": { 895 | // Safely access and validate arguments 896 | const rawArgs = request.params.arguments || {}; 897 | 898 | // Check if type exists and is one of the allowed values 899 | if (!rawArgs || typeof rawArgs !== 'object' || !('type' in rawArgs) || 900 | !['conda', 'venv', 'venv-uv'].includes(String(rawArgs.type))) { 901 | return { 902 | content: [{ 903 | type: "text", 904 | text: JSON.stringify({ 905 | status: 'error', 906 | error: "Invalid arguments: 'type' is required and must be one of 'conda', 'venv', or 'venv-uv'" 907 | }), 908 | isError: true 909 | }] 910 | }; 911 | } 912 | 913 | // Now we can safely create a properly typed object 914 | const args: ConfigureEnvironmentArgs = { 915 | type: String(rawArgs.type) as 'conda' | 'venv' | 'venv-uv', 916 | conda_name: 'conda_name' in rawArgs ? String(rawArgs.conda_name) : undefined, 917 | venv_path: 'venv_path' in rawArgs ? String(rawArgs.venv_path) : undefined, 918 | uv_venv_path: 'uv_venv_path' in rawArgs ? String(rawArgs.uv_venv_path) : undefined, 919 | }; 920 | 921 | // Validate configuration 922 | const validationError = validateEnvironmentConfig(args); 923 | if (validationError) { 924 | return { 925 | content: [{ 926 | type: "text", 927 | text: JSON.stringify({ 928 | status: 'error', 929 | error: validationError 930 | }), 931 | isError: true 932 | }] 933 | }; 934 | } 935 | 936 | // Update configuration 937 | const previousConfig = { ...ENV_CONFIG }; 938 | ENV_CONFIG = { 939 | ...ENV_CONFIG, 940 | type: args.type, 941 | ...(args.conda_name && { conda_name: args.conda_name }), 942 | ...(args.venv_path && { venv_path: args.venv_path }), 943 | ...(args.uv_venv_path && { uv_venv_path: args.uv_venv_path }) 944 | }; 945 | 946 | return { 947 | content: [{ 948 | type: "text", 949 | text: JSON.stringify({ 950 | status: 'success', 951 | message: 'Environment configuration updated', 952 | previous: previousConfig, 953 | current: ENV_CONFIG 954 | }), 955 | isError: false 956 | }] 957 | }; 958 | } 959 | 960 | case "get_environment_config": { 961 | return { 962 | content: [{ 963 | type: "text", 964 | text: JSON.stringify({ 965 | status: 'success', 966 | config: ENV_CONFIG 967 | }), 968 | isError: false 969 | }] 970 | }; 971 | } 972 | 973 | default: 974 | throw new Error("Unknown tool"); 975 | } 976 | }); 977 | 978 | /** 979 | * Start the server using stdio transport. 980 | */ 981 | async function main() { 982 | console.error(` Info: Starting MCP Server with ${ENV_CONFIG.type} environment`); 983 | console.error(`Info: Code storage directory: ${CODE_STORAGE_DIR}`); 984 | 985 | const transport = new StdioServerTransport(); 986 | await server.connect(transport); 987 | } 988 | 989 | main().catch((error) => { 990 | console.error("Server error:", error); 991 | process.exit(1); 992 | }); 993 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2022", 4 | "module": "NodeNext", 5 | "moduleResolution": "NodeNext", 6 | "outDir": "./build", 7 | "rootDir": "./src", 8 | "strict": true, 9 | "esModuleInterop": true, 10 | "skipLibCheck": true, 11 | "forceConsistentCasingInFileNames": true 12 | }, 13 | "include": ["src/**/*"], 14 | "exclude": ["node_modules", "build"] 15 | } --------------------------------------------------------------------------------