RAG In A Box MCP Server
by DevNexsler
README.md
# RAG In A Box
Drop your documents into a folder, run the indexer, and get a production-grade RAG pipeline with an **MCP server** — any MCP-compatible AI assistant (Claude Code, OpenClaw, Claude Desktop, Cursor, etc.) can search your documents with a single config entry.
The app needs no GPU. LiteLLM routes OCR/vision to **cloud APIs**, **local
models**, or fallback chains.
## Use cases
- **Personal knowledge base** — Index your notes, PDFs, documents, images, audio, and video. Ask your AI assistant questions and get answers grounded in your own files.
- **Company document search** — Drop legal contracts, reports, SOPs into a folder. Employees search via any MCP-compatible assistant with metadata filters (by department, doc type, date, tags).
- **Research assistant** — Index papers, datasets, and notes. Search by meaning, not just keywords. LLM enrichment auto-extracts entities, topics, and key facts.
- **Obsidian / Markdown vault** — Works with any markdown source (Obsidian, HackMD, Notion exports, GitBook). Extracts YAML frontmatter for rich filtering.
- **PDF-heavy workflows** — Scanned PDFs get OCR automatically. Page-aware chunking keeps context intact. Metadata (author, dates, page count) extracted from PDF properties.
- **Multi-agent tool** — Expose your document collection as 16 MCP tools. Multiple agents can search, browse, filter, and manage taxonomy concurrently.
- **Local Deep Research integration** — Let LDR run agentic web/academic research while searching this RAG index through a LangChain retriever. See [Local Deep Research Integration](docs/local-deep-research-integration.md).
## Why this over other RAG tools?
| Capability | RAG In A Box | Typical RAG |
|---|---|---|
| Search quality | 10-step hybrid pipeline (vector + BM25 + reranker + MMR) | Vector-only or basic hybrid |
| Document understanding | LLM enrichment extracts summary, entities, topics, importance | Raw chunks, no enrichment |
| Filtering | Pre-filter by tags, folder, doc type, topics, custom fields | Post-filter or none |
| Chunking | Heading-aware (MD) + page-aware (PDF) + semantic boundary detection | Fixed-size windows |
| Chunk context | Each chunk gets title, path, topics prepended for self-describing retrieval | Chunks lose document context |
| Metadata | YAML frontmatter auto-extracted, custom fields auto-promoted to filters | Manual schema setup |
| Taxonomy | Controlled vocabulary with semantic matching, managed via MCP tools | None |
| OCR | Built-in for scanned PDFs and images (cloud or local) | Separate pipeline needed |
| Deployment | Single container, cloud APIs, no GPU | Often needs GPU or complex infra |
| Integration | MCP server (16 tools) — works with Claude, Cursor, any MCP client | Custom API or SDK |
| Resilience | Per-query diagnostics, auto-recovery from DB corruption, structured errors | Silent failures |
## Stack
| Component | Provider |
|---|---|
| Embeddings | Qwen3-Embedding-8B via OpenRouter |
| LLM enrichment | GPT-4.1 Mini via OpenRouter |
| OCR + image description | LiteLLM aliases `ocr` + `vision`; proxy owns local/cloud fallback. Gemini, DeepSeek OCR2, and Ollama remain legacy direct adapters. |
| Reranker | Qwen3-Reranker-8B via DeepInfra |
| Vector + FTS | LanceDB + tantivy (BM25) |
| Orchestration | Prefect 3.x |
## Getting started
### 1. Install
```bash
git clone https://github.com/DevNexsler/RAG-In-A-Box.git
cd RAG-In-A-Box
python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
```
### 2. Configure
Copy the production example config and edit two paths plus the LiteLLM endpoint:
```bash
cp config.yaml.example config.yaml
```
Open `config.yaml` and set:
- `documents_root` — path to your document collection
- `index_root` — where the index will be stored
- `ocr.endpoint` — OpenAI-compatible LiteLLM `/v1` endpoint exposing aliases
`ocr` and `vision`
> **Self-hosting?** Prefer routing LiteLLM's `ocr` and `vision` aliases to local
> models. The legacy direct-provider profile remains available via
> `cp config.local.yaml.example config.yaml`; see [Local mode](#local-mode-optional).
### 3. Add API keys
Create a `.env` file in the project root:
```bash
LITELLM_API_KEY=... # OCR/vision proxy (preferred)
# LITELLM_MASTER_KEY=... # alternative when LITELLM_API_KEY is unset
OPENROUTER_API_KEY=sk-or-... # embeddings + enrichment — https://openrouter.ai/keys
DEEPINFRA_API_KEY=... # reranker — https://deepinfra.com/dash/api_keys
```
### 4. Build the index
```bash
python run_index.py
```
This scans your documents, extracts text (Markdown, PDFs, images, audio, video), generates embeddings, and writes everything to a LanceDB index. Prefect auto-starts a temporary server for flow/task logging — dashboard at `http://127.0.0.1:4200`.
### 5. Connect your AI assistant
The MCP server gives any compatible AI assistant access to your documents via tools like `file_search`, `file_status`, and `file_recent`. The assistant launches the server automatically — you just add a config entry.
#### Claude Code
Add to your project's `.mcp.json` (or `~/.claude.json` for global access):
```json
{
"mcpServers": {
"doc-organizer": {
"command": "/path/to/Document-Organizer/.venv/bin/python",
"args": ["/path/to/Document-Organizer/mcp_server.py"],
"cwd": "/path/to/Document-Organizer"
}
}
}
```
#### OpenClaw
Add to your OpenClaw MCP config:
```json
{
"mcpServers": {
"doc-organizer": {
"command": "/path/to/Document-Organizer/.venv/bin/python",
"args": ["mcp_server.py"],
"cwd": "/path/to/Document-Organizer"
}
}
}
```
#### Claude Desktop
Add to `~/Library/Application Support/Claude/claude_desktop_config.json` (macOS):
```json
{
"mcpServers": {
"doc-organizer": {
"command": "/path/to/Document-Organizer/.venv/bin/python",
"args": ["/path/to/Document-Organizer/mcp_server.py"],
"cwd": "/path/to/Document-Organizer"
}
}
}
```
#### Any MCP-compatible client
The pattern is the same for Cursor, Windsurf, or any tool that supports MCP stdio servers. Point `command` at the venv Python and `args` at `mcp_server.py`. API keys are loaded from the `.env` file automatically — no need to pass them in the MCP config.
#### HTTP mode (remote / non-MCP clients)
```bash
python mcp_server.py --http
# Listens on 0.0.0.0:7788
```
#### VPS / Docker deployment
Run as a standalone HTTP server on any VPS or container platform. ML inference
uses configured APIs and the LiteLLM proxy, so the app container needs no GPU.
```bash
# Docker
docker build -t doc-organizer .
docker run -v /path/to/data:/data -p 7788:7788 \
-e LITELLM_API_KEY=... \
-e OPENROUTER_API_KEY=... \
-e DEEPINFRA_API_KEY=... \
-e API_KEY=your-secret-token \
doc-organizer
# Or run directly
API_KEY=your-secret-token python server.py
```
**Environment variables** (for container/VPS use):
| Variable | Description |
|----------|-------------|
| `DOCUMENTS_ROOT` | Override documents path (default: from config.yaml) |
| `INDEX_ROOT` | Override index path (default: from config.yaml) |
| `PORT` | Server port (default: 7788) |
| `API_KEY` | Bearer token for HTTP auth. No auth when unset. |
| `LITELLM_API_KEY` | Preferred LiteLLM OCR/vision bearer credential |
| `LITELLM_MASTER_KEY` | LiteLLM credential fallback when `LITELLM_API_KEY` is unset |
When `API_KEY` is set, all HTTP requests must include `Authorization: Bearer <API_KEY>`. See `config.vps.yaml.example` for VPS-specific config.
**Render.com:** One-click deploy with `render.yaml` — persistent disk at `/data`, auto-generated API key.
#### REST API (file management)
When running in HTTP mode (`--http` or `server.py`), a REST API is available alongside the MCP server for uploading, downloading, and listing documents. Auth uses the same `API_KEY` bearer token.
**Upload a file:**
```bash
curl -X POST http://localhost:7788/api/upload \
-H "Authorization: Bearer $API_KEY" \
-F "file=@report.pdf" \
-F "directory=2-Area/Legal"
# -> {"uploaded": true, "rel_path": "2-Area/Legal/report.pdf", "doc_id": "2-Area/Legal/report.pdf", "size": 84521}
# "rel_path" is the path under documents_root; "doc_id" is a deprecated alias of
# it (it was never an index doc id) and will be dropped — read "rel_path".
```
**Download a file:**
```bash
curl http://localhost:7788/api/documents/2-Area/Legal/report.pdf \
-H "Authorization: Bearer $API_KEY" -o report.pdf
```
**List files in a directory:**
```bash
curl "http://localhost:7788/api/documents/?directory=2-Area&limit=50" \
-H "Authorization: Bearer $API_KEY"
# -> {"directory": "2-Area", "files": [...], "total": 12, "offset": 0, "limit": 50}
```
| Endpoint | Method | Description |
|---|---|---|
| `/api/upload` | POST | Upload a file (multipart form: `file` + optional `directory`) |
| `/api/documents/{rel_path}` | GET | Download a file by its path under `documents_root` |
| `/api/documents/` | GET | List files (query params: `directory`, `limit`, `offset`) |
**Constraints:** Max upload 100 MB. Allowed types: `.md`, `.pdf`, `.png`, `.jpg`, `.jpeg`. Path traversal is blocked. After uploading, run `file_index_update` (via MCP) to index the new document.
### Local mode (optional)
Preferred local OCR deployment keeps the primary config and routes LiteLLM's
`ocr` and `vision` aliases to local models; LiteLLM can retain cloud fallbacks.
For the legacy direct-provider self-hosted profile:
1. Copy `config.local.yaml.example` to `config.yaml`
2. Install and start **Ollama**: `brew install ollama && ollama serve`
- `ollama pull qwen3-embedding:0.6b` (semantic chunking)
- `ollama pull qwen3-embedding:4b-q8_0` (embeddings)
3. Start **DeepSeek OCR2** on port 8790 (for PDF/image OCR)
That profile runs OCR, embeddings, enrichment, and reranking locally. Its
enabled media provider still uses OpenRouter; set `OPENROUTER_API_KEY` or
disable `media`.
## Features
```
Document Collection AI Assistants
+------------------+ +-------------------+
| Markdown (.md) | | Claude Code |
| PDFs | +---------+ | OpenClaw |
| Images (.png/jpg)|───>| Indexer | | Claude Desktop |
+------------------+ +----+----+ | Cursor / Windsurf |
| +--------+----------+
v |
+----------+----------+ | MCP (stdio)
| LanceDB Index | |
| vectors + metadata |<------+
| + full-text (BM25) | file_search
+---------------------+ file_status
file_recent ...
```
**Hybrid search** — Every query runs vector (semantic) and keyword (BM25) search in parallel, fuses results with Reciprocal Rank Fusion, applies length normalization, importance weighting, optional recency boost with time decay floor, cross-encoder reranking (60/40 blend with cosine fallback), MMR diversity filtering, and minimum score thresholding. Pre-filters (tags, folders, doc type, topics, and complex JSON filters) apply at the database level before retrieval so every result matches.
**Multi-format extraction** — Indexes Markdown, PDFs, images, audio, and video. PDFs use text extraction first, falling back to OCR for scanned pages. Images get OCR text plus visual descriptions. Audio/video files are base64-sent to OpenRouter-compatible media models for transcript/search notes. EXIF metadata (camera, GPS, dates) is extracted automatically.
**LLM enrichment** — Each document is analyzed by an LLM to extract structured metadata: summary, document type, entities (people, places, orgs, dates), topics, keywords, key facts, suggested tags, and suggested folder. All fields are searchable and filterable.
**Taxonomy system** — A controlled vocabulary of tags and folder paths stored in a separate LanceDB table with embedded descriptions. The LLM uses the taxonomy during enrichment to suggest consistent tags and filing locations. Seeded from existing tag/directory databases. Managed via 7 MCP CRUD tools (`file_taxonomy_*`).
**Smart chunking** — Markdown is split by headings, PDFs by pages. Large sections get semantic chunking (topic-boundary detection via sentence embeddings). Every chunk gets a contextual header prepended with its title, path, and topics — so each chunk is self-describing for better retrieval.
**Rich metadata & filtering** — YAML frontmatter (tags, status, author, dates, custom fields) is automatically extracted and promoted to filterable columns. Custom frontmatter keys are auto-promoted — no schema changes needed. `file_search` supports exact filters plus complex JSON filters with `eq`, `ne`, `contains`, `prefix`, `in`, `and`, `or`, and `not`.
**MCP server** — Exposes 16 tools over the Model Context Protocol. Any MCP-compatible assistant can search, browse, filter your documents, and manage taxonomy entries. Works over stdio (launched automatically by the assistant) or HTTP.
**Incremental updates** — Only new and modified files are processed on re-index. Deleted files are cleaned up automatically. Failed documents are tracked and retried.
**Cloud or local** — LiteLLM is the primary OCR/vision routing authority and
can select local models, cloud models, or fallback chains. Legacy direct OCR
adapters remain available. Other providers support cloud and local profiles.
**Resilient by default** — Per-document error handling with retries, structured MCP error responses, search diagnostics on every query (`vector_search_active`, `reranker_applied`, `degraded`), SQL injection protection on filter keys, and automatic LanceDB corruption recovery (version rollback + rebuild).
### MCP tools
| Tool | Description |
|------|-------------|
| `file_search` | Hybrid semantic + keyword search with exact filters plus complex JSON filters (`and`/`or`/`not`, `in`, `contains`, etc.) |
| `comm_lookup` | One-call **compact** lookup for comm / person / phone / call / voicemail / message questions — returns a small verdict envelope with source ids. Use this first instead of raw comm-store SQL. See [docs/comm-lookup.md](docs/comm-lookup.md) |
| `file_get_chunk` | Get full text + metadata for one chunk by doc_id and loc |
| `file_get_doc_chunks` | Get all chunks for a document, sorted by position |
| `file_list_documents` | Browse all indexed documents with pagination and filters |
| `file_recent` | Recently modified/indexed docs (newest first) |
| `file_facets` | Distinct values + counts for all filterable fields |
| `file_folders` | Document folder/directory structure with file counts |
| `file_status` | Index stats, provider settings, health checks |
| `file_index_update` | Incrementally update the index without leaving the assistant |
| `file_taxonomy_list` | List taxonomy entries (tags, folders, doc_types) with filters |
| `file_taxonomy_get` | Get a single taxonomy entry by id |
| `file_taxonomy_search` | Semantic search on taxonomy descriptions |
| `file_taxonomy_add` | Add a new taxonomy entry |
| `file_taxonomy_update` | Update an existing taxonomy entry |
| `file_taxonomy_delete` | Delete a taxonomy entry |
| `file_taxonomy_import` | Import taxonomy from SQLite seed databases |
## Run tests
The maintained path is the **gate** — ordered, fail-fast tiers (static → unit →
integration → staging-e2e → live) where a full pass means "this works in
production". See [docs/TESTING.md](docs/TESTING.md) for the full runbook.
```bash
make gate-fast # static + unit + integration (free, no services) — the dev loop
make gate # all five tiers, incl. a hermetic staging compose stack + live
```
`make gate-fast` needs no API keys or Docker; `make gate`'s staging-e2e tier
brings up an isolated, memory-capped compose stack (production image + provider
simulator + throwaway Postgres) and its live tier is preflight-guarded. The live
tier uses real providers and can spend money. `make gate-real` adds the optional
real-provider e2e stage; `make test-e2e-real` runs only that extra stage.
Raw pytest still works if you want to run a subset directly:
```bash
python -m pytest -m unit -q # offline, no API keys
python -m pytest -m "unit or integration" -q # all local tiers
```
## Project layout
```
core/ Config, storage interface, taxonomy helpers
providers/embed/ Embedding providers (OpenRouter, Ollama, LlamaIndex)
providers/llm/ LLM providers (OpenRouter, Ollama)
providers/ocr/ OCR/vision (LiteLLM primary; legacy Gemini/DeepSeek/Ollama adapters)
taxonomy_store.py Taxonomy LanceDB store (CRUD, vector search, FTS)
doc_enrichment.py LLM metadata extraction (with taxonomy integration)
extractors.py Text extraction (MD, PDF, images, audio/video)
flow_index_vault.py Prefect indexing flow
lancedb_store.py LanceDB storage + search
search_hybrid.py 10-step hybrid search pipeline
mcp_server.py MCP server (stdio + HTTP, 16 tools)
server.py VPS entrypoint — starts HTTP server on $PORT
run_index.py CLI entrypoint
scripts/seed_taxonomy.py Import taxonomy from existing SQLite DBs
config.yaml.example Cloud config template
config.local.yaml.example Local/self-hosted config template
config.vps.yaml.example VPS/container config template
Dockerfile Docker image (Python 3.13-slim, no GPU)
.dockerignore Docker build exclusions
render.yaml Render.com deployment descriptor
tests/ ~454 tests
docs/architecture.md Search pipeline, schema, component details
docs/vps-architecture.md VPS/cloud deployment architecture
```
## License
[PolyForm Noncommercial 1.0.0](https://polyformproject.org/licenses/noncommercial/1.0.0/) — free for personal, research, educational, and nonprofit use. Commercial use requires a separate license.
This server cannot be deployed
Maintenance
ActivityActive
ResponsivenessWithin a week