#!/usr/bin/env node const { Server } = require("@modelcontextprotocol/sdk/server/index.js"); const { StdioServerTransport } = require("@modelcontextprotocol/sdk/server/stdio.js"); const { CallToolRequestSchema, ListToolsRequestSchema, } = require("@modelcontextprotocol/sdk/types.js"); const { exec } = require("child_process"); const util = require("util"); const execAsync = util.promisify(exec); const blocked = ["rm ", "dd ", "mkfs", "format", "shutdown", "reboot", "halt", "sudo", "su", "chmod", "chown", "passwd", "kill ", ">", ">>", "|", "&&", "||", "`", "$("]; const allowed = ["ls", "cat", "head", "tail", "wc", "find", "grep", "which", "pwd", "whoami", "id", "date", "uname", "df", "du", "ps", "free", "uptime", "env", "echo", "sort", "uniq", "stat", "file", "tree", "docker ps", "docker images", "curl", "ping", "host"]; function isSafe(cmd) { const lower = cmd.trim().toLowerCase(); for (const b of blocked) if (lower.includes(b)) return false; for (const a of allowed) if (lower.startsWith(a)) return true; return false; } const server = new Server({ name: "terminal", version: "1.0.0" }, { capabilities: { tools: {} } }); server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: [{ name: "run_command", description: "Execute a safe read-only shell command", inputSchema: { type: "object", properties: { command: { type: "string", description: "Shell command to execute" }, timeout: { type: "number", description: "Timeout in seconds (max 60)", default: 30 } }, required: ["command"] } }] })); server.setRequestHandler(CallToolRequestSchema, async (request) => { if (request.params.name !== "run_command") throw new Error(`Unknown tool: ${request.params.name}`); const { command, timeout = 30 } = request.params.arguments; if (!isSafe(command)) return { content: [{ type: "text", text: `Error: command not allowed` }], isError: true }; try { const { stdout, stderr } = await execAsync(command, { timeout: Math.min(timeout, 60) * 1000, shell: "/bin/sh" }); let text = stdout; if (stderr) text += "\n--- stderr ---\n" + stderr; return { content: [{ type: "text", text: text || "(no output)" }] }; } 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(console.error);