webdocs-mcp

webdocs-mcp

Crawl documentation sites, index them with hybrid search, and expose them as MCP tools so LLM agents can search and retrieve current docs.

Category
访问服务器

README

<p align="center"> <img src="docs/assets/banner.png" alt="webdocs-mcp" width="100%" /> </p>

<p align="center"> <a href="https://github.com/saianthireddy/webdocs-mcp/actions/workflows/ci.yml"><img src="https://github.com/saianthireddy/webdocs-mcp/actions/workflows/ci.yml/badge.svg" alt="CI"></a> <img src="https://img.shields.io/badge/python-3.10%20%7C%203.12-blue" alt="Python"> <img src="https://img.shields.io/badge/license-MIT-green" alt="License"> </p>

🩺 webdocs-mcp

Crawl any documentation site, index it into DuckDB with hybrid (semantic + BM25) search, and expose the whole thing to LLM agents as an MCP server — so your coding assistant reasons over current docs instead of its training cutoff.

Runs completely offline by default: the built-in hashing embedder needs no API key, and every test runs without touching the network. Set one environment variable to switch to OpenAI embeddings in production.

Why

LLM agents hallucinate stale APIs. The fix is retrieval over the actual docs of the actual version you use. webdocs-mcp is the smallest honest stack that does this end to end: point it at a docs site, it crawls with hierarchy tracking, chunks and embeds every page, and serves search_docs / get_doc_page tools over the Model Context Protocol to Cursor, VS Code, or Claude Code.

Architecture

flowchart LR
    U["You / CI"] -->|POST /fetch_url| API["FastAPI"]
    API --> JOBS["Job runner<br/>(background threads)"]
    JOBS --> CRAWL["Crawler<br/>BFS, same-domain,<br/>parent/child hierarchy"]
    CRAWL --> CHUNK["Chunker<br/>paragraph-aware + overlap"]
    CHUNK --> EMB["Embedder<br/>hashing (offline) / OpenAI"]
    EMB --> DB[("DuckDB<br/>pages + chunks + embeddings")]
    AGENT["LLM agent<br/>(Cursor / VS Code / Claude Code)"] -->|MCP JSON-RPC| MCP["/mcp endpoint"]
    MCP --> SEARCH["Hybrid search<br/>cosine + BM25"]
    SEARCH --> DB
    BROWSER["Browser"] -->|/map| MAPS["Site maps<br/>pure HTML tree"]
    MAPS --> DB

Design choices worth calling out:

  • DuckDB over a vector-DB server — a doc index for one team is thousands of chunks, not billions. One portable file, zero ops.
  • BM25 + embeddings, always both — embeddings catch paraphrases ("how do I get my money back" → refund policy); BM25 catches exact identifiers (ERR_LOCK_TIMEOUT) that embeddings smear. Scores are min-max normalised and blended 60/40.
  • Injectable fetcher — the crawler takes a plain (url) -> html callable. Tests inject a dict-backed fake site; production uses httpx; a headless browser slots in without touching crawl logic.
  • MCP implemented directly — the streamable-HTTP core of MCP is JSON-RPC dispatch (initialize, tools/list, tools/call). Owning those ~100 lines keeps the stack dependency-light and fully offline-testable.
  • Threads now, Redis when needed — jobs run on daemon threads behind a JobManager interface shaped like a queue consumer. docker-compose already ships the Redis service for the scale-out path.

Quickstart

git clone https://github.com/saianthireddy/webdocs-mcp.git
cd webdocs-mcp

python -m venv .venv && source .venv/bin/activate
pip install -r requirements-dev.txt

pytest            # 23 tests, fully offline
ruff check src tests

PYTHONPATH=src uvicorn webdocs.api:app --port 9111

Then:

# start a crawl
curl -X POST localhost:9111/fetch_url -H 'Content-Type: application/json' \
     -d '{"url": "https://docs.astral.sh/ruff", "max_pages": 30}'

# watch progress
curl localhost:9111/job_progress

# search what was indexed
curl 'localhost:9111/search_docs?query=how+do+I+ignore+a+rule&top_k=3'

Interactive OpenAPI docs at http://localhost:9111/docs, site maps at http://localhost:9111/map.

Docker

docker compose up --build
# app on :9111, index persisted in the webdocs-data volume

MCP integration

Add to Cursor / VS Code / Claude Code MCP configuration:

{
  "webdocs": {
    "type": "http",
    "url": "http://localhost:9111/mcp"
  }
}

Exposed tools:

Tool Purpose
search_docs Hybrid search over every indexed chunk; returns text + source URLs
list_doc_pages List indexed pages so the agent can pick one
get_doc_page Full extracted text of one page

API reference

Endpoint Description
POST /fetch_url Start a crawl job ({url, max_pages?, max_depth?}); ?sync=true blocks until done
GET /job_progress All jobs, or one via ?job_id=
GET /search_docs ?query=&top_k= hybrid search
GET /list_doc_pages Every indexed page
GET /get_doc_page ?page_id= full page text
GET /map Index of crawled sites (pure HTML, no JS)
GET /map/site/{root_id} Hierarchical tree of one site
GET /map/page/{id} Page view with breadcrumbs, siblings, children
GET /map/page/{id}/raw Raw extracted text
POST /mcp MCP JSON-RPC (initialize, tools/list, tools/call)
GET /health Status + page/chunk counts

Site maps

Crawled pages keep parent/child relationships, so /map renders a real navigable tree per site — breadcrumbs from root, sibling navigation, children listing — in pure HTML (no JavaScript). Pages fetched individually from the same domain group under one root.

Configuration

All optional (see .env.example):

Variable Default Meaning
WEBDOCS_DB_PATH data/webdocs.duckdb Index location (falls back to in-memory if unwritable)
WEBDOCS_EMBEDDER hashing hashing (offline) or openai
OPENAI_API_KEY Only for openai embedder
WEBDOCS_MAX_PAGES / WEBDOCS_MAX_DEPTH 50 / 3 Crawl limits
WEBDOCS_CHUNK_SIZE / WEBDOCS_CHUNK_OVERLAP 1200 / 150 Chunking

Testing

pytest tests/ -v      # 23 tests: crawler, chunker, embedder, DB, hybrid search, API, MCP

No test touches the network — the crawler tests run against an in-memory fake site injected through the fetcher interface, and each test gets a throwaway DuckDB file.

Roadmap

  • [ ] Respect robots.txt and add per-domain rate limiting
  • [ ] Incremental re-crawls (etag/last-modified) instead of full re-index
  • [ ] Redis-backed worker pool using the existing compose service
  • [ ] OCR/text extraction for linked PDFs
  • [ ] DuckDB VSS extension (HNSW) once the index outgrows brute-force cosine

License

MIT — see LICENSE.

推荐服务器

Baidu Map

Baidu Map

百度地图核心API现已全面兼容MCP协议,是国内首家兼容MCP协议的地图服务商。

官方
精选
JavaScript
Playwright MCP Server

Playwright MCP Server

一个模型上下文协议服务器,它使大型语言模型能够通过结构化的可访问性快照与网页进行交互,而无需视觉模型或屏幕截图。

官方
精选
TypeScript
Magic Component Platform (MCP)

Magic Component Platform (MCP)

一个由人工智能驱动的工具,可以从自然语言描述生成现代化的用户界面组件,并与流行的集成开发环境(IDE)集成,从而简化用户界面开发流程。

官方
精选
本地
TypeScript
Audiense Insights MCP Server

Audiense Insights MCP Server

通过模型上下文协议启用与 Audiense Insights 账户的交互,从而促进营销洞察和受众数据的提取和分析,包括人口统计信息、行为和影响者互动。

官方
精选
本地
TypeScript
VeyraX

VeyraX

一个单一的 MCP 工具,连接你所有喜爱的工具:Gmail、日历以及其他 40 多个工具。

官方
精选
本地
graphlit-mcp-server

graphlit-mcp-server

模型上下文协议 (MCP) 服务器实现了 MCP 客户端与 Graphlit 服务之间的集成。 除了网络爬取之外,还可以将任何内容(从 Slack 到 Gmail 再到播客订阅源)导入到 Graphlit 项目中,然后从 MCP 客户端检索相关内容。

官方
精选
TypeScript
Kagi MCP Server

Kagi MCP Server

一个 MCP 服务器,集成了 Kagi 搜索功能和 Claude AI,使 Claude 能够在回答需要最新信息的问题时执行实时网络搜索。

官方
精选
Python
e2b-mcp-server

e2b-mcp-server

使用 MCP 通过 e2b 运行代码。

官方
精选
Neon MCP Server

Neon MCP Server

用于与 Neon 管理 API 和数据库交互的 MCP 服务器

官方
精选
Exa MCP Server

Exa MCP Server

模型上下文协议(MCP)服务器允许像 Claude 这样的 AI 助手使用 Exa AI 搜索 API 进行网络搜索。这种设置允许 AI 模型以安全和受控的方式获取实时的网络信息。

官方
精选