mcp-capability-router 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- mcp_capability_router-0.1.0/.env.example +20 -0
- mcp_capability_router-0.1.0/.github/workflows/ci.yml +19 -0
- mcp_capability_router-0.1.0/.gitignore +35 -0
- mcp_capability_router-0.1.0/.readthedocs.yaml +14 -0
- mcp_capability_router-0.1.0/LICENSE +16 -0
- mcp_capability_router-0.1.0/PKG-INFO +189 -0
- mcp_capability_router-0.1.0/README.md +124 -0
- mcp_capability_router-0.1.0/docs/architecture/decisions.md +53 -0
- mcp_capability_router-0.1.0/docs/architecture/overview.md +51 -0
- mcp_capability_router-0.1.0/docs/assets/grafana-observability-dashboard.png +0 -0
- mcp_capability_router-0.1.0/docs/assets/grafana-prometheus-metrics.png +0 -0
- mcp_capability_router-0.1.0/docs/concepts/overview.md +60 -0
- mcp_capability_router-0.1.0/docs/examples/overview.md +325 -0
- mcp_capability_router-0.1.0/docs/guides/agent-integrations.md +406 -0
- mcp_capability_router-0.1.0/docs/guides/getting-started.md +132 -0
- mcp_capability_router-0.1.0/docs/index.md +34 -0
- mcp_capability_router-0.1.0/docs/javascripts/mermaid.js +1 -0
- mcp_capability_router-0.1.0/docs/operations/concurrency.md +48 -0
- mcp_capability_router-0.1.0/docs/operations/isolation.md +53 -0
- mcp_capability_router-0.1.0/docs/operations/overview.md +23 -0
- mcp_capability_router-0.1.0/docs/operations/refresh.md +144 -0
- mcp_capability_router-0.1.0/docs/operations/resilience.md +112 -0
- mcp_capability_router-0.1.0/docs/reference/api.md +116 -0
- mcp_capability_router-0.1.0/docs/troubleshooting/overview.md +24 -0
- mcp_capability_router-0.1.0/examples/__init__.py +1 -0
- mcp_capability_router-0.1.0/examples/basic_runtime.py +81 -0
- mcp_capability_router-0.1.0/examples/capability_scale_simulation.py +123 -0
- mcp_capability_router-0.1.0/examples/combined_workflow.py +82 -0
- mcp_capability_router-0.1.0/examples/create_agent_routing.py +80 -0
- mcp_capability_router-0.1.0/examples/custom_registry.py +130 -0
- mcp_capability_router-0.1.0/examples/deepagents_network.py +91 -0
- mcp_capability_router-0.1.0/examples/dynamic_registration.py +106 -0
- mcp_capability_router-0.1.0/examples/embedding_ollama.py +114 -0
- mcp_capability_router-0.1.0/examples/end_to_end_full_stack.py +208 -0
- mcp_capability_router-0.1.0/examples/langgraph_dynamic_discovery.py +138 -0
- mcp_capability_router-0.1.0/examples/langgraph_network.py +75 -0
- mcp_capability_router-0.1.0/examples/langgraph_swarm_network.py +103 -0
- mcp_capability_router-0.1.0/examples/manual_refresh.py +89 -0
- mcp_capability_router-0.1.0/examples/mcp_smoke_support.py +52 -0
- mcp_capability_router-0.1.0/examples/metrics_prometheus.py +164 -0
- mcp_capability_router-0.1.0/examples/metrics_prometheus_real_llm.py +292 -0
- mcp_capability_router-0.1.0/examples/multi_runtime_isolation.py +82 -0
- mcp_capability_router-0.1.0/examples/on_demand_connection.py +72 -0
- mcp_capability_router-0.1.0/examples/postgres_registry.py +211 -0
- mcp_capability_router-0.1.0/examples/prompt_selection.py +81 -0
- mcp_capability_router-0.1.0/examples/public_api_overrides.py +518 -0
- mcp_capability_router-0.1.0/examples/real_everything_mcp.py +44 -0
- mcp_capability_router-0.1.0/examples/real_fetch_mcp.py +51 -0
- mcp_capability_router-0.1.0/examples/real_filesystem_mcp.py +49 -0
- mcp_capability_router-0.1.0/examples/real_git_mcp.py +65 -0
- mcp_capability_router-0.1.0/examples/real_integrations.py +28 -0
- mcp_capability_router-0.1.0/examples/real_multi_agent_multi_mcp.py +190 -0
- mcp_capability_router-0.1.0/examples/real_multi_capability_selection.py +78 -0
- mcp_capability_router-0.1.0/examples/real_multi_mcp_single_agent.py +163 -0
- mcp_capability_router-0.1.0/examples/refresh_strategies.py +113 -0
- mcp_capability_router-0.1.0/examples/reranking_bm25.py +132 -0
- mcp_capability_router-0.1.0/examples/resilience_breaker_retry_timeout.py +197 -0
- mcp_capability_router-0.1.0/examples/resilience_bulkhead_rate_limit.py +105 -0
- mcp_capability_router-0.1.0/examples/resource_selection.py +73 -0
- mcp_capability_router-0.1.0/examples/tools_and_resources.py +72 -0
- mcp_capability_router-0.1.0/examples/vector_retrieval_qdrant.py +221 -0
- mcp_capability_router-0.1.0/mkdocs.yml +39 -0
- mcp_capability_router-0.1.0/pyproject.toml +64 -0
- mcp_capability_router-0.1.0/src/mcp_capability_router/__init__.py +39 -0
- mcp_capability_router-0.1.0/src/mcp_capability_router/discovery.py +69 -0
- mcp_capability_router-0.1.0/src/mcp_capability_router/embedding.py +35 -0
- mcp_capability_router-0.1.0/src/mcp_capability_router/errors.py +68 -0
- mcp_capability_router-0.1.0/src/mcp_capability_router/integrations/__init__.py +5 -0
- mcp_capability_router-0.1.0/src/mcp_capability_router/integrations/langchain_mcp.py +111 -0
- mcp_capability_router-0.1.0/src/mcp_capability_router/interceptor/__init__.py +27 -0
- mcp_capability_router-0.1.0/src/mcp_capability_router/loader.py +47 -0
- mcp_capability_router-0.1.0/src/mcp_capability_router/models.py +44 -0
- mcp_capability_router-0.1.0/src/mcp_capability_router/pipeline.py +214 -0
- mcp_capability_router-0.1.0/src/mcp_capability_router/refresh.py +122 -0
- mcp_capability_router-0.1.0/src/mcp_capability_router/registry.py +100 -0
- mcp_capability_router-0.1.0/src/mcp_capability_router/reranking.py +63 -0
- mcp_capability_router-0.1.0/src/mcp_capability_router/resilience/__init__.py +40 -0
- mcp_capability_router-0.1.0/src/mcp_capability_router/resilience/bulkhead.py +29 -0
- mcp_capability_router-0.1.0/src/mcp_capability_router/resilience/circuit_breaker.py +87 -0
- mcp_capability_router-0.1.0/src/mcp_capability_router/resilience/classification.py +157 -0
- mcp_capability_router-0.1.0/src/mcp_capability_router/resilience/fallback.py +47 -0
- mcp_capability_router-0.1.0/src/mcp_capability_router/resilience/health.py +37 -0
- mcp_capability_router-0.1.0/src/mcp_capability_router/resilience/metrics.py +44 -0
- mcp_capability_router-0.1.0/src/mcp_capability_router/resilience/timeout.py +49 -0
- mcp_capability_router-0.1.0/src/mcp_capability_router/retrieval.py +39 -0
- mcp_capability_router-0.1.0/src/mcp_capability_router/routing.py +46 -0
- mcp_capability_router-0.1.0/src/mcp_capability_router/runtime.py +381 -0
- mcp_capability_router-0.1.0/src/mcp_capability_router/selection.py +43 -0
- mcp_capability_router-0.1.0/src/mcp_capability_router/server.py +99 -0
- mcp_capability_router-0.1.0/tests/test_combined_workflow.py +117 -0
- mcp_capability_router-0.1.0/tests/test_custom_registry.py +153 -0
- mcp_capability_router-0.1.0/tests/test_dynamic_registration.py +245 -0
- mcp_capability_router-0.1.0/tests/test_langchain_mcp_adapter.py +57 -0
- mcp_capability_router-0.1.0/tests/test_pipeline.py +176 -0
- mcp_capability_router-0.1.0/tests/test_refresh_engine.py +206 -0
- mcp_capability_router-0.1.0/tests/test_resilience.py +103 -0
- mcp_capability_router-0.1.0/tests/test_resilience_tenacity.py +165 -0
- mcp_capability_router-0.1.0/tests/test_runtime.py +146 -0
- mcp_capability_router-0.1.0/tests/test_scale.py +226 -0
- mcp_capability_router-0.1.0/tests/test_selection.py +75 -0
- mcp_capability_router-0.1.0/uv.lock +2901 -0
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# Environment template. Copy to .env and fill in real values; .env is git-ignored.
|
|
2
|
+
#
|
|
3
|
+
# mcp-capability-router does not define its own model configuration surface.
|
|
4
|
+
# Instantiate whichever LangChain-compatible chat model class your application already
|
|
5
|
+
# uses (ChatOpenAI, ChatLiteLLM, ChatAnthropic, ...) directly in code, and configure it
|
|
6
|
+
# with that class's own constructor arguments or the environment variables it documents.
|
|
7
|
+
#
|
|
8
|
+
# Example (see examples/create_agent_routing.py):
|
|
9
|
+
# from langchain_openai import ChatOpenAI
|
|
10
|
+
# model = ChatOpenAI(model="gpt-5.6-luna", base_url=..., api_key=...)
|
|
11
|
+
|
|
12
|
+
# Used by the ChatOpenAI-based examples in this repository.
|
|
13
|
+
EXPLABS_API_KEY=
|
|
14
|
+
EXPLABS_BASE_URL=https://api.experientiallabs.ai/v1
|
|
15
|
+
EXPLABS_MODEL=gpt-5.6-luna
|
|
16
|
+
|
|
17
|
+
# Optional MCP application settings (adapt to your transport/client).
|
|
18
|
+
MCP_SERVER_NAME=filesystem
|
|
19
|
+
MCP_SERVER_COMMAND=npx
|
|
20
|
+
MCP_SERVER_ROOT=
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
on:
|
|
3
|
+
push:
|
|
4
|
+
pull_request:
|
|
5
|
+
jobs:
|
|
6
|
+
checks:
|
|
7
|
+
runs-on: ubuntu-latest
|
|
8
|
+
steps:
|
|
9
|
+
- uses: actions/checkout@v4
|
|
10
|
+
- uses: astral-sh/setup-uv@v5
|
|
11
|
+
with:
|
|
12
|
+
python-version: "3.12"
|
|
13
|
+
- run: uv sync --extra dev
|
|
14
|
+
- run: uv run pytest
|
|
15
|
+
- run: uv run ruff check .
|
|
16
|
+
- run: uv run ruff format --check .
|
|
17
|
+
- run: uv run pyrefly check
|
|
18
|
+
- run: uv run mkdocs build
|
|
19
|
+
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# Byte-compiled / cache
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
.mypy_cache/
|
|
5
|
+
.pytest_cache/
|
|
6
|
+
.ruff_cache/
|
|
7
|
+
|
|
8
|
+
# Virtual environments
|
|
9
|
+
.venv/
|
|
10
|
+
venv/
|
|
11
|
+
|
|
12
|
+
# uv
|
|
13
|
+
.python-version
|
|
14
|
+
|
|
15
|
+
# Build artifacts
|
|
16
|
+
dist/
|
|
17
|
+
build/
|
|
18
|
+
*.egg-info/
|
|
19
|
+
|
|
20
|
+
# Docs build output
|
|
21
|
+
site/
|
|
22
|
+
|
|
23
|
+
# Playwright MCP output (snapshots written by @playwright/mcp when run locally)
|
|
24
|
+
.playwright-mcp/
|
|
25
|
+
|
|
26
|
+
# Environment secrets (never commit real values)
|
|
27
|
+
.env
|
|
28
|
+
.env.*
|
|
29
|
+
!.env.example
|
|
30
|
+
|
|
31
|
+
# Editor/OS
|
|
32
|
+
.vscode/
|
|
33
|
+
.idea/
|
|
34
|
+
.DS_Store
|
|
35
|
+
Thumbs.db
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
version: 2
|
|
2
|
+
build:
|
|
3
|
+
os: ubuntu-22.04
|
|
4
|
+
tools:
|
|
5
|
+
python: "3.12"
|
|
6
|
+
jobs:
|
|
7
|
+
post_checkout:
|
|
8
|
+
- curl -LsSf https://astral.sh/uv/install.sh | sh
|
|
9
|
+
post_install:
|
|
10
|
+
- $HOME/.local/bin/uv sync --extra docs
|
|
11
|
+
commands:
|
|
12
|
+
build:
|
|
13
|
+
html: $HOME/.local/bin/uv run mkdocs build --site-dir $READTHEDOCS_OUTPUT/html
|
|
14
|
+
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
Apache License
|
|
2
|
+
Version 2.0, January 2004
|
|
3
|
+
http://www.apache.org/licenses/
|
|
4
|
+
|
|
5
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
you may not use this file except in compliance with the License.
|
|
7
|
+
You may obtain a copy of the License at
|
|
8
|
+
|
|
9
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
|
|
11
|
+
Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
See the License for the specific language governing permissions and
|
|
15
|
+
limitations under the License.
|
|
16
|
+
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: mcp-capability-router
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Async capability routing runtime for LangChain, LangGraph, and MCP adapters.
|
|
5
|
+
License: Apache License
|
|
6
|
+
Version 2.0, January 2004
|
|
7
|
+
http://www.apache.org/licenses/
|
|
8
|
+
|
|
9
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
10
|
+
you may not use this file except in compliance with the License.
|
|
11
|
+
You may obtain a copy of the License at
|
|
12
|
+
|
|
13
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
14
|
+
|
|
15
|
+
Unless required by applicable law or agreed to in writing, software
|
|
16
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
17
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
18
|
+
See the License for the specific language governing permissions and
|
|
19
|
+
limitations under the License.
|
|
20
|
+
|
|
21
|
+
License-File: LICENSE
|
|
22
|
+
Requires-Python: <3.13,>=3.12
|
|
23
|
+
Requires-Dist: aiolimiter>=1.1
|
|
24
|
+
Requires-Dist: langchain-core>=0.3
|
|
25
|
+
Requires-Dist: refresh-engine>=0.1.0
|
|
26
|
+
Requires-Dist: tenacity>=9.1
|
|
27
|
+
Provides-Extra: agents
|
|
28
|
+
Requires-Dist: deepagents>=0.1; extra == 'agents'
|
|
29
|
+
Requires-Dist: langchain>=1.0; extra == 'agents'
|
|
30
|
+
Provides-Extra: dev
|
|
31
|
+
Requires-Dist: asyncpg>=0.31.0; extra == 'dev'
|
|
32
|
+
Requires-Dist: black>=24.8; extra == 'dev'
|
|
33
|
+
Requires-Dist: build>=1.2; extra == 'dev'
|
|
34
|
+
Requires-Dist: langchain-mcp-adapters>=0.1; extra == 'dev'
|
|
35
|
+
Requires-Dist: langgraph>=0.2; extra == 'dev'
|
|
36
|
+
Requires-Dist: mkdocs-material>=9.5; extra == 'dev'
|
|
37
|
+
Requires-Dist: mkdocstrings[python]>=0.26; extra == 'dev'
|
|
38
|
+
Requires-Dist: prometheus-client>=0.26.0; extra == 'dev'
|
|
39
|
+
Requires-Dist: pyrefly>=0.18; extra == 'dev'
|
|
40
|
+
Requires-Dist: pytest-asyncio>=0.24; extra == 'dev'
|
|
41
|
+
Requires-Dist: pytest>=8.3; extra == 'dev'
|
|
42
|
+
Requires-Dist: python-dotenv>=1.0; extra == 'dev'
|
|
43
|
+
Requires-Dist: qdrant-client>=1.19.1; extra == 'dev'
|
|
44
|
+
Requires-Dist: rank-bm25>=0.2.2; extra == 'dev'
|
|
45
|
+
Requires-Dist: ruff>=0.6; extra == 'dev'
|
|
46
|
+
Provides-Extra: docs
|
|
47
|
+
Requires-Dist: mkdocs-material>=9.5; extra == 'docs'
|
|
48
|
+
Requires-Dist: mkdocstrings[python]>=0.26; extra == 'docs'
|
|
49
|
+
Provides-Extra: examples
|
|
50
|
+
Requires-Dist: asyncpg>=0.31.0; extra == 'examples'
|
|
51
|
+
Requires-Dist: prometheus-client>=0.26.0; extra == 'examples'
|
|
52
|
+
Requires-Dist: python-dotenv>=1.0; extra == 'examples'
|
|
53
|
+
Requires-Dist: qdrant-client>=1.19.1; extra == 'examples'
|
|
54
|
+
Requires-Dist: rank-bm25>=0.2.2; extra == 'examples'
|
|
55
|
+
Provides-Extra: langgraph
|
|
56
|
+
Requires-Dist: langgraph>=0.2; extra == 'langgraph'
|
|
57
|
+
Provides-Extra: mcp
|
|
58
|
+
Requires-Dist: langchain-mcp-adapters>=0.1; extra == 'mcp'
|
|
59
|
+
Provides-Extra: openai
|
|
60
|
+
Requires-Dist: langchain-openai>=0.3; extra == 'openai'
|
|
61
|
+
Requires-Dist: openai>=1.0; extra == 'openai'
|
|
62
|
+
Provides-Extra: swarm
|
|
63
|
+
Requires-Dist: langgraph-swarm>=0.1; extra == 'swarm'
|
|
64
|
+
Description-Content-Type: text/markdown
|
|
65
|
+
|
|
66
|
+
# MCP Capability Router
|
|
67
|
+
|
|
68
|
+
[](https://github.com/smuniharish/mcp-capabilty-router/actions/workflows/ci.yml)
|
|
69
|
+
[](https://www.python.org/downloads/release/python-3120/)
|
|
70
|
+
[](LICENSE)
|
|
71
|
+
[](docs/index.md)
|
|
72
|
+
|
|
73
|
+
**An async-first, runtime-isolated capability routing layer for LangChain, LangGraph, and
|
|
74
|
+
`langchain-mcp-adapters`.**
|
|
75
|
+
|
|
76
|
+
Model Context Protocol (MCP) servers can expose hundreds or thousands of tools, resources, and
|
|
77
|
+
prompts. Loading all of that into an agent's context on every turn is slow, expensive, and drowns
|
|
78
|
+
out the model's ability to pick the right capability. `mcp-capability-router` sits between your
|
|
79
|
+
agent and your MCP servers: it discovers and indexes capability metadata cheaply, retrieves only
|
|
80
|
+
the handful relevant to a query, and connects to a server lazily -- only when an operation
|
|
81
|
+
actually needs it.
|
|
82
|
+
|
|
83
|
+
## Why this exists
|
|
84
|
+
|
|
85
|
+
Connect an agent to a handful of MCP servers and the naive approach -- fetch every tool, resource,
|
|
86
|
+
and prompt from every server at startup and hand the whole catalog to the model -- works fine.
|
|
87
|
+
Connect it to a dozen servers, or a handful of servers that each expose hundreds of tools, and that
|
|
88
|
+
same approach quietly breaks in three ways at once:
|
|
89
|
+
|
|
90
|
+
1. **Every turn pays for capabilities that are never used.** Tool schemas alone can dwarf the
|
|
91
|
+
actual conversation, burning context budget and latency before the model has read a single
|
|
92
|
+
user message, and a bigger tool list makes the model *worse* at picking the right one, not
|
|
93
|
+
better.
|
|
94
|
+
2. **Every server connection becomes a shared point of failure.** One slow or flaky MCP server,
|
|
95
|
+
connected at startup, blocks or degrades every agent that touches the process -- and most
|
|
96
|
+
teams end up hand-rolling retry loops, circuit breakers, and rate limiters around each
|
|
97
|
+
server's adapter, ad hoc, in application code that has nothing to do with agent logic.
|
|
98
|
+
3. **The registry and the transport get welded together.** Swapping an in-memory tool list for
|
|
99
|
+
Postgres, or a keyword search for vector retrieval, means touching the same code that owns
|
|
100
|
+
connections, retries, and health -- so most implementations never get swapped at all.
|
|
101
|
+
|
|
102
|
+
`mcp-capability-router` exists to make the *scalable* path the default path, without reinventing
|
|
103
|
+
MCP transport or agent orchestration:
|
|
104
|
+
|
|
105
|
+
- **Discover cheaply, load lazily.** Registering a server stores a factory, not a connection.
|
|
106
|
+
Discovery indexes metadata for tools, resources, and prompts -- all three as first-class,
|
|
107
|
+
equally-routable capabilities -- so a registry can hold thousands of records while an agent
|
|
108
|
+
turn only ever touches the handful `retrieve()` actually returns. A server connection is opened
|
|
109
|
+
only when `execute`, `read_resource`, or `get_prompt` needs one.
|
|
110
|
+
- **Borrow resilience instead of reinventing it.** Retry, rate limiting, circuit breaking,
|
|
111
|
+
bulkheads, timeouts, and fallbacks are wired in by default, on top of maintained libraries --
|
|
112
|
+
[`refresh-engine`](https://pypi.org/project/refresh-engine/) for per-server discovery
|
|
113
|
+
reconciliation, [`tenacity`](https://pypi.org/project/tenacity/) for retries, and
|
|
114
|
+
[`aiolimiter`](https://pypi.org/project/aiolimiter/) for rate limiting -- so one misbehaving
|
|
115
|
+
server degrades gracefully instead of taking the whole agent down, and nobody has to hand-write
|
|
116
|
+
that logic per adapter again.
|
|
117
|
+
- **Keep every seam swappable.** The capability registry, the retriever, and the metrics hook are
|
|
118
|
+
small, explicit protocols, isolated from connection and lifecycle handling. Start with the
|
|
119
|
+
built-in in-memory registry and keyword retriever, then drop in Postgres, Qdrant, Ollama
|
|
120
|
+
embeddings, or Prometheus later -- with zero changes to `MCPRuntime` or the resilience pipeline.
|
|
121
|
+
- **Stay a router, not a runtime.** This project owns capability discovery, retrieval, and
|
|
122
|
+
lifecycle policy only. Transport, authentication, and the MCP client itself stay with
|
|
123
|
+
`langchain-mcp-adapters` or your own adapter; model calls and agent orchestration stay with
|
|
124
|
+
LangChain, LangGraph, `langgraph-swarm`, or DeepAgents. Nothing here competes with those layers.
|
|
125
|
+
|
|
126
|
+
## Installation
|
|
127
|
+
|
|
128
|
+
This package is not yet published to PyPI. Install it from source:
|
|
129
|
+
|
|
130
|
+
```bash
|
|
131
|
+
git clone https://github.com/smuniharish/mcp-capabilty-router.git
|
|
132
|
+
cd mcp-capabilty-router
|
|
133
|
+
pip install .
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
Optional extras add integration-specific dependencies as needed:
|
|
137
|
+
|
|
138
|
+
```bash
|
|
139
|
+
pip install ".[mcp]" # langchain-mcp-adapters
|
|
140
|
+
pip install ".[langgraph]" # LangGraph
|
|
141
|
+
pip install ".[agents]" # LangChain create_agent, DeepAgents
|
|
142
|
+
pip install ".[swarm]" # langgraph-swarm
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
Requires Python 3.12.
|
|
146
|
+
|
|
147
|
+
## Quickstart
|
|
148
|
+
|
|
149
|
+
```python
|
|
150
|
+
import asyncio
|
|
151
|
+
from mcp_capability_router import CapabilityType, MCPRuntime
|
|
152
|
+
|
|
153
|
+
async def main():
|
|
154
|
+
async with MCPRuntime() as runtime:
|
|
155
|
+
await runtime.register_server("filesystem", my_filesystem_adapter_factory)
|
|
156
|
+
await runtime.refresh_server("filesystem")
|
|
157
|
+
|
|
158
|
+
candidates = await runtime.retrieve("read a file", type=CapabilityType.TOOL, limit=5)
|
|
159
|
+
result = await runtime.execute(candidates[0].capability_id, {"path": "notes.txt"})
|
|
160
|
+
print(result)
|
|
161
|
+
|
|
162
|
+
asyncio.run(main())
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
See [Getting started](docs/guides/getting-started.md) for wiring in a real
|
|
166
|
+
`langchain-mcp-adapters` client, and [Examples](docs/examples/overview.md) for runnable scripts
|
|
167
|
+
covering real MCP servers, LangGraph/DeepAgents/`langgraph-swarm` integrations, and pluggable
|
|
168
|
+
registries, retrievers, and metrics backends.
|
|
169
|
+
|
|
170
|
+
## Documentation
|
|
171
|
+
|
|
172
|
+
Full documentation -- architecture, concepts, guides, operations, API reference, and
|
|
173
|
+
troubleshooting -- lives in [`docs/`](docs/index.md) and is built with MkDocs Material.
|
|
174
|
+
|
|
175
|
+
## Contributing
|
|
176
|
+
|
|
177
|
+
```powershell
|
|
178
|
+
uv sync --extra dev
|
|
179
|
+
uv run pytest
|
|
180
|
+
uv run ruff check .
|
|
181
|
+
uv run ruff format --check .
|
|
182
|
+
uv run pyrefly check
|
|
183
|
+
uv run mkdocs build
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
## License
|
|
187
|
+
|
|
188
|
+
Apache License 2.0 -- see [LICENSE](LICENSE).
|
|
189
|
+
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
# MCP Capability Router
|
|
2
|
+
|
|
3
|
+
[](https://github.com/smuniharish/mcp-capabilty-router/actions/workflows/ci.yml)
|
|
4
|
+
[](https://www.python.org/downloads/release/python-3120/)
|
|
5
|
+
[](LICENSE)
|
|
6
|
+
[](docs/index.md)
|
|
7
|
+
|
|
8
|
+
**An async-first, runtime-isolated capability routing layer for LangChain, LangGraph, and
|
|
9
|
+
`langchain-mcp-adapters`.**
|
|
10
|
+
|
|
11
|
+
Model Context Protocol (MCP) servers can expose hundreds or thousands of tools, resources, and
|
|
12
|
+
prompts. Loading all of that into an agent's context on every turn is slow, expensive, and drowns
|
|
13
|
+
out the model's ability to pick the right capability. `mcp-capability-router` sits between your
|
|
14
|
+
agent and your MCP servers: it discovers and indexes capability metadata cheaply, retrieves only
|
|
15
|
+
the handful relevant to a query, and connects to a server lazily -- only when an operation
|
|
16
|
+
actually needs it.
|
|
17
|
+
|
|
18
|
+
## Why this exists
|
|
19
|
+
|
|
20
|
+
Connect an agent to a handful of MCP servers and the naive approach -- fetch every tool, resource,
|
|
21
|
+
and prompt from every server at startup and hand the whole catalog to the model -- works fine.
|
|
22
|
+
Connect it to a dozen servers, or a handful of servers that each expose hundreds of tools, and that
|
|
23
|
+
same approach quietly breaks in three ways at once:
|
|
24
|
+
|
|
25
|
+
1. **Every turn pays for capabilities that are never used.** Tool schemas alone can dwarf the
|
|
26
|
+
actual conversation, burning context budget and latency before the model has read a single
|
|
27
|
+
user message, and a bigger tool list makes the model *worse* at picking the right one, not
|
|
28
|
+
better.
|
|
29
|
+
2. **Every server connection becomes a shared point of failure.** One slow or flaky MCP server,
|
|
30
|
+
connected at startup, blocks or degrades every agent that touches the process -- and most
|
|
31
|
+
teams end up hand-rolling retry loops, circuit breakers, and rate limiters around each
|
|
32
|
+
server's adapter, ad hoc, in application code that has nothing to do with agent logic.
|
|
33
|
+
3. **The registry and the transport get welded together.** Swapping an in-memory tool list for
|
|
34
|
+
Postgres, or a keyword search for vector retrieval, means touching the same code that owns
|
|
35
|
+
connections, retries, and health -- so most implementations never get swapped at all.
|
|
36
|
+
|
|
37
|
+
`mcp-capability-router` exists to make the *scalable* path the default path, without reinventing
|
|
38
|
+
MCP transport or agent orchestration:
|
|
39
|
+
|
|
40
|
+
- **Discover cheaply, load lazily.** Registering a server stores a factory, not a connection.
|
|
41
|
+
Discovery indexes metadata for tools, resources, and prompts -- all three as first-class,
|
|
42
|
+
equally-routable capabilities -- so a registry can hold thousands of records while an agent
|
|
43
|
+
turn only ever touches the handful `retrieve()` actually returns. A server connection is opened
|
|
44
|
+
only when `execute`, `read_resource`, or `get_prompt` needs one.
|
|
45
|
+
- **Borrow resilience instead of reinventing it.** Retry, rate limiting, circuit breaking,
|
|
46
|
+
bulkheads, timeouts, and fallbacks are wired in by default, on top of maintained libraries --
|
|
47
|
+
[`refresh-engine`](https://pypi.org/project/refresh-engine/) for per-server discovery
|
|
48
|
+
reconciliation, [`tenacity`](https://pypi.org/project/tenacity/) for retries, and
|
|
49
|
+
[`aiolimiter`](https://pypi.org/project/aiolimiter/) for rate limiting -- so one misbehaving
|
|
50
|
+
server degrades gracefully instead of taking the whole agent down, and nobody has to hand-write
|
|
51
|
+
that logic per adapter again.
|
|
52
|
+
- **Keep every seam swappable.** The capability registry, the retriever, and the metrics hook are
|
|
53
|
+
small, explicit protocols, isolated from connection and lifecycle handling. Start with the
|
|
54
|
+
built-in in-memory registry and keyword retriever, then drop in Postgres, Qdrant, Ollama
|
|
55
|
+
embeddings, or Prometheus later -- with zero changes to `MCPRuntime` or the resilience pipeline.
|
|
56
|
+
- **Stay a router, not a runtime.** This project owns capability discovery, retrieval, and
|
|
57
|
+
lifecycle policy only. Transport, authentication, and the MCP client itself stay with
|
|
58
|
+
`langchain-mcp-adapters` or your own adapter; model calls and agent orchestration stay with
|
|
59
|
+
LangChain, LangGraph, `langgraph-swarm`, or DeepAgents. Nothing here competes with those layers.
|
|
60
|
+
|
|
61
|
+
## Installation
|
|
62
|
+
|
|
63
|
+
This package is not yet published to PyPI. Install it from source:
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
git clone https://github.com/smuniharish/mcp-capabilty-router.git
|
|
67
|
+
cd mcp-capabilty-router
|
|
68
|
+
pip install .
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
Optional extras add integration-specific dependencies as needed:
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
pip install ".[mcp]" # langchain-mcp-adapters
|
|
75
|
+
pip install ".[langgraph]" # LangGraph
|
|
76
|
+
pip install ".[agents]" # LangChain create_agent, DeepAgents
|
|
77
|
+
pip install ".[swarm]" # langgraph-swarm
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
Requires Python 3.12.
|
|
81
|
+
|
|
82
|
+
## Quickstart
|
|
83
|
+
|
|
84
|
+
```python
|
|
85
|
+
import asyncio
|
|
86
|
+
from mcp_capability_router import CapabilityType, MCPRuntime
|
|
87
|
+
|
|
88
|
+
async def main():
|
|
89
|
+
async with MCPRuntime() as runtime:
|
|
90
|
+
await runtime.register_server("filesystem", my_filesystem_adapter_factory)
|
|
91
|
+
await runtime.refresh_server("filesystem")
|
|
92
|
+
|
|
93
|
+
candidates = await runtime.retrieve("read a file", type=CapabilityType.TOOL, limit=5)
|
|
94
|
+
result = await runtime.execute(candidates[0].capability_id, {"path": "notes.txt"})
|
|
95
|
+
print(result)
|
|
96
|
+
|
|
97
|
+
asyncio.run(main())
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
See [Getting started](docs/guides/getting-started.md) for wiring in a real
|
|
101
|
+
`langchain-mcp-adapters` client, and [Examples](docs/examples/overview.md) for runnable scripts
|
|
102
|
+
covering real MCP servers, LangGraph/DeepAgents/`langgraph-swarm` integrations, and pluggable
|
|
103
|
+
registries, retrievers, and metrics backends.
|
|
104
|
+
|
|
105
|
+
## Documentation
|
|
106
|
+
|
|
107
|
+
Full documentation -- architecture, concepts, guides, operations, API reference, and
|
|
108
|
+
troubleshooting -- lives in [`docs/`](docs/index.md) and is built with MkDocs Material.
|
|
109
|
+
|
|
110
|
+
## Contributing
|
|
111
|
+
|
|
112
|
+
```powershell
|
|
113
|
+
uv sync --extra dev
|
|
114
|
+
uv run pytest
|
|
115
|
+
uv run ruff check .
|
|
116
|
+
uv run ruff format --check .
|
|
117
|
+
uv run pyrefly check
|
|
118
|
+
uv run mkdocs build
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
## License
|
|
122
|
+
|
|
123
|
+
Apache License 2.0 -- see [LICENSE](LICENSE).
|
|
124
|
+
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# Architecture decision records
|
|
2
|
+
|
|
3
|
+
## ADR-001: adapter-owned MCP connectivity
|
|
4
|
+
|
|
5
|
+
**Context.** Applications already choose their MCP transport, authentication configuration, and
|
|
6
|
+
client implementation. Reimplementing those concerns in the router would introduce a second MCP
|
|
7
|
+
client stack and split ownership of connection failures and credentials.
|
|
8
|
+
|
|
9
|
+
**Decision.** The runtime delegates transport, authentication, and protocol details to
|
|
10
|
+
`langchain-mcp-adapters` or an application-provided adapter. It owns registration, discovery
|
|
11
|
+
reconciliation, retrieval, loading, and lifecycle coordination. A
|
|
12
|
+
`MultiServerMCPClient` can be registered directly with `register_mcp_client`; no
|
|
13
|
+
Playwright-, filesystem-, or transport-specific client exists in this package.
|
|
14
|
+
|
|
15
|
+
**Consequences.** The router stays interoperable with the official adapter and does not own
|
|
16
|
+
server processes, credentials, or an MCP protocol implementation. Applications must configure
|
|
17
|
+
and diagnose their selected client and transport.
|
|
18
|
+
|
|
19
|
+
## ADR-002: capabilities are first-class
|
|
20
|
+
|
|
21
|
+
**Context.** MCP exposes tools, resources, and prompts. Treating only tools as routable loses
|
|
22
|
+
useful server functionality; flattening their behavior risks calling an operation with the wrong
|
|
23
|
+
semantics.
|
|
24
|
+
|
|
25
|
+
**Decision.** Tools, resources, and prompts share the `Capability` contract but retain
|
|
26
|
+
type-specific semantics. Retrieval and registry operations accept all three types, while
|
|
27
|
+
execution APIs guard the requested capability type.
|
|
28
|
+
|
|
29
|
+
**Consequences.** Selection can use one metadata and retrieval model, while `execute`,
|
|
30
|
+
`read_resource`, and `get_prompt` remain explicit about the requested operation.
|
|
31
|
+
|
|
32
|
+
## ADR-003: instance-scoped mutable state
|
|
33
|
+
|
|
34
|
+
**Context.** A process can host multiple agents or tenants that must not share cached
|
|
35
|
+
capabilities, connections, limits, or health state.
|
|
36
|
+
|
|
37
|
+
**Decision.** Registries, loaders, refresh tasks, circuit breakers, locks, and health are owned
|
|
38
|
+
by a runtime instance. There are no process-global caches or connection managers.
|
|
39
|
+
|
|
40
|
+
**Consequences.** Tests are deterministic and independent application boundaries can coexist in
|
|
41
|
+
one process. Callers must create and close a runtime for each intended isolation boundary.
|
|
42
|
+
|
|
43
|
+
## ADR-004: lazy connection and explicit refresh
|
|
44
|
+
|
|
45
|
+
**Context.** Connecting to every configured server at startup is expensive and may expose a
|
|
46
|
+
large, irrelevant capability set to an agent.
|
|
47
|
+
|
|
48
|
+
**Decision.** Server registration stores a factory only. Discovery is explicit and a successful
|
|
49
|
+
refresh reconciles metadata with the latest server response.
|
|
50
|
+
|
|
51
|
+
**Consequences.** Startup remains lazy and agents can retrieve a small candidate set instead of
|
|
52
|
+
receiving every configured capability. A failed discovery leaves the last successfully
|
|
53
|
+
reconciled metadata intact; applications must schedule refreshes appropriate to their servers.
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# Architecture overview
|
|
2
|
+
|
|
3
|
+
```mermaid
|
|
4
|
+
flowchart LR
|
|
5
|
+
App[LangChain / LangGraph application] --> Runtime[MCPRuntime]
|
|
6
|
+
Runtime --> Registry[Capability registry]
|
|
7
|
+
Runtime --> Route[Retrieve / query]
|
|
8
|
+
Runtime --> Life[Lazy server lifecycle]
|
|
9
|
+
Life --> Adapter[Application-owned adapter]
|
|
10
|
+
Adapter --> MCP[MCP server]
|
|
11
|
+
Route --> JIT[Load or execute selected capability]
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
Each runtime instance owns its servers, registry, caches, locks, semaphores, and background
|
|
15
|
+
tasks. No mutable process-wide singleton is used. Metadata discovery is separate from loading:
|
|
16
|
+
many records can remain in the registry while only a handful are selected for an agent.
|
|
17
|
+
|
|
18
|
+
## Request lifecycle
|
|
19
|
+
|
|
20
|
+
```mermaid
|
|
21
|
+
sequenceDiagram
|
|
22
|
+
participant App
|
|
23
|
+
participant Runtime
|
|
24
|
+
participant Registry
|
|
25
|
+
participant Adapter
|
|
26
|
+
participant Server
|
|
27
|
+
App->>Runtime: register_server(id, factory)
|
|
28
|
+
Note over Runtime,Adapter: No connection is made
|
|
29
|
+
App->>Runtime: refresh_server(id)
|
|
30
|
+
Runtime->>Adapter: connect()
|
|
31
|
+
Adapter->>Server: list tools/resources/prompts
|
|
32
|
+
Runtime->>Registry: upsert metadata and remove stale IDs
|
|
33
|
+
App->>Runtime: retrieve(query)
|
|
34
|
+
Runtime->>Registry: list metadata
|
|
35
|
+
Registry-->>Runtime: ranked capabilities
|
|
36
|
+
App->>Runtime: execute(capability_id)
|
|
37
|
+
Runtime->>Adapter: call_tool(...)
|
|
38
|
+
Adapter->>Server: MCP request
|
|
39
|
+
Server-->>App: result
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## Isolation and extension points
|
|
43
|
+
|
|
44
|
+
* Create one `MCPRuntime` per tenant, agent, or policy boundary; do not share mutable runtimes
|
|
45
|
+
between unrelated applications.
|
|
46
|
+
* Pass an implementation of `CapabilityRegistry` when metadata must live in a database or another
|
|
47
|
+
process. The protocol is intentionally small.
|
|
48
|
+
* Implement the async adapter shape, or wrap an adapter from `langchain-mcp-adapters`. The router
|
|
49
|
+
never opens a transport itself.
|
|
50
|
+
* `retrieve` is deterministic term matching. Applications can apply a model, policy, or graph
|
|
51
|
+
node after retrieval and before execution.
|
|
Binary file
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# Concepts
|
|
2
|
+
|
|
3
|
+
```mermaid
|
|
4
|
+
flowchart LR
|
|
5
|
+
subgraph Metadata["Registry: cheap to hold, even at 10k+ capabilities"]
|
|
6
|
+
T[Tool records]
|
|
7
|
+
R[Resource records]
|
|
8
|
+
P[Prompt records]
|
|
9
|
+
end
|
|
10
|
+
Metadata --> Retrieve[retrieve / query: filter + rank]
|
|
11
|
+
Retrieve --> Selected[Small selected candidate set]
|
|
12
|
+
Selected --> Loaded[load / execute / read_resource / get_prompt]
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
Tools, Resources, and Prompts are routed through the identical retrieve -> select -> load/execute
|
|
16
|
+
path as siblings of one `Capability` model -- none of the three is a special case bolted on
|
|
17
|
+
afterward. Holding 10,000+ metadata records in the registry stays cheap because retrieval indexes
|
|
18
|
+
and filters that metadata; only the handful of capabilities an agent actually selects are ever
|
|
19
|
+
loaded or invoked.
|
|
20
|
+
|
|
21
|
+
## Capabilities
|
|
22
|
+
|
|
23
|
+
`Tool`, `Resource`, and `Prompt` are immutable capability records. Every record has a stable
|
|
24
|
+
`capability_id` (`<server>:<kind>:<name-or-uri>`), `server_id`, `name`, description, metadata,
|
|
25
|
+
and availability. Resources additionally have a URI; prompts have argument names; tools may have
|
|
26
|
+
an input schema.
|
|
27
|
+
|
|
28
|
+
## Discovery, retrieval, and execution
|
|
29
|
+
|
|
30
|
+
* **Discovery** connects and reconciles metadata via a per-server
|
|
31
|
+
[`refresh_engine.RefreshEngine`](https://pypi.org/project/refresh-engine/). It can be explicit
|
|
32
|
+
(`refresh_server`, which accepts `mode=`/`resource_ids=` for `FULL`/`INCREMENTAL`/`TARGETED`/
|
|
33
|
+
`DEPENDENCY_AWARE` reconciliation) or runtime-managed through a per-server `RefreshPolicy`;
|
|
34
|
+
startup remains lazy unless `on_register=True`. See [refresh](../operations/refresh.md) for
|
|
35
|
+
the full reconciliation contract.
|
|
36
|
+
* **Retrieval** (`retrieve` or `query`) filters by type/server and ranks matching terms. It does
|
|
37
|
+
not call an MCP capability.
|
|
38
|
+
* **Loading** (`load`) resolves a selected capability for an application cache.
|
|
39
|
+
* **Operations** (`execute`, `read_resource`, `get_prompt`) connect on demand and invoke the
|
|
40
|
+
corresponding adapter method.
|
|
41
|
+
|
|
42
|
+
`query(...)` searches existing metadata first. After a miss, it automatically refreshes servers
|
|
43
|
+
registered with `RefreshPolicy(on_query_miss=True)`, then searches again. The explicit
|
|
44
|
+
`refresh_servers=[...]` hint remains available for one-off query-scoped refreshes.
|
|
45
|
+
|
|
46
|
+
## Lifecycle and resilience
|
|
47
|
+
|
|
48
|
+
Registration stores a factory or adapter but does not connect, and builds that server's
|
|
49
|
+
`RefreshEngine`. `connect` is idempotent per server, and `reconnect` closes and creates a fresh
|
|
50
|
+
adapter. A runtime context manager stops every scheduler and policy task, closes every server's
|
|
51
|
+
`RefreshEngine`, then closes adapters, registry resources, and loader caches. `max_concurrency`
|
|
52
|
+
bounds operations through the runtime bulkhead.
|
|
53
|
+
|
|
54
|
+
## Registry contract
|
|
55
|
+
|
|
56
|
+
`InMemoryRegistry` is the default. A custom `CapabilityRegistry` must implement `upsert_many`,
|
|
57
|
+
`remove_missing`, `remove`, `get`, `list`, and `close`. `remove` deletes one capability by ID and
|
|
58
|
+
backs incremental/dependency-aware refresh; `remove_missing` stays for bulk reconciliation and
|
|
59
|
+
`unregister_server`. Discovery happens outside registry locks, making network latency
|
|
60
|
+
independent from metadata lock duration.
|