SLM MCP Hub
A local-first MCP gateway that connects once to all your MCP backends and exposes them through a single endpoint. Part of Qualixar's work on AI Reliability Engineering.
Alpha software. File reproducible failures through GitHub Issues.
The problem it solves
Without a hub, every AI client session spawns its own MCP subprocesses. Five sessions with 38 configured servers means 190 processes and roughly 10 GB of RAM. The hub runs those processes once, shares them across every connected client, and adds governance, observability, and reliability on top.
What ships in v0.3.0
Unified call pipeline
Every tool call flows through one dispatch path. A per-backend concurrency gate (default 10 concurrent calls per backend) prevents one slow server from blocking calls to the others. Per-server timeout classes — fast (30 s), default (120 s), extended (600 s), unbounded — let a long-running server finish without cutting off the call at a flat ceiling. Backend notifications/progress are forwarded to the hub's client in real time on both transport modes. Per-server p95 latency and call metrics are recorded on every dispatch.
Transport: stateless default, stateful opt-in
The default run mode is modern stateless MCP 2026-07-28: no session tracking, no event store, no resumable replay. This is the right default for most deployments — stateless means the hub can restart cleanly with no session state to recover.
Set SLM_HUB_STATEFUL=1 (or transport_stateful: true in config) to enable stateful sessions. Stateful mode activates resumable streaming: the SDK's InMemoryEventStore handles client↔hub Last-Event-ID stream resumption automatically. On the hub→backend leg, a safe one-shot retry fires when a backend drops mid-stream — but only when the backend had already issued a resumption token, meaning it can continue from that point rather than restart. A connection drop without a token fails cleanly; the call is not retried.
Resumable streaming is a stateful-mode feature. In the default stateless mode there is no resumption, by design.
Observability
GET /api/servers/enriched reports each backend's live state, uptime, restart count, and tool count. GET /api/events streams lifecycle events over SSE without a slow reader ever stalling the hub. A localhost admin dashboard renders the same data in a browser. Runtime CLI commands: slm-hub servers, slm-hub health, slm-hub warm <server>, slm-hub stop <server>. All admin routes require the hub API key.
RAM governance
Lazy spawn harvests a backend's tools at startup and starts its subprocess only when the first call arrives. Idle eviction shuts a backend down once it has been idle past idle_ttl_seconds, freeing the process while its tools stay discoverable and callable — the next routed call reconnects it transparently. An LRU cap evicts the least-recently-used non-pinned backend when the live process count hits max_live_backends. Mark a server always_on (or spawn: pinned) to keep it hot.
Transport completeness
Backends connect over stdio, Streamable HTTP, SSE, or OAuth 2.0-protected HTTP (authorize once with slm-hub auth login; tokens stored in the OS keychain). Downstream clients connect over Streamable HTTP or native stdio. The combination of SSE backend and OAuth is rejected at configuration time.
Federation
Three meta-tools — search_tools, call_tool, list_servers — let any client discover and invoke any tool across all connected backends through a single hub entry. Tools are namespaced as server__tool. Backward-compatible hub__ prefix aliases are accepted.
Install
Python 3.11 or newer.
pip install slm-mcp-hub
The npm shim installs the matching Python release into an isolated environment it owns:
npm install -g slm-mcp-hub
The two packages are release-locked. Installation fails rather than falling back to a mismatched version or modifying an externally managed Python install.
Quick start
slm-hub config init
slm-hub setup detect
slm-hub setup import ~/.claude.json
slm-hub start
Default HTTP endpoint: http://127.0.0.1:52414/mcp. Health check: http://127.0.0.1:52414/api/health.
Native stdio, for clients that launch MCP servers as subprocesses:
{
"mcpServers": {
"slm-hub": {
"command": "slm-hub",
"args": ["mcp"]
}
}
}
Routing modes
Federated mode exposes three meta-tools. One hub entry in your client config, three tools to reach everything:
slm-hub setup register --client claude_code --mode federated
Transparent mode gives each backend its own route at /mcp/{server-name}. Original tool names, zero behavior change — useful for migration testing or clients that need the backend's native tool surface:
slm-hub setup register --client claude_code --mode transparent
Use federated mode when context size matters. Use transparent mode when a client requires the backend's original tool names or when you are testing before a full migration.
Transport mode
The default is stateless. Stateless means no session IDs, no server-side event store, and no resumable streaming — and also no session state to manage or recover.
Enable stateful sessions when you need resumable streaming:
export SLM_HUB_STATEFUL=1
slm-hub start
Or in config.json:
{
"transport_stateful": true
}
Resumable streaming is only available in stateful mode. On the client↔hub leg, the SDK handles Last-Event-ID reconnection through InMemoryEventStore automatically. On the hub→backend leg, a one-shot retry fires if and only if the backend issued a resumption token before the connection dropped. Without a token, the call fails cleanly — the hub does not blindly re-execute a tool whose idempotency is unknown.
Configuration
Default file: ~/.slm-mcp-hub/config.json. Set SLM_HUB_CONFIG_DIR to move the full runtime directory, including config, database, PID, log, and snapshots.
{
"host": "127.0.0.1",
"port": 52414,
"transport_stateful": false,
"mcpServers": {
"github": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-github"],
"env": {
"GITHUB_PERSONAL_ACCESS_TOKEN": "${GITHUB_TOKEN}"
},
"timeout_class": "default"
},
"deep-research": {
"type": "http",
"url": "${RESEARCH_MCP_URL}",
"headers": {
"Authorization": "Bearer ${RESEARCH_MCP_TOKEN}"
},
"timeout_class": "extended"
},
"remote-oauth": {
"type": "http",
"url": "${REMOTE_MCP_URL}",
"auth": { "mode": "oauth" }
}
},
"plugins_enabled": ["slm", "mesh"]
}
JSON comments are not supported.
Server fields
| Field | Purpose |
|---|---|
command |
Executable for a stdio server. |
args |
Argument array for a stdio server. |
env |
Environment passed to a stdio server. |
type |
stdio, http, or sse. Inferred when omitted. |
url |
Endpoint for an HTTP or SSE server. |
headers |
Request headers for an HTTP or SSE server. |
timeout_class |
fast (30 s), default (120 s), extended (600 s), unbounded. Defaults to default. |
enabled |
Whether the server may connect. |
always_on / spawn: pinned |
Prevents idle eviction. |
idle_ttl_seconds |
Idle eviction threshold for this server. |
no_cache |
Disables hub caching for this server. |
cost_per_call_cents |
Optional accounting value. |
Hub environment variables
| Variable | Purpose |
|---|---|
SLM_HUB_CONFIG_DIR |
Runtime/config directory. |
SLM_HUB_HOST |
HTTP bind host. |
SLM_HUB_PORT |
HTTP bind port. |
SLM_HUB_LOG_LEVEL |
Logging level. |
SLM_HUB_API_KEY |
Required for non-loopback binds; authenticates MCP and all management routes. |
SLM_HUB_STATEFUL |
Set to 1 to enable stateful sessions and resumable streaming. Default is stateless. |
Secret values go in ~/.slm-mcp-hub/secrets.env or ~/.claude-secrets.env. ${VAR} placeholders in config resolve only when a backend connection starts; the hub persists the placeholder, not the resolved value. If an older release wrote a literal secret into config, rotate that credential and remove the contaminated file manually.
Safe changes and recovery
slm-hub server add example --command npx --arg -y --arg package-name
slm-hub server modify example --env TOKEN='${EXAMPLE_TOKEN}'
slm-hub server reload
slm-hub config snapshots
slm-hub config restore <snapshot-name>
Config writes are atomic. Existing non-trivial configs are snapshotted before any write. A large unexpected server-count drop is refused unless explicitly forced.
Authentication
OAuth 2.0-protected upstream servers authorize once per server:
slm-hub auth login SERVER # opens browser once to authorize
slm-hub auth status [SERVER] # metadata only — never prints a token
slm-hub auth status --json
slm-hub auth logout SERVER
Tokens are stored in the OS keychain via keyring — a working keychain backend is required. login is the only command that opens a browser. No command prints an access token, refresh token, client secret, or authorization code. A downstream client's Authorization header is never forwarded to an upstream server; the hub uses only its own stored token for upstream connections.
OAuth metadata and callback URLs are restricted to HTTPS or loopback HTTP. Private, reserved, and link-local IP addresses are blocked, with DNS-rebinding checks across all resolved IPs.
SuperLocalMemory
Run the SLM daemon as its own process, then enable the direct hub plugins:
{
"plugins_enabled": ["slm", "mesh"]
}
export SLM_DAEMON_URL=http://127.0.0.1:8765
export SLM_API_KEY='your-daemon-api-key'
slm-hub start
SLM_API_KEY is sent as X-SLM-API-Key by both the SLM and mesh plugins. Authentication failures disable the affected plugin and remain visible in logs — the key is never logged. Restart the hub after rotating the daemon key.
Do not add the SLM daemon under mcpServers when using these plugins. That creates a nested topology that is not the supported integration path.
Remote access security
The hub refuses a non-loopback bind unless SLM_HUB_API_KEY is set:
export SLM_HUB_HOST=0.0.0.0
export SLM_HUB_API_KEY='generate-a-long-random-value'
slm-hub start
Clients send the key in X-SLM-Hub-API-Key or Authorization: Bearer <key>. Authentication covers /mcp, transparent proxy routes, and all management APIs. /api/health remains available without a key for infrastructure probes. Use TLS at the network boundary whenever traffic leaves the host.
Protocol conformance
The hub targets MCP 2026-07-28. Its own interface is the three meta-tools (search_tools, call_tool, list_servers); upstream tool names are not re-listed at tools/list. Upstream capabilities are exercised through call_tool across the full transport matrix: stdio, Streamable HTTP, SSE, and OAuth-protected HTTP backends.
Development and verification
python -m venv .venv
.venv/bin/pip install -e '.[dev]'
.venv/bin/pytest --cov=slm_mcp_hub
npm test
The release gate requires more than 97% Python line coverage, clean linting, wheel and sdist package inspection, isolated install tests, dependency audits, and supported-Python CI.
Architecture, configuration, migration, and getting-started details are in the docs directory.
Contributing
Bug reports are most useful with a reproduction test. Pull requests must keep both distribution channels version-aligned and pass all release gates. See CONTRIBUTING.md and SECURITY.md.
License
AGPL-3.0-or-later for open-source use. Commercial licenses are available — see LICENSE or contact the Qualixar team.
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 slm_mcp_hub-0.3.0.tar.gz.
File metadata
- Download URL: slm_mcp_hub-0.3.0.tar.gz
- Upload date:
- Size: 409.0 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/7.0.0 CPython/3.14.5
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
a10a5e76da881d45c30a82e04a7f37cc1b6cef72eafee158fa995cfaf8fa76e8
|
|
| MD5 |
0cbf8c4be1ba8a2828cedd6b508d0767
|
|
| BLAKE2b-256 |
d0ff0b9396827c214cc64612ed990270431c1fae2b3c1087d71267ff72da7b6e
|
File details
Details for the file slm_mcp_hub-0.3.0-py3-none-any.whl.
File metadata
- Download URL: slm_mcp_hub-0.3.0-py3-none-any.whl
- Upload date:
- Size: 243.9 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/7.0.0 CPython/3.14.5
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
eed208e05adfd4bfc5381b696a4def412d587a3eb06b9aaa2262e50db8fd4566
|
|
| MD5 |
3fc703b931b0bcf6d8bcafc8fae97b19
|
|
| BLAKE2b-256 |
3be1519a7364bbdfdb6f3d06718b5aa736f183df44f922c3e13913fb79851af9
|