claude-rag
Indexes a folder of local Markdown (.md) files into a SQLite vector store, splitting them on ## headers with configurable chunk size/overlap, and exposes hybrid BM25 + semantic search with reranking so sections can be retrieved by exact identifiers or paraphrased questions.
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@claude-ragfind notes about deploying on AWS"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
claude-rag
Local RAG for Claude Code over a folder of Markdown files.
๐ฌ๐ง English ยท ๐ฎ๐น Italiano
claude-rag turns a folder of .md notes into a knowledge base that Claude Code can search. It runs entirely on your machine: no API keys, no cloud, no data leaving the PC.
Search tool: Claude calls
kb_search("โฆ")and gets back the most relevant sections, each with its source file and a confidence score.Automatic recall (optional): hooks add matching notes to your prompt before Claude even sees it.
Measured, not guessed: a retrieval benchmark (
eval/) backs every default.
๐ Interactive walkthrough: how
kb_searchfinds an answer, step by step โ
Contents
Related MCP server: flightlog
Quick start
1. Clone the repo inside your KB folder. Your .md files live in the parent folder.
git clone https://github.com/sangelastro/claude-rag ~/.claude/my-kb/rag2. Install the dependencies.
cd ~/.claude/my-kb/rag
pip install -r requirements.txt3. Register the server in Claude Code (~/.claude.json, under mcpServers):
"my-kb": {
"command": "python",
"args": ["/absolute/path/to/rag/server.py"]
}If the repo is not inside the KB folder, add "env": {"KB_RAG_DIR": "/absolute/path/to/your/kb"}.
4. Restart Claude Code. On first start the server downloads the embedding model (~120 MB) and indexes every .md file. After that, only changed files are re-indexed.
What Claude can do with it
Tool | What it does |
| Returns the |
| Re-indexes the files changed since the last run ( |
| Files indexed, chunks per file, last update |
| Tokens saved: chunks served vs reading the whole files, split by tool and hook |
How it works
The picture above has three parts.
โ Indexing, at startup and on kb_reindex()
Each
.mdfile is split on its##sections. Long sections become overlapping chunks (400 characters, 80 shared with the next one). YAML frontmatter is dropped; every chunk keeps a header with its file and section.Every chunk is turned into a vector by the embedding model.
Text and vectors go into
kb.db(SQLite). The plain text is also exported tokb_chunks.jsonfor the hooks.Only files whose modification time changed are processed again.
โก Search, when Claude calls kb_search()
BM25 ranks the chunks by exact words: table names, ports, hostnames, error codes.
Semantic search ranks them by meaning (cosine similarity between vectors): paraphrases and natural-language questions.
The two top-50 lists are merged with Reciprocal Rank Fusion.
A local cross-encoder reranker re-reads the best 10 together with the question and puts them in order. Its top score is turned into a calibrated confidence; below 0.5 Claude is told the answer is probably not in the KB.
โข Hooks, optional and automatic: see below.
Choosing the embedding model
MiniLM (default) | EmbeddingGemma 2 (optional) | |
Model |
|
|
Reads per chunk | 128 tokens | 8K tokens, chunk header used as the document title |
Vector size | 384 | 768 (can be cut to 512, 256, 128) |
Right section in top 5 (semantic only) | 0.71 | 0.92 |
Tells "not in the KB" apart | 69% | 93% |
Full reindex (6,900 chunks, 4 CPU threads) | a few minutes | ~1 hour, once |
Per search | a few ms | ~0.5 s with the reranker |
RAM per server | ~0.5 GB | ~2 GB |
Runtime | fastembed (ONNX) | sentence-transformers + torch, in its own virtualenv |
Start with MiniLM. Switch to EmbeddingGemma 2 if your questions are often in natural language, or if your notes are long and full of context.
Switching to EmbeddingGemma 2
python -m venv ~/.venvs/embgemma
~/.venvs/embgemma/Scripts/python -m pip install torch torchvision --index-url https://download.pytorch.org/whl/cpu
~/.venvs/embgemma/Scripts/python -m pip install -r requirements-gemma.txtOn Linux and macOS the interpreter is in bin/ instead of Scripts/. Then point the server at it:
"my-kb": {
"command": "/home/you/.venvs/embgemma/Scripts/python",
"args": ["/absolute/path/to/rag/server.py"],
"env": {"KB_EMBED_MODEL": "embeddinggemma-2"}
}Things to know:
The model is recorded in
kb.db. When it changes, the next start re-embeds the whole KB instead of mixing vectors of different sizes.Every Claude Code session starts its own server. Close or reconnect (
/mcpโ Reconnect) all open sessions after switching; a session still on the old settings would re-index the KB back to the old model.The server keeps only the text part of Gemma in memory (271M of 744M parameters) and runs it in fp32 on CPU: bf16 is ~2.5ร slower without hardware support, fp16 is not supported by the model.
Optional: automatic recall with hooks
Two Claude Code hooks add KB context without an explicit kb_search call. They use keyword matching on kb_chunks.json: ~10 ms, no model loaded.
Hook | Script | What it does |
|
| Adds the KB table of contents at the start of each session |
|
| Adds the best-matching chunks before each prompt, within a size budget |
Copy the sections of hooks/hooks_example.json into ~/.claude/settings.json and replace the paths:
{
"hooks": {
"SessionStart": [{"matcher": "*", "hooks": [
{"type": "command", "command": "python /absolute/path/to/rag/hooks/kb_session_start.py"}]}],
"UserPromptSubmit": [{"matcher": "*", "hooks": [
{"type": "command", "command": "python /absolute/path/to/rag/hooks/kb_recall.py", "timeout": 5}]}]
}
}Configuration
All settings are environment variables in the env block of the server (or of the hooks).
Where the KB lives
Variable | Default | Meaning |
| parent of | Folder with the |
|
| SQLite database |
|
| MCP server name |
Indexing and embedding
Variable | Default | Meaning |
|
| Maximum chunk size; longer sections are split |
|
| Characters shared by consecutive chunks |
|
|
|
|
| EmbeddingGemma 2 only: cut vectors to |
|
| EmbeddingGemma 2 only: CPU threads |
Search
Variable | Default | Meaning |
|
|
|
|
| Reranker: |
|
| Candidates passed to the reranker |
|
| Reranker CPU threads |
|
| Where reranker models are downloaded |
|
| Below this confidence |
Hooks
Variable | Default | Meaning |
|
| Maximum chunks added per prompt |
|
| Minimum score to add anything |
|
| Prompts shorter than this are skipped |
|
| Maximum characters added (~4 characters per token) |
Measuring retrieval quality
eval/eval_retrieval.py checks how often the right section comes back, on a set of real questions labelled with their correct file and section. It compares every stage of the pipeline, only reads kb.db and records nothing.
cp eval/gold_set.example.json eval/gold_set.json # then write questions about your KB
python eval/eval_retrieval.py # add --no-rerank to skip the rerankerAdd some questions the KB cannot answer ("kind": "neg"): the script also reports whether a score threshold tells them apart. --calibrate fits the reranker's confidence on your own data. KB_EVAL_GOLD and KB_RAG_DB point it to another gold set or database.
Results on the author's KB (6,885 chunks, 48 answerable and 7 unanswerable questions, October 2026). The cells show how often the right section is first / in the top 5, then the MRR:
Pipeline | MiniLM | EmbeddingGemma 2 |
semantic only | 0.58 / 0.71 ยท 0.65 | 0.69 / 0.92 ยท 0.80 |
hybrid (BM25 + semantic) | 0.65 / 0.83 ยท 0.74 | 0.73 / 0.90 ยท 0.81 |
hybrid + reranker (default) | 0.77 / 0.92 ยท 0.83 | 0.79 / 0.90 ยท 0.83 |
How each stage got here, release by release, is in the CHANGELOG.
Project layout
rag/
โโโ server.py MCP server: indexing, hybrid search, tools
โโโ reranker.py Local cross-encoder and calibrated confidence
โโโ requirements.txt Default dependencies (fastembed)
โโโ requirements-gemma.txt Extra dependencies for EmbeddingGemma 2
โโโ hooks/
โ โโโ kb_session_start.py SessionStart hook
โ โโโ kb_recall.py UserPromptSubmit hook
โ โโโ hooks_example.json Hook configuration template
โโโ eval/
โ โโโ eval_retrieval.py Retrieval benchmark
โ โโโ gold_set.example.json Gold set format (yours stays local)
โโโ docs/
โโโ architecture.svg The diagram above (architecture.it.svg in Italian)
โโโ how_search_works.html Interactive walkthrough of kb_searchkb.db, kb_chunks.json, eval/gold_set.json and eval/results.json are created locally and excluded from git, because they contain your notes.
architecture.html and kb_rag_slides.html are older presentations written for release 1.3: they do not cover EmbeddingGemma 2.
Credits and license
The RAG system (chunking, embedding, SQLite vector store, MCP server, hybrid search, reranking, evaluation) is original work by Sergio Angelastro, released under the MIT License.
The hook system is inspired by agd-memory by @Pinperepette (MIT): see CREDITS.md.
This server cannot be deployed
Maintenance
Related MCP Connectors
Augments MCP Server - A comprehensive framework documentation provider for Claude Code
Serve a folder of Markdown notes as an MCP server: hybrid search, reading, and sourced answers.
An MCP server that gives your AI access to the source code and docs of all public github repos
Related MCP Servers
- AlicenseNot gradedqualityDmaintenanceA local MCP server that indexes and searches your Claude Code conversation history with both keyword and semantic search, fully private and running locally.MIT
- AlicenseAqualityBmaintenanceAn MCP server that indexes Claude Code conversation history into SQLite, enabling full-text search across past sessions for context recovery and cross-agent observability.103MIT
- AlicenseAqualityCmaintenanceA local MCP server that indexes and searches your past Claude sessions using SQLite FTS5. No cloud, runs entirely on your machine.3MIT
- AlicenseAqualityDmaintenanceMCP server that indexes Claude.ai chats and local Claude Code sessions, enabling semantic and keyword search across all your conversations with Claude.634 PyPI5MIT