mcp-unify
by MagnusCole
README.md
# mcp-unify
[](https://pypi.org/project/mcp-unify/)
[](LICENSE)
[](https://pypi.org/project/mcp-unify/)
Unify multiple MCP servers behind a single endpoint. Lazy loading, auto-cleanup, Python plugins, role-based filtering.
## The Problem
If you use Claude Code or any MCP client with 3+ servers, you get:
- Multiple subprocesses (~50MB each)
- Duplicated config
- No centralized control
- No way to add custom Python tools without a full MCP server
## The Solution
`mcp-unify` runs **one process** that proxies N MCP servers on-demand:
```
Claude Code / MCP Client
│
▼
mcp-unify (1 process)
├─ [plugin] Python @tool functions ← in-process, 0 overhead
├─ [lazy] filesystem-server ← subprocess spawned on first call
├─ [lazy] github-server ← subprocess spawned on first call
└─ [lazy] playwright ← subprocess spawned on first call
↑
5 min idle → auto-kill
```
## Install
```bash
pip install mcp-unify
```
## Quick Start
### 1. Create `gateway.yaml`
```yaml
servers:
filesystem:
command: npx
args: ["-y", "@modelcontextprotocol/server-filesystem", "/tmp"]
github:
command: npx
args: ["-y", "@modelcontextprotocol/server-github"]
env:
GITHUB_TOKEN: "${GITHUB_TOKEN}"
enabled: false
idle_timeout: 300
```
### 2. Run
**stdio** (for Claude Code):
```bash
mcp-unify stdio --config gateway.yaml
```
**SSE** (for remote clients):
```bash
mcp-unify serve --config gateway.yaml --port 8765
```
### 3. Add to Claude Code
```json
{
"mcpServers": {
"gateway": {
"command": "mcp-unify",
"args": ["stdio", "--config", "/path/to/gateway.yaml"]
}
}
}
```
## Python Plugins
Add custom tools with zero boilerplate:
```python
from mcp_gateway import tool
@tool(description="Add two numbers")
def add(a: float, b: float) -> float:
return a + b
```
Reference in your config:
```yaml
plugins:
- module: my_tools.calculator
```
Or register programmatically:
```python
from mcp_gateway import MCPGateway
gw = MCPGateway()
gw.register_tool(add)
await gw.serve_stdio()
```
Sync functions run via `asyncio.to_thread()`. Async functions run natively. Schemas are auto-generated from type hints.
## Role-Based Filtering
Control which tools each client sees:
```yaml
roles:
admin: null # all tools
readonly: ["filesystem_*", "gateway_status"]
developer: ["github_*", "filesystem_*", "gateway_*"]
```
Set the role via environment variable:
```bash
MCP_GATEWAY_ROLE=readonly mcp-unify stdio
```
Uses glob patterns — `filesystem_*` matches all tools from the filesystem server.
## How It Works
1. **Lazy Loading**: Servers aren't started until a tool is called. Each server gets a `_<name>_connect` placeholder tool. Calling it spawns the subprocess and discovers real tool schemas via `session.list_tools()`.
2. **Auto-Cleanup**: A background task checks every 60s and kills server subprocesses idle for longer than `idle_timeout` (default: 5 min).
3. **Real Schemas**: Tool schemas are discovered from the actual server via the MCP SDK, not hardcoded. You always get accurate `inputSchema`.
4. **Plugin Tools**: Python functions decorated with `@tool` run in the gateway process. Type hints are introspected to generate JSON Schema automatically.
## API
```python
from mcp_gateway import MCPGateway, tool
# From YAML
gw = MCPGateway.from_config("gateway.yaml")
# Programmatic
gw = MCPGateway()
gw.register_tool(my_function, name="my_tool", description="...")
# Serve
await gw.serve_stdio() # stdio mode
await gw.serve_sse() # SSE mode on :8765
# Context manager
async with MCPGateway() as gw:
await gw.serve_stdio()
```
## CLI
```
mcp-unify serve [--config FILE] [--host HOST] [--port PORT]
mcp-unify stdio [--config FILE]
mcp-unify list [--config FILE]
```
Config discovery: `--config` > `./gateway.yaml` > `./mcp-unify.yaml`
## Requirements
- Python >= 3.10
- `mcp >= 1.0.0`
- `starlette >= 0.27.0`
- `uvicorn >= 0.23.0`
- `pyyaml >= 6.0`
## License
MIT
This server cannot be deployed
Maintenance
ActivityInactive
ResponsivenessNo issues