MCP Local Hybrid search License
A coding agent should not have to choose between opening files one at a time and dumping an entire repository into context.
Install • Evals • Architecture • How it works • Deep-Dive Post
Repository Layout
Benchmark & Retrieval Evaluations
Measured on 28 October 2026: 153 questions with known answers over five collections (a paper, a textbook, a biography, a novel and a mixed Rust, Python and notebook codebase). For each question the top 5 passages were graded exact (a passage states the answer), partial or miss. One grading model and rubric (Opus 5.5 subagents) graded this run and also regraded the passages saved from the August run, so both runs share one judge. The report, per-question grades and run traces are in evals/experiments/report-2026-10-28/.
Collection Questions Exact, Aug 2026 Exact, Oct 2026 Exact or partial, Oct 2026 Attention paper 30 27 28 30 Brain and Behavior 30 25 25 29 Napoleon 30 14 22 29 Fire and Blood 30 16 21 30 Mixed codebase 33 25 31 32 Total 153 107 (69.9%) 127 (83.0%) 150 (98.0%)
Setups. August: Qwen3 Embedding 0.6B (local), a MiniLM-L6 reranker and 1,000-character chunks. October: BM25 plus Qwen3 Embedding 8B, rank fusion to 50 candidates, Cohere Rerank 4 Fast to 5, and 700-character chunks.
Not chance. 27 questions are exact only in October and 7 only in August (exact sign test, p = 0.0008).
Replaced. The September table of "203 questions" is gone: its rows added up to 223, and its passages were not saved, so it cannot be regraded.
Structured PDF parsing
Since 28 October, PDFs are read as paragraphs and chapter headings (running headers, page numbers and font ligature glyphs removed), and book-length text is cut in 1,600-character windows. On the two books (60 questions, graded blind) exact answers rose from 39 to 48: 10 questions gained, 1 lost (p = 0.0117). Evidence: evals/experiments/structured-chunking-2026-10-28/.
Concurrent queries
A search that arrives while another runs now waits up to 10 minutes instead of failing. With 20 searches started at once, 20 are answered (before: 1). They run one after another, about 4 seconds each. Evidence: evals/experiments/concurrency-2026-10-28/.
Multi-Domain Scaling Experiment (August 2026)
Graded by Gemini 3.7 Flash with a hit-at-5 measure. The October regrade of the same saved passages is in the table above.
Detailed evaluation log: evals/experiments/experiment-unified-scaling/experiment.md and Napoleon Experiment Log.
Evaluated across 5 merged heterogeneous domains (6,314 chunks in a single index) comparing isolated baselines against unified scaling on NVIDIA GPU with qwen3-embedding:0.6b + cross-encoder/ms-marco-MiniLM-L-6-v2 reranker:
Corpus / Domain Queries Isolated Baseline Hit@5 Unified Scaled Hit@5 Isolated Baseline MRR Unified Scaled MRR Domain Purity in Unified Attention Paper (Scientific / AI) 30 90.0% (27/30) 90.0% (27/30) 0.803 0.803 96.7% Brain & Behavior (Neuroscience) 30 93.3% (28/30) 93.3% (28/30) 0.831 0.831 94.7% Napoleon Biography (History / Phil) 30 66.7% (20/30) 66.7% (20/30) 0.558 0.548 100.0% Fire & Blood (Narrative Fiction) 30 46.7% (14/30) 46.7% (14/30) 0.372 0.372 100.0% Mixed Codebase (Py/Rust/IPYNB) 33 87.9% (29/33) 87.9% (29/33) 0.812 0.812 100.0% Overall Experiment 1 Total 153 77.1% (118/153) 77.1% (118/153) 0.675 0.673 98.4%
Key finding: Merging 5 domains into one 6,314-chunk database produces 0.0% retrieval degradation with 98.4% domain isolation purity and 368ms average latency.
[!TIP] Read the complete architectural walkthrough and benchmark breakdown on the official blog post.
Quick start
Prerequisites: Python 3.10+ and uv.
That single command installs dependencies and runs the test suite.
Manual equivalent:
Then register the server with an MCP client.
Claude Code
Elpis
Option A: Global Config (/.elpis/config.toml)
Option B: Standard MCP JSON (.mcp.json or /.elpis/mcp.json)
Codex
Add to /.codex/config.toml:
Expected result: the client discovers queryknowledgebase, and a query returns ranked passages with source paths from the requested scope.
What is rag-mcp
rag-mcp is a local hybrid-search MCP server: point it at a file or folder, ask a question in plain language, and get back the passages that actually answer it — with the exact file and location, not a guess.
Coding agents normally search a codebase by opening files one at a time or dumping an entire repository into the conversation. Both waste time and context. rag-mcp replaces that with one tool call: search by meaning, get ranked results with sources, keep going. Embeddings, vector search, and reranking all run locally; the server exposes one read-only MCP tool to compatible clients.
No third-party logo or benchmark — the image above is the actual retrieval-accuracy evidence this repo ships, not decoration.
Retrieval accuracy
The current measurement is under Benchmark & Retrieval Evaluations. Per-question results for every recorded run are under evals/.
The problem
Coding agents commonly retrieve context by either opening files one by one or loading a large portion of the repository. The first can miss relevant files; the second consumes context with material the current task may not need.
rag-mcp moves retrieval into one local tool call so the agent can search by meaning without making the entire tree part of every prompt.
Architecture
The retrieval pipeline operates across two decoupled stages:
Scope Ingestion & Storage: Target files matching the allowlist (.rs, .py, .ts, .md, .ipynb, .pdf) are processed through structure-aware AST chunking (chunkdocument), preserving markdown heading hierarchies, code symbol scopes, and notebook cells. Chunks are simultaneously indexed into LanceDB's dense vector table (all-MiniLM-L6-v2) and embedded Tantivy inverted full-text search (FTS) index.
Dual Retrieval & Precision Funnel: Each queryknowledgebase invocation executes parallel dense vector cosine search and lexical Tantivy BM25 queries ($top\k=20$). Candidates are unified via Reciprocal Rank Fusion ($\text{RRF}, k=60$), then passed through a neural Cross-Encoder (cross-encoder/ms-marco-MiniLM-L-6-v2) for full-attention relevance re-scoring before delivering ranked source citations.
How it works
Repository structure:
Technical boundaries:
one MCP tool: queryknowledgebase(query, docpath?);
default embeddings: all-MiniLM-L6-v2 (80MB, fast — overridable, see Configuration);
reranking runs by default: cross-encoder/ms-marco-MiniLM-L-6-v2 (80MB, fast — overridable, or disable it entirely);
local embedded/on-disk Qdrant;
docpath can scope each call to a file or directory;
per-path indexes are persisted under rag/ragdbv2/;
common large/build directories such as .git, nodemodules, .venv, dist, build, and target are rejected;
configurable depth/token limits fail explicitly instead of scanning an unbounded tree.
Configuration
Every retrieval knob is an environment variable, not a source edit. Unset means the default shown:
Variable Default What it controls RAGMCPEMBEDPROVIDER sentencetransformer sentencetransformer (local), ollama (local server), or openaicompatible (remote — see below) RAGMCPEMBEDMODEL all-MiniLM-L6-v2 embedding model name, meaning depends on provider RAGMCPEMBEDAPIKEY unset API key, only used by openaicompatible RAGMCPEMBEDBASEURL unset (official OpenAI endpoint) override endpoint, only used by openaicompatible RAGMCPRERANKERTYPE cross-encoder cross-encoder or disabled RAGMCPRERANKERMODEL cross-encoder/ms-marco-MiniLM-L-6-v2 reranker model, used when type is cross-encoder RAGMCPTOPK 5 candidates pulled from vector search before fusion RAGMCPRERANKTOPK 5 results kept after reranking RAGMCPCHUNKSIZE 700 characters per chunk before overlap RAGMCPCHUNKOVERLAP 100 characters shared between adjacent chunks RAGMCPRRFK 60 Reciprocal Rank Fusion constant RAGMCPMAXDEPTH 20 directory-scan depth limit RAGMCPMAXTOKENS 2000000 directory-scan size limit
The shipped defaults use the lightweight, fast pair (all-MiniLM-L6-v2 + ms-marco-MiniLM-L-6-v2, 80MB each) for instant zero-dependency local operation out of the box.
To run the higher-capacity pipeline evaluated in the benchmark suite above, switch to Ollama Qwen embeddings:
openaicompatible is the one non-local option: it leaves the machine. One client implementation covers real OpenAI, Ollama's own /v1 endpoint, and Qwen/DashScope's and Gemini's OpenAI-compatible modes — install the optional openai package (uv sync --extra openai), then set RAGMCPEMBEDPROVIDER=openaicompatible, RAGMCPEMBEDMODEL to the provider's model name, RAGMCPEMBEDAPIKEY, and RAGMCPEMBEDBASEURL if the provider isn't OpenAI itself. The client construction is verified; a real embedding call against a paid provider is not — test it against your own key before trusting it. A cross-encoder reranker type named llm also exists in the code but its scoring is unimplemented scaffolding (every passage gets the same score) — do not set it, it does nothing useful.
Current state
Implemented and verified
MCP initialize → tools/list → tools/call protocol path.
Read-only queryknowledgebase tool.
Workspace-root and explicit docpath scoping.
Local hybrid retrieval and reranking.
Guardrails for excluded directories and oversized scopes.
Text extraction for PDF and Jupyter notebooks, plus local OCR for common image
formats and structured extraction for DOCX, PPTX, XLSX, and CSV files.
End-to-end registration was exercised through a real MCP client during development.
Implemented but not yet covered by the current tests
The alternative Ollama embedding-provider path in rag/core.py.
The openaicompatible embedding provider: client construction is verified, a real
call against a paid provider is not.
Planned
Nothing is formally tracked yet. Extend it when a concrete retrieval failure or client requirement appears.
Intentionally unsupported
Hosted/remote vector databases.
File types outside the extension allowlist in server.py.
Write/mutation tools; this server is retrieval-only.
What sets this apart
These are design choices, not novelty claims:
Local retrieval: source files, embeddings, vector search, and reranking stay on the machine.
Small transport layer: the MCP host uses direct stdio JSON-RPC rather than depending on an MCP SDK.
Per-call scope: one server can search different files/directories instead of requiring one fixed knowledge base per project.
Evidence in the response: returned chunks include source paths rather than only synthesized prose.
Evals and test series
The test suite lives under tests/:
They cover:
read-only tool annotations;
default workspace scoping;
explicit docpath scoping;
rejection of excluded directories;
rejection of depth-limit violations;
bootstrap prerequisite checks;
extraction from DOCX, PPTX, XLSX, and CSV files;
local OCR extraction from a generated image fixture.
Protocol-level check, without another MCP client:
A successful self-query should return evidence pointing at the RRF implementation in rag/core.py.
What the tests prove: MCP transport/scoping/guardrail behavior covered by those cases.
What they do not prove: retrieval quality across arbitrary corpora, cross-client compatibility, or superiority to grep/code-search/RAG alternatives.
Example
The response is intended for the calling agent: ranked source passages it can use as task context rather than a standalone chat answer.
Future development
Keep the surface small. Add capability only when real usage shows a retrieval, compatibility, or performance gap worth testing.
License
MIT — see LICENSE.