Skip to main content
Glama
nickgnd
by nickgnd

execute-command

Execute commands in tmux panes to control terminal sessions, with options for interactive applications and TUI navigation.

Instructions

Execute a command in a tmux pane and get results. For interactive applications (REPLs, editors), use rawMode=true. IMPORTANT: When rawMode=false (default), avoid heredoc syntax (cat << EOF) and other multi-line constructs as they conflict with command wrapping. For file writing, prefer: printf 'content\n' > file, echo statements, or write to temp files instead

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
paneIdYesID of the tmux pane
commandYesCommand to execute
rawModeNoExecute command without wrapper markers for REPL/interactive compatibility. Disables get-command-result status tracking. Use capture-pane after execution to verify command outcome.
noEnterNoSend keystrokes without pressing Enter. For TUI navigation in apps like btop, vim, less. Supports special keys (Up, Down, Escape, Tab, etc.) and strings (sent char-by-char for proper filtering). Automatically applies rawMode. Use capture-pane after to see results.

Implementation Reference

  • The MCP tool handler for 'execute-command'. Prepares parameters, calls tmux.executeCommand, and returns appropriate response or resource URI based on mode.
    async ({ paneId, command, rawMode, noEnter }) => { try { // If noEnter is true, automatically apply rawMode const effectiveRawMode = noEnter || rawMode; const commandId = await tmux.executeCommand(paneId, command, effectiveRawMode, noEnter); if (effectiveRawMode) { const modeText = noEnter ? "Keys sent without Enter" : "Interactive command started (rawMode)"; return { content: [{ type: "text", text: `${modeText}.\n\nStatus tracking is disabled.\nUse 'capture-pane' with paneId '${paneId}' to verify the command outcome.\n\nCommand ID: ${commandId}` }] }; } // Create the resource URI for this command's results const resourceUri = `tmux://command/${commandId}/result`; return { content: [{ type: "text", text: `Command execution started.\n\nTo get results, subscribe to and read resource: ${resourceUri}\n\nStatus will change from 'pending' to 'completed' or 'error' when finished.` }] }; } catch (error) { return { content: [{ type: "text", text: `Error executing command: ${error}` }], isError: true }; }
  • Zod schema defining input parameters for the 'execute-command' tool.
    { paneId: z.string().describe("ID of the tmux pane"), command: z.string().describe("Command to execute"), rawMode: z.boolean().optional().describe("Execute command without wrapper markers for REPL/interactive compatibility. Disables get-command-result status tracking. Use capture-pane after execution to verify command outcome."), noEnter: z.boolean().optional().describe("Send keystrokes without pressing Enter. For TUI navigation in apps like btop, vim, less. Supports special keys (Up, Down, Escape, Tab, etc.) and strings (sent char-by-char for proper filtering). Automatically applies rawMode. Use capture-pane after to see results.") },
  • src/index.ts:347-391 (registration)
    Registration of the 'execute-command' tool on the MCP server using server.tool().
    server.tool( "execute-command", "Execute a command in a tmux pane and get results. For interactive applications (REPLs, editors), use `rawMode=true`. IMPORTANT: When `rawMode=false` (default), avoid heredoc syntax (cat << EOF) and other multi-line constructs as they conflict with command wrapping. For file writing, prefer: printf 'content\\n' > file, echo statements, or write to temp files instead", { paneId: z.string().describe("ID of the tmux pane"), command: z.string().describe("Command to execute"), rawMode: z.boolean().optional().describe("Execute command without wrapper markers for REPL/interactive compatibility. Disables get-command-result status tracking. Use capture-pane after execution to verify command outcome."), noEnter: z.boolean().optional().describe("Send keystrokes without pressing Enter. For TUI navigation in apps like btop, vim, less. Supports special keys (Up, Down, Escape, Tab, etc.) and strings (sent char-by-char for proper filtering). Automatically applies rawMode. Use capture-pane after to see results.") }, async ({ paneId, command, rawMode, noEnter }) => { try { // If noEnter is true, automatically apply rawMode const effectiveRawMode = noEnter || rawMode; const commandId = await tmux.executeCommand(paneId, command, effectiveRawMode, noEnter); if (effectiveRawMode) { const modeText = noEnter ? "Keys sent without Enter" : "Interactive command started (rawMode)"; return { content: [{ type: "text", text: `${modeText}.\n\nStatus tracking is disabled.\nUse 'capture-pane' with paneId '${paneId}' to verify the command outcome.\n\nCommand ID: ${commandId}` }] }; } // Create the resource URI for this command's results const resourceUri = `tmux://command/${commandId}/result`; return { content: [{ type: "text", text: `Command execution started.\n\nTo get results, subscribe to and read resource: ${resourceUri}\n\nStatus will change from 'pending' to 'completed' or 'error' when finished.` }] }; } catch (error) { return { content: [{ type: "text", text: `Error executing command: ${error}` }], isError: true }; } } );
  • Helper function implementing the core logic of sending commands to a tmux pane via tmux send-keys, with support for raw mode, no-enter mode, and execution tracking.
    export async function executeCommand(paneId: string, command: string, rawMode?: boolean, noEnter?: boolean): Promise<string> { // Generate unique ID for this command execution const commandId = uuidv4(); let fullCommand: string; if (rawMode || noEnter) { fullCommand = command; } else { const endMarkerText = getEndMarkerText(); fullCommand = `echo "${startMarkerText}"; ${command}; echo "${endMarkerText}"`; } // Store command in tracking map activeCommands.set(commandId, { id: commandId, paneId, command, status: 'pending', startTime: new Date(), rawMode: rawMode || noEnter }); // Send the command to the tmux pane if (noEnter) { // Check if this is a special key (e.g., Up, Down, Left, Right, Escape, Tab, etc.) // Special keys in tmux are typically capitalized or have special names const specialKeys = ['Up', 'Down', 'Left', 'Right', 'Escape', 'Tab', 'Enter', 'Space', 'BSpace', 'Delete', 'Home', 'End', 'PageUp', 'PageDown', 'F1', 'F2', 'F3', 'F4', 'F5', 'F6', 'F7', 'F8', 'F9', 'F10', 'F11', 'F12']; if (specialKeys.includes(fullCommand)) { // Send special key as-is await executeTmux(`send-keys -t '${paneId}' ${fullCommand}`); } else { // For regular text, send each character individually to ensure proper processing // This handles both single characters (like 'q', 'f') and strings (like 'beam') for (const char of fullCommand) { await executeTmux(`send-keys -t '${paneId}' '${char.replace(/'/g, "'\\''")}'`); } } } else { await executeTmux(`send-keys -t '${paneId}' '${fullCommand.replace(/'/g, "'\\''")}' Enter`); } return commandId; }

Latest Blog Posts

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/nickgnd/tmux-mcp'

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