Skip to main content
Glama
README.md
# claude-rag โ€” MCP RAG Server for Markdown Knowledge Bases

**Author**: [Sergio Angelastro](https://github.com/sangelastro) โ€” MIT License

MCP server that indexes a folder of `.md` files into a local SQLite vector store and exposes semantic search as Claude Code tools. The core RAG system (chunking, embedding, SQLite vector store, MCP server, cosine similarity search) is original work by the author.

> **Hook system** (`hooks/`) inspired by [agd-memory](https://github.com/Pinperepette/agd-memory) by [@Pinperepette](https://github.com/Pinperepette) (MIT) โ€” see [CREDITS.md](CREDITS.md)

## Architecture

![Architecture Diagram](docs/diagram.png)

> ๐Ÿ“Š **[Interactive diagram โ†’](docs/diagram.html)**

Three components working together:

| | What | When |
|---|---|---|
| **โ‘  Indexing** | `.md` files โ†’ Chunker โ†’ Embedder โ†’ SQLite + JSON | on startup / `kb_reindex()` |
| **โ‘ก MCP** | Claude calls `kb_search()` โ†’ cosine sim on `kb.db` โ†’ top-K chunks | explicit semantic search |
| **โ‘ข Hook** | every prompt intercepted โ†’ keyword score on `kb_chunks.json` โ†’ auto-inject | automatic, ~10ms, no model |

**Stack**: Python ยท sentence-transformers (`paraphrase-multilingual-MiniLM-L12-v2`, ~120MB, CPU-only) ยท SQLite ยท MCP stdio

## Tools exposed

| Tool | Description |
|---|---|
| `kb_search(query, top_k=5)` | Semantic search โ€” returns top-K chunks with source file and section |
| `kb_reindex(force=False)` | Re-indexes files modified since last run (mtime-based) |
| `kb_stats()` | Shows indexed files, chunk counts, last update timestamps |
| `kb_savings()` | Shows cumulative token savings: RAG chunks served vs full-file baseline, broken down by source (`mcp` / `hook`) |

## Setup

### 1. Clone

```bash
# Default layout: repo sits inside the KB folder
# KB files (.md) go in the parent directory
git clone https://github.com/sangelastro/claude-rag ~/.claude/my-kb/rag
```

Or clone anywhere and point to your KB folder via env var (see step 3).

### 2. Install dependencies

```bash
cd ~/.claude/my-kb/rag
pip install -r requirements.txt
```

On first run the model (`paraphrase-multilingual-MiniLM-L12-v2`, ~120MB) is downloaded automatically from HuggingFace. This model supports 50+ languages including Italian natively.

### 3. Register in Claude Code

Add to `~/.claude.json` under `mcpServers`:

```json
"my-kb": {
  "command": "python",
  "args": ["/absolute/path/to/rag/server.py"],
  "env": {
    "KB_RAG_DIR": "/absolute/path/to/your/kb/folder"
  }
}
```

- **`KB_RAG_DIR`** โ€” folder containing your `.md` files (default: `../` relative to `server.py`)
- **`KB_RAG_DB`** โ€” SQLite database path (default: `kb.db` next to `server.py`)
- **`CHUNK_MAX_CHARS`** โ€” max chars per chunk before splitting (default: `400`)
- **`CHUNK_OVERLAP`** โ€” overlap in chars between consecutive sub-chunks (default: `80`)

If the repo is cloned inside the KB folder (as in the example above), both env vars can be omitted.

### 4. Restart Claude Code

The server starts automatically. On first launch it indexes all `.md` files in `KB_RAG_DIR`.

## Optional: Claude Code Hooks

Two hooks auto-inject KB context without explicit `kb_search` calls.
Approach inspired by [agd-memory](https://github.com/Pinperepette/agd-memory) (MIT).

| Hook | Script | What it does |
|---|---|---|
| `SessionStart` | `hooks/kb_session_start.py` | Injects KB table of contents at session start |
| `UserPromptSubmit` | `hooks/kb_recall.py` | Auto-injects top matching chunks before each prompt (keyword scoring, ~10ms, no model load) |

### Setup hooks

Copy the relevant sections from `hooks/hooks_example.json` into your `~/.claude/settings.json`, replacing the placeholder paths:

```json
{
  "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}]
    }]
  }
}
```

`kb_chunks.json` (used by `kb_recall.py`) is auto-generated next to `kb.db` on every reindex. No model loading in hooks โ€” scoring uses token overlap only.

Hook behaviour is tunable via env vars:

| Variable | Default | Description |
|---|---|---|
| `KB_RAG_HOOK_TOP_K` | `3` | Max chunks injected per prompt |
| `KB_RAG_HOOK_MIN_SCORE` | `0.15` | Minimum score to trigger injection |
| `KB_RAG_HOOK_MIN_WORDS` | `4` | Skip prompts shorter than N words |
| `KB_RAG_HOOK_TOKEN_BUDGET` | `6000` | Max chars injected (~4 chars/token) |

## File structure

```
rag/
โ”œโ”€โ”€ server.py           # MCP server
โ”œโ”€โ”€ requirements.txt
โ”œโ”€โ”€ .gitignore
โ”œโ”€โ”€ README.md
โ”œโ”€โ”€ hooks/
โ”‚   โ”œโ”€โ”€ kb_session_start.py   # SessionStart hook
โ”‚   โ”œโ”€โ”€ kb_recall.py          # UserPromptSubmit hook
โ”‚   โ””โ”€โ”€ hooks_example.json    # Hook config template
โ”œโ”€โ”€ architecture.html   # Technical documentation
โ””โ”€โ”€ kb_rag_slides.html  # Architecture slide deck
```

`kb.db` and `kb_chunks.json` are generated locally and excluded from git.

## How it works

1. **Chunking** โ€” each `.md` file is split on `##` headers; frontmatter is stripped; sections longer than `CHUNK_MAX_CHARS` (400) are further split into overlapping sub-chunks with `CHUNK_OVERLAP` (80) chars of context continuity
2. **Embedding** โ€” chunks are encoded with `paraphrase-multilingual-MiniLM-L12-v2` (384 dimensions, 50+ languages)
3. **Storage** โ€” vectors stored as `float32` BLOBs in SQLite + `kb_chunks.json` for hooks
4. **Search** โ€” cosine similarity computed in numpy over all chunks; top-K returned
5. **Hooks** โ€” keyword scoring on `kb_chunks.json` (no model), injected before each prompt
6. **Invalidation** โ€” mtime-based: only modified files are re-indexed on startup
7. **Savings tracking** โ€” every search records chars served vs full-file baseline in `search_stats` table; `kb_savings()` aggregates the cumulative token reduction without re-reading any file

## Environment variables

| Variable | Default | Description |
|---|---|---|
| `KB_RAG_DIR` | `../` (relative to `server.py`) | Folder with `.md` files to index |
| `KB_RAG_DB` | `./kb.db` (next to `server.py`) | SQLite database path |
| `KB_RAG_NAME` | `kb-rag` | MCP server name |
| `CHUNK_MAX_CHARS` | `400` | Max chars per chunk; longer sections are split into overlapping sub-chunks |
| `CHUNK_OVERLAP` | `80` | Overlap chars between adjacent sub-chunks to preserve context continuity |

## Credits

Hook architecture inspired by [agd-memory](https://github.com/Pinperepette/agd-memory) by Pinperepette (MIT License) โ€” in particular the UserPromptSubmit recall pattern and guard rail logic.