Manage persistent interactive CLI processes (REPLs, debuggers, database CLIs, NuttX simulator, QEMU). Use when process maintains state across commands. NOT for one-off commands - use Bash...
Manage persistent interactive CLI processes with stateful stdin/stdout communication via MCP tools.
Pattern: Start → Send* → Read* → Stop
This skill requires the executor-mcp MCP server to be installed and configured.
Installation:
pip install executor-mcp
Configuration: Add to Claude Desktop config (~/.config/Claude/claude_desktop_config.json):
{
"mcpServers": {
"executor": {
"command": "executor-mcp"
}
}
}
| Scenario | Tool |
|---|---|
| REPL / debugger / database CLI / NuttX sim / QEMU | executor |
| One-off command that completes immediately | Bash |
| Need terminal emulation (curses, colors, vi) | tmux |
| Multiple concurrent interactive sessions to monitor | tmux |
Use executor when:
All tools return JSON responses. Parameters shown below.
Launch a new interactive process.
Parameters:
{
"command": "python3",
"args": ["-i"],
"working_dir": "/optional/path"
}
Returns: process_id (unique identifier for subsequent operations)
Send text to process stdin and optionally wait for output.
Parameters:
{
"process_id": "abc123",
"text": "x = 42",
"wait_time": 0.1,
"add_newline": true,
"tail_lines": 20,
"full_buffer": false
}
Key parameters:
wait_time: Seconds to wait before reading (default: 0.1)> 0: Wait and return NEW output from this command0: Send immediately, use executor_read_output laterfull_buffer: If true, return full buffer instead of just new output (default: false)tail_lines: Lines to return when using full_buffer=true (default: 20)Returns: New output lines if wait_time > 0, otherwise "Success"
Read buffered output from stdout/stderr.
Parameters:
{
"process_id": "abc123",
"tail_lines": 50,
"stream": "both"
}
Parameters:
tail_lines: Number of recent lines (default: all buffered, max 1000)stream: "both" (merged), "stdout", or "stderr" (default: "both")Returns: Buffered output lines (last 1000 lines kept in memory)
Terminate a running process.
Parameters:
{
"process_id": "abc123",
"force": false
}
force: true = SIGKILL, false = SIGTERM graceful (default)List all active processes (no parameters).
Returns: Array of process info (process_id, command, status, buffer sizes)
Get detailed information about a specific process.
Parameters:
{
"process_id": "abc123"
}
Returns: Full process state, buffer sizes, recent output preview
# Start Python with unbuffered output
executor_start(command="python3", args=["-i", "-u"])
# → Returns: {"process_id": "abc123", ...}
# Wait for startup banner (0.3s)
executor_send(process_id="abc123", text="x = 42", wait_time=0.3)
# → Returns new output: [">>> ", "x = 42\n", ">>> "]
# Execute and read output
executor_send(process_id="abc123", text="print(x * 2)")
# → Returns: ["print(x * 2)\n", "84\n", ">>> "]
# Stop when done
executor_stop(process_id="abc123")
# Start debugger
executor_start(command="gdb", args=["--quiet", "./binary"])
# → process_id: "def456"
# Disable pagination
executor_send(process_id="def456", text="set pagination off", wait_time=0.2)
# Set breakpoint
executor_send(process_id="def456", text="break main", wait_time=0.2)
# Run program
executor_send(process_id="def456", text="run", wait_time=0.5)
# Examine variables
executor_send(process_id="def456", text="info locals")
# Exit debugger
executor_stop(process_id="def456")
# Connect to database
executor_start(command="psql", args=["dbname", "-U", "user"])
# → process_id: "ghi789"
# Run query (wait for results)
executor_send(
process_id="ghi789",
text="SELECT * FROM users LIMIT 5;",
wait_time=0.5
)
# → Returns query results
# List tables
executor_send(process_id="ghi789", text="\\dt")
# Disconnect
executor_send(process_id="ghi789", text="\\q", wait_time=0)
executor_stop(process_id="ghi789")
# Start process
executor_start(command="python3", args=["-i"])
# → process_id: "jkl012"
# Queue multiple commands without waiting
executor_send(process_id="jkl012", text="import sys", wait_time=0)
executor_send(process_id="jkl012", text="import os", wait_time=0)
executor_send(process_id="jkl012", text="import json", wait_time=0)
# Read all output at once
output = executor_read_output(process_id="jkl012", tail_lines=50)
# Start NuttX simulator
executor_start(command="./nuttx/nuttx", working_dir="/path/to/nuttx")
# → process_id: "mno345"
# Wait for NSH prompt
executor_send(process_id="mno345", text="help", wait_time=0.5)
# Run NuttX commands
executor_send(process_id="mno345", text="ps")
executor_send(process_id="mno345", text="free")
# Exit simulator
executor_send(process_id="mno345", text="exit", wait_time=0)
executor_stop(process_id="mno345", force=true)
Debugging a Python script that crashes:
# 1. Start Python debugger with script
executor_start(command="python3", args=["-i", "buggy_script.py"])
# → process_id: "debug001"
# 2. Script crashes, now inspect
executor_send(process_id="debug001", text="import traceback")
executor_send(process_id="debug001", text="traceback.print_exc()")
# 3. Inspect variables at crash point
executor_send(process_id="debug001", text="print(locals())")
# 4. Test fix interactively
executor_send(process_id="debug001", text="x = corrected_value")
executor_send(process_id="debug001", text="retry_operation(x)")
# 5. Verify fix works
output = executor_read_output(process_id="debug001", tail_lines=20)
# 6. Exit when done
executor_stop(process_id="debug001")
Common errors and solutions:
| Error Pattern | Cause | Solution |
|---|---|---|
FileNotFoundError |
Binary not found | Check path, use absolute path |
PermissionError |
Not executable | chmod +x the binary |
Process died |
Binary crashed | Check logs in .executorlog/ |
No output |
Buffered output | Use -u flag for Python, wait longer |
BrokenPipeError |
Process stdin closed | Process may have exited, check status |
Debugging steps:
executor_get_info(process_id).executorlog/{process_id}_{timestamp}_{command}.logwait_time (0.3-0.5s)python3 -u or python3 -i.executorlog/) for debugging - all I/O is timestampedAll I/O is automatically logged to:
Location: .executorlog/{process_id}_{timestamp}_{command}.log
Format:
=== Executor MCP Process Log ===
Process ID: abc123
Command: python3
Started: 2026-01-07T12:00:00
==================================================
[2026-01-07 12:00:00.123] COMMAND: python3 -i -u
[2026-01-07 12:00:00.456] STDOUT: Python 3.13.0 ...
[2026-01-07 12:00:01.789] STDIN: x = 42
[2026-01-07 12:00:01.890] STDOUT: >>>
[2026-01-07 12:00:02.012] STDIN: print(x * 2)
[2026-01-07 12:00:02.123] STDOUT: 84
[2026-01-07 12:00:05.567] TERMINATED: Method: SIGTERM, Return code: 0
Configure log directory:
export EXECUTOR_LOG_DIR="$HOME/.executor-mcp/logs"