Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 36 additions & 19 deletions src/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import * as fs from "fs";
import * as path from "path";
import * as os from "os";
import { languageIdToExecutorMap, languageIdToFileExtensionMap } from "./constants.js";
import { exec } from "child_process";
import { execFile } from "child_process";

export function createServer(): McpServer {
const server = new McpServer({
Expand Down Expand Up @@ -34,54 +34,71 @@ export function createServer(): McpServer {
throw new Error(`Language '${languageId}' is not supported.`);
}

const filePath = await createTmpFile(code, languageId);
const command = `${executor} "${filePath}"`;
const { filePath, tmpDir } = await createTmpFile(code, languageId);

const result = await executeCommand(command);
try {
const result = await executeCommand(executor, filePath);

return {
content: [
{
type: "text",
text: result,
},
],
};
return {
content: [
{
type: "text",
text: result,
},
],
};
} finally {
await fs.promises.rm(tmpDir, { recursive: true, force: true });
}
Comment on lines +39 to +52
},
);

return server;
}

async function createTmpFile(content: string, languageId: string) {
const tmpDir = os.tmpdir();
const tmpDir = await fs.promises.mkdtemp(path.join(os.tmpdir(), "mcp-code-runner-"));
const fileExtension = getFileExtension(languageId);
const fileName = `tmp.${fileExtension}`;
const fileName = `main.${fileExtension}`;
const filePath = path.join(tmpDir, fileName);

await fs.promises.writeFile(filePath, content);

console.debug(`Temporary file created at: ${filePath}`);

return filePath;
return { filePath, tmpDir };
}

function getFileExtension(languageId: string): string {
const fileExtension = languageIdToFileExtensionMap[languageId as keyof typeof languageIdToFileExtensionMap];
return fileExtension ?? languageId;
}

async function executeCommand(command: string): Promise<string> {
function splitCommand(command: string): string[] {
const parts = command.match(/(?:[^\s"']+|"[^"]*"|'[^']*')+/g) ?? [];
return parts.map((part) => part.replace(/^["']|["']$/g, ""));
}

async function executeCommand(executor: string, filePath: string): Promise<string> {
return new Promise((resolve, reject) => {
console.debug(`Executing command: ${command}`);
exec(command, (error: any, stdout: string, stderr: string) => {
const [command, ...args] = splitCommand(executor);
Comment on lines +77 to +84

if (!command) {
reject("Error: Executor is required.");
return;
}

console.debug(`Executing command: ${command} ${[...args, filePath].join(" ")}`);
execFile(command, [...args, filePath], (error: any, stdout: string, stderr: string) => {
if (error) {
reject(`Error: ${error.message}`);
return;
}
if (stderr) {
reject(`Stderr: ${stderr}`);
return;
}
Comment on lines +86 to 100
resolve(stdout);
});
});
}
}