Skip to content

Commit b32ca2c

Browse files
jefcodervishalagrawal-jisr
authored andcommitted
mcp server for python code execution
0 parents  commit b32ca2c

File tree

7 files changed

+1111
-0
lines changed

7 files changed

+1111
-0
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# .gitignore
2+
node_modules/
3+
generated_code/

README.md

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
# MCP Code Executor
2+
3+
The MCP Code Executor is an MCP server that allows LLMs to execute Python code within a specified Conda environment. This enables LLMs to run code with access to libraries and dependencies defined in the Conda environment.
4+
5+
## Features
6+
7+
- Execute Python code from LLM prompts
8+
- Run code within a specified Conda environment
9+
- Configurable code storage directory
10+
11+
## Prerequisites
12+
13+
- Node.js installed
14+
- Conda installed
15+
- Desired Conda environment created
16+
17+
## Setup
18+
19+
1. Clone this repository:
20+
21+
```bash
22+
git clone https://github.com/bazinga012/mcp_code_executor.git
23+
```
24+
25+
2. Navigate to the project directory:
26+
27+
```bash
28+
cd mcp_code_executor
29+
```
30+
31+
3. Install the Node.js dependencies:
32+
33+
```bash
34+
npm install
35+
```
36+
37+
4. Build the project:
38+
39+
```bash
40+
npm run build
41+
```
42+
43+
## Configuration
44+
45+
To configure the MCP Code Executor server, add the following to your MCP servers configuration file:
46+
47+
```json
48+
{
49+
"mcpServers": {
50+
"mcp-code-executor": {
51+
"command": "node",
52+
"args": [
53+
"/path/to/mcp_code_executor/build/index.js"
54+
],
55+
"env": {
56+
"CODE_STORAGE_DIR": "/path/to/code/storage",
57+
"CONDA_ENV_NAME": "your-conda-env"
58+
}
59+
}
60+
}
61+
}
62+
```
63+
64+
Replace the placeholders:
65+
- `/path/to/mcp_code_executor` with the absolute path to where you cloned this repository
66+
- `/path/to/code/storage` with the directory where you want the generated code to be stored
67+
- `your-conda-env` with the name of the Conda environment you want the code to run in
68+
69+
## Usage
70+
71+
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 Conda environment defined by `CONDA_ENV_NAME`.
72+
73+
LLMs can generate and execute code by referencing this MCP server in their prompts.
74+
75+
## Contributing
76+
77+
Contributions are welcome! Please open an issue or submit a pull request.
78+
79+
## License
80+
81+
This project is licensed under the MIT License.

build/index.js

Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
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 } 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+
const CONDA_ENV_NAME = process.env.CONDA_ENV_NAME;
14+
if (!CODE_STORAGE_DIR || !CONDA_ENV_NAME) {
15+
throw new Error('Missing required environment variables: CODE_STORAGE_DIR and CONDA_ENV_NAME');
16+
}
17+
// Ensure storage directory exists
18+
await mkdir(CODE_STORAGE_DIR, { recursive: true });
19+
const execAsync = promisify(exec);
20+
/**
21+
* Get platform-specific command for Conda activation
22+
*/
23+
function getPlatformSpecificCommand(pythonCommand) {
24+
const isWindows = platform() === 'win32';
25+
if (isWindows) {
26+
return {
27+
command: `conda activate ${CONDA_ENV_NAME} && ${pythonCommand}`,
28+
options: {
29+
shell: 'cmd.exe'
30+
}
31+
};
32+
}
33+
else {
34+
return {
35+
command: `source $(conda info --base)/etc/profile.d/conda.sh && conda activate ${CONDA_ENV_NAME} && ${pythonCommand}`,
36+
options: {
37+
shell: '/bin/bash'
38+
}
39+
};
40+
}
41+
}
42+
/**
43+
* Execute Python code and return the result
44+
*/
45+
async function executeCode(code, filePath) {
46+
try {
47+
// Write code to file
48+
await writeFile(filePath, code, 'utf-8');
49+
// Get platform-specific command
50+
const pythonCmd = `python3 "${filePath}"`;
51+
const { command, options } = getPlatformSpecificCommand(pythonCmd);
52+
// Execute code
53+
const { stdout, stderr } = await execAsync(command, {
54+
cwd: CODE_STORAGE_DIR,
55+
env: { ...process.env },
56+
...options
57+
});
58+
const response = {
59+
status: stderr ? 'error' : 'success',
60+
output: stderr || stdout,
61+
file_path: filePath
62+
};
63+
return {
64+
type: 'text',
65+
text: JSON.stringify(response),
66+
isError: !!stderr
67+
};
68+
}
69+
catch (error) {
70+
const response = {
71+
status: 'error',
72+
error: error instanceof Error ? error.message : String(error),
73+
file_path: filePath
74+
};
75+
return {
76+
type: 'text',
77+
text: JSON.stringify(response),
78+
isError: true
79+
};
80+
}
81+
}
82+
/**
83+
* Create an MCP server to handle code execution
84+
*/
85+
const server = new Server({
86+
name: "code-executor",
87+
version: "0.1.0",
88+
}, {
89+
capabilities: {
90+
tools: {},
91+
},
92+
});
93+
/**
94+
* Handler for listing available tools.
95+
* Provides a tool to execute code in Python environment.
96+
*/
97+
server.setRequestHandler(ListToolsRequestSchema, async () => {
98+
return {
99+
tools: [
100+
{
101+
name: "execute_code",
102+
description: "Execute Python code in the specified conda environment",
103+
inputSchema: {
104+
type: "object",
105+
properties: {
106+
code: {
107+
type: "string",
108+
description: "Python code to execute"
109+
},
110+
filename: {
111+
type: "string",
112+
description: "Optional: Name of the file to save the code (default: generated UUID)"
113+
}
114+
},
115+
required: ["code"]
116+
}
117+
}
118+
]
119+
};
120+
});
121+
/**
122+
* Handler for the execute_code tool.
123+
* Executes code and returns the result.
124+
*/
125+
server.setRequestHandler(CallToolRequestSchema, async (request) => {
126+
switch (request.params.name) {
127+
case "execute_code": {
128+
const args = request.params.arguments;
129+
if (!args?.code) {
130+
throw new Error("Code is required");
131+
}
132+
const defaultFilename = `code_${randomBytes(4).toString('hex')}.py`;
133+
const userFilename = args.filename || defaultFilename;
134+
const filename = typeof userFilename === 'string' ? userFilename : defaultFilename;
135+
const filePath = join(CODE_STORAGE_DIR, filename.endsWith('.py') ? filename : `${filename}.py`);
136+
const result = await executeCode(args.code, filePath);
137+
return {
138+
content: [{
139+
type: "text",
140+
text: result.text,
141+
isError: result.isError
142+
}]
143+
};
144+
}
145+
default:
146+
throw new Error("Unknown tool");
147+
}
148+
});
149+
/**
150+
* Start the server using stdio transport.
151+
*/
152+
async function main() {
153+
const transport = new StdioServerTransport();
154+
await server.connect(transport);
155+
}
156+
main().catch((error) => {
157+
console.error("Server error:", error);
158+
process.exit(1);
159+
});

0 commit comments

Comments
 (0)