hybrid-rag-mcp
README.md
# hybrid-rag-mcp
A production-minded **Model Context Protocol (MCP) server** that gives any LLM agent
a high-quality `search_docs` tool backed by **hybrid retrieval** (BM25 + dense vectors,
fused with Reciprocal Rank Fusion) and **cross-encoder reranking** — with a real
**evaluation harness**, **prompt-injection guardrails**, and **OpenTelemetry tracing**
built in.
> One tool, exposed over MCP, that is *measurably* good and *demonstrably* safe.
---
## Documentation
Full documentation lives in **[`docs/`](docs/README.md)**. Quick links:
| | |
|---|---|
| [Overview](docs/overview.md) · [Installation](docs/installation.md) · [Usage](docs/usage.md) | Get started |
| [Architecture](docs/architecture.md) · [API Reference](docs/api-reference.md) · [Configuration](docs/configuration.md) | Reference |
| [Retrieval](docs/retrieval.md) · [Evaluation](docs/evaluation.md) · [Security](docs/security.md) · [Observability](docs/observability.md) | Deep dives |
| [Development](docs/development.md) · [FAQ](docs/faq.md) | Extend & troubleshoot |
---
## Why this project
Most retrieval demos stop at "embed the query, take cosine top-k." Real systems don't.
This repo shows the parts that actually matter in production:
| Capability | What it demonstrates |
|---|---|
| **MCP server** (`FastMCP`, stdio transport) | Tool-calling integration any MCP client (Claude Desktop, IDEs, custom agents) can use |
| **Hybrid retrieval** (BM25 + dense, RRF fusion) | You understand lexical vs. semantic search and how to combine them |
| **Cross-encoder reranking** | You can improve precision@k, not just recall — and *measure* it |
| **Eval harness** (recall@k, MRR) | You prove quality with numbers, before/after each stage |
| **Prompt-injection guardrail** | You treat tool inputs as untrusted (OWASP LLM01) |
| **OpenTelemetry tracing** | You can debug and observe an agent tool in production |
## Architecture
```mermaid
flowchart LR
A[MCP Client] -- search_docs query --> B[FastMCP Server]
B --> G{Injection guardrail}
G -- flagged --> R[Reject + reason]
G -- clean --> P[Retrieval pipeline]
subgraph P [Retrieval pipeline]
C[BM25 lexical] --> F[RRF fusion]
D[Dense vector] --> F
F --> E[Cross-encoder rerank]
end
P --> H[Top-k passages]
H --> A
B -. spans .-> T[(OpenTelemetry)]
```
## Quickstart
Requires **Python 3.10+** (tested on 3.13).
```bash
# 1. Create an isolated environment
python3.13 -m venv .venv && source .venv/bin/activate
# 2. Install the core (BM25 works immediately — no model downloads)
pip install -e .
# 3. Run the tests and the retrieval eval
make test
make eval
# 4. Start the MCP server (stdio)
make run
```
The server ships with a small sample corpus (`src/hybrid_rag_mcp/corpus/sample_docs.jsonl`)
so everything runs end-to-end on first clone. Swap in your own corpus to make it yours.
### Optional: enable dense vectors + reranking
The advanced retrieval stages activate automatically when their (heavier) dependencies
are installed; otherwise the pipeline gracefully degrades to BM25-only.
```bash
pip install -e ".[full]" # sentence-transformers, flashrank, opentelemetry
```
### Connect it to an MCP client
Add this to your client's MCP config (example for Claude Desktop
`claude_desktop_config.json`):
```json
{
"mcpServers": {
"hybrid-rag": {
"command": "/absolute/path/to/.venv/bin/python",
"args": ["-m", "hybrid_rag_mcp.server"]
}
}
}
```
## Evaluation
`make eval` scores retrieval quality on `evals/qa_dataset.jsonl` and prints a table so you
can see the contribution of each stage. Reranking should lift precision — prove it:
| Config | Recall@5 | MRR |
|---|---|---|
| BM25 only | _run `make eval`_ | _._ |
| + dense (RRF fusion) | _._ | _._ |
| + cross-encoder rerank | _._ | _._ |
> Fill this table with your real numbers and screenshot it in your write-up. Numbers win interviews.
## Security / red-teaming
`make redteam` runs a battery of prompt-injection payloads (`redteam/injection_payloads.jsonl`)
through the input guardrail and reports how many were caught. Extend the payload set and the
detector rules — closing the gap between them is the interesting part.
## Observability
Every tool call is wrapped in an OpenTelemetry span (query, stage latencies, result count).
By default spans print to the console; point `OTEL_EXPORTER_OTLP_ENDPOINT` at a collector
(Jaeger, Grafana Tempo, Langfuse) to visualize traces.
## Repository layout
```
hybrid-rag-mcp/
├── src/hybrid_rag_mcp/
│ ├── server.py # FastMCP entrypoint, exposes search_docs
│ ├── pipeline.py # composes guardrail -> retrieve -> fuse -> rerank
│ ├── retrieval/ # bm25 / vector / hybrid (RRF) / rerank
│ ├── security/ # prompt-injection guardrail
│ ├── observability/ # OpenTelemetry tracing helpers
│ └── corpus/ # sample corpus (jsonl)
├── evals/ # recall@k + MRR harness, QA dataset, promptfoo config
├── redteam/ # injection payloads + runner
└── tests/ # pytest
```
## Roadmap — make it yours
These are deliberately left for you to implement and defend in interviews:
- [ ] Replace the sample corpus with a real one (your notes, a docs site, arXiv abstracts).
- [ ] Add a second MCP tool (e.g., `fetch_document(id)` or `summarize(query)`).
- [ ] Swap the embedding model and benchmark quality vs. latency.
- [ ] Add caching for embeddings and rerank scores; measure cost/latency savings.
- [ ] Wire traces into Jaeger or Langfuse and add a screenshot to the README.
- [ ] Expand the red-team set and report your catch rate over time.
- [ ] Migrate to the MCP SDK 2.0 `MCPServer` API once it stabilizes (currently pinned to
the stable 1.x `FastMCP` line for maximum tutorial/Claude Desktop compatibility).
## License
MIT — see [LICENSE](LICENSE).
TDQS
A4.1/5.0
Scored across 1 tool
Disambiguation5/5
With only one tool, there is no possibility of confusion or overlap between tools. The single tool's purpose is clear and distinct by definition.
Naming Consistency5/5
The single tool name 'search_docs' follows a consistent verb_noun convention. Since there is only one tool, no conflicting patterns exist.
Tool Count3/5
One tool is on the thin edge of what is acceptable. For a narrow search-only server this could be sufficient, but for a 'hybrid-rag' service a single tool feels minimal.
Completeness2/5
The server provides only search, with no document ingestion, update, deletion, or management capabilities. In a typical RAG workflow, agents would need upload or indexing tools to make the search useful, so the surface has significant gaps.
Maintenance
ActivitySlowing
ResponsivenessNo issues