mcp-docqa-server
Allows using OpenAI embeddings for semantic search in the document corpus.
Provides integration with PostgreSQL using pgvector extension for storing and searching embeddings.
Enables fetching PubMed abstracts via NCBI E-utilities API for indexing into the corpus.
Uses SQLite as an embedded vector store for exact cosine similarity search.
Click on "Install Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@mcp-docqa-serversearch the corpus: what is stage 2 hypertension?"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
mcp-docqa-server
An MCP server that gives any AI client semantic search over a document corpus — the retrieval half of a RAG pipeline, shipped as reusable infrastructure. Point Claude Desktop (or any MCP host) at it and the model can search, read, and cite your documents autonomously; generation stays in the client, retrieval lives here.
"What does the corpus say about the hour-1 sepsis bundle?"
│
▼ MCP (stdio / HTTP)
┌──────────────┐ search_documents("hour-1 sepsis bundle", k=5) ┌───────────────┐
│ Claude / │ ────────────────────────────────────────────────► │ docqa server │
│ any MCP │ ◄──────────────────────────────────────────────── │ embed → ANN │
│ host │ top-k chunks + titles, urls, scores │ search → rank │
└──────────────┘ └──────┬────────┘
│
SQLite (embedded, exact)
or Postgres + pgvector (HNSW)Why this exists
Most RAG demos hard-wire retrieval into one chatbot. Exposing retrieval through MCP inverts that: index once, query from anywhere — Claude Desktop, an IDE agent, a CI job, your own client. The server is deliberately boring infrastructure: typed tools, two interchangeable storage backends, pluggable embeddings, an eval harness, and loud failures where silent ones usually live (see Design decisions).
Related MCP server: RAG Docs MCP Server
Features
Four typed MCP tools —
search_documents,fetch_document,corpus_stats,ping— with docstrings written for the calling model, because tool descriptions are the interface.Two vector stores, one contract: embedded SQLite (zero infrastructure, exact brute-force cosine) and Postgres + pgvector (HNSW index, production posture). Both pass the same behavioral test battery.
Pluggable embeddings: OpenAI
text-embedding-3-small, or a deterministic keyless hashing embedder so a fresh clone works with no API key, no database, no network.Idempotent ingestion: per-document content hashes mean re-runs skip unchanged docs — nothing gets re-embedded (or re-billed) by accident.
Corpus fetcher for PubMed abstracts via the keyless NCBI E-utilities API (rate-limit aware).
Retrieval eval harness: recall@k and MRR against a labeled testset, usable as a CI quality gate (
docqa-eval --min-recall5 0.9).stdio + Streamable HTTP transports, Dockerfile included.
CI that means it: lint, unit tests, a real MCP client round-trip over stdio, and an integration job against a live pgvector service container that ends by gating on retrieval recall.
Quickstart — 60 seconds, no API key
git clone https://github.com/saivarun161/mcp-docqa-server.git
cd mcp-docqa-server
python3 -m venv .venv && source .venv/bin/activate
pip install -e ".[dev]"
docqa-ingest index --sample # bundle of 12 healthcare docs -> SQLite index
docqa-eval # recall@1/3/5 + MRR against the bundled testsetYou now have a working index at data/index.db. Talk to it over real MCP with the Inspector:
npx @modelcontextprotocol/inspector .venv/bin/docqa-serverWire it into Claude Desktop
Open
~/Library/Application Support/Claude/claude_desktop_config.json(macOS) or%APPDATA%\Claude\claude_desktop_config.json(Windows).Add the block from
claude_desktop_config.example.jsonwith absolute paths — hosts launch servers from their own working directory, so relative paths break.Fully restart Claude Desktop and ask: "Search the docqa corpus: what counts as stage 2 hypertension?"
The model will call search_documents, read the chunks, and answer with sources.
A real corpus
Index a few hundred PubMed abstracts on any topic (keyless, public data):
docqa-ingest fetch --query "semaglutide cardiovascular outcomes" --max-docs 200
docqa-ingest index --corpus data/corpus.jsonl
docqa-ingest statsAny JSONL with id, title, url, text fields works — swap PubMed for arXiv, EDGAR filings, or your own notes.
Production posture
Semantic embeddings — put an OpenAI key in .env (see .env.example) and re-index; DOCQA_EMBEDDINGS=auto picks it up:
pip install -e ".[openai]"
docqa-ingest index --corpus data/corpus.jsonl --forcePostgres + pgvector — vectors move into an HNSW-indexed table; search runs inside the database:
docker compose up -d # pgvector/pgvector:pg16 with the extension enabled
export DOCQA_STORE=pgvector DATABASE_URL=postgresql://docqa:docqa@localhost:5432/docqa
pip install -e ".[pg]"
docqa-ingest index --sampleHTTP transport — for network-reachable deployments instead of stdio:
docqa-server --transport http --host 0.0.0.0 --port 8000
# or containerized:
docker build -t mcp-docqa-server . && docker run -p 8000:8000 mcp-docqa-serverMCP tools
Tool | Arguments | Returns |
|
| top-k chunks with |
|
| the full source document |
| — | doc/chunk counts, backend, embedder that built the index |
| — |
|
Retrieval quality
docqa-eval retrieves for every testset question and reports where the expected document ranked:
Retrieval eval — 12 questions, k=5
store=sqlite embedder=hash-v1-512
[rank 1] What blood pressure reading counts as stage 2 hypertension? (expects sample-001)
...
recall@1=1.00 recall@3=1.00 recall@5=1.00 MRR=1.00The bundled corpus is small and topically distinct, so even the lexical fallback embedder scores perfectly — that run proves the plumbing. The interesting experiments start when you index a few hundred PubMed abstracts and compare hash vs openai embeddings on your own testset; CI runs the eval against a live pgvector container and fails the build if recall@5 drops below 0.9.
Design decisions
Embedder identity is persisted and enforced. Vectors from different embedders live in unrelated spaces; querying an OpenAI-built index with hash vectors doesn't error mathematically — it just returns garbage. The store records which embedder built it and the retriever refuses a mismatch with an actionable message. Silent failure → loud failure.
Brute force is a feature at SQLite scale. Exact cosine over a few thousand chunks is milliseconds with NumPy and has zero recall loss; ANN indexes buy speed at scale, not correctness. The pgvector backend adds HNSW when the corpus outgrows brute force.
Chunks carry their title. Each chunk is prefixed with its document title before embedding, so a chunk ripped out of context still knows what it's about.
One behavioral battery, two backends. The SQLite and pgvector stores pass the identical test suite (
tests/store_suite.py), which is what "interchangeable" actually means.The MCP layer is tested with a real MCP client. CI spawns the server over stdio and drives it with an
mcp.ClientSession— the same handshake Claude Desktop performs — not by calling Python functions directly.Public data only. PubMed abstracts and original sample docs. Never index proprietary or employer documents into a demo corpus.
Project structure
src/docqa/
├── server.py # FastMCP server + tool definitions
├── retriever.py # embed query -> store search, with embedder guard
├── embeddings.py # OpenAIEmbedder | HashingEmbedder (keyless fallback)
├── chunking.py # word windows with overlap
├── config.py # env-driven settings (.env aware)
├── store/
│ ├── base.py # VectorStore contract + embedder guard
│ ├── sqlite_store.py # embedded, exact brute-force cosine
│ └── pgvector_store.py# Postgres + pgvector, HNSW
├── ingest/
│ ├── pubmed.py # NCBI E-utilities fetcher (keyless, rate-limited)
│ ├── pipeline.py # chunk -> embed -> upsert, content-hash idempotent
│ └── cli.py # docqa-ingest fetch | index | stats
├── eval/run_eval.py # recall@k + MRR, CI-gateable
└── data/ # bundled 12-doc sample corpus + labeled testset
tests/ # unit + store battery + MCP stdio round-trip
.github/workflows/ci.yml # lint, tests, live pgvector integration + recall gateRoadmap
MCP tools over stdio + Streamable HTTP
SQLite and pgvector backends behind one contract
Idempotent ingestion + PubMed fetcher
Eval harness with CI recall gate
Hybrid retrieval (BM25 + vector, reciprocal rank fusion)
Cross-encoder reranking stage
More corpus adapters (arXiv, EDGAR)
Bearer-token auth for the HTTP transport
License
MIT — see LICENSE.
Built by Varun Kammadanam — backend + GenAI engineer (Java, Python, AWS, RAG systems).
This server cannot be installed
Maintenance
Resources
Unclaimed servers have limited discoverability.
Looking for Admin?
If you are the server author, to access and configure the admin panel.
Latest Blog Posts
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
MCP directory API
We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/saivarun161/mcp-docqa-server'
If you have feedback or need assistance with the MCP directory API, please join our Discord server