mcp-docs-assistant
README.md
# ๐ MCP Docs Assistant
**Production-grade Retrieval-Augmented Generation (RAG) pipeline over the official [Model Context Protocol](https://modelcontextprotocol.io) documentation โ served over REST, MCP tools, and Docker.**
Ask natural-language questions about MCP (architecture, building servers/clients, tools/resources/prompts, security) and get answers grounded in the real docs, with guardrails, PII masking, reranking, semantic caching, and hallucination checking built in.
---
## โจ Features
| Capability | Implementation |
|---|---|
| ๐ **Multi-key LLM gateway** | Portkey-routed, load-balanced across 2 Gemini keys + 2 Groq keys, with automatic provider fallback |
| ๐ **Grounded retrieval** | Official MCP docs, chunked + embedded into a persistent Qdrant vector store |
| ๐ฏ **Reranking** | Cross-encoder (`ms-marco-MiniLM-L-6-v2`) narrows a wide candidate pool down to the most relevant chunks |
| ๐ก๏ธ **Guardrails** | NeMo Guardrails (Colang 2.x) โ input/output safety checks, jailbreak + instruction-leak detection |
| ๐ต๏ธ **PII masking** | Microsoft Presidio โ masks emails, phone numbers, credit cards in both input and output |
| ๐งฎ **Token budgeting** | Retrieved context is greedily fit to a fixed token budget before hitting the LLM |
| โก **Semantic cache** | Embedding-similarity cache (not exact-match) with TTL + size cap |
| ๐ฌ **Multi-turn conversations** | LangGraph checkpointer + follow-up query condensation ("show a Python example of that") |
| ๐ **Hallucination check** | Runtime LLM-as-judge verdict (`GROUNDED` / `HALLUCINATED`) on every generated answer |
| ๐ **Offline evaluation** | RAGAS metrics (faithfulness, relevancy, context precision/recall) against 25 reference Q&A pairs |
| ๐ **MCP-native** | Exposes itself as MCP tools (`ask_mcp_docs`, `search_mcp_docs`, โฆ) โ usable directly from Claude Desktop, Claude Code, or any MCP host |
| ๐ **REST API** | FastAPI endpoints for any regular HTTP client |
| ๐ณ **Dockerized** | One-command deploy with `docker compose up` |
---
## ๐๏ธ Architecture
```mermaid
flowchart TD
A[User Question] --> B[Guard Input<br/>NeMo Guardrails]
B -->|blocked| Z[Refusal message]
B -->|allowed| C[Mask Input PII<br/>Presidio]
C --> D[Condense Follow-up<br/>into standalone question]
D --> E{Semantic<br/>Cache Hit?}
E -->|yes| F[Return cached answer]
E -->|no| G[Retrieve Top-15<br/>Qdrant Vector Store]
G --> H[Rerank Top-5<br/>Cross-Encoder]
H --> I[Fit to Token Budget]
I --> J[Generate Answer<br/>Portkey: Gemini / Groq]
J --> K[Guard Output<br/>leak / PII pattern check]
K --> L[Hallucination Check<br/>LLM-as-judge]
L --> M[Mask Output PII]
M --> N[Cache + Store History]
N --> O[Return Answer]
```
Every node above is a module in [`rag_pipeline/`](./rag_pipeline), wired together as a LangGraph `StateGraph` in [`rag_pipeline/graph.py`](./rag_pipeline/graph.py). [`rag_core.py`](./rag_core.py) builds every dependency once (as a singleton) and exposes a small stable API โ `chat()`, `search()`, `get_history()`, `cache_stats()` โ consumed identically by both the REST layer ([`main.py`](./main.py)) and the MCP layer ([`mcp_server.py`](./mcp_server.py)), so a single vector store / cache / conversation history is shared no matter which interface a request comes through.
---
## ๐ Project Structure
```
mcp-docs-rag-assistant/
โโโ main.py # FastAPI app โ REST endpoints + mounts MCP at /mcp
โโโ mcp_server.py # MCP tools (stdio standalone, or mounted in main.py)
โโโ rag_core.py # Singleton facade wiring the whole pipeline together
โโโ rag_pipeline/
โ โโโ config.py # Env vars / secrets (single source of truth)
โ โโโ logging_setup.py # Logging + Logfire
โ โโโ gateway.py # Portkey multi-key LLM gateway
โ โโโ errors.py # Retry + safe-node error handling
โ โโโ ingestion.py # MCP docs loader + splitter
โ โโโ vectorstore.py # Embeddings + persistent Qdrant store
โ โโโ reranker.py # Cross-encoder reranking
โ โโโ pii_masking.py # Presidio PII masking
โ โโโ guardrails.py # NeMo Guardrails (Colang 2.x)
โ โโโ token_management.py # Context window budgeting
โ โโโ semantic_cache.py # Embedding-similarity cache
โ โโโ query_condensation.py # Follow-up question rewriting
โ โโโ hallucination.py # Runtime hallucination judge
โ โโโ graph.py # LangGraph StateGraph โ full pipeline
โโโ scripts/
โ โโโ evaluate_ragas.py # Offline RAGAS evaluation (25 reference Q&A)
โโโ tests/
โ โโโ test_pipeline.py # Fast smoke tests (no API keys needed)
โโโ configs/guardrails/ # Colang rail files (generated at first run)
โโโ data/ # Persisted Qdrant vector store (gitignored)
โโโ notebooks/ # Original development notebook
โโโ Dockerfile
โโโ docker-compose.yml
โโโ requirements.txt
โโโ .env.example
```
---
## ๐ Getting Started
### Prerequisites
- Python 3.11+
- API keys: [Google AI Studio](https://aistudio.google.com/) (Gemini, ร2), [Groq](https://console.groq.com/) (ร2), [Portkey](https://portkey.ai/) (gateway + config id)
### 1. Clone and set up a virtual environment
```bash
git clone https://github.com/<your-username>/mcp-docs-rag-assistant.git
cd mcp-docs-rag-assistant
python -m venv venv
venv\Scripts\activate # Windows
# source venv/bin/activate # macOS/Linux
```
### 2. Install dependencies
```bash
pip install -r requirements.txt
python -m spacy download en_core_web_sm # required by Presidio for PII detection
```
### 3. Configure environment variables
```bash
cp .env.example .env
```
Open `.env` and fill in your real keys (`GEMINI_API_KEY_1/2`, `GROQ_API_KEY_1/2`, `PORTKEY_API_KEY`, `PORTKEY_CONFIG_ID`).
### 4. Run the server
```bash
uvicorn main:app --reload
```
> โณ **First run only**: the vector store doesn't exist yet, so the server ingests the MCP docs and embeds them in rate-limited batches โ this can take **5โ10 minutes**. On every subsequent run it loads the persisted store from `data/qdrant_mcp_db/` instantly.
Once you see `Startup: RAG pipeline ready.`, open:
- **`http://localhost:8000/docs`** โ interactive Swagger UI, try `POST /chat`
- **`http://localhost:8000/health`** โ health check
---
## ๐ก REST API
| Method | Endpoint | Description |
|---|---|---|
| `POST` | `/chat` | Ask a question. Body: `{"question": "...", "thread_id": "optional"}` |
| `POST` | `/search` | Retrieve + rerank raw context, no generation. Body: `{"query": "...", "top_n": 5}` |
| `GET` | `/history/{thread_id}` | Get conversation history for a thread |
| `GET` | `/cache/stats` | Semantic cache observability |
| `GET` | `/health` | Health check |
---
## ๐ Using it as an MCP Server
### Standalone (stdio) โ for Claude Desktop
Run directly:
```bash
python mcp_server.py
```
Or point a local MCP host at it, e.g. in Claude Desktop's config (`claude_desktop_config.json`):
```json
{
"mcpServers": {
"mcp-docs-assistant": {
"command": "python",
"args": ["E:\\mcp-docs-rag-assistant\\mcp_server.py"]
}
}
}
```
### Remote (streamable-http) โ via FastAPI
When `main.py` is running, the same MCP tools are reachable at:
```
http://localhost:8000/mcp
```
**Available tools:** `ask_mcp_docs`, `search_mcp_docs`, `get_conversation_history`, `cache_stats`.
---
## ๐ณ Docker
The **only prerequisite is Docker Desktop** (which bundles Docker Compose) โ you do **not** need to separately install Python, the pip dependencies, or `spacy`'s model on your machine. All of that happens automatically *inside* the image when you build it (see the `Dockerfile` โ it runs `pip install -r requirements.txt` and `python -m spacy download en_core_web_sm` as build steps).
```bash
# 1. Make sure .env exists (same as the local setup, step 3 above)
cp .env.example .env # then fill in real keys
# 2. Build and run
docker compose up --build
```
That single command builds the image, installs everything inside it, and starts the container. The `data/` folder is mounted as a volume (see `docker-compose.yml`), so the vector store persists across container restarts โ you only pay the slow first-run ingestion cost once, even with Docker.
Server is reachable the same way as running locally: `http://localhost:8000/docs`.
To stop:
```bash
docker compose down
```
To rebuild after changing code or dependencies:
```bash
docker compose up --build
```
---
## ๐งช Testing
Fast smoke tests โ no API keys or network calls needed (uses fake embeddings):
```bash
pip install pytest
pytest tests/ -v
```
---
## ๐ Offline Evaluation (RAGAS)
Scores the pipeline against 25 hand-written MCP questions with reference answers, using [RAGAS](https://docs.ragas.io/):
```bash
python scripts/evaluate_ragas.py
```
This does **not** need the server running โ it builds the pipeline itself (same singleton as `main.py`/`mcp_server.py`) and prints a metrics table:
- **Faithfulness** โ is the answer grounded in the retrieved context?
- **Response Relevancy** โ does the answer actually address the question?
- **Context Precision** โ is the retrieved context relevant?
- **Context Recall** โ does retrieved context cover what the reference answer needs?
> โฑ๏ธ Takes a few minutes: each of the 25 questions runs a real retrieval + generation pass, then every metric is itself scored by an LLM-as-judge call.
---
## โ๏ธ Configuration Reference
All configuration lives in `.env` (see `.env.example`). Key variables:
| Variable | Purpose |
|---|---|
| `GEMINI_API_KEY_1/2`, `GROQ_API_KEY_1/2` | Provider keys, load-balanced by Portkey |
| `PORTKEY_API_KEY`, `PORTKEY_CONFIG_ID` | Portkey gateway credentials + routing config |
| `QDRANT_PATH`, `QDRANT_COLLECTION` | Vector store location/name |
| `LOGFIRE_TOKEN` | Optional โ omit to fall back to console-only logging |
| `HOST`, `PORT` | Server bind address |
---
## ๐ ๏ธ Tech Stack
`FastAPI` ยท `LangChain` ยท `LangGraph` ยท `Qdrant` ยท `Portkey` ยท `Sentence-Transformers` ยท `Presidio` ยท `NeMo Guardrails` ยท `RAGAS` ยท `MCP Python SDK` ยท `Docker`
---
## ๐ License
MIT โ see [LICENSE](./LICENSE).
This server cannot be deployed
Maintenance
ActivityMaintained
ResponsivenessNo issues