mcp-lens
mcp-lens
Progressive disclosure for large MCP tool catalogs.
Quick Start · Spec · Examples · Contributing
Most MCP servers expose one tool per endpoint. That works until your catalog grows — 20 endpoints becomes 20 tool definitions loaded into every context window, 200 becomes 200, and the model starts guessing wrong between similarly-named tools long before you get there.
mcp-lens is a small, dependency-light pattern (and a Python reference
implementation) for the alternative: expose exactly 3 stable meta-tools —
search_capabilities, get_capability_schema, execute_capability — no
matter how many capabilities sit behind them. The tool-definition cost the
model pays is O(1) in catalog size; only what search_capabilities actually
returns grows with your catalog.
from mcp_lens import Capability, CapabilityRegistry, build_server
registry = CapabilityRegistry()
registry.register(
Capability(
key="billing.create_invoice",
description="Create an invoice for a customer",
input_schema={
"type": "object",
"properties": {"customer_id": {"type": "string"}, "amount": {"type": "number"}},
"required": ["customer_id", "amount"],
},
executor=lambda customer_id, amount: {"invoice_id": "INV-001", "amount": amount},
)
)
# ...register 5, 50, or 5,000 more capabilities the same way...
mcp = build_server(registry, name="my-server")
mcp.run()Whether registry holds 1 capability or 5,000, the MCP client always sees
the same 3 tools.
Why this, specifically
The registry has no MCP dependency.
mcp_lens.registryis plain Python — testable, reusable, and swappable behind any transport. The FastMCP adapter inmcp_lens.serveris a thin, optional layer on top.Search is pluggable. The default matcher is keyword substring matching, fine for demos. Pass your own
search_fntoCapabilityRegistryfor Postgres full-text, embeddings, or whatever search backend you already run — the 3-tool contract doesn't change.It's a spec, not just a library.
SPEC.mddefines the contract (tool names, schemas, semantics) independently of this implementation, so it can be implemented in other languages and still interoperate conceptually.Validated with real traffic, not just a thought experiment — this pattern (search → schema → execute) has been running in production MCP servers before this repository existed; this is the extracted, product-agnostic version of that mechanism.
Installation
Add mcp-lens to a new or existing Python project with uv:
uv add mcp-lens-pyOr with pip:
pip install mcp-lens-pyInstalling from source — track main:
uv add "mcp-lens-py @ git+https://github.com/helygp/mcp-lens.git@main"Quick Start
1. Define your capabilities
A Capability is a key, a description, a JSON Schema for its input, and a
callable (sync or async) that runs it:
from mcp_lens import Capability
def get_weather(city: str) -> str:
return f"Sunny in {city}"
weather = Capability(
key="weather.get_weather",
description="Get current weather for a city",
input_schema={
"type": "object",
"properties": {"city": {"type": "string"}},
"required": ["city"],
},
executor=get_weather,
tags=("weather", "forecast"),
)2. Register them
from mcp_lens import CapabilityRegistry
registry = CapabilityRegistry()
registry.register(weather)
# or: registry.register_many([weather, other_capability, ...])3. Serve them
from mcp_lens import build_server
mcp = build_server(registry, name="my-server")
if __name__ == "__main__":
mcp.run()Run the checked-in example instead of writing your own from scratch:
uv run examples/basic/server.pySee examples/README.md for the full walkthrough, including
the token-cost comparison in examples/benchmark/.
Learn more
SPEC.md — the formal contract: tool names, schemas, semantics, and what's deliberately left out of scope (auth, persistence, discovery UI — those are yours to build).
examples/ — a runnable 5-capability example server and a before/after token-cost comparison as the catalog grows.
CONTRIBUTING.md — how to propose changes, including changes to the spec itself.
Related work
This pattern isn't new — Twenty CRM uses
a similar fixed-meta-tool approach for its MCP server, and "don't load every
tool definition up front" is an increasingly common idea across the MCP
ecosystem as catalogs grow. What mcp-lens adds is not the idea itself but:
a formal, implementation-agnostic contract for it (SPEC.md), a tested
reference implementation with the MCP-specific parts cleanly separated from
the reusable core, and measured numbers for the tradeoff instead of just the
claim. If you know of other implementations of this pattern, a PR adding
them here is welcome.
Non-goals
mcp-lens is deliberately narrow. It does not provide: authentication, capability persistence/storage, an approval or review UI, or AI-assisted onboarding of new capabilities. Those are real, useful things to build on top of this — but they're product decisions, not part of the protocol pattern this repo exists to document and implement.
Contributing
git clone https://github.com/helygp/mcp-lens.git
cd mcp-lens
uv sync --group dev
uv run pytest
uv run ruff checkSee CONTRIBUTING.md for the full workflow.
Citation
If mcp-lens or the pattern in SPEC.md is useful in your work, you can cite
the repository:
@software{mcp_lens,
title = {mcp-lens: Progressive disclosure for large MCP tool catalogs},
author = {Pasqual, Hely},
year = {2026},
url = {https://github.com/helygp/mcp-lens}
}License
Apache 2.0. See LICENSE.