Skip to content

Instantly share code, notes, and snippets.

@csantanapr
Created August 14, 2026 14:26
Show Gist options
  • Select an option

  • Save csantanapr/c37920871ea6548b27b1e8a4f5ee31fb to your computer and use it in GitHub Desktop.

Select an option

Save csantanapr/c37920871ea6548b27b1e8a4f5ee31fb to your computer and use it in GitHub Desktop.
exec mcp server
#!/usr/bin/env node
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { CallToolRequestSchema, ListToolsRequestSchema } from "@modelcontextprotocol/sdk/types.js";
import { exec } from "child_process";
import { promisify } from "util";
import { writeFileSync, unlinkSync, mkdirSync, existsSync } from "fs";
import { join } from "path";
const execAsync = promisify(exec);
const MAX_OUTPUT_LENGTH = 50000;
const DEFAULT_TIMEOUT = 30000;
const SCRIPTS_DIR = join(process.env.HOME || "/tmp", ".quick-shell-mcp");
if (!existsSync(SCRIPTS_DIR)) mkdirSync(SCRIPTS_DIR, { recursive: true });
const server = new Server(
{ name: "shell-exec-mcp", version: "1.0.0" },
{ capabilities: { tools: {} } }
);
// Register tools (shell_exec, shell_script, terminal_launch, process_list, env_info)
server.setRequestHandler(ListToolsRequestSchema, async () => ({
tools: [
{ name: "shell_exec", description: "Execute a shell command",
inputSchema: { type: "object", properties: {
command: { type: "string", description: "Shell command to execute" },
cwd: { type: "string", description: "Working directory (default: $HOME)" },
timeout: { type: "number", description: "Timeout in ms (default: 30000)" }
}, required: ["command"] } },
{ name: "shell_script", description: "Execute a multi-line bash script",
inputSchema: { type: "object", properties: {
script: { type: "string", description: "Multi-line bash script" },
cwd: { type: "string" }, timeout: { type: "number" }
}, required: ["script"] } },
{ name: "terminal_launch", description: "Open a Terminal window with a command",
inputSchema: { type: "object", properties: {
command: { type: "string", description: "Command to run" },
title: { type: "string" },
app: { type: "string", enum: ["terminal", "iterm"] }
}, required: ["command"] } },
{ name: "process_list", description: "List running processes",
inputSchema: { type: "object", properties: {
filter: { type: "string" }, limit: { type: "number" }
} } },
{ name: "env_info", description: "Get system environment info",
inputSchema: { type: "object", properties: {
include: { type: "array", items: { type: "string" } }
} } }
]
}));
// Handle tool calls
server.setRequestHandler(CallToolRequestSchema, async (request) => {
const { name, arguments: args } = request.params;
try {
switch (name) {
case "shell_exec": {
const { command, cwd, timeout = DEFAULT_TIMEOUT } = args;
const result = await execAsync(command, {
cwd: cwd || process.env.HOME, timeout,
maxBuffer: 10*1024*1024, shell: "/bin/zsh"
});
const output = [result.stdout ? `STDOUT:\n${result.stdout}` : "",
result.stderr ? `STDERR:\n${result.stderr}` : ""].filter(Boolean).join("\n");
return { content: [{ type: "text", text: output.slice(0, MAX_OUTPUT_LENGTH) }] };
}
case "shell_script": {
const { script, cwd, timeout = 60000 } = args;
const scriptPath = join(SCRIPTS_DIR, `quick_${Date.now()}.sh`);
writeFileSync(scriptPath, `#!/bin/bash\nset -e\n${script}`, { mode: 0o755 });
try {
const result = await execAsync(scriptPath, {
cwd: cwd || process.env.HOME, timeout,
maxBuffer: 10*1024*1024, shell: "/bin/bash"
});
return { content: [{ type: "text", text: result.stdout || "(no output)" }] };
} finally { try { unlinkSync(scriptPath); } catch {} }
}
case "terminal_launch": {
const { command, title = "Quick Shell", app = "terminal" } = args;
const escaped = command.replace(/"/g, '\\"');
const script = app === "iterm"
? `tell application "iTerm"\n activate\n create window with default profile\nend tell`
: `tell application "Terminal"\n activate\n do script "${escaped}"\nend tell`;
await execAsync(`osascript -e '${script}'`);
return { content: [{ type: "text", text: `Launched: ${command}` }] };
}
case "process_list": {
const { filter, limit = 20 } = args || {};
const cmd = filter
? `ps aux | grep -i "${filter}" | grep -v grep | head -${limit}`
: `ps aux -r | head -${limit + 1}`;
const result = await execAsync(cmd);
return { content: [{ type: "text", text: result.stdout }] };
}
case "env_info": {
const info = [];
info.push((await execAsync("sw_vers")).stdout);
info.push((await execAsync("node -v")).stdout);
return { content: [{ type: "text", text: info.join("\n") }] };
}
}
} catch (e) { return { content: [{ type: "text", text: `Error: ${e.message}` }], isError: true }; }
});
async function main() {
const transport = new StdioServerTransport();
await server.connect(transport);
}
main().catch(e => { console.error(e); process.exit(1); });
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment