MCPNexus
README.md
# MCP Nexus
**The discovery and routing layer that keeps MCP servers out of your context window until you actually need them.**
[](https://github.com/KrzysztofAugiewicz/MCPNexus/actions/workflows/ci.yml)
[](https://pypi.org/project/mcpnexus/)
[](https://python.org)
[](https://github.com/KrzysztofAugiewicz/MCPNexus)
[](https://github.com/KrzysztofAugiewicz/MCPNexus/blob/main/LICENSE)
---
## The problem
The Model Context Protocol lets an LLM talk to any number of servers — GitHub, Slack, Postgres, your internal tools. The catch: most clients load **every** tool definition from **every** configured server at startup. Six servers can mean 70+ tool schemas and thousands of tokens spent before the conversation even starts, most of which the model never touches in a given session.
## What MCP Nexus does
MCP Nexus sits in front of your MCP servers as a thin discovery layer. Instead of loading everything up front, the LLM asks for what it needs — by server or by tool — and MCP Nexus resolves the request and connects on demand. You keep your existing MCP servers unmodified; MCP Nexus only changes how (and when) their tools reach the model's context.
Two modes cover the two ways teams actually want this to work:
| | **Discovery Mode** | **Dynamic Mode** |
|---|---|---|
| Granularity | Whole server | Individual tool |
| Tools always in context | 4 (`mcpd_find`, `mcpd_list`, `mcpd_connect`, `mcpd_get_schema`) | 1 (`find_tools`) |
| Best for | "Connect me to GitHub" style workflows | Cherry-picking one tool from many servers |
| After resolution | LLM talks to the server directly — MCP Nexus exits the data path | MCP Nexus lazy-connects and stays in the loop per tool call |
**v1.0.0** adds Lazy Schema Loading: Discovery Mode can hand back a stub tool list (names only, no schemas) and fetch a single tool's full schema only when it's about to be called — about 92% fewer tokens than loading everything.
## How it works
### Discovery Mode — server-level selection
```
Step 1 LLM -> mcpd_find("github issues")
MCP Nexus searches the registry
returns: { id: "github", tools: ["create_issue", "search_repos", ...] }
Step 2 LLM -> mcpd_connect("github")
MCP Nexus starts the GitHub MCP server
returns: 20 tools now available as github__create_issue, etc.
Step 3 LLM -> github__create_issue({ title: "...", body: "..." })
MCP Nexus proxies to the GitHub MCP server, returns the result
```
| Scenario | Tools in context | ~Tokens |
|---|---:|---:|
| All 6 servers loaded directly | 78 | ~4,778 |
| **Discovery Mode** (before connect) | 4 | ~305 |
| **Discovery Mode** (after connect — 1 server) | 4 + 20 | ~1,578 |
#### Lazy Schema Loading
No proxy, no changes to the target MCP server required:
1. `mcpd_find("github")` → choose a server
2. `mcpd_connect("github", lazy_mode=true)` → get a **stub list** (20 tools, no schemas, ~132 tokens)
3. `mcpd_get_schema("github", "create_issue")` → fetch one full schema (~80 tokens)
4. `github__create_issue(...)` → direct call, as always
**Savings:** ~92% vs. a full load (292 vs. 2,064 tokens for a typical 2-tool session). Pass `--sync-on-start` so the registry has schemas cached ahead of time via `nexus-sync`.
### Dynamic Mode — tool-level selection
```
Step 1 LLM -> find_tools("create issue, post slack message")
MCP Nexus searches the tool index across all servers
returns: create_issue (github), post_message (slack)
both tools added to tools/list
Step 2 LLM -> create_issue({ title: "Bug #42" })
MCP Nexus lazy-connects to the GitHub MCP server
executes create_issue, returns the result
Step 3 LLM -> post_message({ channel: "#eng", text: "Done" })
MCP Nexus lazy-connects to the Slack MCP server
executes post_message, returns the result
```
| Scenario | Tools in context | ~Tokens |
|---|---:|---:|
| All 6 servers loaded directly | 78 | ~4,778 |
| **Dynamic Mode** (before find) | 1 | ~100 |
| **Dynamic Mode** (2 found tools) | 3 | ~300 |
## Installation
```bash
# Core — keyword search, stdio transport
pip install mcpnexus
# With HTTP and SSE transport (remote MCP servers)
pip install mcpnexus[http]
# With semantic search (sentence-transformers)
pip install mcpnexus[embeddings]
# Full installation
pip install mcpnexus[all]
# Development
pip install mcpnexus[dev]
```
## Quick start
### 1. Build your registry
The registry is a lightweight JSON catalog of your MCP servers and their tool summaries. Build it from your MCP client's config (e.g. Cursor: `~/.cursor/mcp.json`, Claude Desktop: `~/Library/Application Support/Claude/claude_desktop_config.json`):
```bash
nexus-sync --config /path/to/your/mcp-config.json --output registry/mcpd-registry.json
```
### 2. Point your MCP client at MCP Nexus
**Discovery Mode** (4 tools, connect to one server at a time):
```json
{
"mcpServers": {
"nexus-server": {
"command": "nexus-server",
"args": ["--registry", "/path/to/mcpd-registry.json", "--sync-on-start"]
}
}
}
```
**Dynamic Mode** (1 tool, cherry-pick tools across all servers):
```json
{
"mcpServers": {
"nexus-gateway": {
"command": "nexus-gateway",
"args": ["--registry", "/path/to/mcpd-registry.json"]
}
}
}
```
Or invoke the Python module directly (avoids PATH issues):
```json
{
"mcpServers": {
"nexus-gateway": {
"command": "python",
"args": ["-m", "mcpnexus.dynamic.server", "--registry", "/path/to/mcpd-registry.json"]
}
}
}
```
### 3. Measure the token savings
```bash
nexus-benchmark --registry registry/mcpd-registry.json
```
*(Generate your registry first with `nexus-sync`.)*
## Architecture
```
┌─────────────────────────────────────────────────────────────┐
│ LLM / AI Client │
└──────────────────────┬──────────────────────────────────────┘
│ MCP (stdio / JSON-RPC 2.0)
┌────────────┴─────────────┐
│ │
┌──────▼──────┐ ┌───────▼──────┐
│ Discovery │ │ Dynamic │
│ Mode │ │ Mode │
│ │ │ │
│ mcpd_find │ │ find_tools │
│ mcpd_list │ │ │
│ mcpd_connect│ │ LazyPool │
│ mcpd_get_schema│ │ │
└──────┬──────┘ └───────┬──────┘
│ │
└────────────┬─────────────┘
│
┌────────────▼─────────────┐
│ Shared Core │
│ │
│ Registry (mcpd-registry) │
│ KeywordSearchEngine │
│ ToolSearchEngine │
│ HybridSearch (TF-IDF + │
│ sentence-transformers) │
│ NexusConnector │
│ ├─ stdio transport │
│ ├─ streamable-http │
│ └─ SSE transport │
└───────────────────────────┘
```
### Design principles
**A discovery layer, not a permanent proxy.** In Discovery Mode, once `mcpd_connect` resolves, the LLM gets direct tool access to the connected server. In Dynamic Mode, server connections stay lazy — a server process starts only when one of its tools is actually called.
**Offline-first registry.** Tool summaries (name, description, tags) are captured at sync time. Searches run against the cached registry with zero network traffic; full tool schemas load only on connection.
**Search degrades gracefully.** Keyword search (TF-IDF with synonyms) is the default — always available, no extra dependencies. Semantic search is optional (`pip install mcpnexus[embeddings]`): when installed, sentence-transformers embeddings blend with keyword results, which helps for loosely-phrased natural-language queries like *"a tool for reading web pages"* → Playwright. The keyword synonym table also understands multilingual input (e.g. Polish query terms resolve to the right English tool concepts). Keyword-first keeps installs frictionless when you don't need semantic search.
## Registry format
The registry file (`mcpd-registry.json`) is a JSON catalog of MCP servers:
```json
{
"mcpd_version": "1.0",
"metadata": {
"name": "My MCP Registry",
"description": "Personal registry of MCP servers"
},
"servers": [
{
"id": "github",
"name": "GitHub MCP Server",
"description": "Official GitHub MCP server (remote). Repositories, issues, pull requests, and code search",
"version": "remote-2025-11",
"transport": {
"type": "streamable-http",
"url": "https://api.githubcopilot.com/mcp/",
"headers": { "Authorization": "Bearer ${GITHUB_MCP_PAT}" }
},
"tags": ["github", "git", "code", "issues"],
"tools_summary": [
{
"name": "issue_write",
"description": "Create or update an issue or pull request",
"tags": ["issues", "create"]
}
],
"estimated_tools_count": 90,
"enabled": true,
"last_synced": "2026-06-11T00:00:00Z"
}
]
}
```
Full schema: [`registry/schemas/mcpd-schema.json`](https://github.com/KrzysztofAugiewicz/MCPNexus/blob/main/registry/schemas/mcpd-schema.json)
## Project structure
```
mcpnexus/
├── mcpnexus/ # Python package
│ ├── __init__.py # Public API and version
│ ├── models.py # Shared dataclasses
│ ├── registry.py # Registry loader (mcpd-registry.json)
│ ├── connector.py # MCP connector — stdio, HTTP, SSE transports
│ ├── sync.py # Registry builder (sync from mcp.json)
│ ├── benchmark.py # Token savings measurement
│ ├── search/
│ │ ├── keyword_search.py # Server-level TF-IDF search
│ │ ├── tool_search.py # Tool-level TF-IDF search
│ │ ├── embeddings.py # Sentence-transformer embedding engine
│ │ └── hybrid.py # Hybrid keyword + semantic search
│ ├── discovery/
│ │ └── server.py # Discovery Mode MCP server
│ └── dynamic/
│ ├── server.py # Dynamic Mode MCP server
│ ├── tool_index.py # O(1) tool lookup index
│ └── lazy_pool.py # On-demand connection pool
├── registry/
│ ├── mcpd-registry.example.json # Example registry
│ └── schemas/
│ └── mcpd-schema.json # JSON Schema for registry validation
├── docs/
│ ├── specification.md # Protocol specification
│ ├── architecture.md # Architecture deep-dive
│ ├── registry-format.md # Registry format reference
│ └── dynamic-mcp.md # Dynamic Mode guide
├── examples/
│ ├── cursor-config-discovery.json
│ ├── cursor-config-dynamic.json
│ └── README.md
├── tests/ # 491 tests, 100% coverage
└── pyproject.toml
```
## Development
```bash
git clone https://github.com/KrzysztofAugiewicz/MCPNexus.git
cd MCPNexus
pip install -e ".[dev]"
# Run tests
pytest
# Run tests with coverage
pytest --cov=mcpnexus --cov-report=term-missing
# Run end-to-end integration test
python test_e2e.py
# Benchmark token savings (generate registry first with nexus-sync)
nexus-benchmark --registry registry/mcpd-registry.json
```
## CLI reference
| Command | Description |
|---|---|
| `nexus-server` | Start the Discovery Mode MCP server |
| `nexus-gateway` | Start the Dynamic Mode MCP server |
| `nexus-sync` | Build or update the registry from an mcp.json config |
| `nexus-benchmark` | Measure token savings for a given registry |
All commands accept `--help` for the full option reference.
## Transport support
| Transport | Install extra | Use case |
|---|---|---|
| **stdio** | _(core)_ | Local process-based MCP servers |
| **Streamable HTTP** | `mcpnexus[http]` | Remote HTTP MCP servers |
| **SSE** | `mcpnexus[http]` | Legacy remote servers (Server-Sent Events) |
Transport type is resolved automatically from the registry entry's `transport.type` field.
## Documentation
- [Protocol Specification](https://github.com/KrzysztofAugiewicz/MCPNexus/blob/main/docs/specification.md) — Tool schemas, message formats, handshake sequence
- [Architecture](https://github.com/KrzysztofAugiewicz/MCPNexus/blob/main/docs/architecture.md) — Data flow, shared core, design decisions
- [Registry Format](https://github.com/KrzysztofAugiewicz/MCPNexus/blob/main/docs/registry-format.md) — Full registry schema reference
- [Dynamic Mode Guide](https://github.com/KrzysztofAugiewicz/MCPNexus/blob/main/docs/dynamic-mcp.md) — Dynamic Mode deep-dive
## Publishing to PyPI
Releases are published automatically when a GitHub Release is created. Prerequisites:
1. Add `PYPI_API_TOKEN` to repository secrets (create at [pypi.org/manage/account/token](https://pypi.org/manage/account/token/))
2. Create a release with a tag (e.g. `v1.0.1`)
The [publish workflow](https://github.com/KrzysztofAugiewicz/MCPNexus/blob/main/.github/workflows/publish.yml) builds and uploads to PyPI.
## Contributing
Contributions are welcome. Please read [CONTRIBUTING.md](https://github.com/KrzysztofAugiewicz/MCPNexus/blob/main/CONTRIBUTING.md) before opening a pull request. For bug reports and feature requests, use [GitHub Issues](https://github.com/KrzysztofAugiewicz/MCPNexus/issues).
## Authors
- **Krzysztof Augiewicz** — Lead Architect & Creator — [LinkedIn](https://www.linkedin.com/in/krzysztof-a-97a170185/) · [GitHub](https://github.com/KrzysztofAugiewicz)
- **Kacper Pisarczyk** — Core Contributor, Discovery & Registry Systems — [LinkedIn](https://www.linkedin.com/in/kacper-pisarczyk-b165311aa/)
- **Sebastian Pawłowski** — Advisory & QA Support (testing, hardware/software provisioning) — [LinkedIn](https://www.linkedin.com/in/sebastianpawlowski/)
- **Mateusz Wiszniowski** — Core Contributor
Full details in [AUTHORS.md](https://github.com/KrzysztofAugiewicz/MCPNexus/blob/main/AUTHORS.md).
## License
[MIT](https://github.com/KrzysztofAugiewicz/MCPNexus/blob/main/LICENSE)
TDQS
A4.3/5.0
Scored across 4 tools
Disambiguation5/5
Each tool has a clear, distinct purpose: listing all servers, searching for servers, connecting to a server, and retrieving a tool schema. No overlaps.
Naming Consistency4/5
All tools start with 'mcpd_' and use snake_case, but the verb patterns vary: some have noun suffixes (get_schema) while others are bare verbs (find, list, connect). Mostly consistent.
Tool Count5/5
With 4 tools, the server is well-scoped for its role as a registry/hub. Each tool earns its place, covering discovery, connection, and schema retrieval.
Completeness4/5
The tools cover the core workflow of discovering and connecting to servers. A minor gap is the lack of a disconnect or management tool, but the description indicates that after connect, MCP Nexus is no longer involved.
Maintenance
ActivityNo data
ResponsivenessSyncing