Skip to main content
Glama

r-doc-mcp

The documentation RAG MCP of r-assist.

Serving side only: the API/MCP servers and the vector DB. Content is built and pushed by r-doc-builder, which lives outside this security boundary and talks to the ingest API — nothing here scrapes, chunks, or calls a completion LLM.

r_doc_mcp/config.py      env-driven query embeddings + DB settings
r_doc_mcp/db.py          vector-DB backends behind one interface (chroma today; DB_BACKEND selects)
r_doc_mcp/api.py         FastAPI: /health /search + token-guarded /ingest/upsert /ingest/reset
r_doc_mcp/mcp_server.py  search_docs MCP tool for AI agents — self-contained, same search as the API
tests/                   self-checks + API / agent notebooks

Setup

uv sync
cp .env.example .env    # then fill in keys/URLs

Embeddings (for queries) use a local Ollama, e.g. bge-m3. This must be the same model r-doc-builder embedded the documents with — vectors from different models don't mix (bge-m3 is 1024-dim, text-embedding-3-small 1536); switching models means a full re-push from the builder.

To run the three repos together from scratch, follow "From clone to chat" in r-assist's README.

Related MCP server: okfy

API

uv run uvicorn r_doc_mcp.api:app --port 8000     # docs at /docs (or: python -m r_doc_mcp.api, binds API_HOST:API_PORT)

Endpoint

Body

Returns

GET /health

—

{status, documents, last_ingest} — last_ingest: {doc_type: UTC time} of each one's last finished push

POST /search

{query, k, mode?, doc_type?, date_from?, date_to?}

ranked chunks + metadata + score

POST /ingest/upsert

[{id, content, embedding, metadata}]

{upserted, documents}

POST /ingest/lookup

{sources: [str], doc_types: [str]} (either may be omitted)

{id: metadata} of the stored chunks with one of those sources or doc_types

POST /ingest/update

[{id, metadata}]

{updated} — metadata only, content and vectors stay

POST /ingest/delete

[id]

{deleted, documents}

POST /ingest/done

{doc_types: [str]}

{last_ingest} — stamps those doc_types as just pushed, changed or not

POST /ingest/reset

—

{status} (clears last_ingest too)

mode is embedding (default; semantic similarity, score = distance, lower is better) or keyword (fuzzy literal word matching — ignores case/punctuation and tolerates small typos, so picsure finds "PIC-SURE"; score = occurrence count, higher is better — use for exact names/acronyms). doc_type is a CSV of types to search (e.g. page,faq). Types and the default scope come from <CONFIG_DIR>/doc_types.yaml (default config/; examples/bdc/doc_types.yaml is a filled-in one). When doc_type is omitted only the types flagged default are searched; with no defaults declared, everything is. date_from/date_to (YYYY-MM-DD, inclusive) filter by date; only chunks that carry a date match, so a date filter implicitly narrows to those.

The /ingest/* endpoints are the write path for r-doc-builder: they take finished records (embeddings pre-computed on the builder side) and require Authorization: Bearer $INGEST_TOKEN; with INGEST_TOKEN unset, ingest is disabled. r-doc-builder looks up what is stored for a file's sources before embedding, so a re-push embeds only new or changed chunks. Metadata merges into what is stored on upsert and update; a null value deletes that key. Answering is the caller's job — an agent brings its own LLM.

Doc types

config/doc_types.yaml declares the doc_type values r-doc-builder pushes for this project — it drives the default search scope and the search_docs tool description, so a new deployment only needs a new YAML file, not a code change. Each type has a description (shown to the agent) and a default flag (searched when the caller gives no doc_type). Set CONFIG_DIR=examples/bdc (or examples/fastapi) to see a filled-in example. examples/fixture serves r-doc-builder's fake, BDC-shaped test corpus: the same doc_types and default scope as examples/bdc (tests/test_doc_types.py keeps them in step). Each CONFIG_DIR gets its own collection in the DB, named after the folder (config, bdc, ...; COLLECTION_NAME overrides it), so switching examples never mixes or overwrites another's chunks, and --reset only clears the current one.

The wording of the search_docs tool description lives in config/prompts.yaml, a template with {project}, {k}, {types} and {scope} filled from doc_types.yaml and SEARCH_K. Edit it freely (literal braces as {{ }}); the server reads it once at startup.

DB backends

r_doc_mcp/db.py keeps the vector DB behind a five-method interface (count/search/scan/upsert/reset); everything chroma-specific — filter syntax, DB_PATH, the collection — lives in its ChromaDB class. To swap in a remote DB (postgres/pgvector, qdrant, ...), implement the same methods, register the class in BACKENDS, and set DB_BACKEND.

MCP

uv run python -m r_doc_mcp.mcp_server           # stdio
uv run python -m r_doc_mcp.mcp_server --http    # streamable HTTP on MCP_HOST:MCP_PORT (default 127.0.0.1:8001), plus the API

Exposes one tool, search_docs — same search as the API but queries the DB directly, so the API service doesn't need to run. Needs a pushed DB + embeddings.

Stdio clients (Claude Desktop/Code, Cursor) launch the server themselves — register it:

{"mcpServers": {"r-doc-mcp": {
  "command": "uv",
  "args": ["--directory", "/path/to/r-doc-mcp", "run", "python", "-m", "r_doc_mcp.mcp_server"]
}}}

The mcpServers key above is the client's own label for the server; the name the server reports for itself is MCP_SERVER_NAME (default r-doc-mcp).

Network clients: run --http and point them at http://host:8001/mcp instead. --http also serves the API routes (/health /search /ingest/*) from the same process, so a deployment runs one server and r-doc-builder pushes to DOC_MCP_URL=http://host:8001: chroma isn't process-safe, so a separate API process ingesting into the same DB_PATH would write under the MCP server's index. Only /ingest/* is token-guarded; keep /mcp and /search on a trusted network. /mcp is stateless (no session state, plain JSON replies): every request stands alone, so a client can keep one session open across server restarts (r-assist's keep_session: true). FastMCP logs at WARNING only: its per-request INFO lines cost 35% of the CPU under load.

Smoke test: uv run python tests/test_mcp.py

Tests

uv run python tests/test_doc_types.py # doc_types.yaml + prompts.yaml -> default scope + tool description — no network
uv run python tests/test_api.py       # ingest+search round-trip over a temp DB, auth — no network
uv run python tests/test_compare_db.py  # the DB comparison below, on throwaway DBs — no network
uv run python tests/compare_db.py DIR_A DIR_B [--collection C] [--atol X]  # do two DB dirs hold the same chunks? (servers stopped)
uv run python tests/test_keyword.py   # keyword ranking, pure function, no DB or API
uv run python tests/test_mcp.py       # starts the server over stdio and exercises its tools; needs a pushed DB + embeddings

Notebooks (each starts the API on a free port and shuts it down at the end; both need a pushed DB):

  • tests/api_test.ipynb — plain API walkthrough: /health, /search, doc_type filter. Only needs the local embeddings.

  • tests/agent_test.ipynb — a tool-calling agent (deepagents): the configured LLM gets search_docs as a LangChain tool and decides when to call it. Also needs the completion provider reachable.

Available Tools

1 tool
search_docsA

Search the BDC (NHLBI BioData Catalyst) documentation database.

Returns the top-k matching chunks with content, metadata (source, doc_type, datetime when available), and a score.

query is the search text. In embedding mode phrase it as a question or topic (e.g. "how do I bring my own data"); in keyword mode give the literal terms to match.

k is the number of chunks to return (default 5). Raise it (10-20) for broad or multi-part questions; each chunk is a small section of a document.

mode toggles the search engine:

  • "embedding" (default): semantic similarity — best for questions, topics, and paraphrased wording. score is a distance (lower = more similar).

  • "keyword": fuzzy literal word matching — ignores case and punctuation ("picsure" finds "PIC-SURE") and tolerates small typos — best for exact names, acronyms, tool names, or error messages the embedding may blur. Chunks matching more of the query terms rank first; score is the total number of occurrences (higher = better).

doc_type is a CSV string of types to search (e.g. "page,faq" or "video"). Available types:

  • docs: BDC GitBook platform documentation — user guides, how-tos, and technical reference (bdcatalyst.gitbook.io)

  • page: key pages of the BDC website — about/overview, joining BDC, analyzing & sharing data, usage costs and terms

  • faq: Freshdesk help-desk FAQ articles (support questions & answers)

  • video: transcripts of BDC YouTube tutorials/webinars, with timestamped links into the video

  • fellow: BDC Fellows profiles — fellowship recipients and their research projects

  • update: dated news posts ("latest updates") from the BDC website

  • event: dated BDC events — webinars, workshops, deadlines When doc_type is omitted, only docs, page, faq, and video are searched — name fellow, update, or event explicitly to search them.

date_from / date_to ("YYYY-MM-DD", inclusive) filter by date. Only event and update docs carry a date, so a date filter implicitly narrows to those types. Results are ranked by relevance, NOT date — for "recent"/"latest" questions, always set date_from to bound the range, then compare the dates returned.

Inputs: query (required), k, mode, date_to, +2 more.

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

Loading tool schema changelog…

    TDQS

    A4.6/5.0

    Scored across 1 tool

    Disambiguation5/5

    With only a single tool, there is no possibility of ambiguity. The tool has a clearly defined purpose for searching documentation.

    Naming Consistency5/5

    The tool name 'search_docs' follows a consistent verb_noun pattern and is descriptive. Since it is the only tool, naming is inherently consistent.

    Tool Count1/5

    The server exposes only one tool, which is extremely thin. Even though the tool is multi-functional, a single tool does not constitute a well-scoped set; most servers with this purpose would benefit from at least a couple of complementary tools (e.g., retrieving a document by ID or listing available types).

    Completeness3/5

    The search tool covers multiple documentation sources and provides filtering and multiple modes, which addresses the core purpose. However, it lacks any other operation such as fetching a specific document, listing available doc types, or managing content, leaving notable gaps for a documentation server.

    Maintenance

    ActivityNo data
    ResponsivenessOutdated data

    Related MCP Connectors

    Related MCP Servers

    • A
      license
      Not graded
      quality
      D
      maintenance
      Provides semantic search over markdown documentation using RAG, allowing natural language queries and integration with MCP clients.
      1
      MIT
    • A
      license
      Not graded
      quality
      D
      maintenance
      Enables AI agents to search, read, and traverse documentation bundles in Open Knowledge Format via MCP tools.
      674 npm
      74
      MIT
    • A
      license
      Not graded
      quality
      D
      maintenance
      Provides RAG (Retrieval Augmented Generation) access to technical documentation through MCP, enabling LLMs to search and retrieve relevant documentation on-demand.
      5
      MIT
    • A
      license
      Not graded
      quality
      B
      maintenance
      Crawl documentation sites, index them with hybrid search, and expose them as MCP tools so LLM agents can search and retrieve current docs.
      MIT