Skip to main content
Glama

docs-to-mcp

Turn a documentation site into an MCP server your coding agent can search.

Crawl the docs once with Firecrawl, normalize the pages into a local corpus, and serve them over FastMCP — so the agent queries the real docs instead of recalling a version of them from training data.

Pipeline

firecrawl map + scrape  ->  ingest (normalize + frontmatter)  ->  sqlite FTS5 index  ->  MCP server
      crawler.py                    ingester.py                      index.py            server.py

All three build steps run behind one pipeline.refresh() entry point, reused by the CLI (docs-to-mcp crawl) and the MCP refresh_docs tool.

Related MCP server: devdoc

Requirements

Python 3.11+, uv, and a Firecrawl API — either one works:

  • Hosted — an API key from firecrawl.dev (has a free tier). Set FIRECRAWL_API_URL=https://api.firecrawl.dev and FIRECRAWL_API_KEY=<key>.

  • Self-hosted — follow Firecrawl's self-host guide. It defaults to http://localhost:3002, which is what this tool assumes when FIRECRAWL_API_URL is unset. Make sure the search backend (searxng) is up: without it Firecrawl can scrape a URL you hand it but cannot discover pages.

Usage

uv sync

# Crawl a docs site into data/docs/<slug>/
uv run docs-to-mcp crawl https://opencode.ai/docs --slug opencode-docs --max-pages 20

# Serve the captured corpus over MCP (stdio)
uv run docs-to-mcp serve --slug opencode-docs

# Rebuild the search index from pages already captured — no network, no re-crawl
uv run docs-to-mcp reindex --slug opencode-docs

When search says the index is stale

If a query fails with "the index was built by an older version", your captured pages are fine — only the index predates a schema change. Run reindex (seconds, offline). Do not re-crawl: that would re-fetch every page and spend real Firecrawl budget to repair something purely local.

Category grouping (MediaWiki)

For MediaWiki sites, tag captured articles with their categories so list_docs can filter by group (Weapons, Characters, …) and category names feed search ranking. Run after a crawl; it scrapes the Category namespace and rebuilds the index:

uv run docs-to-mcp categories --slug vampire-survivors --concurrency 6

Category membership is read from the Category namespace because main-content extraction strips the per-article category footer.

Discovery is resolved automatically and deterministically, so you don't have to figure out a site's shape yourself:

  1. an explicit --sitemap <url> if you pass one;

  2. else a sitemap auto-resolved from the site's robots.txt (Sitemap: directive);

  3. else Firecrawl's link map.

MediaWiki is handled automatically. MediaWiki publishes a sitemap index split by namespace (NS_0 = content, NS_1 = Talk, NS_10 = Template, NS_828 = Module); the tool keeps only NS_0. So a wiki just works with no extra flags:

uv run docs-to-mcp crawl https://vampire.survivors.wiki/ --slug vampire-survivors \
  --max-pages 5000 --concurrency 6

Firecrawl's link map on a large MediaWiki is unreliable (a namespace-mixed, non-deterministic subset dominated by Templates/Modules), which is why sitemap discovery is preferred and automatic. Use --no-sitemap to force the link map.

Large and interrupted crawls

Capture is concurrent and streaming: each page is written to disk the moment it is scraped, and the pages/*.md files double as the resume ledger.

  • --concurrency N — parallel page captures (default 5; raise for big sites, but a self-hosted Firecrawl has limited render workers).

  • Resume is automatic: if a crawl is interrupted (pages written but pages.jsonl never finalized), re-running the same command captures only the pages still missing.

  • --incremental — capture only URLs not already in the corpus. A cheap top-up for a large corpus after new pages are published; untouched pages keep their original crawled_at.

# Big wiki: more parallelism, resume-safe if it dies partway
uv run docs-to-mcp crawl https://example.com/wiki --slug example-wiki --max-pages 5000 --concurrency 10

# Later: pull in only newly published pages
uv run docs-to-mcp crawl https://example.com/wiki --slug example-wiki --max-pages 5000 --incremental

Layout

data/docs/<slug>/
  pages/<page_id>.md   # normalized markdown with frontmatter
  pages.jsonl          # per-page metadata + source URLs
  index.sqlite         # FTS5 search index
  manifest.json        # root_url + last_crawled_at, used by refresh_docs

MCP tools

The server exposes a small, discovery-friendly surface:

  • search_docs(query, limit) — ranked page matches (BM25) with snippets.

  • get_doc(page_id) — one captured page with metadata and full markdown.

  • list_docs(section, limit) — list captured pages, optionally by section.

  • refresh_docs(max_pages) — re-crawl the source and rebuild corpus + index.

Using the server from an MCP client

Register the captured corpus as a local stdio MCP server. Example OpenCode entry (one entry per slug; the same generic server is selected via --slug):

"opencode-docs": {
  "type": "local",
  "command": ["uv", "run", "--project", "docs-to-mcp",
              "docs-to-mcp", "serve", "--slug", "opencode-docs"],
  "environment": { "FIRECRAWL_API_URL": "http://localhost:3002" },
  "enabled": true
}

FIRECRAWL_API_URL is only needed by refresh_docs; plain search/read work offline.

Localized sites

By default a crawl captures the canonical (unprefixed) docs and drops localized paths like /docs/de/... or /docs/pt-br/..., detected via ISO 639-1 codes. Pass --locale de to capture a specific language instead. A site that is entirely localized falls back to keeping its localized pages rather than an empty corpus.

Search ranking

Results are ranked by BM25 with title, heading, and category matches weighted above body text. Because FTS5 normalizes relevance by whole-document length, only the lead (~6 KB) of each page's prose is indexed — otherwise a large page's title/heading match would be drowned out by its length. Headings are indexed from the full page, so structure stays searchable; only deep prose is excluded from search. Full page text is always available via get_doc.

A query first requires all terms (precise); if that returns nothing for a multi-word natural-language question, it falls back to matching any term and lets BM25 rank — so a query like "best weapons for Pasqualina" still returns results.

Known limitations

  • Search matches title, headings, and the lead of each page — a term that appears only deep in a long page's prose may not surface; open the page with get_doc.

  • Ranking is text-relevance only (no page-authority signal), so for an ambiguous one-word query several similarly-titled pages may outrank the canonical one.

  • Category grouping reads MediaWiki Category pages: maintenance/tracking categories (e.g. "Pages needing…") appear alongside content ones, and a category whose page renders its members dynamically may come back empty.

  • On sites larger than --max-pages, discovery keeps the alphabetically-first URLs; raise --max-pages to capture the full corpus.

  • A real doc section named with a 2-letter ISO code (e.g. /docs/is) would be misread as a locale and dropped by default; use --locale to override.

  • Link rewriting handles inline markdown links/images, not reference-style links.

Available Tools

4 tools
get_docA

Return one captured page: metadata, source URL, and full markdown.

ParametersJSON Schema
NameRequiredDescriptionDefault
page_idYes

Output Schema

ParametersJSON Schema
NameRequiredDescription

No output parameters

TDQS

A3.8/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

The description is straightforward about returning page data with no hidden behaviors. No annotations are provided, but the tool is read-only in nature. It does not disclose error handling or edge cases, but the description is sufficient for a simple retrieval tool.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is a single, front-loaded sentence that wastes no words. It efficiently communicates the tool's purpose and output.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness3/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given the low complexity (one parameter, output schema present), the description covers the return value adequately. However, the complete lack of parameter explanation leaves the definition incomplete for correct invocation.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters1/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The input schema has 0% description coverage for the 'page_id' parameter. The tool description does not explain the parameter's format, expected value, or any constraints. This is a significant gap that makes it hard for an agent to know how to correctly provide the parameter.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the verb ('Return') and resource ('one captured page'), and lists what is included (metadata, source URL, full markdown). It is easily distinguished from sibling tools like 'search_docs' (which searches) and 'list_docs' (which lists multiple pages).

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description implies usage for fetching a single page by ID, which is distinct from listing all pages or searching. However, it does not explicitly state when to use this tool versus alternatives or provide any exclusion criteria.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

list_docsB

List captured pages, optionally filtered to one section or category.

ParametersJSON Schema
NameRequiredDescriptionDefault
limitNo
sectionNo
categoryNo

Output Schema

ParametersJSON Schema
NameRequiredDescription

No output parameters

TDQS

B3.4/5.0
Behavior2/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

No annotations are provided, so the description carries full burden. It only states that the tool lists pages with optional filters, but does not disclose whether it is read-only, how pagination works, or what the response format is. The presence of an output schema does not excuse the description from providing behavioral context.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is a single sentence that is efficient and front-loaded. It conveys the core purpose and key parameters without unnecessary words. Could be slightly improved with more detail, but remains appropriately concise.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness3/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a simple list tool with three optional parameters and an output schema, the description covers the basic purpose and filter options. However, it lacks details on ordering, default behavior, and total count. The output schema likely provides return value info, but the description could be more complete regarding pagination and scope.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 0%, so the description must compensate. It explains the 'section' and 'category' parameters by mentioning optional filtering. However, the 'limit' parameter is not described, leaving its purpose (e.g., pagination) unclear. Partial coverage but adds value.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description uses a specific verb 'List' and identifies the resource as 'captured pages'. It distinguishes from sibling tools: search_docs implies query-based search, get_doc retrieves a single document, refresh_docs updates data. Listing with optional filters is a distinct operation.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description mentions optional filtering by section or category, providing context for when to use those parameters. However, it does not explicitly state when to use this tool over alternatives like search_docs, nor does it mention any exclusions or prerequisites.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

refresh_docsC

Re-crawl the source site and rebuild the local corpus and index.

ParametersJSON Schema
NameRequiredDescriptionDefault
max_pagesNo

Output Schema

ParametersJSON Schema
NameRequiredDescription

No output parameters

TDQS

C2.5/5.0
Behavior2/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description carries full burden for behavioral disclosure. It mentions the action but not side effects (e.g., overwriting index, duration, required permissions), leaving the agent uninformed about consequences.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness2/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is overly brief (one short sentence) and omits critical information (parameter meaning, usage context). While concise, it sacrifices completeness and effectiveness.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness1/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a tool with one parameter and no annotation backing, the description is insufficient. It does not cover return values (despite having an output schema), prerequisites, or operational impact, leaving significant gaps.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters1/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 0%, meaning the schema lacks parameter descriptions. The tool description does not mention the 'max_pages' parameter at all, failing to explain its purpose or effect.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the tool's action with specific verbs ('re-crawl', 'rebuild') and resources ('source site', 'local corpus', 'index'), making it distinct from sibling tools (search/get/list) which are read-only operations.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines2/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

No guidance on when to use this tool versus alternatives (e.g., when data is stale) or when to avoid it. The description lacks context for appropriate invocation.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

search_docsC

Search the captured docs and return ranked matching pages.

ParametersJSON Schema
NameRequiredDescriptionDefault
limitNo
queryYes

Output Schema

ParametersJSON Schema
NameRequiredDescription

No output parameters

TDQS

C2.7/5.0
Behavior2/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

No annotations are provided, so the description carries full burden. It mentions 'ranked matching pages' but does not disclose whether results include full content or snippets, how ranking works, or if any side effects exist (e.g., query logging). Important behavioral traits are omitted.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness3/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is a single sentence, which is concise. However, it lacks structure to efficiently convey key details. Every word is necessary, but the brevity sacrifices completeness, making it less helpful despite being short.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness2/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given the tool has 2 parameters, no enums, and an output schema, the description should at least hint at expected input format and output structure. It only says 'ranked matching pages', leaving ambiguity about what the response contains. The output schema exists but is not referenced, so completeness is low.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters1/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 0%. The description does not explain the parameters beyond the schema, such as valid query formats, limit behavior (e.g., max value), or default ranking criteria. With no supplemental info, the agent must rely solely on the schema, which is insufficient.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the verb 'Search', the resource 'captured docs', and the outcome 'return ranked matching pages'. This distinguishes it from sibling tools: get_doc (single document), list_docs (list all), and refresh_docs (update cache).

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines2/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

No guidance on when to use this tool versus alternatives. It does not specify that this should be used for finding relevant content, while get_doc for specific IDs, list_docs for enumeration, and refresh_docs for cache updates. The description lacks context for decision-making.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Tool Schema Changelog

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

  1. 4 tool updatesv0.1.0
    • First observedget_doc
    • First observedlist_docs
    • First observedrefresh_docs
    • First observedsearch_docs

TDQS

A3.5/5.0

Scored across 4 tools

Disambiguation5/5

Each tool has a distinct purpose: searching, retrieving full content, listing, and refreshing the corpus. No overlap.

Naming Consistency5/5

All tool names follow a consistent verb_noun pattern in snake_case (search_docs, get_doc, list_docs, refresh_docs).

Tool Count5/5

4 tools is a compact but sufficient set for documentation management, covering core operations without excess.

Completeness4/5

The set covers search, retrieval, listing, and corpus refresh. Missing delete/update of individual docs, but these are less critical for a read-oriented MCP server.

Maintenance

ActivityMaintained
ResponsivenessNo issues

Related MCP Connectors

Related MCP Servers

  • A
    license
    Not graded
    quality
    A
    maintenance
    Provides a local MCP server for searching and retrieving documentation from 22+ open-source projects, enabling AI coding assistants to access up-to-date docs without network dependency.
    8 npm
    2
    MIT
  • A
    license
    Not graded
    quality
    B
    maintenance
    A documentation MCP server that crawls websites and Git repositories, stores them as Markdown, and provides tools to search and retrieve documentation for local LLMs and AI agents.
    Apache 2.0
  • F
    license
    Not graded
    quality
    B
    maintenance
    Local MCP server that indexes documentation from URLs/files into a vector database, enabling coding agents to search and use up-to-date library and API documentation.
    -