Skip to content

Commit 0fd6480

Browse files
authored
Merge pull request #2 from cforge42/dev
Update index.ts
2 parents 6042d63 + 8dc75f2 commit 0fd6480

File tree

2 files changed

+66
-49
lines changed

2 files changed

+66
-49
lines changed

README.md

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
# MCP Code Executor
2-
[![smithery badge](https://smithery.ai/badge/@bazinga012/mcp_code_executor)](https://smithery.ai/server/@bazinga012/mcp_code_executor)
32

43
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.
54

src/index.ts

Lines changed: 66 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import {
88
} from "@modelcontextprotocol/sdk/types.js";
99
import { randomBytes } from 'crypto';
1010
import { join } from 'path';
11-
import { mkdir, writeFile, appendFile, readFile, access } from 'fs/promises';
11+
import { mkdir, writeFile, appendFile, readFile, access, unlink } from 'fs/promises';
1212
import { exec, ExecOptions } from 'child_process';
1313
import { promisify } from 'util';
1414
import { platform } from 'os';
@@ -53,6 +53,39 @@ await mkdir(CODE_STORAGE_DIR, { recursive: true });
5353

5454
const execAsync = promisify(exec);
5555

56+
// Standardized tool response shape
57+
type ToolResponse = {
58+
type: 'text';
59+
text: string;
60+
isError: boolean;
61+
};
62+
63+
function makeResponse(payload: any, isError = false): ToolResponse {
64+
return {
65+
type: 'text',
66+
text: typeof payload === 'string' ? payload : JSON.stringify(payload),
67+
isError,
68+
};
69+
}
70+
71+
/**
72+
* Run a shell command and capture stdout/stderr even on non-zero exit codes.
73+
* This ensures we return useful information when a process fails.
74+
*/
75+
async function runCommand(command: string, options: ExecOptions & { cwd?: string; env?: NodeJS.ProcessEnv } = {}) {
76+
try {
77+
const { stdout, stderr } = await execAsync(command, options);
78+
return { stdout: stdout ?? '', stderr: stderr ?? '' };
79+
} catch (err: any) {
80+
// err may contain stdout/stderr properties for failed commands
81+
return {
82+
stdout: err.stdout ?? '',
83+
stderr: err.stderr ?? (err.message ? String(err.message) : String(err)),
84+
error: err.message ?? String(err)
85+
};
86+
}
87+
}
88+
5689
/**
5790
* Get platform-specific command for environment activation and execution
5891
*/
@@ -120,24 +153,15 @@ async function executeCode(code: string, filePath: string) {
120153
const pythonCmd = platform() === 'win32' ? `python -u "${filePath}"` : `python3 -u "${filePath}"`;
121154
const { command, options } = getPlatformSpecificCommand(pythonCmd);
122155

123-
// Execute code
124-
const { stdout, stderr } = await execAsync(command, {
156+
// Execute code (capture stdout/stderr even on non-zero exit)
157+
const { stdout, stderr } = await runCommand(command, {
125158
cwd: CODE_STORAGE_DIR,
126159
env: { ...process.env, PYTHONUNBUFFERED: '1' },
127160
...options
128161
});
129162

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-
};
163+
const status = stderr ? 'error' : 'success';
164+
return makeResponse({ status, output: stderr || stdout, file_path: filePath }, !!stderr);
141165
} catch (error) {
142166
const response = {
143167
status: 'error',
@@ -165,24 +189,15 @@ async function executeCodeFromFile(filePath: string) {
165189
const pythonCmd = platform() === 'win32' ? `python -u "${filePath}"` : `python3 -u "${filePath}"`;
166190
const { command, options } = getPlatformSpecificCommand(pythonCmd);
167191

168-
// Execute code with unbuffered Python
169-
const { stdout, stderr } = await execAsync(command, {
192+
// Execute code with unbuffered Python and capture stderr/stdout
193+
const { stdout, stderr } = await runCommand(command, {
170194
cwd: CODE_STORAGE_DIR,
171195
env: { ...process.env, PYTHONUNBUFFERED: '1' },
172196
...options
173197
});
174198

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-
};
199+
const status = stderr ? 'error' : 'success';
200+
return makeResponse({ status, output: stderr || stdout, file_path: filePath }, !!stderr);
186201
} catch (error) {
187202
const response = {
188203
status: 'error',
@@ -351,26 +366,22 @@ async function installDependencies(packages: string[]) {
351366
// Get platform-specific command
352367
const { command, options } = getPlatformSpecificCommand(installCmd);
353368

354-
// Execute installation with unbuffered Python
355-
const { stdout, stderr } = await execAsync(command, {
369+
// Execute installation and capture stdout/stderr
370+
const { stdout, stderr } = await runCommand(command, {
356371
cwd: CODE_STORAGE_DIR,
357372
env: { ...process.env, PYTHONUNBUFFERED: '1' },
358373
...options
359374
});
360375

361376
const response = {
362-
status: 'success',
377+
status: stderr ? 'error' : 'success',
363378
env_type: ENV_CONFIG.type,
364379
installed_packages: packages,
365380
output: stdout,
366381
warnings: stderr || undefined
367382
};
368383

369-
return {
370-
type: 'text',
371-
text: JSON.stringify(response),
372-
isError: false
373-
};
384+
return makeResponse(response, !!stderr);
374385
} catch (error) {
375386
const response = {
376387
status: 'error',
@@ -460,21 +471,28 @@ print(json.dumps(results))
460471
const pythonCmd = platform() === 'win32' ? `python -u "${checkScriptPath}"` : `python3 -u "${checkScriptPath}"`;
461472
const { command, options } = getPlatformSpecificCommand(pythonCmd);
462473

463-
const { stdout, stderr } = await execAsync(command, {
464-
cwd: CODE_STORAGE_DIR,
465-
env: { ...process.env, PYTHONUNBUFFERED: '1' },
466-
...options
467-
});
474+
// Run the command and ensure we remove the temporary script afterwards
475+
let stdout = '';
476+
let stderr = '';
477+
try {
478+
const runResult = await runCommand(command, {
479+
cwd: CODE_STORAGE_DIR,
480+
env: { ...process.env, PYTHONUNBUFFERED: '1' },
481+
...options
482+
});
483+
stdout = runResult.stdout ?? '';
484+
stderr = runResult.stderr ?? '';
485+
} finally {
486+
// Best-effort cleanup of the temporary check script
487+
try {
488+
await unlink(checkScriptPath);
489+
} catch (e) {
490+
// ignore cleanup errors
491+
}
492+
}
468493

469494
if (stderr) {
470-
return {
471-
type: 'text',
472-
text: JSON.stringify({
473-
status: 'error',
474-
error: stderr
475-
}),
476-
isError: true
477-
};
495+
return makeResponse({ status: 'error', error: stderr }, true);
478496
}
479497

480498
// Parse the package information

0 commit comments

Comments
 (0)