mcp-docqa-server
This server provides semantic search and document retrieval over a corpus, acting as the retrieval component of a RAG pipeline via four MCP tools:
search_documents: Find relevant text chunks using natural language queries. Supportshybrid(semantic + keyword, default),vector(semantic-only), andlexical(keyword-only) retrieval modes, returning top-k chunks with metadata (doc ID, title, URL, text, relevance score).fetch_document: Retrieve a complete source document by its ID — useful for reading beyond a single chunk.corpus_stats: Inspect the indexed corpus: document/chunk counts, storage backend (SQLite or Postgres+pgvector), and the embedding model used.ping: Simple health check that returns'pong'to confirm the server is reachable.
Beyond the MCP tools, the server also supports:
Ingesting diverse sources: PubMed abstracts, arXiv papers, RSS/Atom feeds, or custom JSONL files (idempotent ingestion).
Flexible storage: Embedded SQLite (brute-force cosine) or Postgres+pgvector (HNSW indexing + GIN full-text search).
Pluggable embeddings: OpenAI
text-embedding-3-smallor a keyless deterministic hashing embedder.Retrieval evaluation: Built-in
docqa-evalharness measuring recall@k and MRR for CI quality gating.Multiple transports: stdio or Streamable HTTP, with Docker support.
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 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) ┌───────────────┐
│ AI client │ ────────────────────────────────────────────────► │ docqa server │
│ via any │ ◄──────────────────────────────────────────────── │ embed → ANN │
│ MCP 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 — a desktop AI assistant, 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.Hybrid retrieval by default: semantic (vector) and keyword (BM25 / Postgres FTS) search fused with Reciprocal Rank Fusion, so exact terms embeddings blur — identifiers, drug names, error codes — still land. Callers can force
vectororlexicalper query.Two vector stores, one contract: embedded SQLite (zero infrastructure, exact brute-force cosine + FTS5) and Postgres + pgvector (HNSW index + GIN full-text, 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 fetchers for PubMed abstracts (NCBI E-utilities), arXiv papers (Atom API), and any generic RSS 2.0 / Atom 1.0 feed (blogs, changelogs, EDGAR) — all keyless and rate-limit aware, behind one
--sourceswitch.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 your MCP host
Open your MCP host's config file — each host documents its own location (commonly under the host's application-support directory).
Add the block from
mcp_host_config.example.jsonwith absolute paths — hosts launch servers from their own working directory, so relative paths break.Fully restart the host 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 abstracts on any topic from PubMed or arXiv (keyless, public data):
# medical literature (PubMed, the default source)
docqa-ingest fetch --query "semaglutide cardiovascular outcomes" --max-docs 200
# or CS/ML papers (arXiv)
docqa-ingest fetch --source arxiv --query "retrieval augmented generation" --max-docs 200
# or any RSS/Atom feed — the query is the feed URL (blogs, changelogs, EDGAR filings)
docqa-ingest fetch --source feed --query "https://export.arxiv.org/rss/cs.IR"
docqa-ingest index --corpus data/corpus.jsonl
docqa-ingest statsAny JSONL with id, title, url, text fields works, so beyond the built-in adapters you can point it at your own notes. The generic feed adapter auto-detects RSS 2.0 vs Atom 1.0 and strips HTML from entry bodies.
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 |
| — |
|
mode selects the retrieval strategy: hybrid (default) fuses both legs, vector is semantic-only (best for paraphrased/conceptual questions), lexical is keyword-only (best when an exact term must appear).
Retrieval quality
docqa-eval retrieves for every testset question and reports where the expected document ranked:
Retrieval eval — 12 questions, k=5, mode=hybrid
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.00Pass --mode vector|lexical|hybrid to compare retrieval strategies on the same testset. The bundled corpus is small and topically distinct, so every mode 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, or vector vs hybrid, 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
Hybrid retrieval fuses with RRF, not score-mixing. Vector cosine and BM25 live on incomparable scales, so blending their raw scores needs fragile per-corpus tuning. Reciprocal Rank Fusion instead combines ranks — each chunk scores
Σ 1/(60 + rank)over the legs it appears in — which needs no calibration and rewards chunks both legs agree on. Each leg runs in its own engine (NumPy cosine / SQLite FTS5 / Postgres GIN); the retriever pulls a deeper candidate pool from each, then fuses.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 a real MCP host 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 # hybrid/vector/lexical modes + RRF fusion, 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 (vector + lexical) + embedder guard
│ ├── sqlite_store.py # embedded: brute-force cosine + FTS5 BM25
│ └── pgvector_store.py# Postgres: pgvector HNSW + GIN full-text
├── ingest/
│ ├── pubmed.py # NCBI E-utilities fetcher (keyless, rate-limited)
│ ├── arxiv.py # arXiv Atom fetcher (keyless; parser is unit-tested)
│ ├── feed.py # generic RSS 2.0 / Atom 1.0 reader (keyless; parser unit-tested)
│ ├── 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
Corpus adapters: PubMed + arXiv
Generic RSS 2.0 / Atom 1.0 feed adapter (reads EDGAR Atom feeds, blogs, changelogs)
Dedicated EDGAR adapter (CIK/form filtering on top of the feed reader)
Bearer-token auth for the HTTP transport
License
MIT — see LICENSE.
Built by Varun Kammadanam — backend + GenAI engineer (Java, Python, AWS, RAG systems).
Maintenance
Resources
Unclaimed servers have limited discoverability.
Looking for Admin?
If you are the server author, to access and configure the admin panel.
Related MCP Servers
- Alicense-qualityDmaintenanceAn MCP server that indexes documents and serves relevant context to LLMs via Retrieval Augmented Generation (RAG).Last updated3436MIT
- Alicense-qualityCmaintenanceAn MCP server that provides tools for retrieving and processing documentation through vector search, enabling AI assistants to augment their responses with relevant documentation context.Last updated20MIT
- Flicense-qualityBmaintenanceMCP server for semantic search over Google Drive documents, enabling AI tools to search, list, and retrieve document content.Last updated
- Flicense-qualityDmaintenanceA Retrieval Augmented Generation MCP server that ingests documents into a local vector database and enables semantic search queries.Last updated9
Related MCP Connectors
Local-first RAG engine with MCP server for AI agent integration.
Agent-native MCP server over the public saagarpatel.dev corpus. Read-only, stateless.
Driflyte MCP server which lets AI assistants query topic-specific knowledge from web and GitHub.
Latest Blog Posts
- Who's Calling? MCP Hosts Are an Identity Blind Spot (And the Spec Knows It)By Om-Shree-0709 on .mcpAgent IdentityOAuth 2.1
- 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