knowledge-rag 4.5.1 → 4.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/README.md +1706 -1698
  2. package/package.json +1 -1
package/README.md CHANGED
@@ -1,1698 +1,1706 @@
1
- # Knowledge RAG
2
-
3
- <div align="center">
4
-
5
- [![PyPI](https://img.shields.io/pypi/v/knowledge-rag)](https://pypi.org/project/knowledge-rag/)
6
- [![NPM](https://img.shields.io/npm/v/knowledge-rag)](https://www.npmjs.com/package/knowledge-rag)
7
- [![PyPI Downloads](https://static.pepy.tech/personalized-badge/knowledge-rag?period=total&units=INTERNATIONAL_SYSTEM&left_color=BLACK&right_color=GREEN&left_text=downloads)](https://pepy.tech/projects/knowledge-rag)
8
- ![Python](https://img.shields.io/badge/python-3.11%2B-green.svg)
9
- ![License](https://img.shields.io/badge/license-MIT-yellow.svg)
10
- ![Platform](https://img.shields.io/badge/platform-Windows%20%7C%20Linux%20%7C%20macOS-lightgrey.svg)
11
- ![GPU](https://img.shields.io/badge/GPU-NVIDIA%20CUDA-76B900.svg?logo=nvidia)
12
- [![CI](https://github.com/lyonzin/knowledge-rag/actions/workflows/ci.yml/badge.svg)](https://github.com/lyonzin/knowledge-rag/actions/workflows/ci.yml)
13
- [![CodeQL](https://github.com/lyonzin/knowledge-rag/actions/workflows/security.yml/badge.svg)](https://github.com/lyonzin/knowledge-rag/actions/workflows/security.yml)
14
- [![Quality Gate](https://github.com/lyonzin/knowledge-rag/actions/workflows/quality-gate.yml/badge.svg)](https://github.com/lyonzin/knowledge-rag/actions/workflows/quality-gate.yml)
15
- [![Glama Score](https://glama.ai/mcp/servers/lyonzin/knowledge-rag/badges/score.svg)](https://glama.ai/mcp/servers/lyonzin/knowledge-rag)
16
- [![OpenSSF Best Practices](https://bestpractices.coreinfrastructure.org/projects/XXXX/badge)](https://bestpractices.coreinfrastructure.org/projects/XXXX)
17
-
18
- ### Your docs, your machine, zero cloud. Claude Code searches them natively.
19
-
20
- Drop your PDFs, markdown, code, notebooks — **1800+ files, 39K chunks, indexed in under 3 minutes.**<br/>
21
- Hybrid search (BM25 + semantic vectors + cross-encoder reranking) through 13 MCP tools.<br/>
22
- Everything runs locally via ONNX. No Docker, no Ollama, no API keys, no data leaves your machine.
23
-
24
- ```
25
- pip install knowledge-rag → restart Claude Code → search_knowledge("your query")
26
- ```
27
-
28
- ---
29
-
30
- **13 MCP Tools** | **Hybrid Search + Reranking** | **20 File Formats** | **Optional NVIDIA GPU** | **100% Local**
31
-
32
- [What's New](#whats-new-in-v420) | [Supported Formats](#supported-formats) | [Installation](#installation) | [Configuration](#configuration) | [API Reference](#api-reference) | [Architecture](#architecture)
33
-
34
- </div>
35
-
36
- ---
37
-
38
- ## Star History
39
-
40
- <div align="center">
41
-
42
- <a href="https://www.star-history.com/?repos=lyonzin%2Fknowledge-rag&type=date&legend=top-left">
43
- <picture>
44
- <source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/chart?repos=lyonzin/knowledge-rag&type=date&theme=dark&legend=top-left" />
45
- <source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/chart?repos=lyonzin/knowledge-rag&type=date&legend=top-left" />
46
- <img alt="Star History Chart" src="https://api.star-history.com/chart?repos=lyonzin/knowledge-rag&type=date&legend=top-left" />
47
- </picture>
48
- </a>
49
-
50
- </div>
51
-
52
- ---
53
-
54
- ## What's New in v4.2.0
55
-
56
- ### Search Performance & Output Quality (v4.2.0)
57
-
58
- **128× faster BM25 search** — replaced `rank-bm25` full-corpus scan with a custom **inverted-index** implementation. Only documents containing query terms are scored, using `numpy.argpartition` for O(n) top-k selection. Adjacent chunk fetching now uses a single batched ChromaDB call instead of N round-trips, and an O(1) reverse lookup (`_source_to_docid`) eliminates linear scans.
59
-
60
- **Smarter output** — two new parameters on `search_knowledge`:
61
- - **`snippet_mode`** (default: `true`) — truncates content to ~500 characters at natural break points, reducing token consumption by ~72%. Adds `content_length` field with original size; use `get_document()` for full content.
62
- - **`min_score`** — filters results below a normalized relevance threshold (0.0–1.0). Eliminates low-quality noise from results. Response includes `filtered_by_score` count for transparency.
63
-
64
- Both parameters are fully backwards-compatible (existing callers see no change in behavior).
65
-
66
- ### Enterprise Concurrent Access — SSE/HTTP Transport (v4.0.0)
67
-
68
- The server now supports **SSE** and **streamable-http** transport modes. Instead of spawning a separate process per client (stdio), a single server process serves all clients with shared resources — 1 embedding model, 1 ChromaDB, 1 query cache.
69
-
70
- ```yaml
71
- # config.yaml
72
- server:
73
- transport: "sse" # "stdio" | "sse" | "streamable-http"
74
- host: "127.0.0.1"
75
- port: 8179
76
- ```
77
-
78
- Or via CLI: `knowledge-rag --transport sse`
79
-
80
- **Optional enterprise features** (all disabled by default):
81
- - **Rate limiting**: Sliding-window counter, configurable RPM and burst
82
- - **Prometheus metrics**: `/metrics` endpoint on separate port
83
- - **Bearer auth**: Token validation for SSE/HTTP connections
84
-
85
- All 13 MCP tools are instrumented with `@rate_limited` and `@instrument` decorators — zero overhead when features are disabled. Default transport remains **stdio** for full backwards compatibility.
86
-
87
- > **Migration**: Existing users need zero changes. SSE mode is opt-in via `server.transport: "sse"` in config.yaml. See [Configuration](#configuration) for details.
88
-
89
- ### Quality Gate — 7-Pillar PR Validation
90
-
91
- Every PR (including dependabot bumps and one-line fixes) is now evaluated against **35+ automated checks** spread across 7 pillars before any human review:
92
-
93
- | Pillar | What it enforces | Tools |
94
- |---|---|---|
95
- | **1 Security** | SAST, secrets, CVEs, supply chain | bandit, semgrep, gitleaks, pip-audit, dependency-review, Snyk, CodeQL, Socket |
96
- | **2 Stability** | Flake detection, coverage trend, test count, deterministic runs | pytest-rerunfailures, codecov ±0.5pp, test-count guard |
97
- | **3 Memory Leak** | RSS bounded under 1000-query load, no idle bloat | psutil-based baseline tests + nightly 50K-iteration soak |
98
- | **4 Versatility** | 9 OS×Python combos, 14 format parsers, 4 config presets, locale tolerance, property-based fuzzing | matrix CI on Linux+Windows+macOS × 3.11+3.12+3.13, Hypothesis |
99
- | **5 Scalability** | Performance regression > 10% blocks merge, public bench dashboard | pytest-benchmark, GH Pages chart |
100
- | **6 Versioning** | Atomic version sync, API surface diff, conventional commits, CHANGELOG enforcement, backwards compat | griffe-style AST diff, custom guards |
101
- | **7 Quality** | Type strictness, docstring coverage, complexity, dead code | mypy strict, interrogate ≥80%, radon, vulture |
102
-
103
- Plus a **nightly resilience workflow** that runs chaos failure-injection (HF down, ChromaDB corruption, watchdog crash, ONNX zero-byte replay), determinism check (full suite × 3), and mutation testing on selected modules.
104
-
105
- Read the full philosophy in [CONTRIBUTING.md](CONTRIBUTING.md). Report bugs via [SECURITY.md](SECURITY.md) or the [issue templates](.github/ISSUE_TEMPLATE/).
106
-
107
- ### Critical Hotfix — No More Silent Zero-Vector Corruption (v3.8.1)
108
-
109
- `FastEmbedEmbeddings.__call__` no longer swallows exceptions and returns `[[0.0]*dim, ...]` when the ONNX model fails to load. That bug pre-existed in master but was silent: ChromaDB happily stored zero embeddings, `count()` reported normal numbers, smart-reindex skipped them as "already indexed", and queries returned garbage similarity with no error visible. Now raises `EmbeddingModelLoadError` / `EmbeddingError` loudly. **All v3.8.0 users should upgrade.** Full details in [Changelog](#v381-2026-05-10--hotfix).
110
-
111
- ### Lazy-Loaded Embeddings — Cheaper Idle Processes (v3.8.0)
112
-
113
- The FastEmbed ONNX model (~200MB resident) now loads on the **first query**, not at startup. Idle `knowledge-rag` processes are now genuinely cheap. Why this matters: MCP stdio is one-process-per-client by protocol — multiple Claude Code windows, Claude Desktop + IDE simultaneously, or review/approval flows that open extra connections all spawn their own processes. Before v3.8.0, every one of them paid the full embedding-model cost up front. Now only processes that actually serve queries load the model. Public API is unchanged.
114
-
115
- ### Opt-In Single-Instance Guard (v3.8.0)
116
-
117
- For users who measured their setup and want a hard cap of one server per `data_dir`:
118
-
119
- ```bash
120
- export KNOWLEDGE_RAG_SINGLE_INSTANCE=1
121
- ```
122
-
123
- A second instance exits immediately with code 75. **OFF by default** so multi-client MCP usage continues to work unchanged. Stale-PID recovery + SIGINT/SIGTERM cleanup wired correctly. Full guide in [docs/single-instance.md](docs/single-instance.md). Sample MCP config in [examples/mcp-config-single-instance.json](examples/mcp-config-single-instance.json).
124
-
125
- ### 5 Ways to Install
126
-
127
- ```bash
128
- npx -y knowledge-rag # NPM — zero setup, auto-manages Python venv
129
- pip install knowledge-rag # PyPI — classic Python install
130
- curl -fsSL .../install.sh | bash # One-line installer (Linux/macOS/Windows)
131
- docker pull ghcr.io/lyonzin/knowledge-rag # Docker — models pre-downloaded
132
- git clone ... && pip install -r ... # From source
133
- ```
134
-
135
- All methods produce the same MCP server. See [Installation](#installation) for full instructions.
136
-
137
- ### Recent Highlights
138
-
139
- - **v4.0.0** — **Enterprise concurrent access**: SSE/HTTP transport (1 server → N clients), thread-safe shared state, optional rate limiting + Prometheus metrics, ChromaDB WAL mode, `--transport` CLI
140
- - **v3.9.0** — **Quality Gate** activated: 35+ automated PR checks across 7 pillars (Security, Stability, Memory Leak, Versatility, Scalability, Versioning, Quality) + nightly resilience suite (chaos, soak, determinism, mutation)
141
- - **v3.8.1** — Critical hotfix: loud-fail embeddings (no more silent zero-vector corruption); Windows CI flake erradicated (HF_HUB_OFFLINE + shell:bash + atexit wrapper)
142
- - **v3.8.0** — Lazy-load embeddings, opt-in single-instance guard, version sync across PyPI/NPM/Docker
143
- - **v3.6.0** — Multi-language code parsing (C/C++/JS/TS/XML), NPM wrapper, Docker image, automated release pipeline
144
- - **v3.5.2** — CUDA DLL auto-discovery from pip packages, graceful GPU→CPU fallback, explicit CPU provider (no CUDA noise when `gpu: false`), BASE_DIR resolution fix for editable installs
145
- - **v3.5.1** — Remove Python `<3.13` upper bound — 3.13 and 3.14 now supported
146
- - **v3.5.0** — Optional GPU acceleration, supported formats table, full README rewrite
147
- - **v3.4.3** — MCP stdout save/restore fix (v3.4.2 broke JSON-RPC responses)
148
- - **v3.4.0** — Persistent model cache, exclude patterns, Jupyter Notebook parser, inotify resilience, MetaTrader support
149
-
150
- See [Changelog](#changelog) for full history.
151
-
152
- ---
153
-
154
- ## Supported Formats
155
-
156
- | Format | Extension | Parser | Default | Notes |
157
- |--------|-----------|--------|---------|-------|
158
- | Markdown | `.md` | Section-aware (splits at `##`) | Yes | Headers preserved as chunk boundaries |
159
- | Plain Text | `.txt` | Fixed-size chunking | Yes | 1000 chars + 200 overlap |
160
- | PDF | `.pdf` | PyMuPDF extraction | Yes | Text-based PDFs only (no OCR) |
161
- | Python | `.py` | Code-aware parser | Yes | Functions/classes as chunks |
162
- | JSON | `.json` | Structure-aware | Yes | Flattened key-value extraction |
163
- | CSV | `.csv` | Row-based parser | Yes | Headers + rows as text |
164
- | Word | `.docx` | python-docx | Yes | Headings preserved as markdown |
165
- | Excel | `.xlsx` | openpyxl | Yes | Sheet-by-sheet extraction |
166
- | PowerPoint | `.pptx` | python-pptx | Yes | Slide-by-slide extraction |
167
- | Jupyter Notebook | `.ipynb` | Cell-aware parser | Yes | Markdown + code cells only, no outputs/base64 |
168
- | C Source | `.c` | Code-aware parser | Yes | Functions/structs/includes extracted |
169
- | C/C++ Header | `.h` | Code-aware parser | Yes | Function declarations/structs extracted |
170
- | C++ Source | `.cpp` | Code-aware parser | Yes | Classes/structs/includes extracted |
171
- | JavaScript | `.js` | Code-aware parser | Yes | Functions/classes/imports (ESM + CJS) |
172
- | React JSX | `.jsx` | Code-aware parser | Yes | Same as JS parser |
173
- | TypeScript | `.ts` | Code-aware parser | Yes | Functions/classes/interfaces/enums/imports |
174
- | React TSX | `.tsx` | Code-aware parser | Yes | Same as TS parser |
175
- | XML | `.xml` | XML parser | Yes | Root element and namespace extraction |
176
- | MQL4 Header | `.mqh` | Code parser | No | MetaTrader — add to `supported_formats` to enable |
177
- | MQL4 Source | `.mq4` | Code parser | No | MetaTrader — add to `supported_formats` to enable |
178
-
179
- > **Tip:** The parser dispatch is extensible. Any format mapped in `_parsers` can be enabled via `supported_formats` in config.yaml.
180
-
181
- ---
182
-
183
- ## Features
184
-
185
- | Feature | Description |
186
- |---------|-------------|
187
- | **Hybrid Search** | Semantic + BM25 keyword search with Reciprocal Rank Fusion |
188
- | **Cross-Encoder Reranker** | Xenova/ms-marco-MiniLM-L-6-v2 re-scores top candidates for precision |
189
- | **GPU Acceleration** | Optional ONNX CUDA support for 5-10x faster indexing |
190
- | **YAML Configuration** | Fully customizable via `config.yaml` with domain-specific presets |
191
- | **Query Expansion** | Configurable synonym mappings (69 security-term defaults) |
192
- | **Markdown-Aware Chunking** | `.md` files split by `##`/`###` sections instead of fixed windows |
193
- | **In-Process Embeddings** | FastEmbed ONNX Runtime (BAAI/bge-small-en-v1.5, 384D) |
194
- | **Keyword Routing** | Word-boundary aware routing for domain-specific queries |
195
- | **20 Format Parsers** | MD, TXT, PDF, PY, C, H, CPP, JS, JSX, TS, TSX, JSON, XML, CSV, DOCX, XLSX, PPTX, IPYNB + opt-in MQH/MQ4 |
196
- | **Category Organization** | Organize docs by folder, auto-tagged by path |
197
- | **Incremental Indexing** | Change detection via mtime/size — only re-indexes modified files |
198
- | **Chunk Deduplication** | SHA256 content hashing prevents duplicate chunks |
199
- | **Query Cache** | LRU cache with 5-min TTL for instant repeat queries |
200
- | **Document CRUD** | Add, update, remove documents via MCP tools |
201
- | **URL Ingestion** | Fetch URLs, strip HTML, convert to markdown, index |
202
- | **Similarity Search** | Find documents similar to a reference document |
203
- | **Retrieval Evaluation** | Built-in MRR@5 and Recall@5 metrics |
204
- | **File Watcher** | Auto-reindex on document changes via watchdog (5s debounce) |
205
- | **Exclude Patterns** | Glob-based file/directory exclusion during indexing |
206
- | **MMR Diversification** | Maximal Marginal Relevance reduces redundant results |
207
- | **Persistent Model Cache** | Embedding models cached in `models_cache/` — survives reboots |
208
- | **Auto-Migration** | Detects embedding dimension mismatch and rebuilds automatically |
209
- | **13 MCP Tools** | Full CRUD + search + evaluation via Claude Code |
210
-
211
- ---
212
-
213
- ## Architecture
214
-
215
- ### System Overview
216
-
217
- ```mermaid
218
- flowchart TB
219
- subgraph MCP["MCP SERVER (FastMCP)"]
220
- direction TB
221
- TOOLS["13 MCP Tools<br/>search | get | add | update | remove<br/>reindex | reindex_status | list | stats | url | similar | evaluate"]
222
- end
223
-
224
- subgraph SEARCH["HYBRID SEARCH ENGINE"]
225
- direction LR
226
- ROUTER["Keyword Router<br/>(word boundaries)"]
227
- SEMANTIC["Semantic Search<br/>(ChromaDB)"]
228
- BM25["BM25 Keyword<br/>(inverted-index + expansion)"]
229
- RRF["Reciprocal Rank<br/>Fusion (RRF)"]
230
- RERANK["Cross-Encoder<br/>Reranker"]
231
-
232
- ROUTER --> SEMANTIC
233
- ROUTER --> BM25
234
- SEMANTIC --> RRF
235
- BM25 --> RRF
236
- RRF --> RERANK
237
- end
238
-
239
- subgraph STORAGE["STORAGE LAYER"]
240
- direction LR
241
- CHROMA[("ChromaDB<br/>Vector Database")]
242
- COLLECTIONS["Collections<br/>security | ctf<br/>logscale | development"]
243
- CHROMA --- COLLECTIONS
244
- end
245
-
246
- subgraph EMBED["EMBEDDINGS (In-Process)"]
247
- FASTEMBED["FastEmbed ONNX<br/>BAAI/bge-small-en-v1.5<br/>(384D, CPU or GPU)"]
248
- CROSSENC["Cross-Encoder<br/>ms-marco-MiniLM-L-6-v2"]
249
- FASTEMBED --- CROSSENC
250
- end
251
-
252
- subgraph INGEST["DOCUMENT INGESTION"]
253
- PARSERS["20 Parsers<br/>MD | PDF | TXT | PY | C | H | CPP | JS | JSX | TS | TSX | JSON | XML | CSV<br/>DOCX | XLSX | PPTX | IPYNB | MQH | MQ4"]
254
- CHUNKER["Chunking<br/>MD: section-aware<br/>Other: 1000 chars + 200 overlap"]
255
- PARSERS --> CHUNKER
256
- end
257
-
258
- CLAUDE["Claude Code"] --> MCP
259
- MCP --> SEARCH
260
- SEARCH --> STORAGE
261
- STORAGE --> EMBED
262
- INGEST --> EMBED
263
- EMBED --> STORAGE
264
- ```
265
-
266
- ### Query Processing Flow
267
-
268
- ```mermaid
269
- flowchart TB
270
- QUERY["User Query<br/>'mimikatz credential dump'"] --> EXPAND
271
-
272
- subgraph EXPANSION["Query Expansion"]
273
- EXPAND["Synonym Expansion<br/>mimikatz -> mimikatz, sekurlsa, logonpasswords"]
274
- end
275
-
276
- EXPAND --> ROUTER
277
-
278
- subgraph ROUTING["Keyword Routing"]
279
- ROUTER["Keyword Router"]
280
- MATCH{"Word Boundary<br/>Match?"}
281
- CATEGORY["Filter: redteam"]
282
- NOFILTER["No Filter"]
283
-
284
- ROUTER --> MATCH
285
- MATCH -->|Yes| CATEGORY
286
- MATCH -->|No| NOFILTER
287
- end
288
-
289
- subgraph HYBRID["Hybrid Search"]
290
- direction LR
291
- SEMANTIC["Semantic Search<br/>(ChromaDB embeddings)<br/>Conceptual similarity"]
292
- BM25["BM25 Inverted-Index<br/>(posting lists + numpy top-k)<br/>Exact term matching"]
293
- end
294
-
295
- subgraph FUSION["Result Fusion + Reranking"]
296
- RRF["Reciprocal Rank Fusion<br/>score = alpha * 1/(k+rank_sem)<br/>+ (1-alpha) * 1/(k+rank_bm25)"]
297
- RERANK["Cross-Encoder Reranker<br/>Re-scores top 3x candidates<br/>query+doc pair scoring"]
298
- SORT["Sort by Reranker Score<br/>Normalize to 0-1"]
299
- ADJ["Adjacent Chunk Expansion<br/>(batch fetch ±1 chunk)"]
300
-
301
- RRF --> RERANK --> SORT --> ADJ
302
- end
303
-
304
- subgraph OUTPUT["Output Processing"]
305
- MINSCORE["min_score Filter<br/>(discard below threshold)"]
306
- SNIPPET["snippet_mode Truncation<br/>(~500 chars at natural break)"]
307
-
308
- MINSCORE --> SNIPPET
309
- end
310
-
311
- CATEGORY --> HYBRID
312
- NOFILTER --> HYBRID
313
- SEMANTIC --> RRF
314
- BM25 --> RRF
315
-
316
- ADJ --> MINSCORE
317
- SNIPPET --> RESULTS["Results<br/>search_method: hybrid|semantic|keyword<br/>score + filtered_by_score + content_length"]
318
- ```
319
-
320
- ### Document Ingestion Flow
321
-
322
- ```mermaid
323
- flowchart LR
324
- subgraph INPUT["Input"]
325
- FILES["documents/<br/>├── security/<br/>├── development/<br/>├── ctf/<br/>└── general/"]
326
- end
327
-
328
- subgraph PARSE["Parse (20 formats)"]
329
- MD["Markdown"]
330
- PDF["PDF<br/>(PyMuPDF)"]
331
- OFFICE["DOCX | XLSX<br/>PPTX | CSV"]
332
- CODE["PY | C | H | CPP | JS | JSX<br/>TS | TSX | JSON | XML | IPYNB"]
333
- end
334
-
335
- subgraph CHUNK["Chunk"]
336
- MDSPLIT["MD: Section-Aware<br/>Split at ## headers"]
337
- TXTSPLIT["Other: Fixed-Size<br/>1000 chars + 200 overlap"]
338
- DEDUP["SHA256 Dedup<br/>Skip duplicate content"]
339
- end
340
-
341
- subgraph EMBED["Embed"]
342
- FASTEMBED["FastEmbed ONNX<br/>bge-small-en-v1.5<br/>(384D, CPU or GPU)"]
343
- end
344
-
345
- subgraph STORE["Store"]
346
- CHROMADB[("ChromaDB")]
347
- BM25IDX["BM25 Index"]
348
- end
349
-
350
- FILES --> MD & PDF & OFFICE & CODE
351
- MD --> MDSPLIT
352
- PDF & OFFICE & CODE --> TXTSPLIT
353
- MDSPLIT --> DEDUP
354
- TXTSPLIT --> DEDUP
355
- DEDUP --> EMBED
356
- EMBED --> STORE
357
- ```
358
-
359
- ### hybrid_alpha Parameter Effect
360
-
361
- ```mermaid
362
- flowchart LR
363
- subgraph ALPHA["hybrid_alpha values"]
364
- A0["0.0<br/>Pure BM25<br/>Instant"]
365
- A3["0.3 (default)<br/>Keyword-heavy<br/>Fast"]
366
- A5["0.5<br/>Balanced"]
367
- A7["0.7<br/>Semantic-heavy"]
368
- A10["1.0<br/>Pure Semantic"]
369
- end
370
-
371
- subgraph USE["Best For"]
372
- U0["CVEs, tool names<br/>exact matches"]
373
- U3["Technical queries<br/>specific terms"]
374
- U5["General queries"]
375
- U7["Conceptual queries<br/>related topics"]
376
- U10["'How to...' questions<br/>conceptual search"]
377
- end
378
-
379
- A0 --- U0
380
- A3 --- U3
381
- A5 --- U5
382
- A7 --- U7
383
- A10 --- U10
384
- ```
385
-
386
- ---
387
-
388
- ## Installation
389
-
390
- ### Prerequisites
391
-
392
- - Python 3.11+
393
- - Claude Code CLI
394
- - *…or any other MCP client (Claude Desktop, Cursor, VS Code, Antigravity, opencode, Windsurf) — see [Use with other MCP clients](#use-with-other-mcp-clients)*
395
- - ~200MB disk for model cache (auto-downloaded on first run)
396
- - *Optional:* NVIDIA GPU + CUDA 12 for accelerated embeddings (see [GPU Acceleration](#gpu-acceleration) below)
397
-
398
- ### GPU Acceleration
399
-
400
- GPU mode accelerates embedding generation during indexing and search. It requires an NVIDIA GPU with CUDA 12 support. No GPU? No problem — the server runs on CPU by default and GPU is entirely optional.
401
-
402
- **Requirements:**
403
-
404
- | Component | Minimum | How to check / get it |
405
- |-----------|---------|----------------------|
406
- | NVIDIA GPU (Turing+) | RTX 20xx / 30xx / 40xx / 50xx, or Tesla T4+ | `nvidia-smi` |
407
- | NVIDIA Driver | ≥ 525 | `nvidia-smi` — [nvidia.com/drivers](https://www.nvidia.com/drivers) |
408
- | CUDA 12 runtime | Provided by pip packages below | Automatic |
409
-
410
- **Setup (2 steps):**
411
-
412
- ```bash
413
- # 1. Install GPU dependencies (onnxruntime-gpu + all CUDA 12 runtime DLLs)
414
- pip install knowledge-rag[gpu]
415
-
416
- # 2. Enable in config.yaml
417
- # models:
418
- # embedding:
419
- # gpu: true
420
- ```
421
-
422
- The `[gpu]` extra installs `onnxruntime-gpu` plus 7 NVIDIA CUDA 12 packages (`cublas`, `cudnn`, `cuda-runtime`, `cufft`, `cusparse`, `cusolver`, `curand`, `nvjitlink`) so you don't need a full CUDA Toolkit install.
423
-
424
- **Verify GPU is active:**
425
-
426
- On server startup, look for the GPU status banner:
427
- ```
428
- ============================================================
429
- GPU STATUS: ACTIVE
430
- Provider: CUDAExecutionProvider
431
- Device: NVIDIA GeForce RTX 3080 Ti
432
- VRAM: 12.0 GB
433
- ============================================================
434
- ```
435
-
436
- Or programmatically:
437
- ```bash
438
- python -c "import onnxruntime; print(onnxruntime.get_available_providers())"
439
- # Should include: 'CUDAExecutionProvider'
440
- ```
441
-
442
- > **Fallback**: If CUDA is unavailable at runtime (wrong driver, missing DLLs, no GPU), the server falls back to CPU automatically with a `[WARN]` log — it never crashes. The `gpu: true` config is a preference, not a requirement.
443
-
444
- ### Install Methods
445
-
446
- Pick one — all produce the same running server.
447
-
448
- #### Option A: NPX (fastest)
449
-
450
- Requires Node.js 16+. Handles Python venv, pip install, and version upgrades automatically.
451
-
452
- ```bash
453
- claude mcp add knowledge-rag -s user -- npx -y knowledge-rag
454
- ```
455
-
456
- That's it. On first run, `npx` creates a venv at `~/.knowledge-rag/`, installs the PyPI package, and starts the MCP server. Subsequent runs reuse the cached venv.
457
-
458
- #### Option B: One-line installer
459
-
460
- ```bash
461
- # Linux/macOS:
462
- curl -fsSL https://raw.githubusercontent.com/lyonzin/knowledge-rag/master/install.sh | bash
463
-
464
- # Windows (PowerShell):
465
- irm https://raw.githubusercontent.com/lyonzin/knowledge-rag/master/install.ps1 | iex
466
- ```
467
-
468
- Then configure Claude Code:
469
-
470
- ```bash
471
- claude mcp add knowledge-rag -s user -- ~/knowledge-rag/venv/bin/python -m mcp_server.server
472
- ```
473
-
474
- > **Windows**: `claude mcp add knowledge-rag -s user -- %USERPROFILE%\knowledge-rag\venv\Scripts\python.exe -m mcp_server.server`
475
-
476
- #### Option C: pip install
477
-
478
- ```bash
479
- mkdir ~/knowledge-rag && cd ~/knowledge-rag
480
- python3 -m venv venv && source venv/bin/activate
481
- pip install knowledge-rag
482
- knowledge-rag init # Exports config template, presets, creates documents/
483
- ```
484
-
485
- Then configure Claude Code:
486
-
487
- ```bash
488
- claude mcp add knowledge-rag -s user -- ~/knowledge-rag/venv/bin/python -m mcp_server.server
489
- ```
490
-
491
- > **Windows users**: Use `python` instead of `python3`, `venv\Scripts\activate` instead of `source venv/bin/activate`.
492
- > **Windows path**: `claude mcp add knowledge-rag -s user -- %USERPROFILE%\knowledge-rag\venv\Scripts\python.exe -m mcp_server.server`
493
-
494
- #### Option D: Clone from source
495
-
496
- ```bash
497
- git clone https://github.com/lyonzin/knowledge-rag.git ~/knowledge-rag
498
- cd ~/knowledge-rag
499
- python3 -m venv venv && source venv/bin/activate
500
- pip install -r requirements.txt
501
- ```
502
-
503
- Then configure Claude Code:
504
-
505
- ```bash
506
- claude mcp add knowledge-rag -s user -- ~/knowledge-rag/venv/bin/python -m mcp_server.server
507
- ```
508
-
509
- #### Option E: Docker
510
-
511
- ```bash
512
- docker pull ghcr.io/lyonzin/knowledge-rag:latest
513
- ```
514
-
515
- ```bash
516
- claude mcp add knowledge-rag -s user -- \
517
- docker run -i --rm \
518
- -v ~/knowledge-rag/documents:/app/documents \
519
- -v ~/knowledge-rag/data:/app/data \
520
- ghcr.io/lyonzin/knowledge-rag:latest
521
- ```
522
-
523
- Models are pre-downloaded in the image — no first-run delay.
524
-
525
- <details>
526
- <summary>Alternative: manual JSON config</summary>
527
-
528
- Add to `~/.claude.json`:
529
-
530
- **Windows:**
531
- ```json
532
- {
533
- "mcpServers": {
534
- "knowledge-rag": {
535
- "command": "C:\\Users\\YOUR_USER\\knowledge-rag\\venv\\Scripts\\python.exe",
536
- "args": ["-m", "mcp_server.server"]
537
- }
538
- }
539
- }
540
- ```
541
-
542
- **Linux / macOS:**
543
- ```json
544
- {
545
- "mcpServers": {
546
- "knowledge-rag": {
547
- "command": "/home/YOUR_USER/knowledge-rag/venv/bin/python",
548
- "args": ["-m", "mcp_server.server"]
549
- }
550
- }
551
- }
552
- ```
553
- > Replace `YOUR_USER` with your username, or use the full path from `echo $HOME`.
554
- </details>
555
-
556
- #### Option F: SSE Server Mode (multi-agent)
557
-
558
- For multi-agent setups where multiple clients query the same knowledge base simultaneously:
559
-
560
- ```bash
561
- pip install knowledge-rag[server] # Adds uvicorn for SSE/HTTP
562
- knowledge-rag --transport sse # Starts on http://127.0.0.1:8179
563
- ```
564
-
565
- Then configure each MCP client to connect via SSE:
566
-
567
- ```json
568
- {
569
- "mcpServers": {
570
- "knowledge-rag": {
571
- "type": "sse",
572
- "url": "http://127.0.0.1:8179/sse"
573
- }
574
- }
575
- }
576
- ```
577
-
578
- One server process serves all agents — shared embedding model, shared cache, shared ChromaDB. See [Configuration > Server](#server) for rate limiting, metrics, and auth options.
579
-
580
- ### Use with other MCP clients
581
-
582
- `knowledge-rag` supports both **stdio** (default, 1:1) and **SSE** (1:N) transport modes. In stdio mode, it works with any MCP-compatible client, not only Claude Code. The launch command is the same everywhere (the `python -m mcp_server.server` from whichever install method you picked); only the **config file location** and **JSON shape** differ per client.
583
-
584
- #### Clients using the standard `mcpServers` format
585
-
586
- For **Claude Desktop, Cursor, Antigravity, and Windsurf**, use the same block — only the file location changes:
587
-
588
- ```json
589
- {
590
- "mcpServers": {
591
- "knowledge-rag": {
592
- "command": "/home/YOUR_USER/knowledge-rag/venv/bin/python",
593
- "args": ["-m", "mcp_server.server"]
594
- }
595
- }
596
- }
597
- ```
598
-
599
- > **Windows**: set `command` to the full path of `venv\Scripts\python.exe`.
600
-
601
- | Client | Config file | Notes |
602
- |---|---|---|
603
- | **Claude Code** | use `claude mcp add …` (see install methods above) | The CLI writes `~/.claude.json` for you — manual edits to it aren't reliably picked up. |
604
- | **Claude Desktop** | macOS: `~/Library/Application Support/Claude/claude_desktop_config.json` · Windows: `%APPDATA%\Claude\claude_desktop_config.json` | Easiest: **Settings → Developer → Edit Config** opens the correct file (avoids the Windows Store/MSIX path quirk). |
605
- | **Cursor** | `~/.cursor/mcp.json` (global) or `.cursor/mcp.json` (per project) | — |
606
- | **Antigravity** | macOS/Linux: `~/.gemini/antigravity/mcp_config.json` · Windows: `%USERPROFILE%\.gemini\antigravity\mcp_config.json` | Open via Agent panel → **"…" → Manage MCP Servers → View raw config**. |
607
- | **Windsurf** | `~/.codeium/windsurf/mcp_config.json` (global only) | Easiest: Cascade panel → MCP → **View raw config**. |
608
-
609
- #### VS Code — uses a `servers` key
610
-
611
- VS Code (Copilot MCP) nests servers under **`servers`**, not `mcpServers`. Put this in `.vscode/mcp.json` (workspace) or the file opened by the **MCP: Open User Configuration** command:
612
-
613
- ```json
614
- {
615
- "servers": {
616
- "knowledge-rag": {
617
- "type": "stdio",
618
- "command": "/home/YOUR_USER/knowledge-rag/venv/bin/python",
619
- "args": ["-m", "mcp_server.server"]
620
- }
621
- }
622
- }
623
- ```
624
-
625
- #### opencode — uses an `mcp` key
626
-
627
- opencode nests servers under **`mcp`**, takes `command` as a single **array**, and uses `environment` instead of `env`. Put this in `opencode.json` (project root) or `~/.config/opencode/opencode.json` (global):
628
-
629
- ```jsonc
630
- {
631
- "$schema": "https://opencode.ai/config.json",
632
- "mcp": {
633
- "knowledge-rag": {
634
- "type": "local",
635
- "command": ["/home/YOUR_USER/knowledge-rag/venv/bin/python", "-m", "mcp_server.server"],
636
- "enabled": true
637
- }
638
- }
639
- }
640
- ```
641
-
642
- > **Any other MCP client**: point it at the same command + args (`…/venv/bin/python -m mcp_server.server`). If it speaks stdio MCP, knowledge-rag works — only the config file's location and key naming differ. Check your client's docs for the exact path.
643
-
644
- ### Verify
645
-
646
- ```bash
647
- claude mcp list
648
- ```
649
-
650
- On first start, the server will:
651
- 1. Download the embedding model (~50MB, cached in `models_cache/`)
652
- 2. Auto-index any documents in the `documents/` directory
653
- 3. Start watching for file changes (auto-reindex)
654
-
655
- ---
656
-
657
- ## Usage
658
-
659
- ### Adding Documents
660
-
661
- Place your documents in the `documents/` directory, organized by category:
662
-
663
- ```
664
- documents/
665
- ├── security/ # Pentest, exploit, vulnerability docs
666
- ├── development/ # Code, APIs, frameworks
667
- ├── ctf/ # CTF writeups and methodology
668
- ├── logscale/ # LogScale/LQL documentation
669
- └── general/ # Everything else
670
- ```
671
-
672
- Or add documents programmatically via MCP tools:
673
-
674
- ```python
675
- # Add from content
676
- add_document(
677
- content="# My Document\n\nContent here...",
678
- filepath="security/my-technique.md",
679
- category="security"
680
- )
681
-
682
- # Add from URL
683
- add_from_url(
684
- url="https://example.com/article",
685
- category="security",
686
- title="Custom Title"
687
- )
688
- ```
689
-
690
- ### Searching
691
-
692
- Claude uses the RAG system automatically when configured. You can also control search behavior:
693
-
694
- ```python
695
- # Pure keyword search — instant, no embedding needed
696
- search_knowledge("gtfobins suid", hybrid_alpha=0.0)
697
-
698
- # Keyword-heavy (default) — fast, slight semantic boost
699
- search_knowledge("mimikatz", hybrid_alpha=0.3)
700
-
701
- # Balanced hybrid — both engines equally weighted
702
- search_knowledge("SQL injection techniques", hybrid_alpha=0.5)
703
-
704
- # Semantic-heavy — better for conceptual queries
705
- search_knowledge("how to escalate privileges", hybrid_alpha=0.7)
706
-
707
- # Pure semantic — embedding similarity only
708
- search_knowledge("lateral movement strategies", hybrid_alpha=1.0)
709
- ```
710
-
711
- ### Indexing
712
-
713
- Documents are automatically indexed on first startup. All reindex operations run **in background** — they return immediately and you poll progress via `get_reindex_status()`:
714
-
715
- ```python
716
- # Incremental: only re-index changed files (fast)
717
- reindex_documents()
718
-
719
- # Smart reindex: detect changes + rebuild BM25
720
- reindex_documents(force=True)
721
-
722
- # Nuclear rebuild: delete everything, re-embed all (use after model change)
723
- reindex_documents(full_rebuild=True)
724
-
725
- # Poll progress (lightweight, no full stats computation)
726
- get_reindex_status()
727
- # → {"reindex": {"active": true, "percent": 56, "progress": "2090/3734", ...}}
728
- ```
729
-
730
- ### Evaluating Retrieval Quality
731
-
732
- ```python
733
- evaluate_retrieval(test_cases='[
734
- {"query": "sql injection", "expected_filepath": "security/sqli-guide.md"},
735
- {"query": "privilege escalation", "expected_filepath": "security/privesc.md"}
736
- ]')
737
- # Returns: MRR@5, Recall@5, per-query results
738
- ```
739
-
740
- ---
741
-
742
- ## API Reference
743
-
744
- ### Search & Query
745
-
746
- #### `search_knowledge`
747
-
748
- Hybrid search combining semantic search + BM25 keyword search with cross-encoder reranking.
749
-
750
- | Parameter | Type | Default | Description |
751
- |-----------|------|---------|-------------|
752
- | `query` | string | required | Search query text (1-3 keywords recommended) |
753
- | `max_results` | int | 5 | Maximum results to return (1-20) |
754
- | `category` | string | null | Filter by category |
755
- | `hybrid_alpha` | float | 0.3 | Balance: 0.0 = keyword only, 1.0 = semantic only |
756
- | `min_score` | float | 0.0 | Minimum relevance score (0.0-1.0) to include a result. Use 0.2-0.4 to cut noise |
757
- | `snippet_mode` | bool | true | Truncate content to ~500 chars at natural break points. Adds `content_length` field |
758
-
759
- **Returns:**
760
-
761
- ```json
762
- {
763
- "status": "success",
764
- "query": "mimikatz credential dump",
765
- "hybrid_alpha": 0.5,
766
- "result_count": 3,
767
- "filtered_by_score": 2,
768
- "cache_hit_rate": "0.0%",
769
- "results": [
770
- {
771
- "content": "Mimikatz can extract credentials from memory...",
772
- "source": "documents/security/credential-attacks.md",
773
- "filename": "credential-attacks.md",
774
- "category": "security",
775
- "score": 0.9823,
776
- "raw_rrf_score": 0.016393,
777
- "reranker_score": 0.987654,
778
- "semantic_rank": 2,
779
- "bm25_rank": 1,
780
- "search_method": "hybrid",
781
- "keywords": ["mimikatz", "credential", "lsass"],
782
- "routed_by": "redteam"
783
- }
784
- ]
785
- }
786
- ```
787
-
788
- **Search Method Values:**
789
- - `hybrid`: Found by both semantic and BM25 search (highest confidence)
790
- - `semantic`: Found only by semantic search
791
- - `keyword`: Found only by BM25 keyword search
792
-
793
- ---
794
-
795
- #### `get_document`
796
-
797
- Retrieve the full content of a specific document.
798
-
799
- | Parameter | Type | Description |
800
- |-----------|------|-------------|
801
- | `filepath` | string | Path to the document file |
802
-
803
- **Returns:** JSON with document content, metadata, keywords, and chunk count.
804
-
805
- ---
806
-
807
- #### `reindex_documents`
808
-
809
- Index or reindex all documents in the knowledge base. **Runs in background** — returns immediately. Poll progress via `get_reindex_status()`.
810
-
811
- | Parameter | Type | Default | Description |
812
- |-----------|------|---------|-------------|
813
- | `force` | bool | false | Smart reindex: detects changes, rebuilds BM25. Fast. |
814
- | `full_rebuild` | bool | false | Nuclear rebuild: deletes everything, re-embeds all documents. Use after model change. |
815
-
816
- **Returns:** `{"status": "started", "operation": "..."}` immediately. If already running, returns `{"status": "already_running", "progress": "1200/3734"}`.
817
-
818
- ---
819
-
820
- #### `get_reindex_status`
821
-
822
- Get the current status of a background reindex operation. Lightweight — does not compute full index statistics.
823
-
824
- **Returns (active):**
825
- ```json
826
- {
827
- "status": "success",
828
- "reindex": {
829
- "active": true,
830
- "operation": "nuclear_rebuild",
831
- "progress": "1200/3734",
832
- "percent": 32,
833
- "indexed": 1200,
834
- "skipped": 0,
835
- "errors": 0,
836
- "started_at": "2026-06-17T18:29:49"
837
- }
838
- }
839
- ```
840
-
841
- **Returns (idle):** `{"status": "success", "reindex": {"active": false}}`
842
-
843
- ---
844
-
845
- #### `list_categories`
846
-
847
- List all document categories with their document counts.
848
-
849
- **Returns:**
850
-
851
- ```json
852
- {
853
- "status": "success",
854
- "categories": {
855
- "security": 52,
856
- "development": 8,
857
- "ctf": 12,
858
- "general": 3
859
- },
860
- "total_documents": 75
861
- }
862
- ```
863
-
864
- ---
865
-
866
- #### `list_documents`
867
-
868
- List all indexed documents, optionally filtered by category.
869
-
870
- | Parameter | Type | Description |
871
- |-----------|------|-------------|
872
- | `category` | string | Optional category filter |
873
-
874
- **Returns:** JSON array of documents with id, source, category, format, chunks, and keywords.
875
-
876
- ---
877
-
878
- #### `get_index_stats`
879
-
880
- Get statistics about the knowledge base index.
881
-
882
- **Returns:**
883
-
884
- ```json
885
- {
886
- "status": "success",
887
- "stats": {
888
- "total_documents": 75,
889
- "total_chunks": 9256,
890
- "categories": {"security": 52, "development": 8},
891
- "supported_formats": [".md", ".txt", ".pdf", ".py", ".json", ".docx", ".xlsx", ".pptx", ".csv", ".ipynb"],
892
- "embedding_model": "BAAI/bge-small-en-v1.5",
893
- "embedding_dim": 384,
894
- "reranker_model": "Xenova/ms-marco-MiniLM-L-6-v2",
895
- "chunk_size": 1000,
896
- "chunk_overlap": 200,
897
- "query_cache": {
898
- "size": 12,
899
- "max_size": 100,
900
- "ttl_seconds": 300,
901
- "hits": 45,
902
- "misses": 23,
903
- "hit_rate": "66.2%"
904
- }
905
- }
906
- }
907
- ```
908
-
909
- ---
910
-
911
- ### Document Management
912
-
913
- #### `add_document`
914
-
915
- Add a new document to the knowledge base from raw content. Saves the file to the documents directory and indexes it immediately.
916
-
917
- | Parameter | Type | Default | Description |
918
- |-----------|------|---------|-------------|
919
- | `content` | string | required | Full text content of the document |
920
- | `filepath` | string | required | Relative path within documents dir (e.g., `security/new-technique.md`) |
921
- | `category` | string | "general" | Document category |
922
-
923
- ---
924
-
925
- #### `update_document`
926
-
927
- Update an existing document. Removes old chunks from the index and re-indexes with new content.
928
-
929
- | Parameter | Type | Description |
930
- |-----------|------|-------------|
931
- | `filepath` | string | Full path to the document file |
932
- | `content` | string | New content for the document |
933
-
934
- ---
935
-
936
- #### `remove_document`
937
-
938
- Remove a document from the knowledge base index. Optionally deletes the file from disk.
939
-
940
- | Parameter | Type | Default | Description |
941
- |-----------|------|---------|-------------|
942
- | `filepath` | string | required | Path to the document file |
943
- | `delete_file` | bool | false | If true, also delete the file from disk |
944
-
945
- ---
946
-
947
- #### `add_from_url`
948
-
949
- Fetch content from a URL, strip HTML (scripts, styles, nav, footer, header), convert to markdown, and add to the knowledge base.
950
-
951
- | Parameter | Type | Default | Description |
952
- |-----------|------|---------|-------------|
953
- | `url` | string | required | URL to fetch content from |
954
- | `category` | string | "general" | Document category |
955
- | `title` | string | null | Custom title (auto-detected from `<title>` tag if not provided) |
956
-
957
- ---
958
-
959
- #### `search_similar`
960
-
961
- Find documents similar to a given document using embedding similarity.
962
-
963
- | Parameter | Type | Default | Description |
964
- |-----------|------|---------|-------------|
965
- | `filepath` | string | required | Path to the reference document |
966
- | `max_results` | int | 5 | Number of similar documents to return (1-20) |
967
-
968
- ---
969
-
970
- #### `evaluate_retrieval`
971
-
972
- Evaluate retrieval quality with test queries. Useful for tuning `hybrid_alpha`, testing query expansion effectiveness, or validating after reindexing.
973
-
974
- | Parameter | Type | Description |
975
- |-----------|------|-------------|
976
- | `test_cases` | string (JSON) | Array of test cases: `[{"query": "...", "expected_filepath": "..."}, ...]` |
977
-
978
- **Metrics:**
979
- - **MRR@5** (Mean Reciprocal Rank): Average of 1/rank for expected documents. 1.0 = always first result.
980
- - **Recall@5**: Fraction of expected documents found in top 5 results. 1.0 = all found.
981
-
982
- ---
983
-
984
- ## Configuration
985
-
986
- Knowledge RAG is fully configurable via a `config.yaml` file in the project root. If no `config.yaml` exists, sensible defaults are used — the system works out of the box with zero configuration.
987
-
988
- ### Quick Start
989
-
990
- ```bash
991
- # Option 1: Use a preset
992
- cp presets/cybersecurity.yaml config.yaml # Offensive/defensive security, CTFs
993
- cp presets/developer.yaml config.yaml # Software engineering, APIs, DevOps
994
- cp presets/research.yaml config.yaml # Academic research, papers, studies
995
- cp presets/general.yaml config.yaml # Blank slate, pure semantic search
996
-
997
- # Option 2: Start from the documented template
998
- cp config.example.yaml config.yaml
999
- # Edit config.yaml to your needs
1000
- ```
1001
-
1002
- Restart Claude Code after changing `config.yaml`.
1003
-
1004
- ### config.yaml Structure
1005
-
1006
- ```yaml
1007
- # Paths — where your documents live
1008
- paths:
1009
- documents_dir: "./documents" # Scanned recursively
1010
- data_dir: "./data" # Index storage
1011
- models_cache_dir: "./models_cache" # Persistent embedding model cache
1012
-
1013
- # Documents — what gets indexed and how
1014
- documents:
1015
- supported_formats: # File types to index
1016
- - .md
1017
- - .txt
1018
- - .pdf
1019
- - .docx
1020
- - .ipynb
1021
- # - .py # Uncomment to index code
1022
- exclude_patterns: # Glob patterns to skip
1023
- - "node_modules"
1024
- - ".venv"
1025
- - "__pycache__"
1026
- chunking:
1027
- chunk_size: 1000 # Max chars per chunk
1028
- chunk_overlap: 200 # Shared chars between chunks
1029
-
1030
- # Models — AI models for search (all run locally, no API keys)
1031
- models:
1032
- embedding:
1033
- model: "BAAI/bge-small-en-v1.5" # ONNX, ~33MB, auto-downloaded
1034
- dimensions: 384
1035
- gpu: false # Set true + pip install knowledge-rag[gpu]
1036
- reranker:
1037
- enabled: true # Falls back to RRF if model is unavailable
1038
- model: "Xenova/ms-marco-MiniLM-L-6-v2"
1039
- top_k_multiplier: 3 # Candidates fetched before reranking
1040
-
1041
- # Search — result limits and collection name
1042
- search:
1043
- default_results: 5
1044
- max_results: 20
1045
- collection_name: "knowledge_base" # Change for separate knowledge bases
1046
-
1047
- # Categories — auto-tag documents by folder path
1048
- # Set to {} to disable categorization entirely
1049
- category_mappings:
1050
- "security/redteam": "redteam"
1051
- "security/blueteam": "blueteam"
1052
- "notes": "notes"
1053
-
1054
- # Keyword routing — prioritize categories based on query keywords
1055
- # Set to {} for pure semantic search with no routing bias
1056
- keyword_routes:
1057
- redteam:
1058
- - pentest
1059
- - exploit
1060
- - privilege escalation
1061
-
1062
- # Query expansion — expand abbreviations for better BM25 recall
1063
- # Set to {} for no expansion (search terms used as-is)
1064
- query_expansions:
1065
- sqli:
1066
- - sql injection
1067
- - sqli
1068
- privesc:
1069
- - privilege escalation
1070
- - privesc
1071
-
1072
- # Server — enterprise features (new in v4.0.0)
1073
- server:
1074
- transport: "stdio" # "stdio" | "sse" | "streamable-http"
1075
- host: "127.0.0.1" # Bind address (SSE/HTTP only)
1076
- port: 8179 # Bind port (SSE/HTTP only)
1077
- auth:
1078
- bearer_token: "" # Set a secret to enable auth (SSE/HTTP only)
1079
- rate_limit:
1080
- enabled: false
1081
- requests_per_minute: 60
1082
- burst: 10
1083
- metrics:
1084
- enabled: false
1085
- port: 9179 # Separate port for Prometheus scraping
1086
- ```
1087
-
1088
- > See `config.example.yaml` for the fully documented template with explanations for every field.
1089
-
1090
- ### Presets
1091
-
1092
- Pre-built configurations for common use cases:
1093
-
1094
- | Preset | File | Categories | Keywords | Expansions | Best For |
1095
- |--------|------|-----------|----------|-----------|----------|
1096
- | **Cybersecurity** | `presets/cybersecurity.yaml` | 8 | 200+ | 69 | Red/Blue Team, CTFs, threat hunting, exploit dev |
1097
- | **Developer** | `presets/developer.yaml` | 9 | 150+ | 50+ | Full-stack dev, APIs, DevOps, cloud, databases |
1098
- | **Research** | `presets/research.yaml` | 9 | 100+ | 40+ | Academic papers, thesis, lab notebooks, datasets |
1099
- | **General** | `presets/general.yaml` | 0 | 0 | 0 | Blank slate — pure semantic search, no domain logic |
1100
-
1101
- **Creating your own preset**: Copy `config.example.yaml`, fill in your categories/keywords/expansions, save to `presets/your-domain.yaml`.
1102
-
1103
- ### Configuration Reference
1104
-
1105
- #### Server
1106
-
1107
- | Field | Default | Description |
1108
- |-------|---------|-------------|
1109
- | `server.transport` | `"stdio"` | Transport protocol: `"stdio"`, `"sse"`, or `"streamable-http"` |
1110
- | `server.host` | `"127.0.0.1"` | Bind address for SSE/HTTP mode |
1111
- | `server.port` | `8179` | Bind port for SSE/HTTP mode |
1112
- | `server.auth.bearer_token` | `""` (disabled) | Bearer token for SSE/HTTP auth. Empty = no auth |
1113
- | `server.rate_limit.enabled` | `false` | Enable per-client rate limiting |
1114
- | `server.rate_limit.requests_per_minute` | `60` | Max requests per minute |
1115
- | `server.rate_limit.burst` | `10` | Burst allowance above steady rate |
1116
- | `server.metrics.enabled` | `false` | Enable Prometheus `/metrics` endpoint |
1117
- | `server.metrics.port` | `9179` | Port for metrics scraping |
1118
-
1119
- In stdio mode (default), server settings are ignored. SSE/HTTP mode auto-enables the single-instance lock.
1120
-
1121
- #### Paths
1122
-
1123
- | Field | Default | Description |
1124
- |-------|---------|-------------|
1125
- | `paths.documents_dir` | `./documents` | Root folder scanned recursively for documents |
1126
- | `paths.data_dir` | `./data` | Internal storage for ChromaDB and index metadata |
1127
- | `paths.models_cache_dir` | `./models_cache` | Persistent cache for embedding models (~250MB). Survives reboots |
1128
-
1129
- Relative paths resolve from the project root. Absolute paths work too.
1130
-
1131
- #### Documents
1132
-
1133
- | Field | Default | Description |
1134
- |-------|---------|-------------|
1135
- | `documents.supported_formats` | .md .txt .pdf .py .json .docx .xlsx .pptx .csv .ipynb | File extensions to index |
1136
- | `documents.exclude_patterns` | `[]` (empty) | Glob patterns for files/dirs to skip during indexing |
1137
- | `documents.chunking.chunk_size` | 1000 | Max characters per chunk |
1138
- | `documents.chunking.chunk_overlap` | 200 | Characters shared between consecutive chunks |
1139
-
1140
- **Chunking guidelines**: Short notes → 500/100. General use → 1000/200. Long technical docs → 1500/300.
1141
-
1142
- For `.md` files, chunking splits at `##` and `###` header boundaries first. Sections larger than `chunk_size` are sub-chunked with overlap. Non-markdown files use fixed-size chunking.
1143
-
1144
- #### Models
1145
-
1146
- | Field | Default | Description |
1147
- |-------|---------|-------------|
1148
- | `models.embedding.model` | `BAAI/bge-small-en-v1.5` | Embedding model (ONNX, runs locally) |
1149
- | `models.embedding.dimensions` | 384 | Vector dimensions (must match model) |
1150
- | `models.embedding.gpu` | false | Enable CUDA GPU acceleration. See [GPU Acceleration](#gpu-acceleration) for full setup |
1151
- | `models.reranker.enabled` | true | Enable cross-encoder reranking |
1152
- | `models.reranker.model` | `Xenova/ms-marco-MiniLM-L-6-v2` | Reranker model |
1153
- | `models.reranker.top_k_multiplier` | 3 | Fetch N*multiplier candidates for reranking |
1154
-
1155
- If the reranker model is not available locally and the machine cannot download it, search now falls back to the RRF order from hybrid semantic+BM25 retrieval. This keeps `search_knowledge` available offline, but result ordering may be less precise for ambiguous queries until the reranker model is cached.
1156
-
1157
- **Embedding model options** (fastest → most accurate):
1158
- - `BAAI/bge-small-en-v1.5` — 384D, ~33MB (default)
1159
- - `BAAI/bge-base-en-v1.5` — 768D, ~130MB
1160
- - `BAAI/bge-large-en-v1.5` — 1024D, ~335MB
1161
- - `intfloat/multilingual-e5-small` — 384D, 100+ languages
1162
-
1163
- > **Warning**: Changing the embedding model after indexing requires `reindex_documents(full_rebuild=True)`.
1164
-
1165
- #### Search
1166
-
1167
- | Field | Default | Description |
1168
- |-------|---------|-------------|
1169
- | `search.default_results` | 5 | Results returned when no limit specified |
1170
- | `search.max_results` | 20 | Hard cap even if client requests more |
1171
- | `search.collection_name` | `knowledge_base` | ChromaDB collection — change for separate KBs |
1172
-
1173
- #### Categories
1174
-
1175
- Map folder paths to category names. Documents in matching folders get auto-tagged, enabling filtered searches.
1176
-
1177
- ```yaml
1178
- category_mappings:
1179
- "security/redteam": "redteam"
1180
- "security": "security"
1181
- ```
1182
-
1183
- Set `category_mappings: {}` to disable — documents are still searchable, just without category filters.
1184
-
1185
- #### Keyword Routing
1186
-
1187
- Route queries to categories based on keywords. When a query contains listed keywords, results from that category are prioritized (not filtered — other categories still appear, ranked lower).
1188
-
1189
- ```yaml
1190
- keyword_routes:
1191
- redteam:
1192
- - pentest
1193
- - exploit
1194
- - sqli
1195
- ```
1196
-
1197
- Single-word keywords use regex word boundaries (`\b`) — "api" won't match "RAPID". Multi-word keywords use substring matching.
1198
-
1199
- Set `keyword_routes: {}` for pure semantic search.
1200
-
1201
- #### Query Expansion
1202
-
1203
- Expand search terms with synonyms before BM25 search. Supports single tokens, bigrams, and full query matches.
1204
-
1205
- ```yaml
1206
- query_expansions:
1207
- sqli:
1208
- - sql injection
1209
- - sqli
1210
- k8s:
1211
- - kubernetes
1212
- - k8s
1213
- ```
1214
-
1215
- Set `query_expansions: {}` for no expansion.
1216
-
1217
- `query_expansions` is directional: only the key on the left triggers the terms on the right. If you need mutual expansion without duplicating entries, use `query_expansion_groups`.
1218
-
1219
- ```yaml
1220
- query_expansion_groups:
1221
- - ["triple barrier", "tb", "trip_barr"]
1222
- - ["profit factor", "pf"]
1223
- ```
1224
-
1225
- Each group is interpreted symmetrically, so every term expands to the rest of the group. The final internal expansion table is built by merging both sources:
1226
-
1227
- 1. `query_expansions` entries are loaded as-is.
1228
- 2. `query_expansion_groups` adds reciprocal links for every term in each group.
1229
- 3. Overlaps are merged by union with duplicate terms removed.
1230
-
1231
- This keeps backward compatibility while allowing concise synonym groups.
1232
-
1233
- ### Hybrid Search Tuning
1234
-
1235
- | hybrid_alpha | Behavior | Best For |
1236
- |--------------|----------|----------|
1237
- | 0.0 | Pure BM25 keyword | Exact terms, CVEs, tool names |
1238
- | 0.3 | Keyword-heavy **(default)** | Technical queries with specific terms |
1239
- | 0.5 | Balanced | General queries |
1240
- | 0.7 | Semantic-heavy | Conceptual queries, related topics |
1241
- | 1.0 | Pure semantic | "How to..." questions, abstract concepts |
1242
-
1243
- ---
1244
-
1245
- ## Project Structure
1246
-
1247
- ```
1248
- knowledge-rag/
1249
- ├── mcp_server/
1250
- │ ├── __init__.py # Stdout protection + version
1251
- │ ├── config.py # YAML config loader + defaults
1252
- │ ├── ingestion.py # 20 parsers, chunking, metadata extraction
1253
- │ └── server.py # MCP server, ChromaDB, BM25, reranker, 12 tools
1254
- ├── config.example.yaml # Documented config template (copy to config.yaml)
1255
- ├── config.yaml # Your active configuration (git-ignored)
1256
- ├── presets/ # Ready-to-use domain configurations
1257
- │ ├── cybersecurity.yaml
1258
- │ ├── developer.yaml
1259
- │ ├── research.yaml
1260
- │ └── general.yaml
1261
- ├── documents/ # Your documents (scanned recursively)
1262
- ├── data/
1263
- │ ├── chroma_db/ # ChromaDB vector database
1264
- │ └── index_metadata.json # Incremental indexing state
1265
- ├── models_cache/ # Persistent embedding model cache
1266
- ├── tests/ # Test suite (82 tests)
1267
- ├── install.sh # Linux/macOS installer
1268
- ├── install.ps1 # Windows installer
1269
- ├── venv/ # Python virtual environment
1270
- ├── requirements.txt
1271
- ├── pyproject.toml
1272
- ├── LICENSE
1273
- └── README.md
1274
- ```
1275
-
1276
- ---
1277
-
1278
- ## Troubleshooting
1279
-
1280
- ### Python version mismatch
1281
-
1282
- Requires Python 3.11 or newer.
1283
-
1284
- ```bash
1285
- python --version # Must be 3.11+
1286
- ```
1287
-
1288
- ### FastEmbed model download fails
1289
-
1290
- On first run, FastEmbed downloads models to `models_cache/`. If the download fails:
1291
-
1292
- ```bash
1293
- # Clear cache and retry
1294
- # Windows:
1295
- rmdir /s /q models_cache
1296
-
1297
- # Linux/macOS:
1298
- rm -rf models_cache
1299
-
1300
- # Then restart the MCP server
1301
- ```
1302
-
1303
- ### Reranker model download fails
1304
-
1305
- The reranker is lazy-loaded on the first query. If the model is not cached and the machine is offline, search continues without reranking and uses the RRF order from hybrid retrieval. To keep reranking enabled offline, run one query while online or pre-populate `models_cache/` on the target machine.
1306
-
1307
- You can still disable reranking explicitly in `config.yaml`:
1308
-
1309
- ```yaml
1310
- models:
1311
- reranker:
1312
- enabled: false
1313
- ```
1314
-
1315
- Disabling reranking reduces memory use and avoids first-query model loading. The tradeoff is lower ranking precision, especially when several chunks match the same terms but only one is the best answer.
1316
-
1317
- ### ChromaDB index crashes on startup
1318
-
1319
- Native ChromaDB failures can terminate Python before normal exception handling runs. Startup now probes ChromaDB in a child process before initializing the MCP server. If the probe crashes, the active `chroma_db/` and `index_metadata.json` are moved to `data/backups/auto-repair-*`, and the next startup can rebuild a clean index.
1320
-
1321
- The same guarded behavior is available through either console script:
1322
-
1323
- ```bash
1324
- knowledge-rag
1325
- knowledge-rag-guarded
1326
- ```
1327
-
1328
- ### Index is empty
1329
-
1330
- ```bash
1331
- # Check documents directory has files
1332
- ls documents/
1333
-
1334
- # Force reindex via Claude Code:
1335
- # reindex_documents(force=True)
1336
-
1337
- # Or nuclear rebuild if model changed:
1338
- # reindex_documents(full_rebuild=True)
1339
- ```
1340
-
1341
- ### MCP server not loading
1342
-
1343
- 1. Check `~/.claude.json` exists and has valid JSON in the `mcpServers` section
1344
- 2. Verify paths use double backslashes (`\\`) on Windows
1345
- 3. Restart Claude Code completely
1346
- 4. Run `claude mcp list` to check connection status
1347
-
1348
- ### "Failed to connect" error
1349
-
1350
- The MCP server uses stdout for JSON-RPC communication. If a library prints to stdout during init, the stream gets corrupted. v3.4.3+ includes stdout protection that prevents this. If you're on an older version, upgrade:
1351
-
1352
- ```bash
1353
- pip install --upgrade knowledge-rag
1354
- ```
1355
-
1356
- ### Slow first query
1357
-
1358
- The cross-encoder reranker model is lazy-loaded on the first query. This adds a one-time ~2-3 second delay for model download and loading. Subsequent queries are fast. If the model cannot be loaded, search falls back to RRF ordering and does not retry loading the reranker until the server restarts.
1359
-
1360
- ### Memory usage
1361
-
1362
- With ~200 documents, expect ~300-500MB RAM. The embedding model (~200MB ONNX runtime resident, lazy-loaded on first query since v3.8.0) and reranker (~25MB, lazy-loaded) are loaded into memory only when actually used. For very large knowledge bases (1000+ documents), consider enabling GPU acceleration and using exclude patterns to limit index scope.
1363
-
1364
- ### Multiple MCP clients spawn duplicate servers
1365
-
1366
- MCP stdio is one process per client by protocol — multiple Claude Code windows, Claude Desktop + IDE, etc. each spawn their own `knowledge-rag` process. Since v3.8.0 idle processes are cheap (no embedding model loaded until first query). If you've measured and want a hard cap of one server per data directory, opt in:
1367
-
1368
- ```bash
1369
- export KNOWLEDGE_RAG_SINGLE_INSTANCE=1
1370
- ```
1371
-
1372
- A second instance exits immediately with code 75. Default is OFF (multi-client friendly). Full guide: [docs/single-instance.md](docs/single-instance.md). Sample MCP config: [examples/mcp-config-single-instance.json](examples/mcp-config-single-instance.json).
1373
-
1374
- ### SSE server won't start
1375
-
1376
- ```bash
1377
- # Check if port 8179 is already in use
1378
- # Windows:
1379
- netstat -aon | findstr :8179
1380
- # Linux/macOS:
1381
- lsof -i :8179
1382
- ```
1383
-
1384
- If `uvicorn` is not found, install the server extras: `pip install knowledge-rag[server]`
1385
-
1386
- ### Can't connect to SSE server
1387
-
1388
- Verify the server is running and the URL is correct:
1389
-
1390
- ```bash
1391
- curl http://127.0.0.1:8179/sse
1392
- ```
1393
-
1394
- Common issues:
1395
- - Wrong URL: must end with `/sse` (not just the port)
1396
- - Firewall blocking the port
1397
- - Server started with a different host/port than configured in the MCP client
1398
-
1399
- ---
1400
-
1401
- ## Changelog
1402
-
1403
- ### Unreleased
1404
-
1405
- ### v4.5.1 (2026-07-28) — Fase 1 Security Hardening (library, standalone)
1406
-
1407
- - **NEW (security)**: Fase 1 Security Hardening — a standalone `mcp_server.security` library ships with the tools needed to close three attack classes against untrusted document ingestion. **Nothing in the existing call graph changes** in v4.5.1: `security.py` is imported by no other module in this release, so byte-identical behaviour is preserved for every user. Integration with `add_document` / `add_from_url` / `parse_file` / MCP HTTP transports lands in v4.6.0 (14 tests in `tests/security/` are marked `xfail` accordingly — they turn green once the callers are wired up). Users motivated to adopt hardening today can `from mcp_server.security import ...` explicitly. Contract per-defense:
1408
- - **CWE-22 / CWE-59** path traversal + symlink escape: `validate_path_within(base, candidate)` + `is_path_within(base, candidate)` + `PathEscapeError`. Rejects `..`, absolute-outside-base, and symlinks whose target leaves the corpus root.
1409
- - **OWASP LLM01:2025** prompt injection: `neutralize_injection_sentinels` + `sanitize_external_content` + `wrap_external_content` + `detect_external_marker`. Wraps content fetched from URLs / dropped files with tagged sentinels the downstream LLM can distinguish from operator-authored context.
1410
- - **CWE-287** unauthenticated HTTP transport: `BearerAuthMiddleware` + `bearer_token_matches` (constant-time comparison) + `extract_bearer_token`. Attach to a Starlette / FastAPI app; stdio transport bypasses auth by construction (no network surface).
1411
- - **NEW**: `docs/adr/0001-fase1-security-hardening.md` — ADR capturing threat model + v4.6.0 integration roadmap.
1412
- - **NEW**: `.github/SECURITY.md` — GitHub vulnerability reporting policy pointer + `.github/openssf-best-practices.md` (evidence pack for OpenSSF Best Practices badge registration).
1413
- - **NEW**: `tests/security/` (71 tests total; 57 library unit tests green in v4.5.1, 14 integration tests `xfail` pending v4.6.0 wire-up, `strict=False` so they auto-turn-green after integration) + `tests/test_security_docs.py` (9 assertions pinning `SECURITY.md`, README badge block, dependabot weekly pip schedule).
1414
- - **DOCS**: README carries the OpenSSF Best Practices badge (placeholder project ID until registration completes).
1415
- - **DEPS**: `dependabot.yml` pip schedule tightened `monthly` → `weekly` (OpenSSF-recommended floor for language ecosystems).
1416
- - **VERSION**: 4.5.0 → **4.5.1** (PATCH — additive library only, zero behaviour change on the default install).
1417
-
1418
- ### v4.5.0-post (rolling fixes since v4.5.0 release)
1419
-
1420
- - **FIX (deps)**: pin `mcp<2.0.0` in both `requirements.txt` and `pyproject.toml` after the upstream `mcp` package published 2.0.0, which drops/relocates `mcp.server.fastmcp`. Every `mcp_server.server` import chain — and therefore every test module — was failing collection with `ModuleNotFoundError: No module named 'mcp.server.fastmcp'` on fresh CI runners across all 9 OS×Python cells. Pin restores green until we migrate to the new 2.x FastMCP entry point in a follow-up. Root cause: `requirements.txt` had `mcp>=1.0.0` unbounded, so `pip install -r requirements.txt` in CI ignored the pyproject constraint. (#122)
1421
- - **FIX**: `_ensure_bm25_index` no longer traps the guard flag `_bm25_initialized` in a stuck-True state when the server boots against an empty ChromaDB collection. Prior behavior set the flag unconditionally at the end of the guarded block, so a fresh-install bootup or a metadata-mapping loss left BM25 permanently uninitialized: subsequent `add_document()` calls populate `bm25_index.corpus` but do not rebuild the inverted index, and `_ensure_bm25_index` — the only path that does — short-circuited on the stale flag. All keyword-only searches returned zero results; hybrid searches returned only semantic hits with `bm25_rank: null`. The flag is now set only after a build that actually produced an index; empty collections and exceptions leave it False so the next call retries. (#114)
1422
- - **TEST**: New `TestEmptyBootupDoesNotTrapGuardFlag` (5 tests) pins the contract — empty-collection boot leaves flag False, populated collection marks True, empty-then-populated recovery works, empty `get()` result leaves False, and `build_index()` exceptions leave False for retry. Baseline: 271 → 276.
1423
-
1424
- ### v4.5.0 (2026-07-06) — Hybrid Search Ranking & Routing Fix
1425
-
1426
- - **FIX**: `search_knowledge` now searches the entire index when the user omits `category_filter`. The internal `_route_by_keywords()` heuristic previously acted as a hard where-filter on both the semantic and BM25 branches, so queries whose terms happened to map to a sparsely-populated category could return two documents while thousands of relevant chunks in other categories were silently dropped. The router is now **informational only**: the `routed_by` field is still populated in every result for telemetry (public API unchanged), but the candidate set is never restricted. Users who want a hard filter still get it by passing `category_filter=...` explicitly — which continues to filter BM25 consistently with #109. (#112)
1427
- - **NEW**: Hybrid search adds a small bounded ranking boost when query terms match indexed `source` or `filename` metadata, helping navigational queries surface the canonical file for a topic instead of adjacent files that only cross-reference it. The boost is capped at ~20% of typical RRF magnitudes, so it breaks near-ties without overriding semantic or BM25 signal. Public API unchanged. (#110, thanks @Hohlas)
1428
- - **TEST**: New `TestKeywordRoutingBehavior` (3 tests) pins the routing fix — BM25 and semantic branches must both see the full corpus when no explicit filter is passed, and explicit `category_filter` still overrides the router. New `TestPathAwareRanking` covers the path-metadata boost. Baseline: 266 → 271.
1429
- - **UPGRADE NOTE**: warm `query_cache` entries from before v4.5.0 should be invalidated by restarting the server so cached responses no longer reflect the pre-fix restrictive behavior.
1430
-
1431
- ### v4.4.0 (2026-07-06) — Cross-Platform Installer & Hybrid Search Category Filter
1432
-
1433
- - **NEW**: Cross-platform, multi-LLM-client installer (`install.py`) driving both `install.sh` (Linux/macOS) and `install.ps1` (Windows) as thin wrappers. One codebase, one behavior across every OS.
1434
- - **NEW**: Auto-detects and registers `knowledge-rag` in 8 LLM clients — Claude Code, Claude Desktop, Cursor, Windsurf, VS Code (Copilot Chat), Cline, Gemini CLI, Zed — writing to each tool's canonical config path with the correct JSON schema per client (VS Code uses `servers`, Zed uses `context_servers`, everyone else uses `mcpServers`).
1435
- - **NEW**: `--for <clients>` / `--exclude <clients>` opt-in/opt-out selection, `--dry-run` preview, `--list-clients` registry inspection, `--pypi-version <ver>` pinning, `--skip-init` / `--skip-model` for fast reruns. See `python install.py --help`.
1436
- - **FIX**: `install.ps1` no longer writes MCP config to `~/.claude/mcp.json` (a stale secondary path); it now targets `~/.claude.json` — the file Claude Code actually reads — via idempotent JSON merge that preserves every existing MCP server entry with an automatic `.knowledge-rag.bak` backup.
1437
- - **FIX**: `install.ps1` gains PyPI mode (`pip install knowledge-rag`) and runs `mcp_server.server init` on install — feature parity with `install.sh`.
1438
- - **FIX**: MCP server spec no longer uses the fragile `cmd /c cd /d ... && python ...` wrapper on Windows; it emits the standard `command` + `cwd` shape supported natively by every modern client.
1439
- - **FIX**: `install.sh` guards against `sh install.sh` (bash-only features now emit a clear error instead of a cryptic syntax failure).
1440
- - **FIX**: Both scripts now correctly advertise **13 MCP tools** (was outdated at 12; `get_reindex_status` shipped in v4.3.0).
1441
- - **FIX**: Windows-side `install.ps1` prefers `winget install Python.Python.3.12 --scope user` (no admin), falls back to python.org 3.12.7 (was pinned to 3.12.0).
1442
- - **FIX**: Hybrid search now applies the active category filter to BM25 results before RRF fusion, preventing keyword-only BM25 hits from other categories from leaking into filtered searches. Both leak paths are closed: BM25 candidates are metadata-filtered before RRF (with `top_k` widened to `max_results * 20` to compensate for post-filter drop), and the fallback fetch during fusion re-checks `category` before adding a chunk to `combined_scores`. Note: the `category_filter` guard now also applies to the keyword-routed category — `_route_by_keywords()`-inferred routing filters BM25 too, consistent with the semantic branch. Users with warm `query_cache` entries should restart the server to invalidate stale results. (#109, thanks @Hohlas)
1443
- - **TEST**: New `tests/test_installer_no_data_loss.py` (22 tests) locks in the installer's zero-data-loss contract across all three JSON schemas (`mcpServers` / `servers` / `context_servers`): top-level keys preserved, sibling MCP servers byte-identical, `.knowledge-rag.bak` backup written before every mutation, idempotent second run, `--dry-run` writes nothing, atomic `os.replace` write. Baseline: 231 → 266.
1444
- - **TEST**: New `TestHybridCategoryFilter::test_bm25_results_respect_category_filter` in `tests/test_search.py` — deterministic regression covering BM25-only hits with mixed categories. Baseline: 266 → 267.
1445
-
1446
- ### v4.3.1 (2026-06-22) — Hybrid Search Fixes
1447
-
1448
- - **FIX**: Accept `"general"` as a valid category in `search_knowledge`. The parser hardcodes `"general"` as the fallback in `_detect_category` (`ingestion.py`), but the validator only built `valid_categories` from `config.keyword_routes` + `config.category_mappings.values()` — so users who customized `config.yaml` and dropped the default `"general": "general"` mapping hit `Invalid category` even though the index contained `general` documents. Validator now always tolerates `"general"`. (#98, thanks @Hohlas)
1449
- - **FIX**: Skip BM25-only search results when Chroma can no longer resolve the chunk ID. Stale BM25 indices (typically right after `remove_document` or in the window between async reindex and BM25 rebuild) returned hits whose `collection.get()` came back empty; the previous fallback inserted entries with `document=""` / `metadata={}` into the reranker, polluting results with empty matches. The pipeline now `continue`s past those, dropping the stale hit cleanly. (#98, thanks @Hohlas)
1450
- - **TEST**: Added `tests/test_pr98_regression.py` (4 tests) pinning both contracts so future refactors cannot silently revert either fix. Test count baseline: 227 → 231. (#99)
1451
- - **CI**: Bumped `[tool.mypy] python_version` from 3.11 to 3.12 to accept PEP 695 `type` statements in the numpy stub (`numpy/__init__.pyi`) which were breaking the Pillar 7 strict gate. Only affects static analysis; `requires-python = ">=3.11"` unchanged. (#100)
1452
-
1453
- ### v4.3.0 (2026-06-17) — Async Reindex, GPU CUDA 12, 13th MCP Tool
1454
-
1455
- - **NEW**: `get_reindex_status` MCP tool — lightweight reindex progress polling without computing full index stats. Returns active/idle status, percent, processed/total, errors, and last result.
1456
- - **NEW**: `reindex_documents` now runs in background via daemon thread — returns immediately with `{"status": "started"}`. Eliminates MCP timeout on large document sets (5K+ files). Concurrent calls return `already_running` with current progress.
1457
- - **NEW**: GPU acceleration with full CUDA 12 support — `onnxruntime-gpu` + 7 NVIDIA pip packages (`cublas`, `cudnn`, `cuda-runtime`, `cufft`, `cusparse`, `cusolver`, `curand`, `nvjitlink`). Server auto-detects GPU on startup with 4-step verification (providers, DLLs, nvidia-smi, session creation). Falls back to CPU gracefully.
1458
- - **NEW**: `_setup_cuda_dll_paths()` adds NVIDIA pip package DLL directories to `PATH` automatically on Windows — onnxruntime finds CUDA 12 DLLs without a full CUDA Toolkit install.
1459
- - **DEPS**: `[gpu]` extra expanded from 3 to 8 packages (added `cufft`, `cusparse`, `cusolver`, `curand`, `nvjitlink`).
1460
- - **FIX**: GPU status reporting now uses actual ONNX session creation test instead of just checking `get_available_providers()` — prevents false "GPU ACTIVE" when CUDA DLLs are missing.
1461
- - **DOCS**: GPU Acceleration section rewritten with complete requirements table, setup steps, verification instructions, and fallback behavior.
1462
- - **DOCS**: Tool reference updated — `reindex_documents` async behavior documented, `get_reindex_status` reference added.
1463
- - **TEST**: Backwards-compat baseline updated for 13 MCP tools.
1464
-
1465
- ### v4.2.0 (2026-06-17) — Search Performance & Output Quality
1466
-
1467
- - **PERF**: Custom inverted-index BM25 replaces `rank-bm25` full-corpus scan — 128× faster keyword search on 50K+ chunk corpora. Only documents containing query terms are scored via posting lists.
1468
- - **PERF**: `numpy.argpartition` for O(n) top-k selection instead of O(n log n) sort.
1469
- - **PERF**: Batched adjacent chunk fetch — single ChromaDB `collection.get()` call replaces N round-trips per result.
1470
- - **PERF**: O(1) reverse lookup via `_source_to_docid` dict eliminates linear scans of `_indexed_docs` in `search_similar`, `update_document`, `remove_document`, and `_expand_with_adjacent_chunks`.
1471
- - **NEW**: `snippet_mode` parameter on `search_knowledge` (default: `true`) — truncates content to ~500 chars at natural break points with `content_length` field. Reduces token consumption by ~72%.
1472
- - **NEW**: `min_score` parameter on `search_knowledge` (default: `0.0`) — filters results below a normalized relevance threshold. Response includes `filtered_by_score` count.
1473
- - **NEW**: `filtered_by_score` field in search response JSON for transparency.
1474
- - **DEPS**: `numpy` added as direct dependency (was transitive via fastembed); `rank-bm25` import removed from server.py.
1475
- - **TEST**: 6 new tests for `min_score` filtering and `snippet_mode` truncation.
1476
- - **TEST**: Updated backwards-compat baseline to include new `search_knowledge` parameters.
1477
-
1478
- ### v4.1.2 (2026-06-17)
1479
-
1480
- - **FIX**: `_save_metadata` dict snapshot prevents concurrent modification crash during file watcher events.
1481
- - **STYLE**: ruff format applied to server.py.
1482
-
1483
- ### v4.1.1 (2026-06-17)
1484
-
1485
- - **FIX**: All `_indexed_docs` iterations now use `list()` snapshot, preventing `dictionary changed size during iteration` crash when FileWatcher modifies the index concurrently with MCP tool calls (affects `search_knowledge`, `search_similar`, `update_document`, `remove_document`, `evaluate_retrieval`, `list_categories`, `list_documents`)
1486
-
1487
- ### v4.1.0 (2026-06-17)
1488
-
1489
- - **Added:** `query_expansion_groups` config for symmetric synonym expansion (#92)
1490
- - **Improved:** `expand_query()` now returns deterministic expansion order (set → ordered list with dedup)
1491
-
1492
- ### v4.0.1 (2026-06-16)
1493
-
1494
- - **FIX**: Orphan cleanup now runs before indexing loop, preventing chunk loss when files are moved (#90).
1495
- - **FIX**: Chunk deduplication is now per-document instead of global, preventing cross-document chunk deletion (#91).
1496
- - **FIX**: Added `on_moved` handler to `DocumentWatcher` for proper file move detection.
1497
- - **FIX**: Startup preflight probes ChromaDB in a child process and moves crashing persistent indexes to `data/backups/auto-repair-*` before MCP initialization.
1498
- - **FIX**: Reranker load failures now fall back to RRF ordering instead of failing `search_knowledge` on offline machines.
1499
- - **FIX**: Virtualenv project-root detection now handles Python symlinks that resolve to the system interpreter.
1500
- - **NEW**: `knowledge-rag-guarded` console script kept as an explicit guarded startup alias.
1501
-
1502
- ### v4.0.0 (2026-06-09) — Enterprise Concurrent Access
1503
-
1504
- - **NEW**: SSE and streamable-http transport modes — 1 server serves N clients (`server.transport: "sse"` in config.yaml or `--transport sse` CLI).
1505
- - **NEW**: Thread-safe shared state for concurrent queries — QueryCache locking, BM25 build lock, orchestrator double-checked locking.
1506
- - **NEW**: ChromaDB WAL mode enabled automatically in SSE/HTTP mode for concurrent read performance.
1507
- - **NEW**: Optional rate limiting — sliding-window counter, configurable RPM and burst, disabled by default.
1508
- - **NEW**: Optional Prometheus metrics endpoint — tool call counts, latency histograms, separate port, disabled by default.
1509
- - **NEW**: All 13 MCP tools instrumented with `@rate_limited` and `@instrument` decorators (zero-cost when disabled).
1510
- - **NEW**: `--transport` CLI override for Docker/systemd deployments.
1511
- - **NEW**: `pip install knowledge-rag[server]` optional dependency for SSE/HTTP (uvicorn).
1512
- - **CHANGED**: SSE/HTTP mode auto-enables single-instance lock (port collision prevention).
1513
- - **CHANGED**: `mcp` dependency bumped to `>=1.6.0` (SSE/streamable-http support).
1514
- - **MIGRATION**: Default transport remains `stdio` — existing users need zero changes. See config.example.yaml for SSE setup.
1515
-
1516
- ### v3.9.1 (2026-06-08)
1517
-
1518
- - **FIX**: Expand `~` in `config.yaml` path values (`documents_dir`, `data_dir`, `models_cache_dir`) via `expanduser()` on all platforms (#86).
1519
- - **FIX**: Warn when `documents_dir` resolves to a non-existent path instead of silently indexing zero files.
1520
- - **FIX**: File watcher now uses accumulate-mode debounce — bulk file copies no longer starve the reindex trigger.
1521
- - **FIX**: Concurrent `index_all()` calls are serialized via `_index_lock` to prevent ChromaDB SQLite corruption.
1522
- - **FIX**: `collection.add()` is batched (500 chunks/call) to cap memory usage during large reindex operations.
1523
- - **NEW**: `KNOWLEDGE_RAG_WATCHER_DISABLED=1` env var to disable the file watcher for troubleshooting.
1524
- - **NEW**: Progress logging every 10% for reindex operations with >100 documents.
1525
-
1526
- ### v3.9.0 (2026-05-10) — Quality Gate
1527
-
1528
- **Major governance + CI hardening release. No runtime behavior change in `mcp_server/`. Public API surface unchanged from v3.8.1.**
1529
-
1530
- - **NEW** Quality Gate workflow (`.github/workflows/quality-gate.yml`) enforcing the 7 pillars on every PR: Security, Stability, Memory Leak, Versatility, Scalability, Versioning, Quality. 35+ status checks total.
1531
- - **NEW** Nightly resilience workflow (`.github/workflows/nightly.yml`): chaos suite (failure injection), 1h soak test (50K-iteration loop), determinism check (full suite × 3), mutation testing (mutmut). Auto-opens GitHub issue on any nightly failure.
1532
- - **NEW** Performance benchmark suite under `bench/` (12 microbenchmarks, pytest-benchmark) with 10% regression gate on every PR.
1533
- - **NEW** Public performance dashboard via GitHub Pages (`.github/workflows/bench-pages.yml`) — chart of latency/throughput per commit. Dormant until repo Pages is enabled.
1534
- - **NEW** Property-based fuzzing of all parsers via Hypothesis (`tests/test_ingestion_property.py`) — 200 random examples per CI run.
1535
- - **NEW** Memory baseline regression tests (`tests/test_memory_baseline.py`, cross-platform via psutil) — RSS bounded under 1000 queries; nightly soak amplifies to 50K iterations.
1536
- - **NEW** Property/locale/format/preset matrices (`tests/test_presets.py`, `tests/test_locale.py`, `tests/test_format_smoke.py`).
1537
- - **NEW** Backwards-compatibility regression tests (`tests/test_backwards_compat.py`) — legacy YAML configs from v3.6.0 / v3.7.0 still parse; all 13 MCP tool parameter names frozen.
1538
- - **NEW** AST-based public API surface diff (`scripts/check_api_surface.py`) — any breaking change blocks merge, baseline at `.github/api-surface-baseline.json`.
1539
- - **NEW** CHANGELOG enforcement (`scripts/check_changelog.py`) — user-facing PRs must add a bullet under `## Unreleased`; bypass via `skip-changelog` label.
1540
- - **NEW** Test count anti-regression (`scripts/check_test_count.py`) — guards against silent test deletion.
1541
- - **NEW** Conventional commits required on every PR title (commitlint via `amannn/action-semantic-pull-request`).
1542
- - **NEW** mypy `--strict` rolling out per-module (currently `instance_lock.py` + `preflight.py` + `scripts/`); interrogate docstring coverage ≥ 80%; radon, vulture, PR-size guard report-only.
1543
- - **NEW** CI matrix expanded to 9 cells: Linux + Windows + **macOS** × 3.11 + 3.12 + **3.13** (all required at v3.9.0; macOS / 3.13 promoted from experimental after two clean cycles).
1544
- - **NEW** Governance docs: `CONTRIBUTING.md`, `CODE_OF_CONDUCT.md`, `SECURITY.md`, `.github/PULL_REQUEST_TEMPLATE.md`, 3 issue templates, expanded `CODEOWNERS`.
1545
- - **NEW** Pre-commit hooks: ruff, gitleaks, version-sync, conventional commits.
1546
- - **CHORE** `.github/codecov.yml` enforcing coverage trend gate (-0.5pp blocks; new code ≥ 70%).
1547
-
1548
- ### v3.8.1 (2026-05-10) — hotfix
1549
-
1550
- - **FIX (critical)**: `FastEmbedEmbeddings.__call__` no longer returns vectors of zeros when the ONNX model fails to load or `embed()` raises. The previous behavior silently corrupted the index — ChromaDB stored zero embeddings, `count()` reported normal numbers, smart-reindex skipped the bad chunks, and queries returned garbage scores with no error visible. Now raises `EmbeddingModelLoadError` / `EmbeddingError`. (#36)
1551
- - **FIX**: Sticky `_load_failed` flag — after a load failure, subsequent calls re-raise immediately instead of looping through HuggingFace download attempts (was the "frozen query" UX in v3.8.0).
1552
- - **NEW**: Sanity checks in `__call__` — embed count and dim mismatches raise `EmbeddingError` instead of silently returning malformed vectors.
1553
- - **TEST**: 7 new regression cases in `tests/test_lazy_embeddings.py`, including `test_does_not_return_zero_vectors_silently` as a guard for the whole class of bug.
1554
- - **NOTE**: This is a pre-existing bug in master, not introduced by v3.8.0. v3.8.0 lazy-load expanded the impact (failures moved to query time). All v3.8.0 users should upgrade.
1555
-
1556
- ### v3.8.0 (2026-05-10)
1557
-
1558
- - **NEW**: Lazy-load FastEmbed embedding model (~200MB ONNX runtime). Loads on first query instead of startup — idle `knowledge-rag` processes are now cheap, which matters when MCP stdio clients spawn parallel server processes (multiple Claude Code windows, Claude Desktop + IDE, etc.). Public API unchanged. (#32)
1559
- - **NEW**: Opt-in single-instance guard via `KNOWLEDGE_RAG_SINGLE_INSTANCE=1` env var. **OFF by default** — multi-client MCP usage continues to work unchanged. When enabled, a second server process for the same `data_dir` exits with code 75 (`EX_TEMPFAIL`). Includes stale-PID recovery and SIGINT/SIGTERM handlers. See [docs/single-instance.md](docs/single-instance.md). (#33, original concept by @Hohlas in #31)
1560
- - **NEW**: `examples/mcp-config-single-instance.json` — sample MCP client config for the opt-in guard.
1561
- - **DOCS**: New `docs/single-instance.md` — when to use, when NOT to use, troubleshooting, full activation reference.
1562
- - **DOCS**: README troubleshooting section for "Multiple MCP clients spawn duplicate servers" + memory-usage note for lazy embeddings.
1563
- - **CHORE**: Sync version across `pyproject.toml`, `mcp_server/__init__.py`, and `npm/package.json` (was drifting since v3.5.x).
1564
- - **CHORE**: pytest `tmp_path_retention_count=1` to avoid Windows atexit cleanup race in CI.
1565
- - **ROADMAP**: Tracked v4.0 shared-service architecture (one daemon, many thin MCP clients) as the long-term fix for multi-process resource duplication. (#34)
1566
-
1567
- ### v3.6.2 (2026-04-23)
1568
-
1569
- - **INFRA**: NPM provenance attestation (SLSA supply chain security), full README on npm page
1570
- - **DOCS**: Reorganize Installation section — add NPX and Docker install methods, update What's New to v3.6.0
1571
-
1572
- ### v3.6.0 (2026-04-23)
1573
-
1574
- - **NEW**: Multi-language code parsing — C (`.c`), C++ (`.cpp`/`.h`), JavaScript (`.js`/`.jsx`), TypeScript (`.ts`/`.tsx`) with per-language function/class/import extraction
1575
- - **NEW**: XML parser (`.xml`) — root element and namespace metadata extraction
1576
- - **NEW**: All 8 new formats default enabled — no config change needed
1577
- - **NEW**: NPM wrapper (`npx knowledge-rag`) + Docker image (`ghcr.io/lyonzin/knowledge-rag`)
1578
- - **NEW**: Automated release pipeline — PyPI (Trusted Publishing), NPM, Docker GHCR
1579
- - **IMPROVED**: Code parser reports correct `language` metadata per file type (was hardcoded to `"python"` for all code files)
1580
-
1581
- ### v3.5.2 (2026-04-16)
1582
-
1583
- - **NEW**: Auto-discovery of CUDA 12 DLLs from pip-installed NVIDIA packages — no manual PATH configuration needed
1584
- - **NEW**: Graceful GPU→CPU fallback with `[WARN]` log when CUDA init fails (missing drivers, wrong version, etc.)
1585
- - **FIX**: Explicit `CPUExecutionProvider` when `gpu: false` — eliminates noisy CUDA probe errors in logs
1586
- - **FIX**: BASE_DIR resolution now correctly prefers directories with `config.yaml` over those with only `config.example.yaml` (fixes editable installs)
1587
-
1588
- ### v3.5.1 (2026-04-16)
1589
-
1590
- - **FIX**: Removed Python upper bound constraint (`<3.13` → `>=3.11`). Python 3.13 and 3.14 now supported — onnxruntime ships wheels for both.
1591
-
1592
- ### v3.5.0 (2026-04-16)
1593
-
1594
- - **NEW**: Optional GPU acceleration for ONNX embeddings — `pip install knowledge-rag[gpu]` + `models.embedding.gpu: true` in config. 5-10x faster indexing on NVIDIA GPUs with automatic CPU fallback.
1595
- - **DOCS**: Supported formats table added to README (20 formats)
1596
-
1597
- ### v3.4.3 (2026-04-16)
1598
-
1599
- - **FIX**: Correct stdout protection via save/restore pattern — `__init__.py` saves original stdout and redirects to stderr during init, `server.py main()` restores it before `mcp.run()`. v3.4.2's global redirect broke MCP JSON-RPC response channel.
1600
-
1601
- ### v3.4.1 (2026-04-16)
1602
-
1603
- - **FIX**: `pip install knowledge-rag` now auto-detects project directory from venv location
1604
- - **NEW**: `install.sh` — Linux/macOS installer with pip and from-source modes
1605
- - **IMPROVED**: BASE_DIR resolution chain: env var → source dir → venv parent → CWD → fallback
1606
-
1607
- ### v3.4.0 (2026-04-16)
1608
-
1609
- - **NEW**: `models_cache_dir` — persistent embedding model cache, prevents re-download after reboots
1610
- - **NEW**: `exclude_patterns` — glob-based file/directory exclusion during indexing
1611
- - **NEW**: Jupyter Notebook (.ipynb) parser — extracts markdown and code cell sources only
1612
- - **NEW**: MCP stdout protection — redirects stdout to stderr before server start
1613
- - **NEW**: File watcher resilience — graceful fallback when Linux inotify limits are reached
1614
- - **NEW**: MetaTrader (.mq4, .mqh) support — opt-in code parsing
1615
- - **NEW**: 23 new tests (exclude patterns, ipynb parser, stdout protection)
1616
- - Community credit: [@Hohlas](https://github.com/Hohlas) ([PR #18](https://github.com/lyonzin/knowledge-rag/pull/18))
1617
-
1618
- ### v3.3.x
1619
-
1620
- - **v3.3.2**: Full type validation on YAML config, bounds checking, version sync
1621
- - **v3.3.1**: YAML null value crash fix, presets bundled in pip wheel, `knowledge-rag init` CLI
1622
- - **v3.3.0**: YAML configuration system, 4 domain presets, generic use support
1623
-
1624
- ### v3.2.x
1625
-
1626
- - **v3.2.4**: Symlink support with circular loop protection
1627
- - **v3.2.3**: BASE_DIR smart detection for pip installs
1628
- - **v3.2.2**: Plug-and-play pip install, `KNOWLEDGE_RAG_DIR` env var
1629
- - **v3.2.1**: Auto-recovery from corrupted ChromaDB
1630
- - **v3.2.0**: Parallel BM25 + Semantic search, adjacent chunk retrieval
1631
-
1632
- ### v3.1.x
1633
-
1634
- - **v3.1.1**: Code block protection in markdown chunker, AAR category, 14 CVE aliases
1635
- - **v3.1.0**: DOCX/XLSX/PPTX/CSV support, file watcher, MMR diversification, PyPI publish
1636
-
1637
- ### v3.0.0 (2026-03-19)
1638
-
1639
- - Replaced Ollama with FastEmbed (ONNX in-process)
1640
- - Cross-encoder reranking, markdown-aware chunking, query expansion
1641
- - 6 new MCP tools (12 total), auto-migration from v2.x
1642
-
1643
- <details>
1644
- <summary>v2.x and earlier</summary>
1645
-
1646
- - **v2.2.0**: `hybrid_alpha=0` skips Ollama, default changed from 0.5 to 0.3
1647
- - **v2.1.0**: Mermaid architecture diagrams
1648
- - **v2.0.0**: Hybrid search, RRF fusion, `hybrid_alpha` parameter
1649
- - **v1.1.0**: Incremental indexing, query cache, chunk deduplication
1650
- - **v1.0.1**: Auto-cleanup orphan folders, removed hardcoded paths
1651
- - **v1.0.0**: Initial release
1652
- </details>
1653
-
1654
- ---
1655
-
1656
- ## Contributing
1657
-
1658
- 1. Fork the repository
1659
- 2. Create a feature branch (`git checkout -b feature/amazing-feature`)
1660
- 3. Commit your changes
1661
- 4. Push to the branch (`git push origin feature/amazing-feature`)
1662
- 5. Open a Pull Request
1663
-
1664
- ---
1665
-
1666
- ## License
1667
-
1668
- This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
1669
-
1670
- ---
1671
-
1672
- ## Acknowledgments
1673
-
1674
- - [ChromaDB](https://www.trychroma.com/) — Vector database
1675
- - [FastEmbed](https://qdrant.github.io/fastembed/) — ONNX Runtime embeddings
1676
- - [FastMCP](https://github.com/anthropics/mcp) — Model Context Protocol framework
1677
- - [PyMuPDF](https://pymupdf.readthedocs.io/) — PDF parsing
1678
- - [rank-bm25](https://github.com/dorianbrown/rank_bm25) — BM25 Okapi implementation
1679
- - [Watchdog](https://github.com/gorakhargosh/watchdog) — File system monitoring
1680
- - [python-docx](https://python-docx.readthedocs.io/) / [openpyxl](https://openpyxl.readthedocs.io/) / [python-pptx](https://python-pptx.readthedocs.io/) — Office document parsing
1681
- - [PyYAML](https://pyyaml.org/) — YAML configuration parsing
1682
- - [Beautiful Soup](https://www.crummy.com/software/BeautifulSoup/) — HTML parsing for URL ingestion
1683
-
1684
- ---
1685
-
1686
- ## Author
1687
-
1688
- **Lyon.**
1689
-
1690
- Security Researcher | Developer
1691
-
1692
- ---
1693
-
1694
- <div align="center">
1695
-
1696
- **[Back to Top](#knowledge-rag)**
1697
-
1698
- </div>
1
+ # Knowledge RAG
2
+
3
+ <div align="center">
4
+
5
+ [![PyPI](https://img.shields.io/pypi/v/knowledge-rag)](https://pypi.org/project/knowledge-rag/)
6
+ [![NPM](https://img.shields.io/npm/v/knowledge-rag)](https://www.npmjs.com/package/knowledge-rag)
7
+ [![PyPI Downloads](https://static.pepy.tech/personalized-badge/knowledge-rag?period=total&units=INTERNATIONAL_SYSTEM&left_color=BLACK&right_color=GREEN&left_text=downloads)](https://pepy.tech/projects/knowledge-rag)
8
+ ![Python](https://img.shields.io/badge/python-3.11%2B-green.svg)
9
+ ![License](https://img.shields.io/badge/license-MIT-yellow.svg)
10
+ ![Platform](https://img.shields.io/badge/platform-Windows%20%7C%20Linux%20%7C%20macOS-lightgrey.svg)
11
+ ![GPU](https://img.shields.io/badge/GPU-NVIDIA%20CUDA-76B900.svg?logo=nvidia)
12
+ [![CI](https://github.com/lyonzin/knowledge-rag/actions/workflows/ci.yml/badge.svg)](https://github.com/lyonzin/knowledge-rag/actions/workflows/ci.yml)
13
+ [![CodeQL](https://github.com/lyonzin/knowledge-rag/actions/workflows/security.yml/badge.svg)](https://github.com/lyonzin/knowledge-rag/actions/workflows/security.yml)
14
+ [![Quality Gate](https://github.com/lyonzin/knowledge-rag/actions/workflows/quality-gate.yml/badge.svg)](https://github.com/lyonzin/knowledge-rag/actions/workflows/quality-gate.yml)
15
+ [![Glama Score](https://glama.ai/mcp/servers/lyonzin/knowledge-rag/badges/score.svg)](https://glama.ai/mcp/servers/lyonzin/knowledge-rag)
16
+ [![OpenSSF Best Practices](https://bestpractices.coreinfrastructure.org/projects/13864/badge)](https://bestpractices.coreinfrastructure.org/projects/13864)
17
+
18
+ ### Your docs, your machine, zero cloud. Claude Code searches them natively.
19
+
20
+ Drop your PDFs, markdown, code, notebooks — **1800+ files, 39K chunks, indexed in under 3 minutes.**<br/>
21
+ Hybrid search (BM25 + semantic vectors + cross-encoder reranking) through 13 MCP tools.<br/>
22
+ Everything runs locally via ONNX. No Docker, no Ollama, no API keys, no data leaves your machine.
23
+
24
+ ```
25
+ pip install knowledge-rag → restart Claude Code → search_knowledge("your query")
26
+ ```
27
+
28
+ ---
29
+
30
+ **13 MCP Tools** | **Hybrid Search + Reranking** | **20 File Formats** | **Optional NVIDIA GPU** | **100% Local**
31
+
32
+ [What's New](#whats-new-in-v420) | [Supported Formats](#supported-formats) | [Installation](#installation) | [Configuration](#configuration) | [API Reference](#api-reference) | [Architecture](#architecture)
33
+
34
+ </div>
35
+
36
+ ---
37
+
38
+ ## Star History
39
+
40
+ <div align="center">
41
+
42
+ <a href="https://www.star-history.com/?repos=lyonzin%2Fknowledge-rag&type=date&legend=top-left">
43
+ <img alt="Star History Chart" src="./docs/star-history.svg" width="720" />
44
+ </a>
45
+
46
+ <sub>Chart updated daily by <a href="./.github/workflows/update-star-history.yml">GitHub Action</a></sub>
47
+
48
+ </div>
49
+
50
+ ---
51
+
52
+ ## What's New in v4.2.0
53
+
54
+ ### Search Performance & Output Quality (v4.2.0)
55
+
56
+ **128× faster BM25 search** — replaced `rank-bm25` full-corpus scan with a custom **inverted-index** implementation. Only documents containing query terms are scored, using `numpy.argpartition` for O(n) top-k selection. Adjacent chunk fetching now uses a single batched ChromaDB call instead of N round-trips, and an O(1) reverse lookup (`_source_to_docid`) eliminates linear scans.
57
+
58
+ **Smarter output** — two new parameters on `search_knowledge`:
59
+ - **`snippet_mode`** (default: `true`) — truncates content to ~500 characters at natural break points, reducing token consumption by ~72%. Adds `content_length` field with original size; use `get_document()` for full content.
60
+ - **`min_score`** — filters results below a normalized relevance threshold (0.0–1.0). Eliminates low-quality noise from results. Response includes `filtered_by_score` count for transparency.
61
+
62
+ Both parameters are fully backwards-compatible (existing callers see no change in behavior).
63
+
64
+ ### Enterprise Concurrent Access — SSE/HTTP Transport (v4.0.0)
65
+
66
+ The server now supports **SSE** and **streamable-http** transport modes. Instead of spawning a separate process per client (stdio), a single server process serves all clients with shared resources — 1 embedding model, 1 ChromaDB, 1 query cache.
67
+
68
+ ```yaml
69
+ # config.yaml
70
+ server:
71
+ transport: "sse" # "stdio" | "sse" | "streamable-http"
72
+ host: "127.0.0.1"
73
+ port: 8179
74
+ ```
75
+
76
+ Or via CLI: `knowledge-rag --transport sse`
77
+
78
+ **Optional enterprise features** (all disabled by default):
79
+ - **Rate limiting**: Sliding-window counter, configurable RPM and burst
80
+ - **Prometheus metrics**: `/metrics` endpoint on separate port
81
+ - **Bearer auth**: Token validation for SSE/HTTP connections
82
+
83
+ All 13 MCP tools are instrumented with `@rate_limited` and `@instrument` decorators — zero overhead when features are disabled. Default transport remains **stdio** for full backwards compatibility.
84
+
85
+ > **Migration**: Existing users need zero changes. SSE mode is opt-in via `server.transport: "sse"` in config.yaml. See [Configuration](#configuration) for details.
86
+
87
+ ### Quality Gate — 7-Pillar PR Validation
88
+
89
+ Every PR (including dependabot bumps and one-line fixes) is now evaluated against **35+ automated checks** spread across 7 pillars before any human review:
90
+
91
+ | Pillar | What it enforces | Tools |
92
+ |---|---|---|
93
+ | **1 Security** | SAST, secrets, CVEs, supply chain | bandit, semgrep, gitleaks, pip-audit, dependency-review, Snyk, CodeQL, Socket |
94
+ | **2 Stability** | Flake detection, coverage trend, test count, deterministic runs | pytest-rerunfailures, codecov ±0.5pp, test-count guard |
95
+ | **3 Memory Leak** | RSS bounded under 1000-query load, no idle bloat | psutil-based baseline tests + nightly 50K-iteration soak |
96
+ | **4 Versatility** | 9 OS×Python combos, 14 format parsers, 4 config presets, locale tolerance, property-based fuzzing | matrix CI on Linux+Windows+macOS × 3.11+3.12+3.13, Hypothesis |
97
+ | **5 Scalability** | Performance regression > 10% blocks merge, public bench dashboard | pytest-benchmark, GH Pages chart |
98
+ | **6 Versioning** | Atomic version sync, API surface diff, conventional commits, CHANGELOG enforcement, backwards compat | griffe-style AST diff, custom guards |
99
+ | **7 Quality** | Type strictness, docstring coverage, complexity, dead code | mypy strict, interrogate ≥80%, radon, vulture |
100
+
101
+ Plus a **nightly resilience workflow** that runs chaos failure-injection (HF down, ChromaDB corruption, watchdog crash, ONNX zero-byte replay), determinism check (full suite × 3), and mutation testing on selected modules.
102
+
103
+ Read the full philosophy in [CONTRIBUTING.md](CONTRIBUTING.md). Report bugs via [SECURITY.md](SECURITY.md) or the [issue templates](.github/ISSUE_TEMPLATE/).
104
+
105
+ ### Critical Hotfix — No More Silent Zero-Vector Corruption (v3.8.1)
106
+
107
+ `FastEmbedEmbeddings.__call__` no longer swallows exceptions and returns `[[0.0]*dim, ...]` when the ONNX model fails to load. That bug pre-existed in master but was silent: ChromaDB happily stored zero embeddings, `count()` reported normal numbers, smart-reindex skipped them as "already indexed", and queries returned garbage similarity with no error visible. Now raises `EmbeddingModelLoadError` / `EmbeddingError` loudly. **All v3.8.0 users should upgrade.** Full details in [Changelog](#v381-2026-05-10--hotfix).
108
+
109
+ ### Lazy-Loaded Embeddings — Cheaper Idle Processes (v3.8.0)
110
+
111
+ The FastEmbed ONNX model (~200MB resident) now loads on the **first query**, not at startup. Idle `knowledge-rag` processes are now genuinely cheap. Why this matters: MCP stdio is one-process-per-client by protocol — multiple Claude Code windows, Claude Desktop + IDE simultaneously, or review/approval flows that open extra connections all spawn their own processes. Before v3.8.0, every one of them paid the full embedding-model cost up front. Now only processes that actually serve queries load the model. Public API is unchanged.
112
+
113
+ ### Opt-In Single-Instance Guard (v3.8.0)
114
+
115
+ For users who measured their setup and want a hard cap of one server per `data_dir`:
116
+
117
+ ```bash
118
+ export KNOWLEDGE_RAG_SINGLE_INSTANCE=1
119
+ ```
120
+
121
+ A second instance exits immediately with code 75. **OFF by default** so multi-client MCP usage continues to work unchanged. Stale-PID recovery + SIGINT/SIGTERM cleanup wired correctly. Full guide in [docs/single-instance.md](docs/single-instance.md). Sample MCP config in [examples/mcp-config-single-instance.json](examples/mcp-config-single-instance.json).
122
+
123
+ ### 5 Ways to Install
124
+
125
+ ```bash
126
+ npx -y knowledge-rag # NPM — zero setup, auto-manages Python venv
127
+ pip install knowledge-rag # PyPI — classic Python install
128
+ curl -fsSL .../install.sh | bash # One-line installer (Linux/macOS/Windows)
129
+ docker pull ghcr.io/lyonzin/knowledge-rag # Docker — models pre-downloaded
130
+ git clone ... && pip install -r ... # From source
131
+ ```
132
+
133
+ All methods produce the same MCP server. See [Installation](#installation) for full instructions.
134
+
135
+ ### Recent Highlights
136
+
137
+ - **v4.0.0** — **Enterprise concurrent access**: SSE/HTTP transport (1 server → N clients), thread-safe shared state, optional rate limiting + Prometheus metrics, ChromaDB WAL mode, `--transport` CLI
138
+ - **v3.9.0** — **Quality Gate** activated: 35+ automated PR checks across 7 pillars (Security, Stability, Memory Leak, Versatility, Scalability, Versioning, Quality) + nightly resilience suite (chaos, soak, determinism, mutation)
139
+ - **v3.8.1** — Critical hotfix: loud-fail embeddings (no more silent zero-vector corruption); Windows CI flake erradicated (HF_HUB_OFFLINE + shell:bash + atexit wrapper)
140
+ - **v3.8.0** — Lazy-load embeddings, opt-in single-instance guard, version sync across PyPI/NPM/Docker
141
+ - **v3.6.0** — Multi-language code parsing (C/C++/JS/TS/XML), NPM wrapper, Docker image, automated release pipeline
142
+ - **v3.5.2** — CUDA DLL auto-discovery from pip packages, graceful GPU→CPU fallback, explicit CPU provider (no CUDA noise when `gpu: false`), BASE_DIR resolution fix for editable installs
143
+ - **v3.5.1** — Remove Python `<3.13` upper bound — 3.13 and 3.14 now supported
144
+ - **v3.5.0** — Optional GPU acceleration, supported formats table, full README rewrite
145
+ - **v3.4.3** — MCP stdout save/restore fix (v3.4.2 broke JSON-RPC responses)
146
+ - **v3.4.0** — Persistent model cache, exclude patterns, Jupyter Notebook parser, inotify resilience, MetaTrader support
147
+
148
+ See [Changelog](#changelog) for full history.
149
+
150
+ ---
151
+
152
+ ## Supported Formats
153
+
154
+ | Format | Extension | Parser | Default | Notes |
155
+ |--------|-----------|--------|---------|-------|
156
+ | Markdown | `.md` | Section-aware (splits at `##`) | Yes | Headers preserved as chunk boundaries |
157
+ | Plain Text | `.txt` | Fixed-size chunking | Yes | 1000 chars + 200 overlap |
158
+ | PDF | `.pdf` | PyMuPDF extraction | Yes | Text-based PDFs only (no OCR) |
159
+ | Python | `.py` | Code-aware parser | Yes | Functions/classes as chunks |
160
+ | JSON | `.json` | Structure-aware | Yes | Flattened key-value extraction |
161
+ | CSV | `.csv` | Row-based parser | Yes | Headers + rows as text |
162
+ | Word | `.docx` | python-docx | Yes | Headings preserved as markdown |
163
+ | Excel | `.xlsx` | openpyxl | Yes | Sheet-by-sheet extraction |
164
+ | PowerPoint | `.pptx` | python-pptx | Yes | Slide-by-slide extraction |
165
+ | Jupyter Notebook | `.ipynb` | Cell-aware parser | Yes | Markdown + code cells only, no outputs/base64 |
166
+ | C Source | `.c` | Code-aware parser | Yes | Functions/structs/includes extracted |
167
+ | C/C++ Header | `.h` | Code-aware parser | Yes | Function declarations/structs extracted |
168
+ | C++ Source | `.cpp` | Code-aware parser | Yes | Classes/structs/includes extracted |
169
+ | JavaScript | `.js` | Code-aware parser | Yes | Functions/classes/imports (ESM + CJS) |
170
+ | React JSX | `.jsx` | Code-aware parser | Yes | Same as JS parser |
171
+ | TypeScript | `.ts` | Code-aware parser | Yes | Functions/classes/interfaces/enums/imports |
172
+ | React TSX | `.tsx` | Code-aware parser | Yes | Same as TS parser |
173
+ | XML | `.xml` | XML parser | Yes | Root element and namespace extraction |
174
+ | MQL4 Header | `.mqh` | Code parser | No | MetaTrader — add to `supported_formats` to enable |
175
+ | MQL4 Source | `.mq4` | Code parser | No | MetaTrader — add to `supported_formats` to enable |
176
+
177
+ > **Tip:** The parser dispatch is extensible. Any format mapped in `_parsers` can be enabled via `supported_formats` in config.yaml.
178
+
179
+ ---
180
+
181
+ ## Features
182
+
183
+ | Feature | Description |
184
+ |---------|-------------|
185
+ | **Hybrid Search** | Semantic + BM25 keyword search with Reciprocal Rank Fusion |
186
+ | **Cross-Encoder Reranker** | Xenova/ms-marco-MiniLM-L-6-v2 re-scores top candidates for precision |
187
+ | **GPU Acceleration** | Optional ONNX CUDA support for 5-10x faster indexing |
188
+ | **YAML Configuration** | Fully customizable via `config.yaml` with domain-specific presets |
189
+ | **Query Expansion** | Configurable synonym mappings (69 security-term defaults) |
190
+ | **Markdown-Aware Chunking** | `.md` files split by `##`/`###` sections instead of fixed windows |
191
+ | **In-Process Embeddings** | FastEmbed ONNX Runtime (BAAI/bge-small-en-v1.5, 384D) |
192
+ | **Keyword Routing** | Word-boundary aware routing for domain-specific queries |
193
+ | **20 Format Parsers** | MD, TXT, PDF, PY, C, H, CPP, JS, JSX, TS, TSX, JSON, XML, CSV, DOCX, XLSX, PPTX, IPYNB + opt-in MQH/MQ4 |
194
+ | **Category Organization** | Organize docs by folder, auto-tagged by path |
195
+ | **Incremental Indexing** | Change detection via mtime/size — only re-indexes modified files |
196
+ | **Chunk Deduplication** | SHA256 content hashing prevents duplicate chunks |
197
+ | **Query Cache** | LRU cache with 5-min TTL for instant repeat queries |
198
+ | **Document CRUD** | Add, update, remove documents via MCP tools |
199
+ | **URL Ingestion** | Fetch URLs, strip HTML, convert to markdown, index |
200
+ | **Similarity Search** | Find documents similar to a reference document |
201
+ | **Retrieval Evaluation** | Built-in MRR@5 and Recall@5 metrics |
202
+ | **File Watcher** | Auto-reindex on document changes via watchdog (5s debounce) |
203
+ | **Exclude Patterns** | Glob-based file/directory exclusion during indexing |
204
+ | **MMR Diversification** | Maximal Marginal Relevance reduces redundant results |
205
+ | **Persistent Model Cache** | Embedding models cached in `models_cache/` — survives reboots |
206
+ | **Auto-Migration** | Detects embedding dimension mismatch and rebuilds automatically |
207
+ | **13 MCP Tools** | Full CRUD + search + evaluation via Claude Code |
208
+
209
+ ---
210
+
211
+ ## Architecture
212
+
213
+ ### System Overview
214
+
215
+ ```mermaid
216
+ flowchart TB
217
+ subgraph MCP["MCP SERVER (FastMCP)"]
218
+ direction TB
219
+ TOOLS["13 MCP Tools<br/>search | get | add | update | remove<br/>reindex | reindex_status | list | stats | url | similar | evaluate"]
220
+ end
221
+
222
+ subgraph SEARCH["HYBRID SEARCH ENGINE"]
223
+ direction LR
224
+ ROUTER["Keyword Router<br/>(word boundaries)"]
225
+ SEMANTIC["Semantic Search<br/>(ChromaDB)"]
226
+ BM25["BM25 Keyword<br/>(inverted-index + expansion)"]
227
+ RRF["Reciprocal Rank<br/>Fusion (RRF)"]
228
+ RERANK["Cross-Encoder<br/>Reranker"]
229
+
230
+ ROUTER --> SEMANTIC
231
+ ROUTER --> BM25
232
+ SEMANTIC --> RRF
233
+ BM25 --> RRF
234
+ RRF --> RERANK
235
+ end
236
+
237
+ subgraph STORAGE["STORAGE LAYER"]
238
+ direction LR
239
+ CHROMA[("ChromaDB<br/>Vector Database")]
240
+ COLLECTIONS["Collections<br/>security | ctf<br/>logscale | development"]
241
+ CHROMA --- COLLECTIONS
242
+ end
243
+
244
+ subgraph EMBED["EMBEDDINGS (In-Process)"]
245
+ FASTEMBED["FastEmbed ONNX<br/>BAAI/bge-small-en-v1.5<br/>(384D, CPU or GPU)"]
246
+ CROSSENC["Cross-Encoder<br/>ms-marco-MiniLM-L-6-v2"]
247
+ FASTEMBED --- CROSSENC
248
+ end
249
+
250
+ subgraph INGEST["DOCUMENT INGESTION"]
251
+ PARSERS["20 Parsers<br/>MD | PDF | TXT | PY | C | H | CPP | JS | JSX | TS | TSX | JSON | XML | CSV<br/>DOCX | XLSX | PPTX | IPYNB | MQH | MQ4"]
252
+ CHUNKER["Chunking<br/>MD: section-aware<br/>Other: 1000 chars + 200 overlap"]
253
+ PARSERS --> CHUNKER
254
+ end
255
+
256
+ CLAUDE["Claude Code"] --> MCP
257
+ MCP --> SEARCH
258
+ SEARCH --> STORAGE
259
+ STORAGE --> EMBED
260
+ INGEST --> EMBED
261
+ EMBED --> STORAGE
262
+ ```
263
+
264
+ ### Query Processing Flow
265
+
266
+ ```mermaid
267
+ flowchart TB
268
+ QUERY["User Query<br/>'mimikatz credential dump'"] --> EXPAND
269
+
270
+ subgraph EXPANSION["Query Expansion"]
271
+ EXPAND["Synonym Expansion<br/>mimikatz -> mimikatz, sekurlsa, logonpasswords"]
272
+ end
273
+
274
+ EXPAND --> ROUTER
275
+
276
+ subgraph ROUTING["Keyword Routing"]
277
+ ROUTER["Keyword Router"]
278
+ MATCH{"Word Boundary<br/>Match?"}
279
+ CATEGORY["Filter: redteam"]
280
+ NOFILTER["No Filter"]
281
+
282
+ ROUTER --> MATCH
283
+ MATCH -->|Yes| CATEGORY
284
+ MATCH -->|No| NOFILTER
285
+ end
286
+
287
+ subgraph HYBRID["Hybrid Search"]
288
+ direction LR
289
+ SEMANTIC["Semantic Search<br/>(ChromaDB embeddings)<br/>Conceptual similarity"]
290
+ BM25["BM25 Inverted-Index<br/>(posting lists + numpy top-k)<br/>Exact term matching"]
291
+ end
292
+
293
+ subgraph FUSION["Result Fusion + Reranking"]
294
+ RRF["Reciprocal Rank Fusion<br/>score = alpha * 1/(k+rank_sem)<br/>+ (1-alpha) * 1/(k+rank_bm25)"]
295
+ RERANK["Cross-Encoder Reranker<br/>Re-scores top 3x candidates<br/>query+doc pair scoring"]
296
+ SORT["Sort by Reranker Score<br/>Normalize to 0-1"]
297
+ ADJ["Adjacent Chunk Expansion<br/>(batch fetch ±1 chunk)"]
298
+
299
+ RRF --> RERANK --> SORT --> ADJ
300
+ end
301
+
302
+ subgraph OUTPUT["Output Processing"]
303
+ MINSCORE["min_score Filter<br/>(discard below threshold)"]
304
+ SNIPPET["snippet_mode Truncation<br/>(~500 chars at natural break)"]
305
+
306
+ MINSCORE --> SNIPPET
307
+ end
308
+
309
+ CATEGORY --> HYBRID
310
+ NOFILTER --> HYBRID
311
+ SEMANTIC --> RRF
312
+ BM25 --> RRF
313
+
314
+ ADJ --> MINSCORE
315
+ SNIPPET --> RESULTS["Results<br/>search_method: hybrid|semantic|keyword<br/>score + filtered_by_score + content_length"]
316
+ ```
317
+
318
+ ### Document Ingestion Flow
319
+
320
+ ```mermaid
321
+ flowchart LR
322
+ subgraph INPUT["Input"]
323
+ FILES["documents/<br/>├── security/<br/>├── development/<br/>├── ctf/<br/>└── general/"]
324
+ end
325
+
326
+ subgraph PARSE["Parse (20 formats)"]
327
+ MD["Markdown"]
328
+ PDF["PDF<br/>(PyMuPDF)"]
329
+ OFFICE["DOCX | XLSX<br/>PPTX | CSV"]
330
+ CODE["PY | C | H | CPP | JS | JSX<br/>TS | TSX | JSON | XML | IPYNB"]
331
+ end
332
+
333
+ subgraph CHUNK["Chunk"]
334
+ MDSPLIT["MD: Section-Aware<br/>Split at ## headers"]
335
+ TXTSPLIT["Other: Fixed-Size<br/>1000 chars + 200 overlap"]
336
+ DEDUP["SHA256 Dedup<br/>Skip duplicate content"]
337
+ end
338
+
339
+ subgraph EMBED["Embed"]
340
+ FASTEMBED["FastEmbed ONNX<br/>bge-small-en-v1.5<br/>(384D, CPU or GPU)"]
341
+ end
342
+
343
+ subgraph STORE["Store"]
344
+ CHROMADB[("ChromaDB")]
345
+ BM25IDX["BM25 Index"]
346
+ end
347
+
348
+ FILES --> MD & PDF & OFFICE & CODE
349
+ MD --> MDSPLIT
350
+ PDF & OFFICE & CODE --> TXTSPLIT
351
+ MDSPLIT --> DEDUP
352
+ TXTSPLIT --> DEDUP
353
+ DEDUP --> EMBED
354
+ EMBED --> STORE
355
+ ```
356
+
357
+ ### hybrid_alpha Parameter Effect
358
+
359
+ ```mermaid
360
+ flowchart LR
361
+ subgraph ALPHA["hybrid_alpha values"]
362
+ A0["0.0<br/>Pure BM25<br/>Instant"]
363
+ A3["0.3 (default)<br/>Keyword-heavy<br/>Fast"]
364
+ A5["0.5<br/>Balanced"]
365
+ A7["0.7<br/>Semantic-heavy"]
366
+ A10["1.0<br/>Pure Semantic"]
367
+ end
368
+
369
+ subgraph USE["Best For"]
370
+ U0["CVEs, tool names<br/>exact matches"]
371
+ U3["Technical queries<br/>specific terms"]
372
+ U5["General queries"]
373
+ U7["Conceptual queries<br/>related topics"]
374
+ U10["'How to...' questions<br/>conceptual search"]
375
+ end
376
+
377
+ A0 --- U0
378
+ A3 --- U3
379
+ A5 --- U5
380
+ A7 --- U7
381
+ A10 --- U10
382
+ ```
383
+
384
+ ---
385
+
386
+ ## Installation
387
+
388
+ ### Prerequisites
389
+
390
+ - Python 3.11+
391
+ - Claude Code CLI
392
+ - *…or any other MCP client (Claude Desktop, Cursor, VS Code, Antigravity, opencode, Windsurf) — see [Use with other MCP clients](#use-with-other-mcp-clients)*
393
+ - ~200MB disk for model cache (auto-downloaded on first run)
394
+ - *Optional:* NVIDIA GPU + CUDA 12 for accelerated embeddings (see [GPU Acceleration](#gpu-acceleration) below)
395
+
396
+ ### GPU Acceleration
397
+
398
+ GPU mode accelerates embedding generation during indexing and search. It requires an NVIDIA GPU with CUDA 12 support. No GPU? No problem — the server runs on CPU by default and GPU is entirely optional.
399
+
400
+ **Requirements:**
401
+
402
+ | Component | Minimum | How to check / get it |
403
+ |-----------|---------|----------------------|
404
+ | NVIDIA GPU (Turing+) | RTX 20xx / 30xx / 40xx / 50xx, or Tesla T4+ | `nvidia-smi` |
405
+ | NVIDIA Driver | ≥ 525 | `nvidia-smi` — [nvidia.com/drivers](https://www.nvidia.com/drivers) |
406
+ | CUDA 12 runtime | Provided by pip packages below | Automatic |
407
+
408
+ **Setup (2 steps):**
409
+
410
+ ```bash
411
+ # 1. Install GPU dependencies (onnxruntime-gpu + all CUDA 12 runtime DLLs)
412
+ pip install knowledge-rag[gpu]
413
+
414
+ # 2. Enable in config.yaml
415
+ # models:
416
+ # embedding:
417
+ # gpu: true
418
+ ```
419
+
420
+ The `[gpu]` extra installs `onnxruntime-gpu` plus 7 NVIDIA CUDA 12 packages (`cublas`, `cudnn`, `cuda-runtime`, `cufft`, `cusparse`, `cusolver`, `curand`, `nvjitlink`) so you don't need a full CUDA Toolkit install.
421
+
422
+ **Verify GPU is active:**
423
+
424
+ On server startup, look for the GPU status banner:
425
+ ```
426
+ ============================================================
427
+ GPU STATUS: ACTIVE
428
+ Provider: CUDAExecutionProvider
429
+ Device: NVIDIA GeForce RTX 3080 Ti
430
+ VRAM: 12.0 GB
431
+ ============================================================
432
+ ```
433
+
434
+ Or programmatically:
435
+ ```bash
436
+ python -c "import onnxruntime; print(onnxruntime.get_available_providers())"
437
+ # Should include: 'CUDAExecutionProvider'
438
+ ```
439
+
440
+ > **Fallback**: If CUDA is unavailable at runtime (wrong driver, missing DLLs, no GPU), the server falls back to CPU automatically with a `[WARN]` log — it never crashes. The `gpu: true` config is a preference, not a requirement.
441
+
442
+ ### Install Methods
443
+
444
+ Pick one — all produce the same running server.
445
+
446
+ #### Option A: NPX (fastest)
447
+
448
+ Requires Node.js 16+. Handles Python venv, pip install, and version upgrades automatically.
449
+
450
+ ```bash
451
+ claude mcp add knowledge-rag -s user -- npx -y knowledge-rag
452
+ ```
453
+
454
+ That's it. On first run, `npx` creates a venv at `~/.knowledge-rag/`, installs the PyPI package, and starts the MCP server. Subsequent runs reuse the cached venv.
455
+
456
+ #### Option B: One-line installer
457
+
458
+ ```bash
459
+ # Linux/macOS:
460
+ curl -fsSL https://raw.githubusercontent.com/lyonzin/knowledge-rag/master/install.sh | bash
461
+
462
+ # Windows (PowerShell):
463
+ irm https://raw.githubusercontent.com/lyonzin/knowledge-rag/master/install.ps1 | iex
464
+ ```
465
+
466
+ Then configure Claude Code:
467
+
468
+ ```bash
469
+ claude mcp add knowledge-rag -s user -- ~/knowledge-rag/venv/bin/python -m mcp_server.server
470
+ ```
471
+
472
+ > **Windows**: `claude mcp add knowledge-rag -s user -- %USERPROFILE%\knowledge-rag\venv\Scripts\python.exe -m mcp_server.server`
473
+
474
+ #### Option C: pip install
475
+
476
+ ```bash
477
+ mkdir ~/knowledge-rag && cd ~/knowledge-rag
478
+ python3 -m venv venv && source venv/bin/activate
479
+ pip install knowledge-rag
480
+ knowledge-rag init # Exports config template, presets, creates documents/
481
+ ```
482
+
483
+ Then configure Claude Code:
484
+
485
+ ```bash
486
+ claude mcp add knowledge-rag -s user -- ~/knowledge-rag/venv/bin/python -m mcp_server.server
487
+ ```
488
+
489
+ > **Windows users**: Use `python` instead of `python3`, `venv\Scripts\activate` instead of `source venv/bin/activate`.
490
+ > **Windows path**: `claude mcp add knowledge-rag -s user -- %USERPROFILE%\knowledge-rag\venv\Scripts\python.exe -m mcp_server.server`
491
+
492
+ #### Option D: Clone from source
493
+
494
+ ```bash
495
+ git clone https://github.com/lyonzin/knowledge-rag.git ~/knowledge-rag
496
+ cd ~/knowledge-rag
497
+ python3 -m venv venv && source venv/bin/activate
498
+ pip install -r requirements.txt
499
+ ```
500
+
501
+ Then configure Claude Code:
502
+
503
+ ```bash
504
+ claude mcp add knowledge-rag -s user -- ~/knowledge-rag/venv/bin/python -m mcp_server.server
505
+ ```
506
+
507
+ #### Option E: Docker
508
+
509
+ ```bash
510
+ docker pull ghcr.io/lyonzin/knowledge-rag:latest
511
+ ```
512
+
513
+ ```bash
514
+ claude mcp add knowledge-rag -s user -- \
515
+ docker run -i --rm \
516
+ -v ~/knowledge-rag/documents:/app/documents \
517
+ -v ~/knowledge-rag/data:/app/data \
518
+ ghcr.io/lyonzin/knowledge-rag:latest
519
+ ```
520
+
521
+ Models are pre-downloaded in the image — no first-run delay.
522
+
523
+ <details>
524
+ <summary>Alternative: manual JSON config</summary>
525
+
526
+ Add to `~/.claude.json`:
527
+
528
+ **Windows:**
529
+ ```json
530
+ {
531
+ "mcpServers": {
532
+ "knowledge-rag": {
533
+ "command": "C:\\Users\\YOUR_USER\\knowledge-rag\\venv\\Scripts\\python.exe",
534
+ "args": ["-m", "mcp_server.server"]
535
+ }
536
+ }
537
+ }
538
+ ```
539
+
540
+ **Linux / macOS:**
541
+ ```json
542
+ {
543
+ "mcpServers": {
544
+ "knowledge-rag": {
545
+ "command": "/home/YOUR_USER/knowledge-rag/venv/bin/python",
546
+ "args": ["-m", "mcp_server.server"]
547
+ }
548
+ }
549
+ }
550
+ ```
551
+ > Replace `YOUR_USER` with your username, or use the full path from `echo $HOME`.
552
+ </details>
553
+
554
+ #### Option F: SSE Server Mode (multi-agent)
555
+
556
+ For multi-agent setups where multiple clients query the same knowledge base simultaneously:
557
+
558
+ ```bash
559
+ pip install knowledge-rag[server] # Adds uvicorn for SSE/HTTP
560
+ knowledge-rag --transport sse # Starts on http://127.0.0.1:8179
561
+ ```
562
+
563
+ Then configure each MCP client to connect via SSE:
564
+
565
+ ```json
566
+ {
567
+ "mcpServers": {
568
+ "knowledge-rag": {
569
+ "type": "sse",
570
+ "url": "http://127.0.0.1:8179/sse"
571
+ }
572
+ }
573
+ }
574
+ ```
575
+
576
+ One server process serves all agents — shared embedding model, shared cache, shared ChromaDB. See [Configuration > Server](#server) for rate limiting, metrics, and auth options.
577
+
578
+ ### Use with other MCP clients
579
+
580
+ `knowledge-rag` supports both **stdio** (default, 1:1) and **SSE** (1:N) transport modes. In stdio mode, it works with any MCP-compatible client, not only Claude Code. The launch command is the same everywhere (the `python -m mcp_server.server` from whichever install method you picked); only the **config file location** and **JSON shape** differ per client.
581
+
582
+ #### Clients using the standard `mcpServers` format
583
+
584
+ For **Claude Desktop, Cursor, Antigravity, and Windsurf**, use the same block — only the file location changes:
585
+
586
+ ```json
587
+ {
588
+ "mcpServers": {
589
+ "knowledge-rag": {
590
+ "command": "/home/YOUR_USER/knowledge-rag/venv/bin/python",
591
+ "args": ["-m", "mcp_server.server"]
592
+ }
593
+ }
594
+ }
595
+ ```
596
+
597
+ > **Windows**: set `command` to the full path of `venv\Scripts\python.exe`.
598
+
599
+ | Client | Config file | Notes |
600
+ |---|---|---|
601
+ | **Claude Code** | use `claude mcp add …` (see install methods above) | The CLI writes `~/.claude.json` for you — manual edits to it aren't reliably picked up. |
602
+ | **Claude Desktop** | macOS: `~/Library/Application Support/Claude/claude_desktop_config.json` · Windows: `%APPDATA%\Claude\claude_desktop_config.json` | Easiest: **Settings → Developer → Edit Config** opens the correct file (avoids the Windows Store/MSIX path quirk). |
603
+ | **Cursor** | `~/.cursor/mcp.json` (global) or `.cursor/mcp.json` (per project) | — |
604
+ | **Antigravity** | macOS/Linux: `~/.gemini/antigravity/mcp_config.json` · Windows: `%USERPROFILE%\.gemini\antigravity\mcp_config.json` | Open via Agent panel → **"…" → Manage MCP Servers → View raw config**. |
605
+ | **Windsurf** | `~/.codeium/windsurf/mcp_config.json` (global only) | Easiest: Cascade panel → MCP → **View raw config**. |
606
+
607
+ #### VS Code — uses a `servers` key
608
+
609
+ VS Code (Copilot MCP) nests servers under **`servers`**, not `mcpServers`. Put this in `.vscode/mcp.json` (workspace) or the file opened by the **MCP: Open User Configuration** command:
610
+
611
+ ```json
612
+ {
613
+ "servers": {
614
+ "knowledge-rag": {
615
+ "type": "stdio",
616
+ "command": "/home/YOUR_USER/knowledge-rag/venv/bin/python",
617
+ "args": ["-m", "mcp_server.server"]
618
+ }
619
+ }
620
+ }
621
+ ```
622
+
623
+ #### opencode — uses an `mcp` key
624
+
625
+ opencode nests servers under **`mcp`**, takes `command` as a single **array**, and uses `environment` instead of `env`. Put this in `opencode.json` (project root) or `~/.config/opencode/opencode.json` (global):
626
+
627
+ ```jsonc
628
+ {
629
+ "$schema": "https://opencode.ai/config.json",
630
+ "mcp": {
631
+ "knowledge-rag": {
632
+ "type": "local",
633
+ "command": ["/home/YOUR_USER/knowledge-rag/venv/bin/python", "-m", "mcp_server.server"],
634
+ "enabled": true
635
+ }
636
+ }
637
+ }
638
+ ```
639
+
640
+ > **Any other MCP client**: point it at the same command + args (`…/venv/bin/python -m mcp_server.server`). If it speaks stdio MCP, knowledge-rag works — only the config file's location and key naming differ. Check your client's docs for the exact path.
641
+
642
+ ### Verify
643
+
644
+ ```bash
645
+ claude mcp list
646
+ ```
647
+
648
+ On first start, the server will:
649
+ 1. Download the embedding model (~50MB, cached in `models_cache/`)
650
+ 2. Auto-index any documents in the `documents/` directory
651
+ 3. Start watching for file changes (auto-reindex)
652
+
653
+ ---
654
+
655
+ ## Usage
656
+
657
+ ### Adding Documents
658
+
659
+ Place your documents in the `documents/` directory, organized by category:
660
+
661
+ ```
662
+ documents/
663
+ ├── security/ # Pentest, exploit, vulnerability docs
664
+ ├── development/ # Code, APIs, frameworks
665
+ ├── ctf/ # CTF writeups and methodology
666
+ ├── logscale/ # LogScale/LQL documentation
667
+ └── general/ # Everything else
668
+ ```
669
+
670
+ Or add documents programmatically via MCP tools:
671
+
672
+ ```python
673
+ # Add from content
674
+ add_document(
675
+ content="# My Document\n\nContent here...",
676
+ filepath="security/my-technique.md",
677
+ category="security"
678
+ )
679
+
680
+ # Add from URL
681
+ add_from_url(
682
+ url="https://example.com/article",
683
+ category="security",
684
+ title="Custom Title"
685
+ )
686
+ ```
687
+
688
+ ### Searching
689
+
690
+ Claude uses the RAG system automatically when configured. You can also control search behavior:
691
+
692
+ ```python
693
+ # Pure keyword search — instant, no embedding needed
694
+ search_knowledge("gtfobins suid", hybrid_alpha=0.0)
695
+
696
+ # Keyword-heavy (default) — fast, slight semantic boost
697
+ search_knowledge("mimikatz", hybrid_alpha=0.3)
698
+
699
+ # Balanced hybrid — both engines equally weighted
700
+ search_knowledge("SQL injection techniques", hybrid_alpha=0.5)
701
+
702
+ # Semantic-heavy — better for conceptual queries
703
+ search_knowledge("how to escalate privileges", hybrid_alpha=0.7)
704
+
705
+ # Pure semantic — embedding similarity only
706
+ search_knowledge("lateral movement strategies", hybrid_alpha=1.0)
707
+ ```
708
+
709
+ ### Indexing
710
+
711
+ Documents are automatically indexed on first startup. All reindex operations run **in background** — they return immediately and you poll progress via `get_reindex_status()`:
712
+
713
+ ```python
714
+ # Incremental: only re-index changed files (fast)
715
+ reindex_documents()
716
+
717
+ # Smart reindex: detect changes + rebuild BM25
718
+ reindex_documents(force=True)
719
+
720
+ # Nuclear rebuild: delete everything, re-embed all (use after model change)
721
+ reindex_documents(full_rebuild=True)
722
+
723
+ # Poll progress (lightweight, no full stats computation)
724
+ get_reindex_status()
725
+ # → {"reindex": {"active": true, "percent": 56, "progress": "2090/3734", ...}}
726
+ ```
727
+
728
+ ### Evaluating Retrieval Quality
729
+
730
+ ```python
731
+ evaluate_retrieval(test_cases='[
732
+ {"query": "sql injection", "expected_filepath": "security/sqli-guide.md"},
733
+ {"query": "privilege escalation", "expected_filepath": "security/privesc.md"}
734
+ ]')
735
+ # Returns: MRR@5, Recall@5, per-query results
736
+ ```
737
+
738
+ ---
739
+
740
+ ## API Reference
741
+
742
+ ### Search & Query
743
+
744
+ #### `search_knowledge`
745
+
746
+ Hybrid search combining semantic search + BM25 keyword search with cross-encoder reranking.
747
+
748
+ | Parameter | Type | Default | Description |
749
+ |-----------|------|---------|-------------|
750
+ | `query` | string | required | Search query text (1-3 keywords recommended) |
751
+ | `max_results` | int | 5 | Maximum results to return (1-20) |
752
+ | `category` | string | null | Filter by category |
753
+ | `hybrid_alpha` | float | 0.3 | Balance: 0.0 = keyword only, 1.0 = semantic only |
754
+ | `min_score` | float | 0.0 | Minimum relevance score (0.0-1.0) to include a result. Use 0.2-0.4 to cut noise |
755
+ | `snippet_mode` | bool | true | Truncate content to ~500 chars at natural break points. Adds `content_length` field |
756
+
757
+ **Returns:**
758
+
759
+ ```json
760
+ {
761
+ "status": "success",
762
+ "query": "mimikatz credential dump",
763
+ "hybrid_alpha": 0.5,
764
+ "result_count": 3,
765
+ "filtered_by_score": 2,
766
+ "cache_hit_rate": "0.0%",
767
+ "results": [
768
+ {
769
+ "content": "Mimikatz can extract credentials from memory...",
770
+ "source": "documents/security/credential-attacks.md",
771
+ "filename": "credential-attacks.md",
772
+ "category": "security",
773
+ "score": 0.9823,
774
+ "raw_rrf_score": 0.016393,
775
+ "reranker_score": 0.987654,
776
+ "semantic_rank": 2,
777
+ "bm25_rank": 1,
778
+ "search_method": "hybrid",
779
+ "keywords": ["mimikatz", "credential", "lsass"],
780
+ "routed_by": "redteam"
781
+ }
782
+ ]
783
+ }
784
+ ```
785
+
786
+ **Search Method Values:**
787
+ - `hybrid`: Found by both semantic and BM25 search (highest confidence)
788
+ - `semantic`: Found only by semantic search
789
+ - `keyword`: Found only by BM25 keyword search
790
+
791
+ ---
792
+
793
+ #### `get_document`
794
+
795
+ Retrieve the full content of a specific document.
796
+
797
+ | Parameter | Type | Description |
798
+ |-----------|------|-------------|
799
+ | `filepath` | string | Path to the document file |
800
+
801
+ **Returns:** JSON with document content, metadata, keywords, and chunk count.
802
+
803
+ ---
804
+
805
+ #### `reindex_documents`
806
+
807
+ Index or reindex all documents in the knowledge base. **Runs in background** — returns immediately. Poll progress via `get_reindex_status()`.
808
+
809
+ | Parameter | Type | Default | Description |
810
+ |-----------|------|---------|-------------|
811
+ | `force` | bool | false | Smart reindex: detects changes, rebuilds BM25. Fast. |
812
+ | `full_rebuild` | bool | false | Nuclear rebuild: deletes everything, re-embeds all documents. Use after model change. |
813
+
814
+ **Returns:** `{"status": "started", "operation": "..."}` immediately. If already running, returns `{"status": "already_running", "progress": "1200/3734"}`.
815
+
816
+ ---
817
+
818
+ #### `get_reindex_status`
819
+
820
+ Get the current status of a background reindex operation. Lightweight — does not compute full index statistics.
821
+
822
+ **Returns (active):**
823
+ ```json
824
+ {
825
+ "status": "success",
826
+ "reindex": {
827
+ "active": true,
828
+ "operation": "nuclear_rebuild",
829
+ "progress": "1200/3734",
830
+ "percent": 32,
831
+ "indexed": 1200,
832
+ "skipped": 0,
833
+ "errors": 0,
834
+ "started_at": "2026-06-17T18:29:49"
835
+ }
836
+ }
837
+ ```
838
+
839
+ **Returns (idle):** `{"status": "success", "reindex": {"active": false}}`
840
+
841
+ ---
842
+
843
+ #### `list_categories`
844
+
845
+ List all document categories with their document counts.
846
+
847
+ **Returns:**
848
+
849
+ ```json
850
+ {
851
+ "status": "success",
852
+ "categories": {
853
+ "security": 52,
854
+ "development": 8,
855
+ "ctf": 12,
856
+ "general": 3
857
+ },
858
+ "total_documents": 75
859
+ }
860
+ ```
861
+
862
+ ---
863
+
864
+ #### `list_documents`
865
+
866
+ List all indexed documents, optionally filtered by category.
867
+
868
+ | Parameter | Type | Description |
869
+ |-----------|------|-------------|
870
+ | `category` | string | Optional category filter |
871
+
872
+ **Returns:** JSON array of documents with id, source, category, format, chunks, and keywords.
873
+
874
+ ---
875
+
876
+ #### `get_index_stats`
877
+
878
+ Get statistics about the knowledge base index.
879
+
880
+ **Returns:**
881
+
882
+ ```json
883
+ {
884
+ "status": "success",
885
+ "stats": {
886
+ "total_documents": 75,
887
+ "total_chunks": 9256,
888
+ "categories": {"security": 52, "development": 8},
889
+ "supported_formats": [".md", ".txt", ".pdf", ".py", ".json", ".docx", ".xlsx", ".pptx", ".csv", ".ipynb"],
890
+ "embedding_model": "BAAI/bge-small-en-v1.5",
891
+ "embedding_dim": 384,
892
+ "reranker_model": "Xenova/ms-marco-MiniLM-L-6-v2",
893
+ "chunk_size": 1000,
894
+ "chunk_overlap": 200,
895
+ "query_cache": {
896
+ "size": 12,
897
+ "max_size": 100,
898
+ "ttl_seconds": 300,
899
+ "hits": 45,
900
+ "misses": 23,
901
+ "hit_rate": "66.2%"
902
+ }
903
+ }
904
+ }
905
+ ```
906
+
907
+ ---
908
+
909
+ ### Document Management
910
+
911
+ #### `add_document`
912
+
913
+ Add a new document to the knowledge base from raw content. Saves the file to the documents directory and indexes it immediately.
914
+
915
+ | Parameter | Type | Default | Description |
916
+ |-----------|------|---------|-------------|
917
+ | `content` | string | required | Full text content of the document |
918
+ | `filepath` | string | required | Relative path within documents dir (e.g., `security/new-technique.md`) |
919
+ | `category` | string | "general" | Document category |
920
+
921
+ ---
922
+
923
+ #### `update_document`
924
+
925
+ Update an existing document. Removes old chunks from the index and re-indexes with new content.
926
+
927
+ | Parameter | Type | Description |
928
+ |-----------|------|-------------|
929
+ | `filepath` | string | Full path to the document file |
930
+ | `content` | string | New content for the document |
931
+
932
+ ---
933
+
934
+ #### `remove_document`
935
+
936
+ Remove a document from the knowledge base index. Optionally deletes the file from disk.
937
+
938
+ | Parameter | Type | Default | Description |
939
+ |-----------|------|---------|-------------|
940
+ | `filepath` | string | required | Path to the document file |
941
+ | `delete_file` | bool | false | If true, also delete the file from disk |
942
+
943
+ ---
944
+
945
+ #### `add_from_url`
946
+
947
+ Fetch content from a URL, strip HTML (scripts, styles, nav, footer, header), convert to markdown, and add to the knowledge base.
948
+
949
+ | Parameter | Type | Default | Description |
950
+ |-----------|------|---------|-------------|
951
+ | `url` | string | required | URL to fetch content from |
952
+ | `category` | string | "general" | Document category |
953
+ | `title` | string | null | Custom title (auto-detected from `<title>` tag if not provided) |
954
+
955
+ ---
956
+
957
+ #### `search_similar`
958
+
959
+ Find documents similar to a given document using embedding similarity.
960
+
961
+ | Parameter | Type | Default | Description |
962
+ |-----------|------|---------|-------------|
963
+ | `filepath` | string | required | Path to the reference document |
964
+ | `max_results` | int | 5 | Number of similar documents to return (1-20) |
965
+
966
+ ---
967
+
968
+ #### `evaluate_retrieval`
969
+
970
+ Evaluate retrieval quality with test queries. Useful for tuning `hybrid_alpha`, testing query expansion effectiveness, or validating after reindexing.
971
+
972
+ | Parameter | Type | Description |
973
+ |-----------|------|-------------|
974
+ | `test_cases` | string (JSON) | Array of test cases: `[{"query": "...", "expected_filepath": "..."}, ...]` |
975
+
976
+ **Metrics:**
977
+ - **MRR@5** (Mean Reciprocal Rank): Average of 1/rank for expected documents. 1.0 = always first result.
978
+ - **Recall@5**: Fraction of expected documents found in top 5 results. 1.0 = all found.
979
+
980
+ ---
981
+
982
+ ## Configuration
983
+
984
+ Knowledge RAG is fully configurable via a `config.yaml` file in the project root. If no `config.yaml` exists, sensible defaults are used — the system works out of the box with zero configuration.
985
+
986
+ ### Quick Start
987
+
988
+ ```bash
989
+ # Option 1: Use a preset
990
+ cp presets/cybersecurity.yaml config.yaml # Offensive/defensive security, CTFs
991
+ cp presets/developer.yaml config.yaml # Software engineering, APIs, DevOps
992
+ cp presets/research.yaml config.yaml # Academic research, papers, studies
993
+ cp presets/general.yaml config.yaml # Blank slate, pure semantic search
994
+
995
+ # Option 2: Start from the documented template
996
+ cp config.example.yaml config.yaml
997
+ # Edit config.yaml to your needs
998
+ ```
999
+
1000
+ Restart Claude Code after changing `config.yaml`.
1001
+
1002
+ ### config.yaml Structure
1003
+
1004
+ ```yaml
1005
+ # Paths — where your documents live
1006
+ paths:
1007
+ documents_dir: "./documents" # Scanned recursively
1008
+ data_dir: "./data" # Index storage
1009
+ models_cache_dir: "./models_cache" # Persistent embedding model cache
1010
+
1011
+ # Documents — what gets indexed and how
1012
+ documents:
1013
+ supported_formats: # File types to index
1014
+ - .md
1015
+ - .txt
1016
+ - .pdf
1017
+ - .docx
1018
+ - .ipynb
1019
+ # - .py # Uncomment to index code
1020
+ exclude_patterns: # Glob patterns to skip
1021
+ - "node_modules"
1022
+ - ".venv"
1023
+ - "__pycache__"
1024
+ chunking:
1025
+ chunk_size: 1000 # Max chars per chunk
1026
+ chunk_overlap: 200 # Shared chars between chunks
1027
+
1028
+ # Models — AI models for search (all run locally, no API keys)
1029
+ models:
1030
+ embedding:
1031
+ model: "BAAI/bge-small-en-v1.5" # ONNX, ~33MB, auto-downloaded
1032
+ dimensions: 384
1033
+ gpu: false # Set true + pip install knowledge-rag[gpu]
1034
+ reranker:
1035
+ enabled: true # Falls back to RRF if model is unavailable
1036
+ model: "Xenova/ms-marco-MiniLM-L-6-v2"
1037
+ top_k_multiplier: 3 # Candidates fetched before reranking
1038
+
1039
+ # Search — result limits and collection name
1040
+ search:
1041
+ default_results: 5
1042
+ max_results: 20
1043
+ collection_name: "knowledge_base" # Change for separate knowledge bases
1044
+
1045
+ # Categories — auto-tag documents by folder path
1046
+ # Set to {} to disable categorization entirely
1047
+ category_mappings:
1048
+ "security/redteam": "redteam"
1049
+ "security/blueteam": "blueteam"
1050
+ "notes": "notes"
1051
+
1052
+ # Keyword routing — prioritize categories based on query keywords
1053
+ # Set to {} for pure semantic search with no routing bias
1054
+ keyword_routes:
1055
+ redteam:
1056
+ - pentest
1057
+ - exploit
1058
+ - privilege escalation
1059
+
1060
+ # Query expansion — expand abbreviations for better BM25 recall
1061
+ # Set to {} for no expansion (search terms used as-is)
1062
+ query_expansions:
1063
+ sqli:
1064
+ - sql injection
1065
+ - sqli
1066
+ privesc:
1067
+ - privilege escalation
1068
+ - privesc
1069
+
1070
+ # Server — enterprise features (new in v4.0.0)
1071
+ server:
1072
+ transport: "stdio" # "stdio" | "sse" | "streamable-http"
1073
+ host: "127.0.0.1" # Bind address (SSE/HTTP only)
1074
+ port: 8179 # Bind port (SSE/HTTP only)
1075
+ auth:
1076
+ bearer_token: "" # Set a secret to enable auth (SSE/HTTP only)
1077
+ rate_limit:
1078
+ enabled: false
1079
+ requests_per_minute: 60
1080
+ burst: 10
1081
+ metrics:
1082
+ enabled: false
1083
+ port: 9179 # Separate port for Prometheus scraping
1084
+ ```
1085
+
1086
+ > See `config.example.yaml` for the fully documented template with explanations for every field.
1087
+
1088
+ ### Presets
1089
+
1090
+ Pre-built configurations for common use cases:
1091
+
1092
+ | Preset | File | Categories | Keywords | Expansions | Best For |
1093
+ |--------|------|-----------|----------|-----------|----------|
1094
+ | **Cybersecurity** | `presets/cybersecurity.yaml` | 8 | 200+ | 69 | Red/Blue Team, CTFs, threat hunting, exploit dev |
1095
+ | **Developer** | `presets/developer.yaml` | 9 | 150+ | 50+ | Full-stack dev, APIs, DevOps, cloud, databases |
1096
+ | **Research** | `presets/research.yaml` | 9 | 100+ | 40+ | Academic papers, thesis, lab notebooks, datasets |
1097
+ | **General** | `presets/general.yaml` | 0 | 0 | 0 | Blank slate — pure semantic search, no domain logic |
1098
+
1099
+ **Creating your own preset**: Copy `config.example.yaml`, fill in your categories/keywords/expansions, save to `presets/your-domain.yaml`.
1100
+
1101
+ ### Configuration Reference
1102
+
1103
+ #### Server
1104
+
1105
+ | Field | Default | Description |
1106
+ |-------|---------|-------------|
1107
+ | `server.transport` | `"stdio"` | Transport protocol: `"stdio"`, `"sse"`, or `"streamable-http"` |
1108
+ | `server.host` | `"127.0.0.1"` | Bind address for SSE/HTTP mode |
1109
+ | `server.port` | `8179` | Bind port for SSE/HTTP mode |
1110
+ | `server.auth.bearer_token` | `""` (disabled) | Bearer token for SSE/HTTP auth. Empty = no auth |
1111
+ | `server.rate_limit.enabled` | `false` | Enable per-client rate limiting |
1112
+ | `server.rate_limit.requests_per_minute` | `60` | Max requests per minute |
1113
+ | `server.rate_limit.burst` | `10` | Burst allowance above steady rate |
1114
+ | `server.metrics.enabled` | `false` | Enable Prometheus `/metrics` endpoint |
1115
+ | `server.metrics.port` | `9179` | Port for metrics scraping |
1116
+
1117
+ In stdio mode (default), server settings are ignored. SSE/HTTP mode auto-enables the single-instance lock.
1118
+
1119
+ #### Paths
1120
+
1121
+ | Field | Default | Description |
1122
+ |-------|---------|-------------|
1123
+ | `paths.documents_dir` | `./documents` | Root folder scanned recursively for documents |
1124
+ | `paths.data_dir` | `./data` | Internal storage for ChromaDB and index metadata |
1125
+ | `paths.models_cache_dir` | `./models_cache` | Persistent cache for embedding models (~250MB). Survives reboots |
1126
+
1127
+ Relative paths resolve from the project root. Absolute paths work too.
1128
+
1129
+ #### Documents
1130
+
1131
+ | Field | Default | Description |
1132
+ |-------|---------|-------------|
1133
+ | `documents.supported_formats` | .md .txt .pdf .py .json .docx .xlsx .pptx .csv .ipynb | File extensions to index |
1134
+ | `documents.exclude_patterns` | `[]` (empty) | Glob patterns for files/dirs to skip during indexing |
1135
+ | `documents.chunking.chunk_size` | 1000 | Max characters per chunk |
1136
+ | `documents.chunking.chunk_overlap` | 200 | Characters shared between consecutive chunks |
1137
+
1138
+ **Chunking guidelines**: Short notes → 500/100. General use → 1000/200. Long technical docs → 1500/300.
1139
+
1140
+ For `.md` files, chunking splits at `##` and `###` header boundaries first. Sections larger than `chunk_size` are sub-chunked with overlap. Non-markdown files use fixed-size chunking.
1141
+
1142
+ #### Models
1143
+
1144
+ | Field | Default | Description |
1145
+ |-------|---------|-------------|
1146
+ | `models.embedding.model` | `BAAI/bge-small-en-v1.5` | Embedding model (ONNX, runs locally) |
1147
+ | `models.embedding.dimensions` | 384 | Vector dimensions (must match model) |
1148
+ | `models.embedding.gpu` | false | Enable CUDA GPU acceleration. See [GPU Acceleration](#gpu-acceleration) for full setup |
1149
+ | `models.reranker.enabled` | true | Enable cross-encoder reranking |
1150
+ | `models.reranker.model` | `Xenova/ms-marco-MiniLM-L-6-v2` | Reranker model |
1151
+ | `models.reranker.top_k_multiplier` | 3 | Fetch N*multiplier candidates for reranking |
1152
+
1153
+ If the reranker model is not available locally and the machine cannot download it, search now falls back to the RRF order from hybrid semantic+BM25 retrieval. This keeps `search_knowledge` available offline, but result ordering may be less precise for ambiguous queries until the reranker model is cached.
1154
+
1155
+ **Embedding model options** (fastest → most accurate):
1156
+ - `BAAI/bge-small-en-v1.5` — 384D, ~33MB (default)
1157
+ - `BAAI/bge-base-en-v1.5` — 768D, ~130MB
1158
+ - `BAAI/bge-large-en-v1.5` — 1024D, ~335MB
1159
+ - `intfloat/multilingual-e5-small` — 384D, 100+ languages
1160
+
1161
+ > **Warning**: Changing the embedding model after indexing requires `reindex_documents(full_rebuild=True)`.
1162
+
1163
+ #### Search
1164
+
1165
+ | Field | Default | Description |
1166
+ |-------|---------|-------------|
1167
+ | `search.default_results` | 5 | Results returned when no limit specified |
1168
+ | `search.max_results` | 20 | Hard cap even if client requests more |
1169
+ | `search.collection_name` | `knowledge_base` | ChromaDB collection — change for separate KBs |
1170
+
1171
+ #### Categories
1172
+
1173
+ Map folder paths to category names. Documents in matching folders get auto-tagged, enabling filtered searches.
1174
+
1175
+ ```yaml
1176
+ category_mappings:
1177
+ "security/redteam": "redteam"
1178
+ "security": "security"
1179
+ ```
1180
+
1181
+ Set `category_mappings: {}` to disable — documents are still searchable, just without category filters.
1182
+
1183
+ #### Keyword Routing
1184
+
1185
+ Route queries to categories based on keywords. When a query contains listed keywords, results from that category are prioritized (not filtered — other categories still appear, ranked lower).
1186
+
1187
+ ```yaml
1188
+ keyword_routes:
1189
+ redteam:
1190
+ - pentest
1191
+ - exploit
1192
+ - sqli
1193
+ ```
1194
+
1195
+ Single-word keywords use regex word boundaries (`\b`) — "api" won't match "RAPID". Multi-word keywords use substring matching.
1196
+
1197
+ Set `keyword_routes: {}` for pure semantic search.
1198
+
1199
+ #### Query Expansion
1200
+
1201
+ Expand search terms with synonyms before BM25 search. Supports single tokens, bigrams, and full query matches.
1202
+
1203
+ ```yaml
1204
+ query_expansions:
1205
+ sqli:
1206
+ - sql injection
1207
+ - sqli
1208
+ k8s:
1209
+ - kubernetes
1210
+ - k8s
1211
+ ```
1212
+
1213
+ Set `query_expansions: {}` for no expansion.
1214
+
1215
+ `query_expansions` is directional: only the key on the left triggers the terms on the right. If you need mutual expansion without duplicating entries, use `query_expansion_groups`.
1216
+
1217
+ ```yaml
1218
+ query_expansion_groups:
1219
+ - ["triple barrier", "tb", "trip_barr"]
1220
+ - ["profit factor", "pf"]
1221
+ ```
1222
+
1223
+ Each group is interpreted symmetrically, so every term expands to the rest of the group. The final internal expansion table is built by merging both sources:
1224
+
1225
+ 1. `query_expansions` entries are loaded as-is.
1226
+ 2. `query_expansion_groups` adds reciprocal links for every term in each group.
1227
+ 3. Overlaps are merged by union with duplicate terms removed.
1228
+
1229
+ This keeps backward compatibility while allowing concise synonym groups.
1230
+
1231
+ ### Hybrid Search Tuning
1232
+
1233
+ | hybrid_alpha | Behavior | Best For |
1234
+ |--------------|----------|----------|
1235
+ | 0.0 | Pure BM25 keyword | Exact terms, CVEs, tool names |
1236
+ | 0.3 | Keyword-heavy **(default)** | Technical queries with specific terms |
1237
+ | 0.5 | Balanced | General queries |
1238
+ | 0.7 | Semantic-heavy | Conceptual queries, related topics |
1239
+ | 1.0 | Pure semantic | "How to..." questions, abstract concepts |
1240
+
1241
+ ---
1242
+
1243
+ ## Project Structure
1244
+
1245
+ ```
1246
+ knowledge-rag/
1247
+ ├── mcp_server/
1248
+ │ ├── __init__.py # Stdout protection + version
1249
+ │ ├── config.py # YAML config loader + defaults
1250
+ │ ├── ingestion.py # 20 parsers, chunking, metadata extraction
1251
+ │ └── server.py # MCP server, ChromaDB, BM25, reranker, 12 tools
1252
+ ├── config.example.yaml # Documented config template (copy to config.yaml)
1253
+ ├── config.yaml # Your active configuration (git-ignored)
1254
+ ├── presets/ # Ready-to-use domain configurations
1255
+ │ ├── cybersecurity.yaml
1256
+ │ ├── developer.yaml
1257
+ │ ├── research.yaml
1258
+ │ └── general.yaml
1259
+ ├── documents/ # Your documents (scanned recursively)
1260
+ ├── data/
1261
+ │ ├── chroma_db/ # ChromaDB vector database
1262
+ │ └── index_metadata.json # Incremental indexing state
1263
+ ├── models_cache/ # Persistent embedding model cache
1264
+ ├── tests/ # Test suite (82 tests)
1265
+ ├── install.sh # Linux/macOS installer
1266
+ ├── install.ps1 # Windows installer
1267
+ ├── venv/ # Python virtual environment
1268
+ ├── requirements.txt
1269
+ ├── pyproject.toml
1270
+ ├── LICENSE
1271
+ └── README.md
1272
+ ```
1273
+
1274
+ ---
1275
+
1276
+ ## Troubleshooting
1277
+
1278
+ ### Python version mismatch
1279
+
1280
+ Requires Python 3.11 or newer.
1281
+
1282
+ ```bash
1283
+ python --version # Must be 3.11+
1284
+ ```
1285
+
1286
+ ### FastEmbed model download fails
1287
+
1288
+ On first run, FastEmbed downloads models to `models_cache/`. If the download fails:
1289
+
1290
+ ```bash
1291
+ # Clear cache and retry
1292
+ # Windows:
1293
+ rmdir /s /q models_cache
1294
+
1295
+ # Linux/macOS:
1296
+ rm -rf models_cache
1297
+
1298
+ # Then restart the MCP server
1299
+ ```
1300
+
1301
+ ### Reranker model download fails
1302
+
1303
+ The reranker is lazy-loaded on the first query. If the model is not cached and the machine is offline, search continues without reranking and uses the RRF order from hybrid retrieval. To keep reranking enabled offline, run one query while online or pre-populate `models_cache/` on the target machine.
1304
+
1305
+ You can still disable reranking explicitly in `config.yaml`:
1306
+
1307
+ ```yaml
1308
+ models:
1309
+ reranker:
1310
+ enabled: false
1311
+ ```
1312
+
1313
+ Disabling reranking reduces memory use and avoids first-query model loading. The tradeoff is lower ranking precision, especially when several chunks match the same terms but only one is the best answer.
1314
+
1315
+ ### ChromaDB index crashes on startup
1316
+
1317
+ Native ChromaDB failures can terminate Python before normal exception handling runs. Startup now probes ChromaDB in a child process before initializing the MCP server. If the probe crashes, the active `chroma_db/` and `index_metadata.json` are moved to `data/backups/auto-repair-*`, and the next startup can rebuild a clean index.
1318
+
1319
+ The same guarded behavior is available through either console script:
1320
+
1321
+ ```bash
1322
+ knowledge-rag
1323
+ knowledge-rag-guarded
1324
+ ```
1325
+
1326
+ ### Index is empty
1327
+
1328
+ ```bash
1329
+ # Check documents directory has files
1330
+ ls documents/
1331
+
1332
+ # Force reindex via Claude Code:
1333
+ # reindex_documents(force=True)
1334
+
1335
+ # Or nuclear rebuild if model changed:
1336
+ # reindex_documents(full_rebuild=True)
1337
+ ```
1338
+
1339
+ ### MCP server not loading
1340
+
1341
+ 1. Check `~/.claude.json` exists and has valid JSON in the `mcpServers` section
1342
+ 2. Verify paths use double backslashes (`\\`) on Windows
1343
+ 3. Restart Claude Code completely
1344
+ 4. Run `claude mcp list` to check connection status
1345
+
1346
+ ### "Failed to connect" error
1347
+
1348
+ The MCP server uses stdout for JSON-RPC communication. If a library prints to stdout during init, the stream gets corrupted. v3.4.3+ includes stdout protection that prevents this. If you're on an older version, upgrade:
1349
+
1350
+ ```bash
1351
+ pip install --upgrade knowledge-rag
1352
+ ```
1353
+
1354
+ ### Slow first query
1355
+
1356
+ The cross-encoder reranker model is lazy-loaded on the first query. This adds a one-time ~2-3 second delay for model download and loading. Subsequent queries are fast. If the model cannot be loaded, search falls back to RRF ordering and does not retry loading the reranker until the server restarts.
1357
+
1358
+ ### Memory usage
1359
+
1360
+ With ~200 documents, expect ~300-500MB RAM. The embedding model (~200MB ONNX runtime resident, lazy-loaded on first query since v3.8.0) and reranker (~25MB, lazy-loaded) are loaded into memory only when actually used. For very large knowledge bases (1000+ documents), consider enabling GPU acceleration and using exclude patterns to limit index scope.
1361
+
1362
+ ### Multiple MCP clients spawn duplicate servers
1363
+
1364
+ MCP stdio is one process per client by protocol — multiple Claude Code windows, Claude Desktop + IDE, etc. each spawn their own `knowledge-rag` process. Since v3.8.0 idle processes are cheap (no embedding model loaded until first query). If you've measured and want a hard cap of one server per data directory, opt in:
1365
+
1366
+ ```bash
1367
+ export KNOWLEDGE_RAG_SINGLE_INSTANCE=1
1368
+ ```
1369
+
1370
+ A second instance exits immediately with code 75. Default is OFF (multi-client friendly). Full guide: [docs/single-instance.md](docs/single-instance.md). Sample MCP config: [examples/mcp-config-single-instance.json](examples/mcp-config-single-instance.json).
1371
+
1372
+ ### SSE server won't start
1373
+
1374
+ ```bash
1375
+ # Check if port 8179 is already in use
1376
+ # Windows:
1377
+ netstat -aon | findstr :8179
1378
+ # Linux/macOS:
1379
+ lsof -i :8179
1380
+ ```
1381
+
1382
+ If `uvicorn` is not found, install the server extras: `pip install knowledge-rag[server]`
1383
+
1384
+ ### Can't connect to SSE server
1385
+
1386
+ Verify the server is running and the URL is correct:
1387
+
1388
+ ```bash
1389
+ curl http://127.0.0.1:8179/sse
1390
+ ```
1391
+
1392
+ Common issues:
1393
+ - Wrong URL: must end with `/sse` (not just the port)
1394
+ - Firewall blocking the port
1395
+ - Server started with a different host/port than configured in the MCP client
1396
+
1397
+ ---
1398
+
1399
+ ## Changelog
1400
+
1401
+ ### Unreleased
1402
+
1403
+ ### v4.6.0 (2026-07-30) — MCP spec 2026-07-28 via Anthropic Tier 1 SDK (mcp 2.x)
1404
+
1405
+ - **NEW (protocol)**: adopt the [MCP spec `2026-07-28`](https://blog.modelcontextprotocol.io/posts/2026-07-28/) — stateless request/response core, per-request `_meta.io.modelcontextprotocol/protocolVersion` + `clientCapabilities`, `serverInfo` on responses, retirement of the `initialize`/`initialized` handshake and the `Mcp-Session-Id` header. The MCP client Claude Code (and every Tier 1 client) negotiates the spec version per request, so this release stays backwards-compatible for clients speaking earlier spec revisions.
1406
+ - **NEW (deps)**: bump `mcp` from `>=1.6.0,<2.0.0` to `>=2.0.0,<3.0.0`. Anthropic published `mcp 2.0.0` on 2026-07-28 as the Tier 1 Python SDK — it speaks the new spec natively and is the direct upstream reference. Delivers the follow-up promised in v4.5.0-post — no longer trapped on `mcp.server.fastmcp` which upstream `mcp 2.0.0` renamed to `mcp.server.MCPServer`.
1407
+ - **CHANGE (imports)**: `mcp_server/server.py` now imports `MCPServer` from `mcp.server` instead of `FastMCP` from `mcp.server.fastmcp`. `@mcp.tool()` / `@mcp.resource()` / `@mcp.prompt()` decorator ergonomics are unchanged — only the class name and import path move. All 13 MCP tool signatures, all tool names, and every public function under `mcp_server.*` are **frozen and unchanged** — `check_api_surface.py --check` passes clean, `test_backwards_compat.py` stays green.
1408
+ - **CHANGE (constructor)**: `MCPServer(...)` (like FastMCP 3.x) no longer accepts `host` / `port` on the constructor; those parameters now belong to the transport and are passed to `mcp.run(transport=..., host=..., port=...)` on the non-stdio branches. stdio users see zero behaviour change.
1409
+ - **CHANGE (server identity)**: `MCPServer(name="knowledge-rag", version="4.6.0")` — server now advertises its version explicitly in `serverInfo`, matching the `2026-07-28` spec.
1410
+ - **NON-GOAL — why not `fastmcp` (jlowin)**: an initial attempt via the third-party standalone `fastmcp` package added ~20 transitive dependencies (from its `[client,server]` extras) and pushed `test_bench_orchestrator_idle_rss` +21.9% and `test_bench_query_cache_5000_entries` +17.1% — a real regression, not jitter. Cutting the extras to `fastmcp-slim[mcp]` broke `@mcp.tool()` at runtime (`ImportError: FastMCP server support is not installed`). Anthropic's official `mcp 2.x` is the correct entry point.
1411
+ - **VERSION**: 4.5.1 → **4.6.0** (MINOR — new spec adoption via Tier 1 SDK; every public API + every MCP tool signature preserved).
1412
+
1413
+ ### v4.5.1 (2026-07-28) — Fase 1 Security Hardening (library, standalone)
1414
+
1415
+ - **NEW (security)**: Fase 1 Security Hardening — a standalone `mcp_server.security` library ships with the tools needed to close three attack classes against untrusted document ingestion. **Nothing in the existing call graph changes** in v4.5.1: `security.py` is imported by no other module in this release, so byte-identical behaviour is preserved for every user. Integration with `add_document` / `add_from_url` / `parse_file` / MCP HTTP transports lands in v4.6.0 (14 tests in `tests/security/` are marked `xfail` accordingly — they turn green once the callers are wired up). Users motivated to adopt hardening today can `from mcp_server.security import ...` explicitly. Contract per-defense:
1416
+ - **CWE-22 / CWE-59** path traversal + symlink escape: `validate_path_within(base, candidate)` + `is_path_within(base, candidate)` + `PathEscapeError`. Rejects `..`, absolute-outside-base, and symlinks whose target leaves the corpus root.
1417
+ - **OWASP LLM01:2025** prompt injection: `neutralize_injection_sentinels` + `sanitize_external_content` + `wrap_external_content` + `detect_external_marker`. Wraps content fetched from URLs / dropped files with tagged sentinels the downstream LLM can distinguish from operator-authored context.
1418
+ - **CWE-287** unauthenticated HTTP transport: `BearerAuthMiddleware` + `bearer_token_matches` (constant-time comparison) + `extract_bearer_token`. Attach to a Starlette / FastAPI app; stdio transport bypasses auth by construction (no network surface).
1419
+ - **NEW**: `docs/adr/0001-fase1-security-hardening.md` — ADR capturing threat model + v4.6.0 integration roadmap.
1420
+ - **NEW**: `.github/SECURITY.md` — GitHub vulnerability reporting policy pointer + `.github/openssf-best-practices.md` (evidence pack for OpenSSF Best Practices badge registration).
1421
+ - **NEW**: `tests/security/` (71 tests total; 57 library unit tests green in v4.5.1, 14 integration tests `xfail` pending v4.6.0 wire-up, `strict=False` so they auto-turn-green after integration) + `tests/test_security_docs.py` (9 assertions pinning `SECURITY.md`, README badge block, dependabot weekly pip schedule).
1422
+ - **DOCS**: README carries the OpenSSF Best Practices badge (placeholder project ID until registration completes).
1423
+ - **DEPS**: `dependabot.yml` pip schedule tightened `monthly` → `weekly` (OpenSSF-recommended floor for language ecosystems).
1424
+ - **VERSION**: 4.5.0 → **4.5.1** (PATCH — additive library only, zero behaviour change on the default install).
1425
+
1426
+ ### v4.5.0-post (rolling fixes since v4.5.0 release)
1427
+
1428
+ - **FIX (deps)**: pin `mcp<2.0.0` in both `requirements.txt` and `pyproject.toml` after the upstream `mcp` package published 2.0.0, which drops/relocates `mcp.server.fastmcp`. Every `mcp_server.server` import chain — and therefore every test module — was failing collection with `ModuleNotFoundError: No module named 'mcp.server.fastmcp'` on fresh CI runners across all 9 OS×Python cells. Pin restores green until we migrate to the new 2.x FastMCP entry point in a follow-up. Root cause: `requirements.txt` had `mcp>=1.0.0` unbounded, so `pip install -r requirements.txt` in CI ignored the pyproject constraint. (#122)
1429
+ - **FIX**: `_ensure_bm25_index` no longer traps the guard flag `_bm25_initialized` in a stuck-True state when the server boots against an empty ChromaDB collection. Prior behavior set the flag unconditionally at the end of the guarded block, so a fresh-install bootup or a metadata-mapping loss left BM25 permanently uninitialized: subsequent `add_document()` calls populate `bm25_index.corpus` but do not rebuild the inverted index, and `_ensure_bm25_index` — the only path that does — short-circuited on the stale flag. All keyword-only searches returned zero results; hybrid searches returned only semantic hits with `bm25_rank: null`. The flag is now set only after a build that actually produced an index; empty collections and exceptions leave it False so the next call retries. (#114)
1430
+ - **TEST**: New `TestEmptyBootupDoesNotTrapGuardFlag` (5 tests) pins the contract — empty-collection boot leaves flag False, populated collection marks True, empty-then-populated recovery works, empty `get()` result leaves False, and `build_index()` exceptions leave False for retry. Baseline: 271 → 276.
1431
+
1432
+ ### v4.5.0 (2026-07-06) — Hybrid Search Ranking & Routing Fix
1433
+
1434
+ - **FIX**: `search_knowledge` now searches the entire index when the user omits `category_filter`. The internal `_route_by_keywords()` heuristic previously acted as a hard where-filter on both the semantic and BM25 branches, so queries whose terms happened to map to a sparsely-populated category could return two documents while thousands of relevant chunks in other categories were silently dropped. The router is now **informational only**: the `routed_by` field is still populated in every result for telemetry (public API unchanged), but the candidate set is never restricted. Users who want a hard filter still get it by passing `category_filter=...` explicitly — which continues to filter BM25 consistently with #109. (#112)
1435
+ - **NEW**: Hybrid search adds a small bounded ranking boost when query terms match indexed `source` or `filename` metadata, helping navigational queries surface the canonical file for a topic instead of adjacent files that only cross-reference it. The boost is capped at ~20% of typical RRF magnitudes, so it breaks near-ties without overriding semantic or BM25 signal. Public API unchanged. (#110, thanks @Hohlas)
1436
+ - **TEST**: New `TestKeywordRoutingBehavior` (3 tests) pins the routing fix — BM25 and semantic branches must both see the full corpus when no explicit filter is passed, and explicit `category_filter` still overrides the router. New `TestPathAwareRanking` covers the path-metadata boost. Baseline: 266 → 271.
1437
+ - **UPGRADE NOTE**: warm `query_cache` entries from before v4.5.0 should be invalidated by restarting the server so cached responses no longer reflect the pre-fix restrictive behavior.
1438
+
1439
+ ### v4.4.0 (2026-07-06) — Cross-Platform Installer & Hybrid Search Category Filter
1440
+
1441
+ - **NEW**: Cross-platform, multi-LLM-client installer (`install.py`) driving both `install.sh` (Linux/macOS) and `install.ps1` (Windows) as thin wrappers. One codebase, one behavior across every OS.
1442
+ - **NEW**: Auto-detects and registers `knowledge-rag` in 8 LLM clients — Claude Code, Claude Desktop, Cursor, Windsurf, VS Code (Copilot Chat), Cline, Gemini CLI, Zed — writing to each tool's canonical config path with the correct JSON schema per client (VS Code uses `servers`, Zed uses `context_servers`, everyone else uses `mcpServers`).
1443
+ - **NEW**: `--for <clients>` / `--exclude <clients>` opt-in/opt-out selection, `--dry-run` preview, `--list-clients` registry inspection, `--pypi-version <ver>` pinning, `--skip-init` / `--skip-model` for fast reruns. See `python install.py --help`.
1444
+ - **FIX**: `install.ps1` no longer writes MCP config to `~/.claude/mcp.json` (a stale secondary path); it now targets `~/.claude.json` — the file Claude Code actually reads — via idempotent JSON merge that preserves every existing MCP server entry with an automatic `.knowledge-rag.bak` backup.
1445
+ - **FIX**: `install.ps1` gains PyPI mode (`pip install knowledge-rag`) and runs `mcp_server.server init` on install — feature parity with `install.sh`.
1446
+ - **FIX**: MCP server spec no longer uses the fragile `cmd /c cd /d ... && python ...` wrapper on Windows; it emits the standard `command` + `cwd` shape supported natively by every modern client.
1447
+ - **FIX**: `install.sh` guards against `sh install.sh` (bash-only features now emit a clear error instead of a cryptic syntax failure).
1448
+ - **FIX**: Both scripts now correctly advertise **13 MCP tools** (was outdated at 12; `get_reindex_status` shipped in v4.3.0).
1449
+ - **FIX**: Windows-side `install.ps1` prefers `winget install Python.Python.3.12 --scope user` (no admin), falls back to python.org 3.12.7 (was pinned to 3.12.0).
1450
+ - **FIX**: Hybrid search now applies the active category filter to BM25 results before RRF fusion, preventing keyword-only BM25 hits from other categories from leaking into filtered searches. Both leak paths are closed: BM25 candidates are metadata-filtered before RRF (with `top_k` widened to `max_results * 20` to compensate for post-filter drop), and the fallback fetch during fusion re-checks `category` before adding a chunk to `combined_scores`. Note: the `category_filter` guard now also applies to the keyword-routed category — `_route_by_keywords()`-inferred routing filters BM25 too, consistent with the semantic branch. Users with warm `query_cache` entries should restart the server to invalidate stale results. (#109, thanks @Hohlas)
1451
+ - **TEST**: New `tests/test_installer_no_data_loss.py` (22 tests) locks in the installer's zero-data-loss contract across all three JSON schemas (`mcpServers` / `servers` / `context_servers`): top-level keys preserved, sibling MCP servers byte-identical, `.knowledge-rag.bak` backup written before every mutation, idempotent second run, `--dry-run` writes nothing, atomic `os.replace` write. Baseline: 231 → 266.
1452
+ - **TEST**: New `TestHybridCategoryFilter::test_bm25_results_respect_category_filter` in `tests/test_search.py` — deterministic regression covering BM25-only hits with mixed categories. Baseline: 266 → 267.
1453
+
1454
+ ### v4.3.1 (2026-06-22) — Hybrid Search Fixes
1455
+
1456
+ - **FIX**: Accept `"general"` as a valid category in `search_knowledge`. The parser hardcodes `"general"` as the fallback in `_detect_category` (`ingestion.py`), but the validator only built `valid_categories` from `config.keyword_routes` + `config.category_mappings.values()` — so users who customized `config.yaml` and dropped the default `"general": "general"` mapping hit `Invalid category` even though the index contained `general` documents. Validator now always tolerates `"general"`. (#98, thanks @Hohlas)
1457
+ - **FIX**: Skip BM25-only search results when Chroma can no longer resolve the chunk ID. Stale BM25 indices (typically right after `remove_document` or in the window between async reindex and BM25 rebuild) returned hits whose `collection.get()` came back empty; the previous fallback inserted entries with `document=""` / `metadata={}` into the reranker, polluting results with empty matches. The pipeline now `continue`s past those, dropping the stale hit cleanly. (#98, thanks @Hohlas)
1458
+ - **TEST**: Added `tests/test_pr98_regression.py` (4 tests) pinning both contracts so future refactors cannot silently revert either fix. Test count baseline: 227 → 231. (#99)
1459
+ - **CI**: Bumped `[tool.mypy] python_version` from 3.11 to 3.12 to accept PEP 695 `type` statements in the numpy stub (`numpy/__init__.pyi`) which were breaking the Pillar 7 strict gate. Only affects static analysis; `requires-python = ">=3.11"` unchanged. (#100)
1460
+
1461
+ ### v4.3.0 (2026-06-17) — Async Reindex, GPU CUDA 12, 13th MCP Tool
1462
+
1463
+ - **NEW**: `get_reindex_status` MCP tool — lightweight reindex progress polling without computing full index stats. Returns active/idle status, percent, processed/total, errors, and last result.
1464
+ - **NEW**: `reindex_documents` now runs in background via daemon thread — returns immediately with `{"status": "started"}`. Eliminates MCP timeout on large document sets (5K+ files). Concurrent calls return `already_running` with current progress.
1465
+ - **NEW**: GPU acceleration with full CUDA 12 support — `onnxruntime-gpu` + 7 NVIDIA pip packages (`cublas`, `cudnn`, `cuda-runtime`, `cufft`, `cusparse`, `cusolver`, `curand`, `nvjitlink`). Server auto-detects GPU on startup with 4-step verification (providers, DLLs, nvidia-smi, session creation). Falls back to CPU gracefully.
1466
+ - **NEW**: `_setup_cuda_dll_paths()` adds NVIDIA pip package DLL directories to `PATH` automatically on Windows — onnxruntime finds CUDA 12 DLLs without a full CUDA Toolkit install.
1467
+ - **DEPS**: `[gpu]` extra expanded from 3 to 8 packages (added `cufft`, `cusparse`, `cusolver`, `curand`, `nvjitlink`).
1468
+ - **FIX**: GPU status reporting now uses actual ONNX session creation test instead of just checking `get_available_providers()` — prevents false "GPU ACTIVE" when CUDA DLLs are missing.
1469
+ - **DOCS**: GPU Acceleration section rewritten with complete requirements table, setup steps, verification instructions, and fallback behavior.
1470
+ - **DOCS**: Tool reference updated — `reindex_documents` async behavior documented, `get_reindex_status` reference added.
1471
+ - **TEST**: Backwards-compat baseline updated for 13 MCP tools.
1472
+
1473
+ ### v4.2.0 (2026-06-17) — Search Performance & Output Quality
1474
+
1475
+ - **PERF**: Custom inverted-index BM25 replaces `rank-bm25` full-corpus scan — 128× faster keyword search on 50K+ chunk corpora. Only documents containing query terms are scored via posting lists.
1476
+ - **PERF**: `numpy.argpartition` for O(n) top-k selection instead of O(n log n) sort.
1477
+ - **PERF**: Batched adjacent chunk fetch — single ChromaDB `collection.get()` call replaces N round-trips per result.
1478
+ - **PERF**: O(1) reverse lookup via `_source_to_docid` dict eliminates linear scans of `_indexed_docs` in `search_similar`, `update_document`, `remove_document`, and `_expand_with_adjacent_chunks`.
1479
+ - **NEW**: `snippet_mode` parameter on `search_knowledge` (default: `true`) — truncates content to ~500 chars at natural break points with `content_length` field. Reduces token consumption by ~72%.
1480
+ - **NEW**: `min_score` parameter on `search_knowledge` (default: `0.0`) — filters results below a normalized relevance threshold. Response includes `filtered_by_score` count.
1481
+ - **NEW**: `filtered_by_score` field in search response JSON for transparency.
1482
+ - **DEPS**: `numpy` added as direct dependency (was transitive via fastembed); `rank-bm25` import removed from server.py.
1483
+ - **TEST**: 6 new tests for `min_score` filtering and `snippet_mode` truncation.
1484
+ - **TEST**: Updated backwards-compat baseline to include new `search_knowledge` parameters.
1485
+
1486
+ ### v4.1.2 (2026-06-17)
1487
+
1488
+ - **FIX**: `_save_metadata` dict snapshot prevents concurrent modification crash during file watcher events.
1489
+ - **STYLE**: ruff format applied to server.py.
1490
+
1491
+ ### v4.1.1 (2026-06-17)
1492
+
1493
+ - **FIX**: All `_indexed_docs` iterations now use `list()` snapshot, preventing `dictionary changed size during iteration` crash when FileWatcher modifies the index concurrently with MCP tool calls (affects `search_knowledge`, `search_similar`, `update_document`, `remove_document`, `evaluate_retrieval`, `list_categories`, `list_documents`)
1494
+
1495
+ ### v4.1.0 (2026-06-17)
1496
+
1497
+ - **Added:** `query_expansion_groups` config for symmetric synonym expansion (#92)
1498
+ - **Improved:** `expand_query()` now returns deterministic expansion order (set → ordered list with dedup)
1499
+
1500
+ ### v4.0.1 (2026-06-16)
1501
+
1502
+ - **FIX**: Orphan cleanup now runs before indexing loop, preventing chunk loss when files are moved (#90).
1503
+ - **FIX**: Chunk deduplication is now per-document instead of global, preventing cross-document chunk deletion (#91).
1504
+ - **FIX**: Added `on_moved` handler to `DocumentWatcher` for proper file move detection.
1505
+ - **FIX**: Startup preflight probes ChromaDB in a child process and moves crashing persistent indexes to `data/backups/auto-repair-*` before MCP initialization.
1506
+ - **FIX**: Reranker load failures now fall back to RRF ordering instead of failing `search_knowledge` on offline machines.
1507
+ - **FIX**: Virtualenv project-root detection now handles Python symlinks that resolve to the system interpreter.
1508
+ - **NEW**: `knowledge-rag-guarded` console script kept as an explicit guarded startup alias.
1509
+
1510
+ ### v4.0.0 (2026-06-09) — Enterprise Concurrent Access
1511
+
1512
+ - **NEW**: SSE and streamable-http transport modes — 1 server serves N clients (`server.transport: "sse"` in config.yaml or `--transport sse` CLI).
1513
+ - **NEW**: Thread-safe shared state for concurrent queries — QueryCache locking, BM25 build lock, orchestrator double-checked locking.
1514
+ - **NEW**: ChromaDB WAL mode enabled automatically in SSE/HTTP mode for concurrent read performance.
1515
+ - **NEW**: Optional rate limiting — sliding-window counter, configurable RPM and burst, disabled by default.
1516
+ - **NEW**: Optional Prometheus metrics endpoint — tool call counts, latency histograms, separate port, disabled by default.
1517
+ - **NEW**: All 13 MCP tools instrumented with `@rate_limited` and `@instrument` decorators (zero-cost when disabled).
1518
+ - **NEW**: `--transport` CLI override for Docker/systemd deployments.
1519
+ - **NEW**: `pip install knowledge-rag[server]` optional dependency for SSE/HTTP (uvicorn).
1520
+ - **CHANGED**: SSE/HTTP mode auto-enables single-instance lock (port collision prevention).
1521
+ - **CHANGED**: `mcp` dependency bumped to `>=1.6.0` (SSE/streamable-http support).
1522
+ - **MIGRATION**: Default transport remains `stdio` — existing users need zero changes. See config.example.yaml for SSE setup.
1523
+
1524
+ ### v3.9.1 (2026-06-08)
1525
+
1526
+ - **FIX**: Expand `~` in `config.yaml` path values (`documents_dir`, `data_dir`, `models_cache_dir`) via `expanduser()` on all platforms (#86).
1527
+ - **FIX**: Warn when `documents_dir` resolves to a non-existent path instead of silently indexing zero files.
1528
+ - **FIX**: File watcher now uses accumulate-mode debounce — bulk file copies no longer starve the reindex trigger.
1529
+ - **FIX**: Concurrent `index_all()` calls are serialized via `_index_lock` to prevent ChromaDB SQLite corruption.
1530
+ - **FIX**: `collection.add()` is batched (500 chunks/call) to cap memory usage during large reindex operations.
1531
+ - **NEW**: `KNOWLEDGE_RAG_WATCHER_DISABLED=1` env var to disable the file watcher for troubleshooting.
1532
+ - **NEW**: Progress logging every 10% for reindex operations with >100 documents.
1533
+
1534
+ ### v3.9.0 (2026-05-10) — Quality Gate
1535
+
1536
+ **Major governance + CI hardening release. No runtime behavior change in `mcp_server/`. Public API surface unchanged from v3.8.1.**
1537
+
1538
+ - **NEW** Quality Gate workflow (`.github/workflows/quality-gate.yml`) enforcing the 7 pillars on every PR: Security, Stability, Memory Leak, Versatility, Scalability, Versioning, Quality. 35+ status checks total.
1539
+ - **NEW** Nightly resilience workflow (`.github/workflows/nightly.yml`): chaos suite (failure injection), 1h soak test (50K-iteration loop), determinism check (full suite × 3), mutation testing (mutmut). Auto-opens GitHub issue on any nightly failure.
1540
+ - **NEW** Performance benchmark suite under `bench/` (12 microbenchmarks, pytest-benchmark) with 10% regression gate on every PR.
1541
+ - **NEW** Public performance dashboard via GitHub Pages (`.github/workflows/bench-pages.yml`) — chart of latency/throughput per commit. Dormant until repo Pages is enabled.
1542
+ - **NEW** Property-based fuzzing of all parsers via Hypothesis (`tests/test_ingestion_property.py`) — 200 random examples per CI run.
1543
+ - **NEW** Memory baseline regression tests (`tests/test_memory_baseline.py`, cross-platform via psutil) — RSS bounded under 1000 queries; nightly soak amplifies to 50K iterations.
1544
+ - **NEW** Property/locale/format/preset matrices (`tests/test_presets.py`, `tests/test_locale.py`, `tests/test_format_smoke.py`).
1545
+ - **NEW** Backwards-compatibility regression tests (`tests/test_backwards_compat.py`) — legacy YAML configs from v3.6.0 / v3.7.0 still parse; all 13 MCP tool parameter names frozen.
1546
+ - **NEW** AST-based public API surface diff (`scripts/check_api_surface.py`) — any breaking change blocks merge, baseline at `.github/api-surface-baseline.json`.
1547
+ - **NEW** CHANGELOG enforcement (`scripts/check_changelog.py`) — user-facing PRs must add a bullet under `## Unreleased`; bypass via `skip-changelog` label.
1548
+ - **NEW** Test count anti-regression (`scripts/check_test_count.py`) — guards against silent test deletion.
1549
+ - **NEW** Conventional commits required on every PR title (commitlint via `amannn/action-semantic-pull-request`).
1550
+ - **NEW** mypy `--strict` rolling out per-module (currently `instance_lock.py` + `preflight.py` + `scripts/`); interrogate docstring coverage ≥ 80%; radon, vulture, PR-size guard report-only.
1551
+ - **NEW** CI matrix expanded to 9 cells: Linux + Windows + **macOS** × 3.11 + 3.12 + **3.13** (all required at v3.9.0; macOS / 3.13 promoted from experimental after two clean cycles).
1552
+ - **NEW** Governance docs: `CONTRIBUTING.md`, `CODE_OF_CONDUCT.md`, `SECURITY.md`, `.github/PULL_REQUEST_TEMPLATE.md`, 3 issue templates, expanded `CODEOWNERS`.
1553
+ - **NEW** Pre-commit hooks: ruff, gitleaks, version-sync, conventional commits.
1554
+ - **CHORE** `.github/codecov.yml` enforcing coverage trend gate (-0.5pp blocks; new code ≥ 70%).
1555
+
1556
+ ### v3.8.1 (2026-05-10) — hotfix
1557
+
1558
+ - **FIX (critical)**: `FastEmbedEmbeddings.__call__` no longer returns vectors of zeros when the ONNX model fails to load or `embed()` raises. The previous behavior silently corrupted the index — ChromaDB stored zero embeddings, `count()` reported normal numbers, smart-reindex skipped the bad chunks, and queries returned garbage scores with no error visible. Now raises `EmbeddingModelLoadError` / `EmbeddingError`. (#36)
1559
+ - **FIX**: Sticky `_load_failed` flag — after a load failure, subsequent calls re-raise immediately instead of looping through HuggingFace download attempts (was the "frozen query" UX in v3.8.0).
1560
+ - **NEW**: Sanity checks in `__call__` — embed count and dim mismatches raise `EmbeddingError` instead of silently returning malformed vectors.
1561
+ - **TEST**: 7 new regression cases in `tests/test_lazy_embeddings.py`, including `test_does_not_return_zero_vectors_silently` as a guard for the whole class of bug.
1562
+ - **NOTE**: This is a pre-existing bug in master, not introduced by v3.8.0. v3.8.0 lazy-load expanded the impact (failures moved to query time). All v3.8.0 users should upgrade.
1563
+
1564
+ ### v3.8.0 (2026-05-10)
1565
+
1566
+ - **NEW**: Lazy-load FastEmbed embedding model (~200MB ONNX runtime). Loads on first query instead of startup — idle `knowledge-rag` processes are now cheap, which matters when MCP stdio clients spawn parallel server processes (multiple Claude Code windows, Claude Desktop + IDE, etc.). Public API unchanged. (#32)
1567
+ - **NEW**: Opt-in single-instance guard via `KNOWLEDGE_RAG_SINGLE_INSTANCE=1` env var. **OFF by default** — multi-client MCP usage continues to work unchanged. When enabled, a second server process for the same `data_dir` exits with code 75 (`EX_TEMPFAIL`). Includes stale-PID recovery and SIGINT/SIGTERM handlers. See [docs/single-instance.md](docs/single-instance.md). (#33, original concept by @Hohlas in #31)
1568
+ - **NEW**: `examples/mcp-config-single-instance.json` — sample MCP client config for the opt-in guard.
1569
+ - **DOCS**: New `docs/single-instance.md` — when to use, when NOT to use, troubleshooting, full activation reference.
1570
+ - **DOCS**: README troubleshooting section for "Multiple MCP clients spawn duplicate servers" + memory-usage note for lazy embeddings.
1571
+ - **CHORE**: Sync version across `pyproject.toml`, `mcp_server/__init__.py`, and `npm/package.json` (was drifting since v3.5.x).
1572
+ - **CHORE**: pytest `tmp_path_retention_count=1` to avoid Windows atexit cleanup race in CI.
1573
+ - **ROADMAP**: Tracked v4.0 shared-service architecture (one daemon, many thin MCP clients) as the long-term fix for multi-process resource duplication. (#34)
1574
+
1575
+ ### v3.6.2 (2026-04-23)
1576
+
1577
+ - **INFRA**: NPM provenance attestation (SLSA supply chain security), full README on npm page
1578
+ - **DOCS**: Reorganize Installation section — add NPX and Docker install methods, update What's New to v3.6.0
1579
+
1580
+ ### v3.6.0 (2026-04-23)
1581
+
1582
+ - **NEW**: Multi-language code parsing — C (`.c`), C++ (`.cpp`/`.h`), JavaScript (`.js`/`.jsx`), TypeScript (`.ts`/`.tsx`) with per-language function/class/import extraction
1583
+ - **NEW**: XML parser (`.xml`) — root element and namespace metadata extraction
1584
+ - **NEW**: All 8 new formats default enabled — no config change needed
1585
+ - **NEW**: NPM wrapper (`npx knowledge-rag`) + Docker image (`ghcr.io/lyonzin/knowledge-rag`)
1586
+ - **NEW**: Automated release pipeline — PyPI (Trusted Publishing), NPM, Docker GHCR
1587
+ - **IMPROVED**: Code parser reports correct `language` metadata per file type (was hardcoded to `"python"` for all code files)
1588
+
1589
+ ### v3.5.2 (2026-04-16)
1590
+
1591
+ - **NEW**: Auto-discovery of CUDA 12 DLLs from pip-installed NVIDIA packages — no manual PATH configuration needed
1592
+ - **NEW**: Graceful GPU→CPU fallback with `[WARN]` log when CUDA init fails (missing drivers, wrong version, etc.)
1593
+ - **FIX**: Explicit `CPUExecutionProvider` when `gpu: false` — eliminates noisy CUDA probe errors in logs
1594
+ - **FIX**: BASE_DIR resolution now correctly prefers directories with `config.yaml` over those with only `config.example.yaml` (fixes editable installs)
1595
+
1596
+ ### v3.5.1 (2026-04-16)
1597
+
1598
+ - **FIX**: Removed Python upper bound constraint (`<3.13` → `>=3.11`). Python 3.13 and 3.14 now supported — onnxruntime ships wheels for both.
1599
+
1600
+ ### v3.5.0 (2026-04-16)
1601
+
1602
+ - **NEW**: Optional GPU acceleration for ONNX embeddings — `pip install knowledge-rag[gpu]` + `models.embedding.gpu: true` in config. 5-10x faster indexing on NVIDIA GPUs with automatic CPU fallback.
1603
+ - **DOCS**: Supported formats table added to README (20 formats)
1604
+
1605
+ ### v3.4.3 (2026-04-16)
1606
+
1607
+ - **FIX**: Correct stdout protection via save/restore pattern — `__init__.py` saves original stdout and redirects to stderr during init, `server.py main()` restores it before `mcp.run()`. v3.4.2's global redirect broke MCP JSON-RPC response channel.
1608
+
1609
+ ### v3.4.1 (2026-04-16)
1610
+
1611
+ - **FIX**: `pip install knowledge-rag` now auto-detects project directory from venv location
1612
+ - **NEW**: `install.sh` — Linux/macOS installer with pip and from-source modes
1613
+ - **IMPROVED**: BASE_DIR resolution chain: env var → source dir → venv parent → CWD → fallback
1614
+
1615
+ ### v3.4.0 (2026-04-16)
1616
+
1617
+ - **NEW**: `models_cache_dir` — persistent embedding model cache, prevents re-download after reboots
1618
+ - **NEW**: `exclude_patterns` — glob-based file/directory exclusion during indexing
1619
+ - **NEW**: Jupyter Notebook (.ipynb) parser — extracts markdown and code cell sources only
1620
+ - **NEW**: MCP stdout protection — redirects stdout to stderr before server start
1621
+ - **NEW**: File watcher resilience — graceful fallback when Linux inotify limits are reached
1622
+ - **NEW**: MetaTrader (.mq4, .mqh) support — opt-in code parsing
1623
+ - **NEW**: 23 new tests (exclude patterns, ipynb parser, stdout protection)
1624
+ - Community credit: [@Hohlas](https://github.com/Hohlas) ([PR #18](https://github.com/lyonzin/knowledge-rag/pull/18))
1625
+
1626
+ ### v3.3.x
1627
+
1628
+ - **v3.3.2**: Full type validation on YAML config, bounds checking, version sync
1629
+ - **v3.3.1**: YAML null value crash fix, presets bundled in pip wheel, `knowledge-rag init` CLI
1630
+ - **v3.3.0**: YAML configuration system, 4 domain presets, generic use support
1631
+
1632
+ ### v3.2.x
1633
+
1634
+ - **v3.2.4**: Symlink support with circular loop protection
1635
+ - **v3.2.3**: BASE_DIR smart detection for pip installs
1636
+ - **v3.2.2**: Plug-and-play pip install, `KNOWLEDGE_RAG_DIR` env var
1637
+ - **v3.2.1**: Auto-recovery from corrupted ChromaDB
1638
+ - **v3.2.0**: Parallel BM25 + Semantic search, adjacent chunk retrieval
1639
+
1640
+ ### v3.1.x
1641
+
1642
+ - **v3.1.1**: Code block protection in markdown chunker, AAR category, 14 CVE aliases
1643
+ - **v3.1.0**: DOCX/XLSX/PPTX/CSV support, file watcher, MMR diversification, PyPI publish
1644
+
1645
+ ### v3.0.0 (2026-03-19)
1646
+
1647
+ - Replaced Ollama with FastEmbed (ONNX in-process)
1648
+ - Cross-encoder reranking, markdown-aware chunking, query expansion
1649
+ - 6 new MCP tools (12 total), auto-migration from v2.x
1650
+
1651
+ <details>
1652
+ <summary>v2.x and earlier</summary>
1653
+
1654
+ - **v2.2.0**: `hybrid_alpha=0` skips Ollama, default changed from 0.5 to 0.3
1655
+ - **v2.1.0**: Mermaid architecture diagrams
1656
+ - **v2.0.0**: Hybrid search, RRF fusion, `hybrid_alpha` parameter
1657
+ - **v1.1.0**: Incremental indexing, query cache, chunk deduplication
1658
+ - **v1.0.1**: Auto-cleanup orphan folders, removed hardcoded paths
1659
+ - **v1.0.0**: Initial release
1660
+ </details>
1661
+
1662
+ ---
1663
+
1664
+ ## Contributing
1665
+
1666
+ 1. Fork the repository
1667
+ 2. Create a feature branch (`git checkout -b feature/amazing-feature`)
1668
+ 3. Commit your changes
1669
+ 4. Push to the branch (`git push origin feature/amazing-feature`)
1670
+ 5. Open a Pull Request
1671
+
1672
+ ---
1673
+
1674
+ ## License
1675
+
1676
+ This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
1677
+
1678
+ ---
1679
+
1680
+ ## Acknowledgments
1681
+
1682
+ - [ChromaDB](https://www.trychroma.com/) — Vector database
1683
+ - [FastEmbed](https://qdrant.github.io/fastembed/) — ONNX Runtime embeddings
1684
+ - [FastMCP](https://github.com/anthropics/mcp) — Model Context Protocol framework
1685
+ - [PyMuPDF](https://pymupdf.readthedocs.io/) — PDF parsing
1686
+ - [rank-bm25](https://github.com/dorianbrown/rank_bm25) — BM25 Okapi implementation
1687
+ - [Watchdog](https://github.com/gorakhargosh/watchdog) — File system monitoring
1688
+ - [python-docx](https://python-docx.readthedocs.io/) / [openpyxl](https://openpyxl.readthedocs.io/) / [python-pptx](https://python-pptx.readthedocs.io/) — Office document parsing
1689
+ - [PyYAML](https://pyyaml.org/) — YAML configuration parsing
1690
+ - [Beautiful Soup](https://www.crummy.com/software/BeautifulSoup/) — HTML parsing for URL ingestion
1691
+
1692
+ ---
1693
+
1694
+ ## Author
1695
+
1696
+ **Lyon.**
1697
+
1698
+ Security Researcher | Developer
1699
+
1700
+ ---
1701
+
1702
+ <div align="center">
1703
+
1704
+ **[Back to Top](#knowledge-rag)**
1705
+
1706
+ </div>