terminal-server.js 2.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354
  1. #!/usr/bin/env node
  2. const { Server } = require("@modelcontextprotocol/sdk/server/index.js");
  3. const { StdioServerTransport } = require("@modelcontextprotocol/sdk/server/stdio.js");
  4. const { CallToolRequestSchema, ListToolsRequestSchema, } = require("@modelcontextprotocol/sdk/types.js");
  5. const { exec } = require("child_process");
  6. const util = require("util");
  7. const execAsync = util.promisify(exec);
  8. const blocked = ["rm ", "dd ", "mkfs", "format", "shutdown", "reboot", "halt", "sudo", "su", "chmod", "chown", "passwd", "kill ", ">", ">>", "|", "&&", "||", "`", "$("];
  9. 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"];
  10. function isSafe(cmd) {
  11. const lower = cmd.trim().toLowerCase();
  12. for (const b of blocked) if (lower.includes(b)) return false;
  13. for (const a of allowed) if (lower.startsWith(a)) return true;
  14. return false;
  15. }
  16. const server = new Server({ name: "terminal", version: "1.0.0" }, { capabilities: { tools: {} } });
  17. server.setRequestHandler(ListToolsRequestSchema, async () => ({
  18. tools: [{
  19. name: "run_command",
  20. description: "Execute a safe read-only shell command",
  21. inputSchema: {
  22. type: "object",
  23. properties: {
  24. command: { type: "string", description: "Shell command to execute" },
  25. timeout: { type: "number", description: "Timeout in seconds (max 60)", default: 30 }
  26. },
  27. required: ["command"]
  28. }
  29. }]
  30. }));
  31. server.setRequestHandler(CallToolRequestSchema, async (request) => {
  32. if (request.params.name !== "run_command") throw new Error(`Unknown tool: ${request.params.name}`);
  33. const { command, timeout = 30 } = request.params.arguments;
  34. if (!isSafe(command)) return { content: [{ type: "text", text: `Error: command not allowed` }], isError: true };
  35. try {
  36. const { stdout, stderr } = await execAsync(command, { timeout: Math.min(timeout, 60) * 1000, shell: "/bin/sh" });
  37. let text = stdout;
  38. if (stderr) text += "\n--- stderr ---\n" + stderr;
  39. return { content: [{ type: "text", text: text || "(no output)" }] };
  40. } catch (e) {
  41. return { content: [{ type: "text", text: `Error: ${e.message}` }], isError: true };
  42. }
  43. });
  44. async function main() {
  45. const transport = new StdioServerTransport();
  46. await server.connect(transport);
  47. }
  48. main().catch(console.error);