Skip to main content
Glama

iTerm MCP Server

by rishabkoul
index.js6.36 kB
#!/usr/bin/env node import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import { z } from "zod"; import { spawn, exec } from "node:child_process"; import { promisify } from "node:util"; // Store active terminals const terminals = new Map(); let terminalCounter = 0; // Helper function to execute AppleScript for iTerm async function executeITermScript(script) { const execPromise = promisify(exec); // Simple launch script const launchScript = ` tell application "iTerm" activate end tell `; try { // First try to launch/activate iTerm await execPromise(`osascript -e '${launchScript}'`); // Wait a brief moment await new Promise((resolve) => setTimeout(resolve, 1000)); // Now execute the actual script with iTerm instead of iTerm2 const modifiedScript = script.replace(/iTerm2/g, "iTerm"); const { stdout } = await execPromise(`osascript -e '${modifiedScript}'`); return stdout.trim(); } catch (error) { console.error("iTerm AppleScript error:", error); throw error; } } // Create server instance const server = new McpServer({ name: "terminal", version: "1.0.0", }); // Register terminal tools server.tool("open-terminal", "Open a new terminal instance", {}, async () => { const terminalId = `terminal-${terminalCounter++}`; // Create both GUI terminal and background process for output collection const shell = process.platform === "win32" ? "cmd.exe" : "/bin/bash"; const terminal = spawn(shell, [], { stdio: ["pipe", "pipe", "pipe"], shell: true, }); const output = []; terminal.stdout.on("data", (data) => { output.push(data.toString()); }); terminal.stderr.on("data", (data) => { output.push(data.toString()); }); // Create iTerm window const script = ` tell application "iTerm2" activate tell current window create tab with default profile tell current session write text "echo Terminal ${terminalId} ready" end tell end tell end tell `; try { await executeITermScript(script); terminals.set(terminalId, { process: terminal, output, id: terminalId, }); return { content: [ { type: "text", text: `Terminal opened with ID: ${terminalId}`, }, ], }; } catch (error) { terminal.kill(); // Clean up background process if iTerm fails throw error; } }); server.tool( "execute-command", "Execute a command in a specific terminal", { terminalId: z.string().describe("ID of the terminal to execute command in"), command: z.string().describe("Command to execute"), }, async ({ terminalId, command }) => { const terminal = terminals.get(terminalId); if (!terminal) { return { content: [ { type: "text", text: `Terminal ${terminalId} not found`, }, ], }; } // Execute in both GUI and background process terminal.process.stdin.write(command + "\n"); const script = ` tell application "iTerm2" tell current session of current window write text "${command.replace(/"/g, '\\"')}" end tell end tell `; try { await executeITermScript(script); // Give some time for the command to execute and output to be collected await new Promise((resolve) => setTimeout(resolve, 100)); return { content: [ { type: "text", text: `Command executed in ${terminalId}`, }, ], }; } catch (error) { return { content: [ { type: "text", text: `Failed to execute in GUI terminal but command ran in background: ${error.message}`, }, ], }; } } ); server.tool( "read-output", "Read the output from a specific terminal", { terminalId: z.string().describe("ID of the terminal to read output from"), }, async ({ terminalId }) => { const terminal = terminals.get(terminalId); if (!terminal) { return { content: [ { type: "text", text: `Terminal ${terminalId} not found`, }, ], }; } const output = terminal.output.join(""); terminal.output.length = 0; // Clear the output buffer return { content: [ { type: "text", text: output || "No output available", }, ], }; } ); server.tool( "close-terminal", "Close a specific terminal", { terminalId: z.string().describe("ID of the terminal to close"), }, async ({ terminalId }) => { const terminal = terminals.get(terminalId); if (!terminal) { return { content: [ { type: "text", text: `Terminal ${terminalId} not found`, }, ], }; } // Close both GUI and background process terminal.process.kill(); const script = ` tell application "iTerm2" close current window end tell `; try { await executeITermScript(script); } catch (error) { console.error("Failed to close iTerm window:", error); } terminals.delete(terminalId); // Safely decrement the terminal counter terminalCounter = Math.max(0, terminalCounter - 1); return { content: [ { type: "text", text: `Terminal ${terminalId} closed`, }, ], }; } ); server.tool( "list-terminals", "List all active terminals and their information", {}, async () => { const activeTerminals = Array.from(terminals.entries()).map(([id]) => id); const count = terminals.size; return { content: [ { type: "text", text: `Number of active terminals: ${count}\nActive terminal IDs: ${ activeTerminals.join(", ") || "None" }`, }, ], }; } ); async function main() { const transport = new StdioServerTransport(); await server.connect(transport); console.error("Terminal MCP Server running on stdio"); } main().catch((error) => { console.error("Fatal error in main():", error); process.exit(1); });

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/rishabkoul/iTerm-MCP-Server'

If you have feedback or need assistance with the MCP directory API, please join our Discord server