Skip to main content

MCP server for managing interactive CLI binary processes

Project description

Executor MCP Server

An MCP (Model Context Protocol) server for managing interactive CLI binary processes. This server enables AI assistants to launch persistent binary processes, send stdin commands, and read stdout/stderr responses over time.

Features

  • Persistent Process Management: Launch binaries that run in the background
  • Interactive I/O: Send commands to stdin and read from stdout/stderr
  • Output Buffering: Keeps last 1000 lines in memory per stream
  • Comprehensive Logging: All I/O is logged to timestamped files
  • Multiple Process Support: Manage multiple binaries simultaneously
  • Real-time Output: Background tasks continuously capture output

Important Note

This MCP server uses stdio transport for communication with MCP clients (like Claude Desktop). This is separate from the stdin/stdout of the binaries being managed. The server manages the stdin/stdout of your target binaries independently.

Installation

From Source

# Clone or navigate to the directory
cd skills/mcp-builder/runner

# Install in development mode
pip install -e .

# Or install from the directory
pip install .

Dependencies

  • Python >= 3.10
  • mcp >= 1.1.0
  • pydantic >= 2.0.0

Usage

Starting the MCP Server

The server runs using stdio transport (default for MCP):

executor-mcp

Or run directly:

python executor_mcp.py

Configuration

Configure via environment variable:

export EXECUTOR_LOG_DIR="$HOME/.executor-mcp/logs"
executor-mcp

Default log directory: .executor-mcp/

Configuring with Claude Desktop

Add to your Claude Desktop configuration:

MacOS: ~/Library/Application Support/Claude/claude_desktop_config.json

Windows: %APPDATA%\Claude\claude_desktop_config.json

Linux: ~/.config/Claude/claude_desktop_config.json

{
  "mcpServers": {
    "executor": {
      "command": "python",
      "args": ["/absolute/path/to/executor_mcp.py"],
      "env": {
        "EXECUTOR_LOG_DIR": "/path/to/logs"
      }
    }
  }
}

Or if installed via pip:

{
  "mcpServers": {
    "executor": {
      "command": "executor-mcp"
    }
  }
}

Available Tools

1. executor_start

Launch a new interactive binary process.

Parameters:

  • command (string, required): Path to the binary to execute
  • args (list[string], optional): Command-line arguments
  • working_dir (string, optional): Working directory for the process

Example:

{
  "command": "/usr/bin/python3",
  "args": ["-i"],
  "working_dir": "/home/user/project"
}

Returns:

{
  "success": true,
  "process_id": "a3f5d2b1",
  "command": "/usr/bin/python3",
  "args": ["-i"],
  "pid": 12345,
  "log_file": "./cli_runner_logs/a3f5d2b1_20260107_120000_python3.log",
  "message": "Process started successfully. Use process_id 'a3f5d2b1' for subsequent operations."
}

2. executor_send

Send text to a process's stdin and optionally wait for output.

Parameters:

  • process_id (string, required): Process ID from executor_start
  • text (string, required): Text to send to stdin
  • add_newline (boolean, optional): Append newline (default: true)
  • wait_time (float, optional): Seconds to wait after sending (default: 0.1)
    • 0: Wait and return only NEW output generated by this command

    • 0: Send immediately without waiting (use executor_read_output later)
  • tail_lines (integer, optional): Number of lines to return (default: 20, only used with full_buffer=True)
  • full_buffer (boolean, optional): Return full buffer instead of just new output (default: false)

Examples:

{
  "process_id": "abc123",
  "text": "print('Hello')",
  "wait_time": 0.5
}

Returns:

{
  "success": true,
  "process_id": "abc123",
  "output": ["Hello\n"],
  "lines_returned": 1,
  "message": "Retrieved 1 new lines"
}

Usage Patterns:

  • Most commands: Use wait_time > 0 for send + output in one call
  • Long-running: Use wait_time = 0, then executor_read_output when ready
  • Debugging: Use full_buffer = true to see full buffer history

3. executor_read_output

Read output from a process's stdout/stderr buffer.

Parameters:

  • process_id (string, required): Process ID to read from
  • tail_lines (integer, optional): Number of recent lines (default: all buffered)
  • stream (string, optional): Which stream - "stdout", "stderr", or "both" (default: "both")
    • "both": Merges stdout and stderr (recommended)
    • "stdout": Stdout only
    • "stderr": Stderr only

Example:

{
  "process_id": "a3f5d2b1",
  "tail_lines": 10,
  "stream": "both"
}

Returns:

{
  "success": true,
  "process_id": "a3f5d2b1",
  "is_running": true,
  "return_code": null,
  "lines_returned": 10,
  "output": ["Hello, World!\n", ">>> "],
  "log_file": "./cli_runner_logs/a3f5d2b1_20260107_120000_python3.log",
  "message": "Retrieved 10 lines from both"
}

Returns:

{
  "success": true,
  "process_id": "a3f5d2b1",
  "is_running": true,
  "return_code": null,
  "lines_returned": 10,
  "output": ["Hello, World!\n", ">>> "],
  "log_file": "./cli_runner_logs/a3f5d2b1_20260107_120000_python3.log",
  "message": "Retrieved 10 lines from stdout"
}

4. executor_stop

Stop a running process.

Parameters:

  • process_id (string, required): Process ID to stop
  • force (boolean, optional): Use SIGKILL instead of SIGTERM (default: false)

Example:

{
  "process_id": "a3f5d2b1",
  "force": false
}

Returns:

{
  "success": true,
  "process_id": "a3f5d2b1",
  "pid": 12345,
  "return_code": 0,
  "termination_method": "SIGTERM (graceful)",
  "log_file": "./cli_runner_logs/a3f5d2b1_20260107_120000_python3.log",
  "message": "Process terminated successfully using SIGTERM (graceful)"
}

5. executor_list

List all active processes.

Parameters: None

Returns:

{
  "success": true,
  "count": 2,
  "processes": [
    {
      "process_id": "a3f5d2b1",
      "command": "/usr/bin/python3",
      "args": ["-i"],
      "started_at": "2026-01-07T12:00:00",
      "is_running": true,
      "pid": 12345,
      "return_code": null,
      "log_file": "./cli_runner_logs/a3f5d2b1_20260107_120000_python3.log",
      "stdout_lines_buffered": 42,
      "stderr_lines_buffered": 0
    }
  ],
  "message": "Found 2 active process(es)"
}

6. executor_get_info

Get detailed information about a specific process.

Parameters:

  • process_id (string, required): Process ID

Returns:

{
  "success": true,
  "process_id": "a3f5d2b1",
  "command": "/usr/bin/python3",
  "args": ["-i"],
  "started_at": "2026-01-07T12:00:00",
  "is_running": true,
  "pid": 12345,
  "return_code": null,
  "log_file": "./cli_runner_logs/a3f5d2b1_20260107_120000_python3.log",
  "stdout_lines_buffered": 42,
  "stderr_lines_buffered": 0,
  "recent_stdout": [">>> ", "Hello, World!\n", ">>> "],
  "recent_stderr": []
}

Typical Workflow

  1. Start a binary:

    Use executor_start to launch your interactive CLI tool
    → Returns a process_id
    
  2. Send commands:

    Use executor_send to write to stdin
    → Process executes command
    
  3. Read responses:

    Use executor_read_output to retrieve stdout/stderr
    → Get the command's output
    
  4. Repeat steps 2-3 as needed for ongoing interaction

  5. Stop when done:

    Use executor_stop to terminate the process
    

Example Use Cases

Interactive Python REPL

# Start Python
executor_start(command="python3", args=["-i"])
# → process_id: "abc123"

# Send Python code
executor_send(process_id="abc123", text="x = 42")
executor_send(process_id="abc123", text="print(x * 2)")

# Read output
executor_read_output(process_id="abc123", tail_lines=5)
# → [">>> x = 42\n", ">>> print(x * 2)\n", "84\n", ">>> "]

# Stop Python
executor_stop(process_id="abc123")

Database CLI Tool

# Start PostgreSQL CLI
executor_start(command="psql", args=["mydb"])
# → process_id: "def456"

# Execute queries
executor_send(process_id="def456", text="SELECT * FROM users LIMIT 5;")
executor_read_output(process_id="def456")

# Continue querying
executor_send(process_id="def456", text="\\dt")  # List tables
executor_read_output(process_id="def456")

# Exit
executor_stop(process_id="def456")

Node.js REPL

# Start Node.js
executor_start(command="node")
# → process_id: "ghi789"

# Evaluate JavaScript
executor_send(process_id="ghi789", text="const fs = require('fs')")
executor_send(process_id="ghi789", text="fs.readdirSync('.')")
executor_read_output(process_id="ghi789")

# Stop
executor_stop(process_id="ghi789")

Log Files

All process I/O is logged to timestamped files in the log directory:

.executor-mcp/
├── abc123_20260107_120000_python3.log
├── def456_20260107_120530_psql.log
└── ghi789_20260107_121045_node.log

Log Format:

=== Executor MCP Process Log ===
Process ID: abc123
Command: python3
Started: 2026-01-07T12:00:00.123456
==================================================

[2026-01-07 12:00:00.123] COMMAND: python3 -i
[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:02.234] STDOUT: >>>
[2026-01-07 12:00:05.567] TERMINATED: Method: SIGTERM (graceful), Return code: 0

Architecture

Process Lifecycle

  1. Start: asyncio.create_subprocess_exec launches the binary with PIPE'd stdin/stdout/stderr
  2. Background Tasks: Two asyncio tasks continuously read from stdout and stderr
  3. Buffering: Output is stored in circular buffers (deque with maxlen=1000)
  4. Logging: All I/O is written to log files with timestamps
  5. Interaction: AI can send stdin and read buffered output at any time
  6. Termination: Process is stopped with SIGTERM/SIGKILL, tasks are cancelled

Memory Management

  • Each process keeps 1000 lines in memory per stream (configurable via DEFAULT_BUFFER_SIZE)
  • Older lines are automatically dropped from buffers (circular buffer)
  • Complete history is preserved in log files
  • Multiple processes can run simultaneously

Error Handling

All tools return structured JSON with:

  • success: boolean indicating operation success
  • error: error message (if failed)
  • suggestion: actionable guidance for fixing the issue

Development

Testing

Test the server manually:

# Terminal 1: Start the server
python executor_mcp.py

# Terminal 2: Use MCP Inspector
npx @modelcontextprotocol/inspector python executor_mcp.py

Syntax Check

python -m py_compile executor_mcp.py

Packaging

Build a distributable package:

pip install build
python -m build

This creates:

  • dist/executor_mcp-0.1.0.tar.gz
  • dist/executor_mcp-0.1.0-py3-none-any.whl

License

Apache License 2.0 - See LICENSE.txt for details.

Contributing

This MCP server follows the MCP Best Practices and Python Implementation Guide.

Key design principles:

  • ✅ Clear, descriptive tool names with executor_ prefix
  • ✅ Comprehensive error messages with actionable suggestions
  • ✅ Proper async/await for all I/O operations
  • ✅ Type-safe Pydantic models for input validation
  • ✅ JSON responses for structured data
  • ✅ Tool annotations (readOnlyHint, destructiveHint, etc.)
  • ✅ Comprehensive logging for debugging

Support

For issues or questions:

  1. Check the log files in CLI_RUNNER_LOG_DIR
  2. Review error messages (they include suggestions)
  3. Verify binary paths and permissions
  4. Ensure Python >= 3.10 and dependencies are installed

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

executor_mcp-0.1.2.tar.gz (14.1 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

executor_mcp-0.1.2-py3-none-any.whl (14.9 kB view details)

Uploaded Python 3

File details

Details for the file executor_mcp-0.1.2.tar.gz.

File metadata

  • Download URL: executor_mcp-0.1.2.tar.gz
  • Upload date:
  • Size: 14.1 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for executor_mcp-0.1.2.tar.gz
Algorithm Hash digest
SHA256 1e6394e4cbe24fd82782d75f8b8f766541719afa121f12ac5d1fc5a88532bfdc
MD5 5542051165f198020bd222f04134be99
BLAKE2b-256 5f8b00471efb5d094837ba82053c82628516ed2d618d1cc9711878a80372d7b4

See more details on using hashes here.

Provenance

The following attestation bundles were made for executor_mcp-0.1.2.tar.gz:

Publisher: publish.yml on XuNeo/executor-mcp

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file executor_mcp-0.1.2-py3-none-any.whl.

File metadata

  • Download URL: executor_mcp-0.1.2-py3-none-any.whl
  • Upload date:
  • Size: 14.9 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for executor_mcp-0.1.2-py3-none-any.whl
Algorithm Hash digest
SHA256 8851a01b422fe0dd652b9b5c4dfe5384842b7234a03c94b7ff831bae9849b23a
MD5 0b84e0874fb6142710eb530ed96bd5fc
BLAKE2b-256 a56dddacbc9d3ab704952eb6b49b69c4042cb1d80192e9e6966054080c6d24b3

See more details on using hashes here.

Provenance

The following attestation bundles were made for executor_mcp-0.1.2-py3-none-any.whl:

Publisher: publish.yml on XuNeo/executor-mcp

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page