knowledge-rag is a local retrieval-augmented generation (RAG) server for the Model Context Protocol (MCP). It indexes the documents and code on your machine and gives Claude Code, Cursor, VS Code and other MCP clients 13 tools to search them, using hybrid search (BM25 keywords plus vector similarity) with cross-encoder reranking on by default. Everything runs in one Python process with embedded ChromaDB and FastEmbed ONNX models: no Docker, no database server, no API key. Indexing and search work offline once the models are cached.
- 11.1 ms median uncached hybrid query on CPU, 6.1 ms on CUDA: 36 queries over 149 chunks on one Windows host, reranker off (audit)
- 0.03 ms median for a repeated query served from the cache, same workload (audit)
- 75,016 chunks from 5,889 documents: full rebuild completed in a production reproduction (v4.8.3)
- 1,500+ tests collected on each of 9 OS × Python CI cells (platform-specific ones skip), 84% line coverage against an 80% floor (CI run, 2026-11-06)
Install and register with Claude Code (needs Python 3.11+ and Node.js 16+):
npx -y knowledge-rag --install-only
claude mcp add knowledge-rag -s user --env KNOWLEDGE_RAG_DIR=~/knowledge-rag -- npx -y knowledge-ragPut files in ~/knowledge-rag/documents/ and ask your agent to search them. The folder is created on first start, new and changed files are indexed automatically, and the models (about 150 MB) download the first time they are needed.
Install · Results · How it works · Tools · Skills · Comparison · FAQ · Docs
Updated daily by the star-history workflow.
Measurements from the project's own audits. They come from one host and small corpora, so treat them as orders of magnitude for your planning, not as guarantees.
| Measurement | CPU | NVIDIA CUDA |
|---|---|---|
| Hybrid query, uncached, median | 11.13 ms | 6.10 ms |
| Hybrid query, uncached, p95 | 13.91 ms | 7.44 ms |
| Repeated query (cache hit), median | 0.033 ms | 0.028 ms |
| Initial indexing, including first model load | 13.655 s | 0.832 s |
| Sampled peak process memory (RSS) | 1,416 MB | 990 MB |
Workload: scripts/audit_retrieval.py indexing 7 public repository documents (149 chunks) into a temporary ChromaDB and FTS5 index; 36 uncached queries; cross-encoder disabled to isolate retrieval; Windows 11, RTX 3080 Ti, Python 3.12.7, FastEmbed 0.8.1, ChromaDB 1.5.9, model BAAI/bge-small-en-v1.5, Hugging Face offline mode. Source: audit 2026-10-03.
| Retrieval check | Result | Workload and source |
|---|---|---|
| Expected source in the top 5 | 12 of 12 questions; MRR@5 0.875 (English), 0.792 (Portuguese) | 6 English and 6 Portuguese questions over the same 149 chunks, default English model; a small smoke test, not a general quality claim (audit) |
| Cross-encoder rerank of 8 candidates | 141.9 ms first call including model load, 19.7 ms next call (CPU) | One English and one Portuguese query, network blocked (audit) |
| Large corpus | Full rebuild completed and filled 75,016 FTS5 rows on the first attempt | 5,889 documents / 75,016 chunks, v4.8.3 reproduction (CHANGELOG) |
More measurements: memory, model comparison, historical baseline
- BM25 memory: retained Python memory fell from 120.7 MB to 26.9 MB (−77.75%) for 10,000 documents / 1.99 million tokens after the token pool was added in v4.10.0 (audit).
- Metrics memory: 100,001 metric observations retained 328 bytes instead of 3,201,024 (audit).
- Multilingual model: on a 60-question diagnostic set over a private ~70,000-chunk corpus,
intfloat/multilingual-e5-largefound the expected source for 36 questions against 37 for the defaultBAAI/bge-small-en-v1.5; re-embedding 70,083 chunks on CUDA took 1,186.58 s (audit 2026-10-04). - Historical baseline: v4.7.1 microbenchmarks and a 100-document rebuild on CPU (perf-baseline-v4.7.1). The ratios apply to those workloads only.
- Live dashboard: the benchmark dashboard tracks CI microbenchmarks. They use mocked storage and models, so they answer a different question than the real-model numbers above.
- Ingest. Files under
documents/are parsed by format: code files get their function, class and import names as metadata, notebooks keep only Markdown and code cells, PDFs contribute their text. Markdown is split at##/###sections; every other format is cut into 1,000-character chunks with 200 characters of overlap. Chunks are de-duplicated by SHA-256, embedded with FastEmbed (defaultBAAI/bge-small-en-v1.5, 384 dimensions) and stored in an embedded ChromaDB. - Watch. While the server runs, a watcher re-indexes changed files after 10 seconds without new changes. Full rebuilds fill a staging collection and swap it in after validation, so searches keep using the current index.
- Search.
search_knowledgechecks a query cache (100 entries, 5-minute TTL), runs BM25 over an inverted index and vector search, fuses both rankings with weighted reciprocal rank fusion (hybrid_alpha, default 0.3, leaning towards keywords) and reranks the top candidates with theXenova/ms-marco-MiniLM-L-6-v2cross-encoder. With the opt-in FTS5 fast path, identifier-like queries such as CVE IDs, ticket keys and hashes go to SQLite FTS5 first. - Answer. Results go back to the MCP client with the file path, chunk index, snippet and scores. The client's model writes the answer; knowledge-rag never calls an LLM.
- Serve. Over stdio for one client, or as one Streamable HTTP or SSE server for many. HTTP mode answers health probes first, then checks the bearer token. Every tool call passes through the optional rate limiter and metrics.
config.yamland five presets configure all of it.
flowchart TB
subgraph CLIENTS["MCP clients"]
direction LR
C1[Claude Code]
C2[Claude Desktop]
C3[Cursor]
C4[Windsurf]
C5[VS Code · other stdio or HTTP clients]
end
subgraph TRANSPORT["Transport"]
T1[stdio<br/>1 process per client]
T2[Streamable HTTP / SSE<br/>1 server for N clients]
end
subgraph MIDDLEWARE["ASGI middleware (HTTP transports)"]
M1[HealthMiddleware<br/>/health · /healthz]
M2[BearerAuthMiddleware<br/>constant-time compare]
end
subgraph MCP["13 MCP tools (contract checked in CI)"]
W[Tool wrappers<br/>optional rate limit · metrics]
MT1[search_knowledge]
MT2[get_document · search_similar]
MT3[add_document · add_from_url · update · remove]
MT4[reindex_documents · get_reindex_status]
MT5[list_documents · list_categories · get_index_stats · evaluate_retrieval]
end
subgraph SEARCH["Retrieval pipeline"]
QC[Query cache<br/>LRU 100 · 5-min TTL]
R[Query router<br/>regex, FTS5 fast path only]
F[FTS5 fast path<br/>opt-in lexical retrieval]
H[Hybrid search]
BM[BM25<br/>inverted index]
SE[Vector search<br/>FastEmbed ONNX, lazy-loaded]
RRF[Weighted reciprocal rank fusion]
CE[Cross-encoder rerank<br/>MiniLM-L-6-v2]
end
subgraph STORAGE["Local storage (data_dir)"]
CH[ChromaDB<br/>vectors + metadata<br/>WAL with HTTP transports]
FT[SQLite FTS5<br/>lexical index<br/>WAL + busy timeout]
MD[index_metadata.json<br/>durable state]
end
subgraph INGEST["Document ingestion"]
FS[documents/ folder]
WD[Watcher<br/>10 s debounce]
PA[Format parsers<br/>MD · PDF · DOCX · code · IaC · IPYNB]
CK[Chunker<br/>Markdown sections · 1,000-char windows]
EM[FastEmbed ONNX<br/>default bge-small-en-v1.5, 384-d]
DD[SHA-256 dedup]
SW[Staging rebuild<br/>rollback on handled publication failure]
end
subgraph OBS["Observability (opt-in)"]
PM[Prometheus /metrics<br/>separate port, HTTP transports]
LG[Structured JSON logs]
HC[Health payload<br/>version · uptime · cache stats]
end
subgraph CFG["Configuration"]
YM[config.yaml<br/>+ 5 presets]
end
CLIENTS -->|MCP over stdio| T1
CLIENTS -.->|MCP over HTTP| T2
T1 --> W
T2 --> M1 --> M2 --> W
W --> MT1 & MT2 & MT3 & MT4 & MT5
MT1 --> QC
QC -->|cache miss| R
R -->|identifier-like| F
R -->|everything else| H
H --> BM
H --> SE
F --> FT
BM --> CH
SE --> CH
BM --> RRF
SE --> RRF
RRF --> CE
CE --> QC
MT2 --> CH
MT3 --> INGEST
MT4 --> SW
MT5 --> CH
FS --> WD --> PA
PA --> CK --> DD --> EM --> CH
SW -.->|validated swap| CH
SW -.-> FT
CH -.-> MD
W -.->|instrumented| PM
W -.->|logs| LG
M1 --> HC
YM -.-> SEARCH
YM -.-> STORAGE
YM -.-> OBS
YM -.-> MIDDLEWARE
classDef client fill:#3776AB,stroke:#1e5a8a,color:#fff
classDef transport fill:#00A67E,stroke:#006e54,color:#fff
classDef middleware fill:#6b46c1,stroke:#4c1d95,color:#fff
classDef storage fill:#4b5563,stroke:#1f2937,color:#fff
classDef obs fill:#dc2626,stroke:#7f1d1d,color:#fff
classDef ingest fill:#b45309,stroke:#78350f,color:#fff
class C1,C2,C3,C4,C5 client
class T1,T2 transport
class M1,M2 middleware
class CH,FT,MD storage
class PM,LG,HC obs
class FS,WD,PA,CK,EM,DD,SW ingest
Component ownership, memory and concurrency boundaries and the hybrid_alpha weights: docs/ARCHITECTURE.md.
Every path below runs the same server. Each claude mcp add sets KNOWLEDGE_RAG_DIR, the folder that holds config.yaml, documents/, data/ and models_cache/. Without it, the server picks a folder from its install location and the working directory the client starts it in, which can be a project you did not intend (config.py).
npx -y knowledge-rag --install-only
claude mcp add knowledge-rag -s user --env KNOWLEDGE_RAG_DIR=~/knowledge-rag -- npx -y knowledge-ragThe npm package is a launcher. It finds Python 3.11+ (python3/python, or python/py -3 on Windows), creates a virtual environment in ~/.knowledge-rag/venv, installs the matching knowledge-rag version from PyPI and starts the server over stdio. --install-only does the installation once ahead of time, so the client's first start does not wait for pip. A new npm version reinstalls the package on the next start. If a client on native Windows fails to start npx, register cmd /c npx -y knowledge-rag instead.
python3 -m venv ~/knowledge-rag/venv
~/knowledge-rag/venv/bin/python -m pip install knowledge-rag
claude mcp add knowledge-rag -s user --env KNOWLEDGE_RAG_DIR=~/knowledge-rag \
-- ~/knowledge-rag/venv/bin/python -m mcp_server.serverOn Windows (PowerShell), use python and $HOME\knowledge-rag\venv\Scripts\python.exe in place of python3 and ~/knowledge-rag/venv/bin/python.
Optional: knowledge-rag init, run inside ~/knowledge-rag with the virtual environment active, copies config.example.yaml and the five presets and creates documents/. It does not create config.yaml or register the server: copy the template or a preset to config.yaml yourself. Without a config.yaml, the defaults apply, including all 33 default file types.
The image is published for linux/amd64 and linux/arm64. For stdio, the client starts one container per session:
claude mcp add knowledge-rag -s user -- docker run -i --rm \
-v ~/knowledge-rag/documents:/app/documents \
-v ~/knowledge-rag/data:/app/data \
-v knowledge-rag-models:/app/models_cache \
ghcr.io/lyonzin/knowledge-rag:latestThe image runs python -m mcp_server.server, which reads /app/config.yaml and defaults to stdio on 127.0.0.1. To serve HTTP from a container, mount a config that listens on all container interfaces and pass --transport:
mkdir -p ~/knowledge-rag
cat > ~/knowledge-rag/config.yaml <<'EOF'
server:
host: "0.0.0.0"
auth:
bearer_token: "replace-with-a-long-random-token"
EOF
docker run -d --name knowledge-rag -p 127.0.0.1:8179:8179 \
-v ~/knowledge-rag/config.yaml:/app/config.yaml:ro \
-v ~/knowledge-rag/documents:/app/documents \
-v ~/knowledge-rag/data:/app/data \
-v knowledge-rag-models:/app/models_cache \
ghcr.io/lyonzin/knowledge-rag:latest --transport streamable-http
curl http://127.0.0.1:8179/health
claude mcp add --transport http knowledge-rag http://127.0.0.1:8179/mcp \
# if server.auth.bearer_token is set, add the token as a Bearer auth headerThe image builds its model copies into FastEmbed's default cache, but the server reads /app/models_cache, so the models download again the first time they are needed. The knowledge-rag-models volume keeps them across containers.
stdio fits one client that owns one process. For several clients on one index (Claude Code, Cursor, LM Studio, more terminals), run one Streamable HTTP server and point every client at it (#217, #223):
# ~/knowledge-rag/config.yaml
server:
transport: "streamable-http" # "sse" for clients that only speak SSE
host: "127.0.0.1"
port: 8179
auth:
bearer_token: "replace-with-a-long-random-token"
rate_limit:
enabled: true
requests_per_minute: 60
burst: 10
metrics:
enabled: true
port: 9179 # binds 0.0.0.0 with no auth: firewall it
logging:
format: "json"KNOWLEDGE_RAG_DIR=~/knowledge-rag knowledge-rag # run from the venv where you installed it
curl http://127.0.0.1:8179/health # {"status":"ok","version":"5.0.0",...}
claude mcp add --transport http knowledge-rag http://127.0.0.1:8179/mcp \
# if server.auth.bearer_token is set, add the token as a Bearer auth header| Transport | Clients per server process | Who runs the process | Use it for |
|---|---|---|---|
stdio (default) |
1 | The MCP client, per session | One client; models load in each process on first use |
streamable-http |
Many | You, one background server | Several clients sharing one index and one model load (preferred) |
sse |
Many | You, one background server | HTTP clients that only support SSE |
HTTP transports switch on the single-instance lock (KNOWLEDGE_RAG_SINGLE_INSTANCE=1), so a second server cannot open the same data directory. The default host is 127.0.0.1. If you bind to 0.0.0.0 or serve a network, set server.auth.bearer_token and put TLS in front with a reverse proxy such as Caddy or Nginx; without a token, the server prints a warning that the endpoint is unauthenticated.
Claude custom connectors (remote MCP)
- Network requirement: custom connector requests come from Anthropic's cloud on web, desktop and mobile, so they cannot reach
127.0.0.1. Expose the server through a secure public HTTPS endpoint (for example Cloudflare Tunnel, ngrok or a reverse proxy) athttps://your-public-domain/mcp. Configure the proxy to send an upstreamHostthat the HTTP app accepts forserver.host(with the default loopback configuration,127.0.0.1:8179); forwarding the public hostname unchanged can produce421 Invalid Host header. Keep bearer authentication and HTTPS on. A localclaude_desktop_config.jsonusescommand/argsfor local stdio processes and does not support remote HTTP endpoints. - Setup and auth: in Claude (Pro/Max: Customize > Connectors > + Add > Add custom connector; Team/Enterprise owners: Organization settings > Connectors), enter the public endpoint. Under Request headers, set
AuthorizationtoBearer <your-token>, matchingserver.auth.bearer_tokeninconfig.yaml. See Anthropic's custom connectors guide.
Any client that starts a stdio MCP server from a command, or connects to a Streamable HTTP endpoint, can use knowledge-rag. The installation guide has the exact JSON for each of these:
| Client | Where the server entry goes |
|---|---|
| Claude Desktop | claude_desktop_config.json, under mcpServers (Settings → Developer → Edit Config) |
| Cursor | ~/.cursor/mcp.json or .cursor/mcp.json, under mcpServers |
| Windsurf | ~/.codeium/windsurf/mcp_config.json, under mcpServers |
| Antigravity | ~/.gemini/antigravity/mcp_config.json, under mcpServers |
| VS Code (Copilot) | .vscode/mcp.json, under servers, with "type": "stdio" |
| opencode | opencode.json, under mcp, with command as an array |
{
"mcpServers": {
"knowledge-rag": {
"command": "npx",
"args": ["-y", "knowledge-rag"],
"env": { "KNOWLEDGE_RAG_DIR": "~/knowledge-rag" }
}
}
}Warning
One writer per data directory. Over stdio, each MCP client starts and owns its own knowledge-rag process. Do not run a second writer against the same data_dir while that client is open: on Windows, two writers corrupted the ChromaDB HNSW segment (#216). Add "KNOWLEDGE_RAG_SINGLE_INSTANCE": "1" to the client's env block as a safety net, or share one HTTP server. Running CLI commands safely: docs/single-instance.md.
Warning
Keep data_dir out of cloud-sync folders. OneDrive (including OneDrive - <tenant>), iCloud Drive, Dropbox, Google Drive, Box, pCloud, MEGAsync, Nextcloud/ownCloud and Sync hold handles on the live index while they upload it. That races with ChromaDB's HNSW memory maps and SQLite WAL writes and can crash the process (0xC0000005 on Windows, SIGBUS elsewhere); startup auto-repair then moves the index to data/backups/auto-repair-* and you reindex from scratch. Since v4.11.0, startup stops with exit code 78 (EX_CONFIG) when data_dir resolves under one of these providers. Use a local path such as %LOCALAPPDATA%\knowledge-rag\data on Windows or ~/.local/share/knowledge-rag/data on Linux and macOS, or set paths.allow_cloud_sync: true to accept the risk.
All install methods, GPU setup and troubleshooting for each client: docs/INSTALLATION.md.
| Tool | What it does |
|---|---|
search_knowledge |
Hybrid BM25 + vector search with cross-encoder reranking; filters by category, folders (include_folders, exclude_folders) and min_score |
get_document |
Returns the full content of one indexed file |
search_similar |
Finds documents similar to an indexed file |
evaluate_retrieval |
Reports MRR@5 and Recall@5 for your own query → expected file pairs |
list_documents |
Lists indexed documents, 100 per page by default (up to 200 with limit), paged with offset |
list_categories |
Lists categories, derived from folder paths, with document counts |
get_index_stats |
Document and chunk counts, embedding model, BM25 status, cache hit rate, watcher status, HNSW self-recall probe |
add_document |
Saves text as a file under documents/ and indexes it |
update_document |
Replaces a document's content and re-indexes it |
remove_document |
Removes a document's chunks from the index, and optionally the file |
add_from_url |
Fetches an http(s) page, strips the HTML, saves it as Markdown with a provenance marker and indexes it |
reindex_documents |
Incremental by default; force re-embeds every file, full_rebuild builds and swaps a staging collection; runs in the background |
get_reindex_status |
Reports the progress of a background reindex |
Each result carries source (the file path), chunk_index and, in snippet mode, match_offset (a character offset into the content), so citations point to a file and chunk, not a line number. score is normalized per query, so the top result is always 1.0 and min_score filters relative to the other results; compare reranker_score, the raw cross-encoder score, across queries. Parameters, return shapes and examples: docs/API.md.
The 13 tools tell an agent what it can do; they do not tell it when to search. The skills/ folder adds ten MIT-licensed skills for Claude Code that make the agent check your index first, cite what it used and keep the index current. Other agents can use the same instructions as rules or system prompts (skills/README.md).
| Skill | Kind | What it changes |
|---|---|---|
rag-check-first |
Foundation | Searches the corpus before answering technical questions |
rag-cite-sources |
Foundation | Cites the source file and chunk for every claim taken from the corpus |
rag-onboard-context |
Foundation | Probes what is indexed at the start of a session |
rag-deep-dive |
Workflow | Search, read the best document in full, then find similar ones |
rag-web-fallback |
Workflow | Goes to the web only when the local corpus comes back empty or insufficient |
rag-troubleshoot |
Workflow | Looks up prior fixes and runbooks before debugging an error |
rag-code-review |
Workflow | Checks ADRs, standards and similar files before commenting on a change |
rag-index-decisions |
Maintenance | Indexes decisions and fixes back into the corpus |
rag-evaluate-quality |
Maintenance | Measures MRR@5 and Recall@5 after ingestion or configuration changes |
rag-security-first |
Domain | Consults MITRE ATT&CK, CVE and runbook notes first on security tasks |
Each skill installs as ~/.claude/skills/<skill-name>/SKILL.md (or .claude/skills/<skill-name>/SKILL.md for one project):
# Node.js 22.20+: choose the skills and agents interactively; -g installs for all projects
npx skills add lyonzin/knowledge-rag -g
# Without Node (Linux, macOS, WSL, Git Bash): all ten skills into ~/.claude/skills
curl -fsSL https://raw.githubusercontent.com/lyonzin/knowledge-rag/master/skills/install.sh | bashinstall.sh also takes --project, --target <path>, --only rag-check-first,rag-cite-sources, --dry-run, --list and --help. Restart Claude Code after installing. Skill chains and per-client setup: skills/CATALOG.md, skills/README.md.
As of 2026-11-06. 🗸 default: built in and on by default. 🗸 opt-in: built in, off until you enable it. 🗸: available, default state not compared. component: a library piece you assemble yourself. ✗: not built in, meaning absent from the code, needing an external provider (local embeddings column), or not found in code or docs; ✗ manual: you start it yourself. Words in parentheses name the mechanism.
| Project (stars, license) | MCP server | MCP client | Hybrid | Rerank | Auto re-index | Local emb. |
|---|---|---|---|---|---|---|
| knowledge-rag (301, MIT) | 🗸 13 tools | ✗ | 🗸 default (+FTS5 opt-in) | 🗸 default | 🗸 default | 🗸 |
| Dify (160.5k, Dify license) | 🗸 | 🗸 | 🗸 | 🗸 opt-in | ✗ manual sync | ✗ |
| Open WebUI (157.9k, Open WebUI license) | ✗ | 🗸 | 🗸 opt-in | 🗸 opt-in | ✗ manual (add-on) | 🗸 |
| Langflow (156.4k, MIT) | 🗸 | 🗸 | component | component | component (Schedule Trigger) | component |
| LangChain (149.1k, MIT) | ✗ | 🗸 | component | component | component (indexing API) | component |
| RAGFlow (93.7k, Apache-2.0) | 🗸 | 🗸 | 🗸 default | 🗸 opt-in | 🗸 connectors | ✗ |
| AnythingLLM (68.0k, MIT) | ✗ | 🗸 | ✗ | 🗸 opt-in | 🗸 opt-in (beta, per file, hourly) | 🗸 |
| Serviette, ex pathwaycom/llm-app (58.7k, MIT) | ✗ | ✗ | 🗸 opt-in | 🗸 opt-in | 🗸 default | 🗸 |
| PrivateGPT (57.6k, Apache-2.0) | ✗ | 🗸 | 🗸 opt-in (Qdrant) | ✗ | 🗸 opt-in (CLI --watch) | ✗ |
| LlamaIndex (52.8k, MIT) | ✗ | 🗸 | component | component | component (refresh_ref_docs) | ✗ |
| LightRAG (40.6k, MIT) | ✗ | ✗ | ✗ | 🗸 opt-in (API) | ✗ manual scan | ✗ |
| Quivr 2 (39.6k, MIT) | 🗸 | ✗ | 🗸 | 🗸 opt-in (paid service) | 🗸 scheduled feeds | 🗸 |
| Khoj (38.0k, AGPL-3.0) | ✗ | 🗸 | ✗ | 🗸 opt-in | 🗸 via clients, every 10 min | 🗸 |
| GraphRAG (36.6k, MIT) | ✗ | ✗ | ✗ | ✗ | ✗ manual (graphrag update) | ✗ |
| Onyx (33.3k, MIT CE + EE) | 🗸 | 🗸 | 🗸 default | ✗ removed in 2026 | 🗸 connectors | 🗸 |
| FastGPT (30,000, FastGPT license) | 🗸 | 🗸 | 🗸 | 🗸 with a rerank model | 🗸 opt-in (daily sync) | ✗ |
| Project | Type | Needs for first answer | Formats |
|---|---|---|---|
| knowledge-rag | MCP server | pip + MCP client | 33 (+2 opt-in), code/IaC; no OCR |
| Dify | platform | Docker (Postgres, Redis, Weaviate…) + model | PDF, Office, CSV, MD, HTML, EML, EPUB |
| Open WebUI | chat app | pip/Docker + Ollama/API | PDF, Office, EPUB, CSV, HTML, MD |
| Langflow | builder | pip/Docker + provider key | TXT, MD, CSV, JSON, HTML, PDF, DOCX… |
| LangChain | library | code + provider | loaders |
| RAGFlow | engine + UI | Docker (Elasticsearch, MySQL, MinIO, Kvrocks, NATS), 16 GB + model | Office, PDF, scans, web |
| AnythingLLM | desktop/Docker app | installer (built-in LLM) | PDF, TXT, DOCX… |
| Serviette | RAG server | pip + Pathway licence key | text, Office, PDF, scans |
| PrivateGPT | API server | LLM + embedding servers | PDF, DOCX, PPT/X, EPUB, IPYNB, images, audio |
| LlamaIndex | library | code + OPENAI_API_KEY | ~20 incl. images, MP3/4 |
| LightRAG | graph lib + server | LLM + embedding providers | TXT, MD, PDF, DOCX |
| Quivr 2 | HTTP engine | Docker (Postgres, object store) | PDF plugin, RSS, mail |
| Khoj | app | Docker (pgvector, SearxNG…) + LLM | PDF, MD, Org, Word, Notion |
| GraphRAG | library/CLI | OpenAI/Azure key | TXT, CSV, JSON |
| Onyx | enterprise app | Docker (~12 containers: OpenSearch, Postgres, Redis…) + LLM | PDF, DOCX, PPTX, XLSX, EML, EPUB, HTML, TXT, MD, JSON, YAML, XML, CSV, SQL, PNG, JPG, WebP |
| FastGPT | platform | Docker (MongoDB, PgVector, AI Proxy) + LLM + embedding | TXT, MD, HTML, PDF, Office, CSV |
Notes: Serviette was renamed from pathwaycom/llm-app and inherited its stars. The LlamaIndex README says its focus moved to LlamaParse. Quivr was repurposed as a content-stream search engine. The GraphRAG README says it is in maintenance mode. PrivateGPT's changelog lists a SentenceTransformer reranker (#1810) that the current code no longer contains.
Where knowledge-rag differs. It is the only row with hybrid search and cross-encoder reranking both on by default, and the only MCP server in the table that answers without Docker, a database server or a model key. Auto re-index is on by default only in knowledge-rag and Serviette, and Serviette ships no MCP server; everywhere else it takes a connector, a client, a setting, assembly or a manual run. It is code-first: 23 of its 33 default file types are code or infrastructure files. The license is plain MIT.
Where others are stronger. Multi-user scale: Postgres, OpenSearch and Elasticsearch stacks against one writer on an embedded ChromaDB. Web UIs and visual builders: knowledge-rag has no UI and does not generate answers; the MCP client's LLM does. Connectors, OCR, images and audio, and graph RAG. Evidence and reach: knowledge-rag's published quality check is a 12-question smoke test (Recall@5 1.0) and it has 301 stars. This table compares features, not retrieval quality.
Checklist for your own evaluation
Feature checklists are not benchmarks. Before you choose, measure what your deployment needs on comparable hardware:
| Decision | What to verify |
|---|---|
| Local or remote processing | Where models run, which client receives content, which features need network access |
| Retrieval quality | Expected-source queries on your corpus, including its languages and identifiers |
| Performance | Cold start, indexing, uncached queries, memory peaks and concurrent use |
| Operations | Authentication, metrics exposure, backups, recovery and who owns the single writer |
| Extensibility | Supported parsers, model profiles and the public MCP contract |
Results this project measured against an older version of itself, such as the audit, are not speed comparisons with other RAG projects.
Sources for every row
- knowledge-rag:
mcp_server/config.py,mcp_server/server.py - Dify: github.com/langgenius/dify (
docker/.env.example,api/controllers/mcp/mcp.py,api/core/tools/mcp_tool/provider.py,api/core/rag/extractor/extract_processor.py; re-index:api/services/knowledge/document_sync.pysyncs on request only, andapi/schedule/has no document sync job); indexing methods docs - Open WebUI: github.com/open-webui/open-webui (
backend/open_webui/config.py); github.com/open-webui/docs (docs/features/extensibility/mcp.mdx,docs/features/workspace/knowledge.mdx) - Langflow: MCP client docs, quickstart, github.com/langflow-ai/langflow (
src/lfx/src/lfx/utils/schemas.py; hybrid:src/bundles/datastax/src/lfx_datastax/components/datastax/astradb_vectorstore.py; rerank:src/bundles/cohere/src/lfx_cohere/components/cohere/cohere_rerank.py; re-index:src/lfx/src/lfx/components/triggers/schedule_trigger.py) - LangChain: MCP docs; github.com/langchain-ai/langchain (
libs/langchain/langchain_classic/retrievers/ensemble.py,document_compressors/cross_encoder_rerank.py; re-index:libs/core/langchain_core/indexing/api.py) - RAGFlow: github.com/infiniflow/ragflow (README,
docs/develop/mcp/overview.md,docs/guides/dataset/retrieval_testing.md,docker/.env) - AnythingLLM: github.com/Mintplex-Labs/anything-llm (README,
frontend/src/pages/WorkspaceSettings/VectorDatabase/VectorSearchMode/index.jsx); anythingllm-docspages/beta-preview/active-features/live-document-sync.mdx - Serviette: github.com/pathwaycom/serviette
- PrivateGPT: github.com/zylon-ai/private-gpt (README,
fern/docs/pages/api-guide/ingestion.mdx; hybrid:settings.yaml,qdrant.hybrid_search: false; rerank:CHANGELOG.md(#1810), with no reranker in the current code) - LlamaIndex: starter example; github.com/run-llama/llama_index (
llama-index-core/llama_index/core/readers/file/base.py;module_guides/mcp/llamaindex_mcp.md; re-index:llama-index-core/llama_index/core/indices/base.py) - LightRAG: github.com/HKUDS/LightRAG (
docs/LightRAG-API-Server.md,lightrag/parser/registry.py) - Quivr: github.com/The-Vibe-Company/quivr (README,
docs-site/run-quivr/rerank-with-jev.mdx; local embeddings:deploy/cpu/embedding_server.py,docs-site/run-quivr/choose-an-embedding-model.mdx) - Khoj: github.com/khoj-ai/khoj (
docker-compose.yml,src/khoj/database/models/__init__.py,src/khoj/search_type/text_search.py,src/khoj/processor/tools/mcp.py,src/interface/desktop/main.js) - GraphRAG: github.com/microsoft/graphrag (README,
docs/get_started.md; re-index:pyproject.toml(graphrag update),docs/config/yaml.md(update_output_storage)) - Onyx: github.com/onyx-dot-app/onyx (README,
deployment/docker_compose/docker-compose.yml,backend/shared_configs/configs.py,backend/onyx/configs/chat_configs.py,backend/alembic/versions/78ebc66946a0_remove_reranking_from_search_settings.py; formats:backend/onyx/file_processing/file_types.py) - FastGPT: github.com/labring/FastGPT (re-index:
packages/service/core/dataset/datasetSync/index.ts,packages/web/i18n/en/dataset.json), Docker deployment docs
Each capability says whether it is on by default and links to the test, document or release note that backs it. Opt-in features do nothing until you set their key in config.yaml or the environment variable shown.
Security
| Capability | Default | Enable / tune | Proof |
|---|---|---|---|
| Listens on loopback only | On | server.host |
code |
| Bearer token on HTTP transports | Opt-in | server.auth.bearer_token |
test |
Client paths kept inside documents_dir |
On | — | test |
| Fence on fetched web content | On | — | test |
| Rate limit, one window per process | Opt-in | server.rate_limit |
code |
Refuses a cloud-synced data_dir |
On | paths.allow_cloud_sync |
v4.11.0 |
| Bandit, Semgrep, pip-audit, Gitleaks, dependency review, Socket | Every PR | — | workflow |
| CodeQL | Master and weekly | — | workflow |
| PyPI Trusted Publishing, npm provenance | Every release | — | workflow |
| OpenSSF Best Practices | Passing | — | #13864 |
Observability
| Capability | Default | Enable / tune | Proof |
|---|---|---|---|
/health and /healthz, no token needed |
On (HTTP) | — | test |
| Prometheus metrics on a separate port | Opt-in (HTTP) | server.metrics |
test |
| Structured JSON logs | Opt-in | server.logging.format |
test |
| Public benchmark dashboard | Every push to master | — | dashboard |
Scale and performance
| Capability | Default | Enable / tune | Proof |
|---|---|---|---|
| One HTTP server for many clients | Opt-in | server.transport |
doc |
| ChromaDB WAL journal | Auto (HTTP) | — | test |
| BM25 over an inverted index | On | — | test |
| Cross-encoder rerank, fused-order fallback | On | models.reranker |
test |
| FTS5 fast path for identifiers | Opt-in | search.lexical_fast_path |
doc |
| NVIDIA CUDA with CPU fallback | Auto | models.embedding.gpu |
test |
| Windows AMD DirectML, experimental | Opt-in | gpu: "directml" |
test |
| Embedding batch size per provider | Auto | documents.embed_batch_size |
test |
| Query cache, cleared on writes | On | — | test |
| Background reindex with progress | On | — | doc |
| Staging rebuild while the index serves | On | — | test |
Reliability
| Capability | Default | Enable / tune | Proof |
|---|---|---|---|
| Single-instance lock | Auto (HTTP) | KNOWLEDGE_RAG_SINGLE_INSTANCE |
test |
| Startup probe, auto-repair, 3 backups kept | On | — | test |
Index kept if documents_dir is empty or missing |
On | — | test |
| File watcher, 10 s quiet period | On | KNOWLEDGE_RAG_WATCHER_DISABLED |
test |
| Rollback on handled publication failures | On | — | test |
| 1,500+ tests on 9 OS × Python cells, 80% floor | Every PR | — | run |
| MCP tool contract and API surface vs master | Every PR | — | test |
| Legacy configs and call shapes | Every PR | — | test |
| Performance gate, 10% | Every PR | — | workflow |
| Chaos tests, 3 failure scenarios | Nightly | — | tests |
| 50,000-call soak, stub embeddings | Nightly | — | test |
| Mutation testing of lock and preflight | Nightly | — | workflow |
| Full suite three times for flaky tests | Nightly | — | workflow |
Compliance: the project holds no LGPD, GDPR or HIPAA certification. Local indexing can help with data-locality requirements, but the MCP client sends the results it retrieves to its own model provider as part of the conversation.
Operational notes: what each control does and does not cover
- Bearer token: compared in constant time (
hmac.compare_digest). A missing or wrong token gets401withWWW-Authenticate: Bearer realm="knowledge-rag". Health probes answer without it. stdio has no token: the client owns the process. There is no built-in TLS, so put a reverse proxy (Caddy, Nginx) in front of anything that leaves loopback. - Rate limit: one sliding window for the whole process, shared by every client:
requests_per_minuteplusburst(defaults 60 and 10) calls per 60 seconds. It is not per client. - Metrics: only with HTTP transports, on
server.metrics.port(default 9179). The endpoint binds0.0.0.0with no authentication, so firewall it. It exposes per-tool calls, errors and durations plus FTS5 fast-path counters. - Paths: the six tools that accept a path resolve it inside
documents_dirand reject traversal, escaping symlinks, NUL bytes and NTFS alternate data streams (CWE-22, CWE-59). - Fetched content:
add_from_urlneutralizes known model control tokens and stores a provenance marker with the page. That limits prompt injection; it does not prevent it, so treat retrieved text as untrusted (SECURITY.md). The tool fetches whatever http(s) URL it receives, private network addresses included, and follows redirects; on a shared server, decide who may call it. - Network use: the first model download contacts Hugging Face; set
HF_HUB_OFFLINE=1once the models are cached. - Health payload:
GET /healthreturnsstatus,version,uptime_secondsand query-cache statistics on the MCP port. - Single instance: HTTP transports turn the lock on automatically; for stdio clients, add
KNOWLEDGE_RAG_SINGLE_INSTANCE=1to the client'senvblock. A second process on the samedata_dirthen exits with code 75. - Index safety: a full rebuild fills a staging collection and swaps it in after validation; handled failures roll back. Startup retries a failing probe 3 times before moving the index to
data/backups/auto-repair-*, keeping the 3 newest. Recovery steps: docs/reindex-operations.md. - Dependencies:
requirements.txtandpyproject.tomldeclare version ranges, not pins. For a reproducible deployment, resolve once and record the tested environment. - CI gates: every pull request runs 34 GitHub Actions checks. The performance gate fails a benchmark only when every branch run is more than 10% slower than every master run. Nightly jobs open issues instead of blocking merges; mutation testing is exploratory and never fails the run.
- Disclosure: private security advisories, acknowledged within 48 hours, initial assessment within 5 business days, 90-day coordinated disclosure (SECURITY.md).
knowledge-rag init # copies config.example.yaml, the presets and an empty documents/
cp presets/general.yaml config.yaml # or cybersecurity, developer, research, multilingual
# drop your files into documents/, restart, ask the agentSettings live in config.yaml inside KNOWLEDGE_RAG_DIR. Every key is optional; these are the defaults:
paths:
documents_dir: "./documents"
data_dir: "./data" # local disk only, see the cloud-sync warning
models_cache_dir: "./models_cache"
documents:
chunking:
chunk_size: 1000
chunk_overlap: 200
models:
embedding:
profile: "custom" # "compact" | "quality" | "multilingual" | "custom"
model: "BAAI/bge-small-en-v1.5" # used when profile is "custom"
dimensions: 384
gpu: "auto" # "auto" | "true" | "false" | "directml"
reranker:
enabled: true
model: "Xenova/ms-marco-MiniLM-L-6-v2"
search:
default_results: 5
max_results: 100
lexical_fast_path:
enabled: false # SQLite FTS5 for identifier-like queries
server:
transport: "stdio" # "stdio" | "streamable-http" | "sse"
host: "127.0.0.1"
port: 8179| Profile | Embedding model | Dimensions |
|---|---|---|
compact |
BAAI/bge-small-en-v1.5 |
384 |
quality |
BAAI/bge-large-en-v1.5 |
1024 |
multilingual |
intfloat/multilingual-e5-large, with query:/passage: prefixes |
1024 |
custom (default) |
models.embedding.model |
models.embedding.dimensions |
Changing the embedding model or its prefixes needs a full reindex. GPU: auto tries NVIDIA CUDA on the first embedding and falls back to CPU; the [gpu] extra targets CUDA 12. directml is the experimental Windows AMD mode and needs models.embedding.device_id (GPU setup).
Presets are complete config.yaml files (knowledge-rag init copies them):
| Preset | For | Categories | Routing keywords | Query expansions | File types enabled |
|---|---|---|---|---|---|
cybersecurity |
Red and blue team, CTF, threat hunting, incident response | 8 | 128 | 69 | 14 |
developer |
Design docs, ADRs, runbooks, APIs, code | 9 | 137 | 64 | 16 |
research |
Papers, lab notebooks, datasets | 9 | 80 | 40 | 8 |
general |
Anything else: no categories, routing or expansions | 0 | 0 | 0 | 4 |
multilingual |
Non-English corpora, multilingual profile |
0 | 0 | 0 | 4 |
The presets list their own documents.supported_formats, so they index fewer file types than the defaults: general and multilingual index only .md, .txt, .pdf and .docx. Every field, every default and tuning recipes: docs/CONFIGURATION.md.
35 parsers; 33 file types enabled by default
Without a config.yaml, 33 file types are indexed. The MetaTrader extensions .mq4 and .mqh are opt-in. The shipped config.example.yaml enables 29 (it leaves out .xlsx, .pptx, .ipynb and .csv), and each preset enables its own list. Chunking is fixed-size (1,000 characters, 200 overlap) for every format except Markdown.
| Format | Match | How it is parsed | Default |
|---|---|---|---|
| Markdown | .md |
Splits at ##/### sections, code blocks protected |
Yes |
| Plain text | .txt |
Fixed-size chunks | Yes |
.pdf |
PyMuPDF text extraction, or an explicit page-marked UTF-8 extraction (below); no OCR | Yes | |
| Word | .docx |
python-docx, headings kept as Markdown | Yes |
| Excel | .xlsx |
openpyxl, sheet by sheet | Yes |
| PowerPoint | .pptx |
python-pptx, slide by slide | Yes |
| Jupyter Notebook | .ipynb |
Markdown and code cells only; outputs and base64 skipped | Yes |
| JSON | .json |
Structure-aware, flattened key-value text | Yes |
| CSV | .csv |
Header and rows as text | Yes |
| XML | .xml |
Root element and namespace metadata | Yes |
| Python | .py |
Function and class names as metadata | Yes |
| C | .c |
Functions, structs and includes as metadata | Yes |
| C/C++ header | .h |
Function declarations and structs as metadata | Yes |
| C++ | .cpp |
Classes, structs and includes as metadata | Yes |
| JavaScript | .js |
Functions, classes and imports (ESM and CJS) as metadata | Yes |
| React JSX | .jsx |
Same as JavaScript | Yes |
| TypeScript | .ts |
Functions, classes, interfaces, enums and imports as metadata | Yes |
| React TSX | .tsx |
Same as TypeScript | Yes |
| Go | .go |
Functions, structs and imports as metadata | Yes |
| Rust | .rs |
Functions, structs, enums, traits and use imports as metadata |
Yes |
| Kotlin | .kt |
Functions (including class members) and classes as metadata | Yes |
| YAML | .yaml |
Kubernetes kind, apiVersion and name as metadata |
Yes |
| YAML | .yml |
Same as .yaml |
Yes |
| HuJSON | .hujson |
JSON with comments and trailing commas (for example Tailscale ACLs) | Yes |
| CUE | .cue |
Package and imports as metadata | Yes |
| Protocol Buffers | .proto |
Services, messages and RPCs as metadata | Yes |
| Rego | .rego |
OPA policies: package and imports as metadata | Yes |
| SQL | .sql |
Table names and statement types as metadata | Yes |
| Shell | .sh |
Function names as metadata | Yes |
| jq | .jq |
Indexed as a shell-style script | Yes |
| Dockerfile | Dockerfile |
Plain text, matched by exact file name | Yes |
| Makefile | Makefile |
Plain text, matched by exact file name | Yes |
| Tiltfile | Tiltfile |
Starlark: def functions and load() calls as metadata |
Yes |
| MQL4 source | .mq4 |
Code parser | Opt-in |
| MQL4 header | .mqh |
Code parser | Opt-in |
A file with a .pdf extension can also hold an explicit text extraction: the first line must be [Page N] with a positive page number, followed by non-empty UTF-8 text. An optional UTF-8 BOM and tab/CR/LF whitespace are accepted. Recognition is limited to 4 MiB, rejects other C0/C1 control characters and DEL, and never treats a file with PDF magic bytes in its first 1 KiB as text. Accepted extractions keep their source path and are marked content_format=extracted_text and page_count_source=markers; the marker count does not prove the original PDF's page count. Binary PDFs keep the native parser and its error messages, and invalid or oversized text lookalikes remain errors.
To enable an opt-in format, list it in documents.supported_formats. The list replaces the default, so include every type you want:
documents:
supported_formats: [".md", ".pdf", ".mq4", ".mqh"]Per-format notes: docs/CONFIGURATION.md.
Does knowledge-rag send my documents to the cloud?
Indexing, embeddings and search run on your machine. Three things use the network: the first model download from Hugging Face, add_from_url when you call it, and your MCP client, which sends the results it retrieves to its model provider as part of the conversation.
Can it run fully offline or air-gapped?
Yes, once the models are in models_cache/. Start it once with network access, or copy a populated models_cache/ from another machine, then set HF_HUB_OFFLINE=1. The audit measurements above ran in that mode.
How is it different from LangChain, LlamaIndex or a vector database?
Those are building blocks you assemble in code. knowledge-rag is a finished MCP server: hybrid search, reranking and folder watching behind 13 tools, configured with a YAML file. See the comparison.
Do I need a GPU?
No. CPU is the default and the fallback. NVIDIA CUDA speeds up embedding (13.7 s against 0.8 s for the first indexing in the audit workload); on Windows, AMD GPUs can opt into experimental DirectML. See docs/gpu-setup.md.
Do I have to reindex after editing files?
Not while the server runs: the watcher picks up changes after 10 seconds without new ones. Changes made while it was stopped are indexed by the next change or by calling reindex_documents() with no arguments.
Can several clients or agents share one index?
Yes. Run one Streamable HTTP server and point every client at it. Do not run two writers against the same data_dir.
Does it handle non-English documents?
The default model is English-trained, yet it found the expected source for all 6 Portuguese test questions (MRR@5 0.792) in the audit. The multilingual profile uses intfloat/multilingual-e5-large; on the 60-question diagnostic it found 36 expected sources against 37 for the default, so measure on your own corpus before switching.
How do I measure search quality on my documents?
Call evaluate_retrieval with your own pairs of query and expected file; it returns MRR@5, Recall@5 and per-query hits. Use reranker_score, not score, to compare results across queries, because score is rescaled so that each query's top result is 1.0.
Does it work with Cline, Gemini CLI, Zed or other clients?
Any client that can start a stdio MCP server from a command, or connect to a Streamable HTTP endpoint, can use it. The repository has written setup for Claude Code, Claude Desktop, Cursor, Windsurf, Antigravity, VS Code and opencode.
| Document | Contents |
|---|---|
| Installation guide | Install methods (npx, installer, pip, source, Docker, SSE/HTTP), client configurations, GPU acceleration |
| API reference | All 13 MCP tools: parameters, return shapes, examples |
| Configuration reference | Every config.yaml field, presets, hybrid_alpha tuning |
| Architecture | Components, query and ingestion flow, memory and concurrency boundaries, hybrid weights |
| Search scope and retrieval quality | Folder filters, evaluation method, quality limits |
| Troubleshooting | Common problems and their fixes |
| FTS5 fast path | When and how to enable lexical retrieval |
| Reindex operations | Staging rebuilds, resume, checkpoints, recovery |
| Operating model and single instance | Who runs the server, running CLI commands safely |
| GPU setup | CUDA runtime compatibility, CPU fallback, AMD and macOS limits |
| Migration to v4.8.0 | Embedding profiles, multilingual, zero-downtime rebuild |
| Memory and GPU audit | Reproducible workloads, measured results, validation limits |
| Release review | Independent review findings, retrieval diagnostics, model comparison |
| Security policy | Threat model, disclosure channel and timelines |
| Contributing | Development, testing, pull request process |
| Changelog | Release notes since v1.0.0 |
- v5.0.0 (2026-11-05): breaking
list_documentsnow returns 100 documents per page by default (up to 200 withlimit); call it again withoffsetuntil you reachtotal. Also: an empty or missingdocuments_dirno longer clears the index, search snippets start at the match,add_from_urlstops overwriting other pages, and failing tests now block merges. - v4.11.1 (2026-10-25): fresh installs start again, Windows state writes retry through brief file locks, and the Docker image adds
linux/arm64. - v4.11.0 (2026-10-04): startup refuses a
data_dirinside cloud-sync folders. - v4.10.0 (2026-10-04): indexing reliability, bounded memory,
include_folders/exclude_folderssearch scopes and opt-in Windows DirectML. - v4.9.0 to v4.9.2 (2026-09): 15 more file types, watcher and index resilience, and a fix for the reindex loop on Windows stdio clients.
Full history, including the v4.8 releases: CHANGELOG.md.
- Report a bug: open an issue
- Ask a question: GitHub Discussions
- Report a vulnerability: private security advisory
- Contribute: CONTRIBUTING.md
Security reports are acknowledged within 48 hours (SECURITY.md).
MIT. You may use, modify and redistribute knowledge-rag, including in commercial products, under its terms. There is no paid edition.
Built on these open-source projects:
- Model Context Protocol: the protocol and its Python SDK
- ChromaDB: embedded vector database
- FastEmbed: ONNX embeddings and reranking without PyTorch
- Hugging Face: model hosting, including the
Xenova/ms-marco-MiniLM-L-6-v2cross-encoder - BAAI: the
bge-small-en-v1.5embedding model
Community contributors: @Hohlas, @eeshsaxena, Sergey Khokhlov, and everyone who filed issues or pull requests (all contributors).
Built by Ailton Rocha (Lyon.) · Star the repository if it saves you time · Report an issue · Contribute
knowledge-rag: a local RAG server with hybrid search and reranking for Claude Code, Cursor and other MCP clients.
