Skip to main content
Glama
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).