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 executeargs(list[string], optional): Command-line argumentsworking_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.
Parameters:
process_id(string, required): Process ID fromexecutor_starttext(string, required): Text to sendadd_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. executor_read_output
Read output from a process's stdout/stderr buffer.
Parameters:
process_id(string, required): Process ID to read fromtail_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. executor_stop
Stop a running process.
Parameters:
process_id(string, required): Process ID to stopforce(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
-
Start a binary:
Use executor_start to launch your interactive CLI tool → Returns a process_id -
Send commands:
Use executor_send to write to stdin → Process executes command -
Read responses:
Use executor_read_output to retrieve stdout/stderr → Get the command's output -
Repeat steps 2-3 as needed for ongoing interaction
-
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
- Start:
asyncio.create_subprocess_execlaunches the binary with PIPE'd stdin/stdout/stderr - Background Tasks: Two asyncio tasks continuously read from stdout and stderr
- Buffering: Output is stored in circular buffers (deque with maxlen=1000)
- Logging: All I/O is written to log files with timestamps
- Interaction: AI can send stdin and read buffered output at any time
- 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 successerror: 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.gzdist/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:
- Check the log files in
CLI_RUNNER_LOG_DIR - Review error messages (they include suggestions)
- Verify binary paths and permissions
- Ensure Python >= 3.10 and dependencies are installed
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file executor_mcp-0.1.0.tar.gz.
File metadata
- Download URL: executor_mcp-0.1.0.tar.gz
- Upload date:
- Size: 14.0 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
639553124d28ee33a354889f11622f14c0d3d8aa86c3707c1f3782e2e35652f4
|
|
| MD5 |
54641be56e96aec7ad756f2f9a3a5961
|
|
| BLAKE2b-256 |
b63480edb9838010e9a63126fe8bc70a9f144f74a6bab6ee3d477c990ac4b031
|
Provenance
The following attestation bundles were made for executor_mcp-0.1.0.tar.gz:
Publisher:
publish.yml on XuNeo/executor-mcp
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
executor_mcp-0.1.0.tar.gz -
Subject digest:
639553124d28ee33a354889f11622f14c0d3d8aa86c3707c1f3782e2e35652f4 - Sigstore transparency entry: 829789505
- Sigstore integration time:
-
Permalink:
XuNeo/executor-mcp@f9f3699895afbd2febb33cc60bf613488e549ec7 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/XuNeo
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@f9f3699895afbd2febb33cc60bf613488e549ec7 -
Trigger Event:
release
-
Statement type:
File details
Details for the file executor_mcp-0.1.0-py3-none-any.whl.
File metadata
- Download URL: executor_mcp-0.1.0-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.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
fb35d2383aafdce8b4d838ca4ff312c2022010265ffab680e9e236d8a233f821
|
|
| MD5 |
18a3f8091a95ed0fed807bf74d13dff3
|
|
| BLAKE2b-256 |
cdc862ad6a6f38a67eac12c5832823220e651eee975d81bf84bf2daa6662a605
|
Provenance
The following attestation bundles were made for executor_mcp-0.1.0-py3-none-any.whl:
Publisher:
publish.yml on XuNeo/executor-mcp
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
executor_mcp-0.1.0-py3-none-any.whl -
Subject digest:
fb35d2383aafdce8b4d838ca4ff312c2022010265ffab680e9e236d8a233f821 - Sigstore transparency entry: 829789509
- Sigstore integration time:
-
Permalink:
XuNeo/executor-mcp@f9f3699895afbd2febb33cc60bf613488e549ec7 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/XuNeo
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@f9f3699895afbd2febb33cc60bf613488e549ec7 -
Trigger Event:
release
-
Statement type: