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.
Files changed (101) hide show
  1. mcp_capability_router-0.1.0/.env.example +20 -0
  2. mcp_capability_router-0.1.0/.github/workflows/ci.yml +19 -0
  3. mcp_capability_router-0.1.0/.gitignore +35 -0
  4. mcp_capability_router-0.1.0/.readthedocs.yaml +14 -0
  5. mcp_capability_router-0.1.0/LICENSE +16 -0
  6. mcp_capability_router-0.1.0/PKG-INFO +189 -0
  7. mcp_capability_router-0.1.0/README.md +124 -0
  8. mcp_capability_router-0.1.0/docs/architecture/decisions.md +53 -0
  9. mcp_capability_router-0.1.0/docs/architecture/overview.md +51 -0
  10. mcp_capability_router-0.1.0/docs/assets/grafana-observability-dashboard.png +0 -0
  11. mcp_capability_router-0.1.0/docs/assets/grafana-prometheus-metrics.png +0 -0
  12. mcp_capability_router-0.1.0/docs/concepts/overview.md +60 -0
  13. mcp_capability_router-0.1.0/docs/examples/overview.md +325 -0
  14. mcp_capability_router-0.1.0/docs/guides/agent-integrations.md +406 -0
  15. mcp_capability_router-0.1.0/docs/guides/getting-started.md +132 -0
  16. mcp_capability_router-0.1.0/docs/index.md +34 -0
  17. mcp_capability_router-0.1.0/docs/javascripts/mermaid.js +1 -0
  18. mcp_capability_router-0.1.0/docs/operations/concurrency.md +48 -0
  19. mcp_capability_router-0.1.0/docs/operations/isolation.md +53 -0
  20. mcp_capability_router-0.1.0/docs/operations/overview.md +23 -0
  21. mcp_capability_router-0.1.0/docs/operations/refresh.md +144 -0
  22. mcp_capability_router-0.1.0/docs/operations/resilience.md +112 -0
  23. mcp_capability_router-0.1.0/docs/reference/api.md +116 -0
  24. mcp_capability_router-0.1.0/docs/troubleshooting/overview.md +24 -0
  25. mcp_capability_router-0.1.0/examples/__init__.py +1 -0
  26. mcp_capability_router-0.1.0/examples/basic_runtime.py +81 -0
  27. mcp_capability_router-0.1.0/examples/capability_scale_simulation.py +123 -0
  28. mcp_capability_router-0.1.0/examples/combined_workflow.py +82 -0
  29. mcp_capability_router-0.1.0/examples/create_agent_routing.py +80 -0
  30. mcp_capability_router-0.1.0/examples/custom_registry.py +130 -0
  31. mcp_capability_router-0.1.0/examples/deepagents_network.py +91 -0
  32. mcp_capability_router-0.1.0/examples/dynamic_registration.py +106 -0
  33. mcp_capability_router-0.1.0/examples/embedding_ollama.py +114 -0
  34. mcp_capability_router-0.1.0/examples/end_to_end_full_stack.py +208 -0
  35. mcp_capability_router-0.1.0/examples/langgraph_dynamic_discovery.py +138 -0
  36. mcp_capability_router-0.1.0/examples/langgraph_network.py +75 -0
  37. mcp_capability_router-0.1.0/examples/langgraph_swarm_network.py +103 -0
  38. mcp_capability_router-0.1.0/examples/manual_refresh.py +89 -0
  39. mcp_capability_router-0.1.0/examples/mcp_smoke_support.py +52 -0
  40. mcp_capability_router-0.1.0/examples/metrics_prometheus.py +164 -0
  41. mcp_capability_router-0.1.0/examples/metrics_prometheus_real_llm.py +292 -0
  42. mcp_capability_router-0.1.0/examples/multi_runtime_isolation.py +82 -0
  43. mcp_capability_router-0.1.0/examples/on_demand_connection.py +72 -0
  44. mcp_capability_router-0.1.0/examples/postgres_registry.py +211 -0
  45. mcp_capability_router-0.1.0/examples/prompt_selection.py +81 -0
  46. mcp_capability_router-0.1.0/examples/public_api_overrides.py +518 -0
  47. mcp_capability_router-0.1.0/examples/real_everything_mcp.py +44 -0
  48. mcp_capability_router-0.1.0/examples/real_fetch_mcp.py +51 -0
  49. mcp_capability_router-0.1.0/examples/real_filesystem_mcp.py +49 -0
  50. mcp_capability_router-0.1.0/examples/real_git_mcp.py +65 -0
  51. mcp_capability_router-0.1.0/examples/real_integrations.py +28 -0
  52. mcp_capability_router-0.1.0/examples/real_multi_agent_multi_mcp.py +190 -0
  53. mcp_capability_router-0.1.0/examples/real_multi_capability_selection.py +78 -0
  54. mcp_capability_router-0.1.0/examples/real_multi_mcp_single_agent.py +163 -0
  55. mcp_capability_router-0.1.0/examples/refresh_strategies.py +113 -0
  56. mcp_capability_router-0.1.0/examples/reranking_bm25.py +132 -0
  57. mcp_capability_router-0.1.0/examples/resilience_breaker_retry_timeout.py +197 -0
  58. mcp_capability_router-0.1.0/examples/resilience_bulkhead_rate_limit.py +105 -0
  59. mcp_capability_router-0.1.0/examples/resource_selection.py +73 -0
  60. mcp_capability_router-0.1.0/examples/tools_and_resources.py +72 -0
  61. mcp_capability_router-0.1.0/examples/vector_retrieval_qdrant.py +221 -0
  62. mcp_capability_router-0.1.0/mkdocs.yml +39 -0
  63. mcp_capability_router-0.1.0/pyproject.toml +64 -0
  64. mcp_capability_router-0.1.0/src/mcp_capability_router/__init__.py +39 -0
  65. mcp_capability_router-0.1.0/src/mcp_capability_router/discovery.py +69 -0
  66. mcp_capability_router-0.1.0/src/mcp_capability_router/embedding.py +35 -0
  67. mcp_capability_router-0.1.0/src/mcp_capability_router/errors.py +68 -0
  68. mcp_capability_router-0.1.0/src/mcp_capability_router/integrations/__init__.py +5 -0
  69. mcp_capability_router-0.1.0/src/mcp_capability_router/integrations/langchain_mcp.py +111 -0
  70. mcp_capability_router-0.1.0/src/mcp_capability_router/interceptor/__init__.py +27 -0
  71. mcp_capability_router-0.1.0/src/mcp_capability_router/loader.py +47 -0
  72. mcp_capability_router-0.1.0/src/mcp_capability_router/models.py +44 -0
  73. mcp_capability_router-0.1.0/src/mcp_capability_router/pipeline.py +214 -0
  74. mcp_capability_router-0.1.0/src/mcp_capability_router/refresh.py +122 -0
  75. mcp_capability_router-0.1.0/src/mcp_capability_router/registry.py +100 -0
  76. mcp_capability_router-0.1.0/src/mcp_capability_router/reranking.py +63 -0
  77. mcp_capability_router-0.1.0/src/mcp_capability_router/resilience/__init__.py +40 -0
  78. mcp_capability_router-0.1.0/src/mcp_capability_router/resilience/bulkhead.py +29 -0
  79. mcp_capability_router-0.1.0/src/mcp_capability_router/resilience/circuit_breaker.py +87 -0
  80. mcp_capability_router-0.1.0/src/mcp_capability_router/resilience/classification.py +157 -0
  81. mcp_capability_router-0.1.0/src/mcp_capability_router/resilience/fallback.py +47 -0
  82. mcp_capability_router-0.1.0/src/mcp_capability_router/resilience/health.py +37 -0
  83. mcp_capability_router-0.1.0/src/mcp_capability_router/resilience/metrics.py +44 -0
  84. mcp_capability_router-0.1.0/src/mcp_capability_router/resilience/timeout.py +49 -0
  85. mcp_capability_router-0.1.0/src/mcp_capability_router/retrieval.py +39 -0
  86. mcp_capability_router-0.1.0/src/mcp_capability_router/routing.py +46 -0
  87. mcp_capability_router-0.1.0/src/mcp_capability_router/runtime.py +381 -0
  88. mcp_capability_router-0.1.0/src/mcp_capability_router/selection.py +43 -0
  89. mcp_capability_router-0.1.0/src/mcp_capability_router/server.py +99 -0
  90. mcp_capability_router-0.1.0/tests/test_combined_workflow.py +117 -0
  91. mcp_capability_router-0.1.0/tests/test_custom_registry.py +153 -0
  92. mcp_capability_router-0.1.0/tests/test_dynamic_registration.py +245 -0
  93. mcp_capability_router-0.1.0/tests/test_langchain_mcp_adapter.py +57 -0
  94. mcp_capability_router-0.1.0/tests/test_pipeline.py +176 -0
  95. mcp_capability_router-0.1.0/tests/test_refresh_engine.py +206 -0
  96. mcp_capability_router-0.1.0/tests/test_resilience.py +103 -0
  97. mcp_capability_router-0.1.0/tests/test_resilience_tenacity.py +165 -0
  98. mcp_capability_router-0.1.0/tests/test_runtime.py +146 -0
  99. mcp_capability_router-0.1.0/tests/test_scale.py +226 -0
  100. mcp_capability_router-0.1.0/tests/test_selection.py +75 -0
  101. 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
+ [![CI](https://github.com/smuniharish/mcp-capabilty-router/actions/workflows/ci.yml/badge.svg)](https://github.com/smuniharish/mcp-capabilty-router/actions/workflows/ci.yml)
69
+ [![Python 3.12](https://img.shields.io/badge/python-3.12-blue.svg)](https://www.python.org/downloads/release/python-3120/)
70
+ [![License: Apache 2.0](https://img.shields.io/badge/license-Apache%202.0-green.svg)](LICENSE)
71
+ [![Docs](https://img.shields.io/badge/docs-mkdocs--material-blueviolet)](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
+ [![CI](https://github.com/smuniharish/mcp-capabilty-router/actions/workflows/ci.yml/badge.svg)](https://github.com/smuniharish/mcp-capabilty-router/actions/workflows/ci.yml)
4
+ [![Python 3.12](https://img.shields.io/badge/python-3.12-blue.svg)](https://www.python.org/downloads/release/python-3120/)
5
+ [![License: Apache 2.0](https://img.shields.io/badge/license-Apache%202.0-green.svg)](LICENSE)
6
+ [![Docs](https://img.shields.io/badge/docs-mkdocs--material-blueviolet)](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.
@@ -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.