Domain-neutral MCP router: front many upstream MCP servers behind two meta-tools (search + dispatch) so an agent gets lazy, searchable tool discovery instead of a huge up-front tool payload.
Project description
dhis2w-mcp-router
A domain-neutral MCP router: front many upstream MCP servers behind two meta-tools so an agent gets lazy, searchable tool discovery instead of a huge up-front tool payload.
It is the portable, MCP-native equivalent of the Claude Agent SDK's ToolSearch — but it works with any MCP client (local models via LM Studio / Ollama / llama.cpp, or cloud agents), not just Claude's SDK.
Why
A big MCP server (e.g. ~337 dhis2-mcp tools) dumps ~49k tokens of tool schemas into context up front —
which overflows small local models and costs cloud models on every call. The single-tool dhis2_cli
bridge avoids that by collapsing everything behind one tool, but then the agent must discover a CLI
(run --help, trial commands) — discovery overhead. The router is the middle ground:
| tool payload | discovery | typed schemas | guardable chokepoint | |
|---|---|---|---|---|
| full MCP server | huge (all schemas) | none | yes | per-tool |
| single-tool bridge | tiny (1 tool) | high (learn a CLI) | no | yes (1 tool) |
| router (this) | tiny (2 meta-tools) | low (search) | yes (search returns schemas) | yes (1 dispatch) |
The two tools
search_tools(query, limit)— matching tools with their namespaced names (server__tool) and input schemas.call_tool(name, arguments)— dispatch one tool to its upstream and return the result.
call_tool is a single chokepoint, so a policy guard sits there — the same security property as the
bridge, with typed discovery on top.
Read-only mode
Set MCP_ROUTER_READONLY=1 (global) or mark a single upstream "readonly": true in the config to run
read-only: write tools are hidden from search_tools and refused by call_tool (a
PermissionError). Tools are classified by their readOnlyHint annotation when present, else by a
read-verb heuristic on the tool name (fail-closed). This is how a local model safely drives the surface
on a shared host — front play read-only and local read-write in the same config.
Config
Point it at any MCP servers via a JSON config (env MCP_ROUTER_CONFIG, default mcp-router.json):
{
"servers": [
{"name": "dhis2", "command": "uv", "args": ["run", "--directory", "/repo", "dhis2w-mcp"],
"env": {"DHIS2_PROFILE": "play42"}}
]
}
Run it as a stdio MCP server: uv run dhis2w-mcp-router.
Ranking
search_tools ranking is pluggable. The default KeywordRanker scores by query-term hits (no deps).
Add an embeddings block (url + model, an OpenAI-compatible /v1/embeddings endpoint — e.g. a
local embedder in LM Studio / Ollama) to rank by semantic similarity instead, which fixes keyword
mis-ranks (e.g. "data element count" → metadata_count first). It is not a silver bullet on terse
queries with a small local embedder; a larger embedder or a hybrid is the further upgrade.
Status
Published to PyPI from 1.2.0, in lockstep with the rest of the dhis2w-* workspace. The core
(core.py) is domain-neutral (FastMCP + httpx only, no dhis2w-* imports), so the same code could
also extract to a standalone mcp-router repo without a rewrite. Whether the router should become the
default MCP surface for all clients (not just small local models) is still gated on the bench-router
numbers — see the roadmap.
Project details
Release history Release notifications | RSS feed
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 dhis2w_mcp_router-1.3.0.tar.gz.
File metadata
- Download URL: dhis2w_mcp_router-1.3.0.tar.gz
- Upload date:
- Size: 9.0 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
6bb85390e1834e1cf1b476f6d649ac362c49d640b5e7499a233297f61a6265e8
|
|
| MD5 |
9c2339e6bc2cc475c6de74b588217dce
|
|
| BLAKE2b-256 |
92267c659e4cf1888b1410e95db04d7b26e511e4bfc7fa8291f19d8f3005c91a
|
Provenance
The following attestation bundles were made for dhis2w_mcp_router-1.3.0.tar.gz:
Publisher:
pypi-publish.yml on winterop-com/dhis2w-utils
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
dhis2w_mcp_router-1.3.0.tar.gz -
Subject digest:
6bb85390e1834e1cf1b476f6d649ac362c49d640b5e7499a233297f61a6265e8 - Sigstore transparency entry: 2155389628
- Sigstore integration time:
-
Permalink:
winterop-com/dhis2w-utils@f5d32a63038bb6fbac5d2f31d63e1844182632db -
Branch / Tag:
refs/tags/v1.3.0 - Owner: https://github.com/winterop-com
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
pypi-publish.yml@f5d32a63038bb6fbac5d2f31d63e1844182632db -
Trigger Event:
push
-
Statement type:
File details
Details for the file dhis2w_mcp_router-1.3.0-py3-none-any.whl.
File metadata
- Download URL: dhis2w_mcp_router-1.3.0-py3-none-any.whl
- Upload date:
- Size: 12.4 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
0ad6b49b9c2c10c3aec3d24729ab9112c53155239e7ab6a6d291a1341ec2d3d0
|
|
| MD5 |
95b79e57d8dfe1ef545673d4f099da14
|
|
| BLAKE2b-256 |
ff9fa22244f171330de842b50efe21c16ea58d5c1eb4283912b4eed0ce26959b
|
Provenance
The following attestation bundles were made for dhis2w_mcp_router-1.3.0-py3-none-any.whl:
Publisher:
pypi-publish.yml on winterop-com/dhis2w-utils
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
dhis2w_mcp_router-1.3.0-py3-none-any.whl -
Subject digest:
0ad6b49b9c2c10c3aec3d24729ab9112c53155239e7ab6a6d291a1341ec2d3d0 - Sigstore transparency entry: 2155390382
- Sigstore integration time:
-
Permalink:
winterop-com/dhis2w-utils@f5d32a63038bb6fbac5d2f31d63e1844182632db -
Branch / Tag:
refs/tags/v1.3.0 - Owner: https://github.com/winterop-com
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
pypi-publish.yml@f5d32a63038bb6fbac5d2f31d63e1844182632db -
Trigger Event:
push
-
Statement type: