Skip to main content

CLI Runner 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):

cli-runner-mcp

Or run directly:

python cli_runner_mcp.py

Configuration

Configure via environment variable:

export CLI_RUNNER_LOG_DIR="$HOME/.cli_runner/logs"
cli-runner-mcp

Default log directory: ./cli_runner_logs/

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": {
    "cli-runner": {
      "command": "python",
      "args": ["/absolute/path/to/cli_runner_mcp.py"],
      "env": {
        "CLI_RUNNER_LOG_DIR": "/path/to/logs"
      }
    }
  }
}

Or if installed via pip:

{
  "mcpServers": {
    "cli-runner": {
      "command": "cli-runner-mcp"
    }
  }
}

Available Tools

1. cli_runner_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. cli_runner_send

Send text to a process's stdin.

Parameters:

  • process_id (string, required): Process ID from cli_runner_start
  • text (string, required): Text to send
  • add_newline (boolean, optional): Append newline (default: true)

Example:

{
  "process_id": "a3f5d2b1",
  "text": "print('Hello, World!')",
  "add_newline": true
}

Returns:

{
  "success": true,
  "process_id": "a3f5d2b1",
  "bytes_sent": 23,
  "message": "Input sent successfully"
}

3. cli_runner_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: "stdout")

Example:

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

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. cli_runner_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. cli_runner_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. cli_runner_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 cli_runner_start to launch your interactive CLI tool
    → Returns a process_id
    
  2. Send commands:

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

    Use cli_runner_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 cli_runner_stop to terminate the process
    

Example Use Cases

Interactive Python REPL

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

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

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

# Stop Python
cli_runner_stop(process_id="abc123")

Database CLI Tool

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

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

# Continue querying
cli_runner_send(process_id="def456", text="\\dt")  # List tables
cli_runner_read_output(process_id="def456")

# Exit
cli_runner_stop(process_id="def456")

Node.js REPL

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

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

# Stop
cli_runner_stop(process_id="ghi789")

Log Files

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

cli_runner_logs/
├── abc123_20260107_120000_python3.log
├── def456_20260107_120530_psql.log
└── ghi789_20260107_121045_node.log

Log Format:

=== CLI Runner 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 cli_runner_mcp.py

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

Syntax Check

python -m py_compile cli_runner_mcp.py

Packaging

Build a distributable package:

pip install build
python -m build

This creates:

  • dist/cli_runner_mcp-0.1.0.tar.gz
  • dist/cli_runner_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 cli_runner_ 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

Metadata

Release files for runnermcp 0.1.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for runnermcp 0.1.0
File Size Uploaded
runnermcp-0.1.0.tar.gz 17.0 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for runnermcp 0.1.0
File Interpreter ABI Platform
runnermcp-0.1.0-py3-none-any.whl Python 3 none any Details

Total release size: 31.6 kB

Release files / runnermcp-0.1.0.tar.gz

Download URL runnermcp-0.1.0.tar.gz
Size 17.0 kB
Tags Source
SHA-256 checksum
How to use checksums
f858e761d4b551f50d6df54c68505bd4f1b0cad93dde746ca1eae7259409bf0d
BLAKE2b-256 checksum
How to use checksums
1f9a5234c6fbb6b10036e362f6ad554435ab7c02c3207206d4c53db4f1f41962
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.7

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Jan 7, 2026.

Transparency log

Release files / runnermcp-0.1.0-py3-none-any.whl

Download URL runnermcp-0.1.0-py3-none-any.whl
Size 14.6 kB
Tags Python 3
SHA-256 checksum
How to use checksums
e393b38e2cc1197226823493f029de00cbc5598221dae58e3fa9bdd3f5e359f3
BLAKE2b-256 checksum
How to use checksums
b7f27365dbb19538a5c10b3fba14c2f063be738595a416e1c05f3de4662a792a
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.7

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Jan 7, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.1.0 This release

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page