Skip to main content
Glama
nickgnd

Tmux MCP Server

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;
    }
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations provided, the description carries full burden and does well by disclosing key behavioral traits: the rawMode parameter's impact on wrapper markers and status tracking, the noEnter parameter's automatic rawMode application, and important constraints about multi-line construct conflicts. It doesn't mention rate limits or authentication needs, but covers most operational aspects.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is perfectly front-loaded with the core purpose, followed by specific usage guidelines. Every sentence earns its place by providing essential operational guidance without redundancy. The structure moves from general to specific with clear imperative statements.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a 4-parameter tool with no annotations and no output schema, the description does well by covering execution behavior, parameter interactions, and sibling tool coordination. It could be more complete by explicitly mentioning error handling or return value expectations, but it provides sufficient context for safe tool invocation given the complexity.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, so the baseline is 3. The description adds some value by explaining rawMode's purpose ('for REPL/interactive compatibility') and noEnter's use cases ('For TUI navigation in apps like btop, vim, less'), but doesn't provide significant additional semantics beyond what's already in the schema descriptions.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the verb ('Execute a command') and resource ('in a tmux pane') with specific scope ('and get results'). It distinguishes from sibling tools like 'capture-pane' (which only captures output) and 'get-command-result' (which retrieves results separately) by emphasizing the execution aspect.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description provides explicit when-to-use guidance: 'For interactive applications (REPLs, editors), use `rawMode=true`' and 'When `rawMode=false` (default), avoid heredoc syntax...'. It also offers alternatives for file writing ('prefer: printf...') and references sibling tools ('Use capture-pane after execution to verify command outcome').

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Install Server

Other Tools

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