# Directory Structure
```
├── .gitignore
├── build
│ └── index.js
├── Dockerfile
├── LICENSE
├── package-lock.json
├── package.json
├── README.md
├── smithery.yaml
├── src
│ └── index.ts
└── tsconfig.json
```
# Files
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
```
1 | # .gitignore
2 | node_modules/
3 | generated_code/
4 |
```
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
```markdown
1 | # MCP Code Executor
2 | [](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 | <a href="https://glama.ai/mcp/servers/45ix8xode3"><img width="380" height="200" src="https://glama.ai/mcp/servers/45ix8xode3/badge" alt="Code Executor MCP server" /></a>
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 |
```
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
```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 | }
```
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
```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:
--------------------------------------------------------------------------------
```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 |
```
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
```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 |
```
--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------
```typescript
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 |
```