Metadata-Version: 2.4
Name: mcp-subprocess-pool
Version: 0.1.0
Summary: Persistent subprocess connection pool for MCP servers — 10-40x faster tool calls
Author-email: NexVigilant <matthew@nexvigilant.com>
License: MIT
Project-URL: Homepage, https://github.com/nexvigilant/mcp-subprocess-pool
Project-URL: Repository, https://github.com/nexvigilant/mcp-subprocess-pool
Project-URL: Issues, https://github.com/nexvigilant/mcp-subprocess-pool/issues
Keywords: mcp,subprocess,pool,performance,model-context-protocol
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Software Development :: Libraries
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Dynamic: license-file

# mcp-subprocess-pool

Persistent subprocess connection pool for MCP servers. 10-40x faster tool calls.

Instead of spawning a fresh MCP server process per tool call (~200-500ms overhead from process spawn + JSON-RPC handshake), this pool keeps a single process alive and reuses it (~5-20ms per call).

Works with any MCP server binary that speaks JSON-RPC over stdio.

**Built by [NexVigilant](https://nexvigilant.com) | [mcp.nexvigilant.com](https://mcp.nexvigilant.com)**

## Installation

```bash
pip install mcp-subprocess-pool
```

## Quick Start

### Pool (persistent MCP server connection)

```python
from mcp_subprocess_pool import McpPool

# Connect to any MCP server binary
pool = McpPool("/path/to/your-mcp-server")

# First call: ~60ms (spawn + handshake)
result = pool.call("your_tool", {"arg": "value"})

# Subsequent calls: ~5ms (reuses connection)
result = pool.call("another_tool", {"arg": "value"})

pool.close()
```

### Context manager

```python
with McpPool("/path/to/mcp-server") as pool:
    result = pool.call("tool_name", {"key": "value"})
```

### Unified dispatcher

Some MCP servers expose a single mega-tool that routes internally:

```python
pool = McpPool(
    "/path/to/mcp-server",
    dispatcher="nexcore",  # All calls route through this tool
)
# Internally sends: tools/call(name="nexcore", arguments={command="levenshtein", params={...}})
result = pool.call("levenshtein", {"source": "cat", "target": "bat"})
```

### Daemon (persistent stdin/stdout dispatcher)

For parent processes (Rust, Go, etc.) that need to call Python handlers without per-call Python startup:

```python
from mcp_subprocess_pool import McpDaemon

def handle(tool_name: str, arguments: dict) -> dict:
    # Your routing logic here
    return {"result": "ok"}

daemon = McpDaemon(
    handler=handle,
    on_startup=lambda: print("Warming up...", file=sys.stderr),
)
daemon.run()  # Reads JSON lines from stdin until shutdown
```

Protocol:
```json
{"id": 1, "tool": "my_tool", "arguments": {"key": "value"}}
```

Response:
```json
{"id": 1, "result": "ok"}
```

Shutdown:
```json
{"method": "shutdown"}
```

## Performance

Measured on NexVigilant Station (228 configs, 1,954 tools):

| Mode | Latency | vs Subprocess |
|------|---------|---------------|
| Subprocess per call | 77ms avg | baseline |
| Pool (warm) | 5ms avg | **15x faster** |
| Daemon (warm) | 6ms avg | **13x faster** |
| Pool cold start | 60ms | one-time cost |

## Thread Safety

`McpPool` is thread-safe via internal `threading.Lock`. Multiple threads can call `pool.call()` concurrently — calls are serialized internally.

Auto-respawns if the MCP server process dies.

## API

### `McpPool(binary, client_name, client_version, request_timeout, protocol_version, dispatcher)`

- `binary`: Path to MCP server binary
- `client_name`: Name in MCP handshake (default: "mcp-subprocess-pool")
- `client_version`: Version in MCP handshake (default: "0.1.0")
- `request_timeout`: Seconds before timeout (default: 25.0)
- `protocol_version`: MCP protocol version (default: "2024-11-05")
- `dispatcher`: Unified dispatch tool name, or None for direct calls

### `McpDaemon(handler, on_startup, on_shutdown)`

- `handler`: `(tool_name: str, arguments: dict) -> dict`
- `on_startup`: Optional warmup function
- `on_shutdown`: Optional cleanup function

## License

MIT
