Skip to main content
Glama

LLM Documentation Scraper (doc-scraper)

Go Version Go Reference License

A configurable, concurrent, and resumable web crawler written in Go. Specifically designed to scrape technical documentation websites, extract core content, convert it cleanly to Markdown format suitable for ingestion by Large Language Models (LLMs), and save the results locally.

doc-scraper crawling a docs site and answering search queries offline

Overview

This project provides a powerful command-line tool to crawl documentation sites based on settings defined in a config.yaml file. It navigates the site structure, extracts content from specified HTML sections using CSS selectors, and converts it into clean Markdown files.

Why Use This Tool?

  • Built for LLM Training & RAG Systems - Creates clean, consistent Markdown optimized for ingestion

  • Preserves Documentation Structure - Maintains the original site hierarchy for context preservation

  • Production-Ready Features - Offers resumable crawls, rate limiting, and graceful error handling

  • High Performance - Uses Go's concurrency model for efficient parallel processing

Related MCP server: go-docs-mcp

Goal: Preparing Documentation for LLMs

The main objective of this tool is to automate the often tedious process of gathering and cleaning web-based documentation for use with Large Language Models. By converting structured web content into clean Markdown, it aims to provide a dataset that is:

  • Text-Focused: Prioritizes the textual content extracted via CSS selectors

  • Structured: Maintains the directory hierarchy of the original documentation site, preserving context

  • Cleaned: Converts HTML to Markdown, removing web-specific markup and clutter

  • Locally Accessible: Provides the content as local files for easier processing and pipeline integration

Key Features

Feature

Description

Configurable Crawling

Uses YAML for global and site-specific settings

Scope Control

Limits crawling by domain, path prefix, and disallowed path patterns (regex)

Content Extraction

Extracts main content using CSS selectors

HTML-to-Markdown

Converts extracted HTML to clean GitHub-Flavored Markdown (tables, task lists, strikethrough)

Image Handling

Opt-in downloading and local rewriting of image links with domain and size filtering (disabled by default; doc-scraper is text-first)

Link Rewriting

Rewrites internal links to relative paths for local structure

JSONL Output

Optional one-record-per-page JSONL with a trailing crawl-summary record, for RAG ingestion

Concurrency

Configurable worker pools and semaphore-based request limits (global and per-host)

Rate Limiting

Configurable per-host delays with jitter

Robots.txt & Sitemaps

Respects robots.txt and processes discovered sitemaps

State Persistence

Uses BadgerDB for state; supports resuming crawls via crawl --resume

Graceful Shutdown

Handles SIGINT/SIGTERM with proper cleanup

HTTP Retries

Exponential backoff with jitter for transient errors

Observability

Structured logging (log/slog); optional pprof endpoint (build with -tags pprof)

Modular Code

Organized into packages for clarity and maintainability

CLI Utilities

Built-in config validate and config list commands for configuration management

MCP Server Mode

Expose as Model Context Protocol server for Claude Code/Cursor integration

Full-Text Search

Offline BM25 search over crawled docs (SQLite FTS5) via the search_docs MCP tool

Auto Content Detection

Automatic framework detection (Docusaurus, MkDocs, Sphinx, GitBook, ReadTheDocs) with readability fallback

Parallel Site Crawling

Crawl multiple sites concurrently with shared resource management

Watch Mode

Scheduled periodic re-crawling with state persistence

Getting Started

Prerequisites

  • Go: Version 1.26 or later

  • Git: For cloning the repository

  • Disk Space: Sufficient for storing crawled content and state database

Installation

Option 1: Direct Installation (Recommended)

Install the latest version directly from GitHub:

go install github.com/Sriram-PR/doc-scraper/v2/cmd/doc-scraper@latest

This installs the doc-scraper binary to your GOPATH/bin directory (usually ~/go/bin or %USERPROFILE%\go\bin). Make sure this directory is in your PATH.

Option 2: Clone and Build

  1. Clone the repository:

    git clone https://github.com/Sriram-PR/doc-scraper.git
    cd doc-scraper
  2. Install Dependencies:

    go mod tidy
  3. Build the Binary:

    make build
    # or: go build -o doc-scraper ./cmd/doc-scraper

    This creates an executable named doc-scraper in the project root.

Quick Start

Create a minimal config.yaml in the project root:

output_base_dir: "./crawled_docs"
state_dir: "./crawler_state"
enable_jsonl_output: true
sites:
  rust_cli_book:
    start_urls:
      - "https://rust-cli.github.io/book/index.html"
    allowed_domain: "rust-cli.github.io"
    allowed_path_prefix: "/book/"
    content_selector: "#content, main"
    max_depth: 2          # seed plus one level; set 0 for the whole book

Run the crawl:

./doc-scraper crawl -site rust_cli_book -loglevel info

The Markdown, plus pages.jsonl, llms.txt, and llms-full.txt, lands under ./crawled_docs/rust_cli_book/ (output is organized by site key). A small book like this finishes in a few seconds; large sites can take minutes, so start with a low max_depth to gauge size before removing the bound.

Configuration (config.yaml)

A config.yaml file is required to run the crawler. Create this file in the project root or specify its path using the -config flag.

Key Settings for LLM Use

When configuring for LLM documentation processing, pay special attention to these settings:

  • sites.<your_site_key>.content_selector: Define precisely to capture only relevant text

  • sites.<your_site_key>.allowed_domain / allowed_path_prefix: Define scope accurately

  • skip_images: Images are not downloaded by default (text-first). Set to false globally or per-site to download and localize images for offline consumption

  • Adjust concurrency/delay settings based on the target site and your resources

Example Configuration

# Global settings (applied if not overridden by site)
default_delay_per_host: 500ms
num_workers: 8
num_image_workers: 8
max_requests: 48
max_requests_per_host: 4
output_base_dir: "./crawled_docs"
state_dir: "./crawler_state"
max_retries: 4
initial_retry_delay: 1s
max_retry_delay: 30s
global_crawl_timeout: 0s
skip_images: true # Default. Set to false to download and localize images
max_image_size_bytes: 10485760 # 10 MiB (applies only when images are downloaded)
enable_jsonl_output: true
jsonl_output_filename: "pages.jsonl"

# HTTP Client Settings
http_client_settings:
  timeout: 45s
  max_idle_conns_per_host: 6

# Site-specific configurations
sites:
  # Key used with -site flag
  pytorch_docs:
    start_urls:
      - "https://pytorch.org/docs/stable/"
    allowed_domain: "pytorch.org"
    allowed_path_prefix: "/docs/stable/"
    content_selector: "article.pytorch-article .body"
    max_depth: 0 # 0 for unlimited depth
    skip_images: false # Opt in to downloading images for this site
    disallowed_path_patterns:
      - "/docs/stable/.*/_modules/.*"
      - "/docs/stable/.*\.html#.*"

  tensorflow_docs:
    start_urls:
      - "https://www.tensorflow.org/guide"
      - "https://www.tensorflow.org/tutorials"
    allowed_domain: "www.tensorflow.org"
    allowed_path_prefix: "/"
    content_selector: ".devsite-article-body"
    max_depth: 0
    delay_per_host: 1s  # Site-specific override
    # Disable JSONL output for this site, overriding global
    enable_jsonl_output: false
    disallowed_path_patterns:
      - "/install/.*"
      - "/js/.*"

Full Configuration Options

Option

Type

Description

Default

default_user_agent

String

Default User-Agent header for requests

"" (Go default)

default_delay_per_host

Duration

Time to wait between requests to the same host

0s (no delay)

num_workers

Integer

Number of concurrent crawl workers

4

num_image_workers

Integer

Number of concurrent image download workers

same as num_workers

max_requests

Integer

Maximum concurrent requests (global)

10

max_requests_per_host

Integer

Maximum concurrent requests per host

2

output_base_dir

String

Base directory for crawled content

"./crawled_docs"

state_dir

String

Directory for BadgerDB state data

"./crawler_state"

max_retries

Integer

Maximum retry attempts for HTTP requests. To disable retries, set this to 0 together with a non-zero initial_retry_delay; max_retries: 0 on its own is treated as unset and falls back to the default

3

initial_retry_delay

Duration

Initial delay for retry backoff

1s

max_retry_delay

Duration

Maximum delay for retry backoff

30s

global_crawl_timeout

Duration

Overall timeout for the entire crawl

0s (no timeout)

per_page_timeout

Duration

Timeout for processing a single page

0s (no timeout)

skip_images

Boolean

Whether to skip downloading images. Image downloading is opt-in

true (skip)

max_image_size_bytes

Integer

Maximum allowed image size (applies only when images are downloaded)

0 (unlimited)

max_page_size_bytes

Integer

Maximum HTML page body size

52428800 (50 MiB)

enable_jsonl_output

Boolean

Enable JSONL page output (one record per page plus a trailing crawl_meta record) for RAG pipelines

false

jsonl_output_filename

String

Filename for JSONL output

"pages.jsonl"

enable_incremental

Boolean

Enable incremental crawling globally

false

crawl_history_retention

Integer

Number of past crawls per site kept in the SQLite history index (powers get_freshness/diff_crawl)

10

http_client_settings

Object

HTTP client configuration

(see below)

sites

Map

Site-specific configurations

(required)

HTTP Client Settings: (Global; cannot be overridden per site. Pool, dialer, and TLS timings are baked into pkg/fetch with sane defaults and are not exposed as config knobs.)

  • timeout: Overall request timeout (default 45s)

  • max_idle_conns_per_host: Idle connections per host (default 2)

  • allow_private_networks: Disables the SSRF guard that blocks dials to loopback / private / link-local / CGNAT / multicast addresses. Default false. Set to true only if you intentionally crawl internal documentation servers reachable via private IPs.

Site-Specific Configuration Options:

  • start_urls: Array of starting URLs for crawling (Required)

  • allowed_domain: Restrict crawling to this domain (Required)

  • allowed_path_prefix: Restrict crawling to URLs under this path prefix (Optional; defaults to /, the whole domain). Setting it is strongly recommended to bound scope

  • content_selector: CSS selector for main content extraction, or "auto" for automatic detection (Required)

  • max_depth: Exclusive upper bound on crawl depth from start URLs. Start pages are depth 0, so 1 crawls only the start pages, 2 adds their directly-linked pages, and so on. 0 = unlimited. URLs discovered from a sitemap.xml are seeded at depth 1 (one hop from the site root), so they are still bounded by max_depth: max_depth: 1 stays start-only and skips sitemap expansion

  • delay_per_host: Override global delay setting for this site

  • disallowed_path_patterns: Array of regex patterns for URLs to skip

  • link_extraction_selectors: Array of CSS selectors for additional link extraction areas

  • respect_nofollow: Boolean. Whether to respect rel="nofollow" links

  • user_agent: String. Override global user agent for this site

  • skip_images: Override the global image setting for this site. Images are skipped unless this (or the global skip_images) is set to false

  • max_image_size_bytes: Integer. Override global max image size for this site

  • allowed_image_domains: Array of domains from which to download images

  • disallowed_image_domains: Array of domains to block image downloads from

  • enable_jsonl_output: true or false. Override global JSONL output enablement for this site

  • jsonl_output_filename: String. Override global JSONL output filename for this site

Usage

Execute the compiled binary from the project root directory:

./doc-scraper <command> [options]

Commands

Command

Description

crawl

Start a crawl (add --resume to continue an interrupted one)

add

Probe a docs site and draft a config entry for it: detects the framework, proposes crawl scope from the sitemap, previews one extracted page, and writes only after confirmation

config validate

Validate configuration file without crawling

config list

List available site keys from config

mcp-server

Start MCP server for AI tool integration

search

Ranked full-text search over the crawled corpus (BM25, stemming, section anchors)

watch

Watch sites and re-crawl on schedule

version

Show version information

run

Read a JSON task spec from stdin and dispatch a crawl or watch (for orchestration/automation)

Command Options

crawl:

Flag

Description

Default

-config <path>

Path to config file

config.yaml

-site <key>

Site key from config (single site)

-

-sites <keys>

Comma-separated site keys for parallel crawling

-

--all-sites

Crawl all configured sites in parallel

false

--resume

Resume an interrupted crawl from existing state

false

-loglevel <level>

Log level (debug, info, warn, error)

info

-json

Emit logs as JSON (one record per line) instead of text

false

-pprof <addr>

pprof server address. Only effective in builds with -tags pprof; default builds log a warning and ignore the flag

"" (disabled)

-incremental

Enable incremental crawling (skip unchanged pages)

false

-full

Force full crawl (ignore incremental settings)

false

Note: One of -site, -sites, or --all-sites is required.

add:

doc-scraper add https://vitepress.dev/guide/what-is-vitepress

Probes the site with a handful of polite requests (the page, robots.txt, llms.txt, the sitemap), then shows what it found before anything is written: the detected framework and content selector (validated against the fetched page), a crawl scope clustered from the sitemap with page counts as evidence, sibling version/locale trees proposed as exclusions, and a markdown preview of the extracted page with code-block fidelity numbers. The entry is appended to your config only after you confirm; the rest of the file is preserved byte-for-byte, comments included.

Flag

Description

Default

-config <path>

Path to config file (created if missing)

config.yaml

-site <key>

Site key to use instead of the derived one

-

-selector <css>

Content CSS selector, skipping auto-detection

-

-depth <n>

Override the proposed max_depth

-

-yes

Write without prompting

false

-dry-run

Draft only, never write (exit code 2)

false

-json

Emit the draft as JSON on stdout (human text goes to stderr)

false

Exit codes: 0 written, 1 error, 2 drafted but not written. For agents and scripts: add -dry-run -json <url> inspects, then add -yes <url> commits; with no terminal attached the command fails fast instead of waiting on stdin. Sites whose robots.txt disallows crawling the given path are refused, and robots rules that restrict AI crawlers are surfaced as a warning.

config validate:

Flag

Description

Default

-config <path>

Path to config file

config.yaml

-site <key>

Site key to validate (optional, validates all if empty)

-

-json

Emit a single JSON object instead of human-readable text

false

config list:

Flag

Description

Default

-config <path>

Path to config file

config.yaml

-json

Emit a single JSON object instead of human-readable text

false

mcp-server: (stdio transport only; the SSE transport was removed in v2.x)

Flag

Description

Default

-config <path>

Path to config file

config.yaml

-loglevel <level>

Log level (debug, info, warn, error)

info

watch:

Flag

Description

Default

-config <path>

Path to config file

config.yaml

-site <key>

Site key to watch (single site)

-

-sites <keys>

Comma-separated site keys to watch

-

--all-sites

Watch all configured sites

false

-interval <duration>

Crawl interval (e.g., 1h, 24h, 7d)

24h

-loglevel <level>

Log level (debug, info, warn, error)

info

-json

Emit logs as JSON (one record per line) instead of text

false

Note: One of -site, -sites, or --all-sites is required.

Example Usage Scenarios

Basic Crawl:

./doc-scraper crawl -site tensorflow_docs -loglevel info

Resume a Large Crawl:

./doc-scraper crawl -site pytorch_docs --resume -loglevel info

Validate Configuration:

./doc-scraper config validate -config config.yaml
./doc-scraper config validate -site pytorch_docs  # Validate specific site

List Available Sites:

./doc-scraper config list

High Performance Crawl with Profiling:

./doc-scraper crawl -site small_docs -loglevel warn -pprof localhost:6060

Debug Mode for Troubleshooting:

./doc-scraper crawl -site test_site -loglevel debug

Parallel Crawl of Multiple Sites:

./doc-scraper crawl -sites pytorch_docs,tensorflow_docs,langchain_docs

Crawl All Configured Sites:

./doc-scraper crawl --all-sites

Start MCP Server for Claude Desktop:

./doc-scraper mcp-server -config config.yaml

Incremental Crawling

crawl -incremental (which implies --resume, and is also what watch mode uses) re-fetches every previously-crawled page and re-checks it for changes:

  • Change detection is content-scoped: it hashes the extracted content-selector region, not the raw page. Churn in the page shell (navigation, analytics, build timestamps, CSRF tokens) outside the content selector does not count as a change.

  • Pages whose content region is unchanged are skipped without re-converting, re-downloading images, or rewriting output.

  • Pages whose content region changed are fully reprocessed and their output is rewritten.

  • A page that now returns an error (e.g. 404) on re-crawl leaves its previously-crawled output as-is; nothing is pruned.

Because there is no conditional-request support yet, incremental mode still performs the HTTP fetch for each known page; the savings come from skipping the downstream processing of unchanged pages.

Output Structure

Crawled content is saved under the output_base_dir defined in the config, organized by site key and preserving the site structure. Keying by site key (rather than domain) keeps two site configs that target the same domain in separate trees:

<output_base_dir>/
└── <sanitized_site_key>/            # e.g., flask_docs
    ├── images/                       # Always created; only populated when skip_images: false
    │   ├── image1.png
    │   └── image2.jpg
    ├── index.md                      # Markdown for the root path
    ├── <jsonl_output_filename>       # If enable_jsonl_output: true
    ├── llms.txt                      # Manifest of pages (auto-generated, when JSONL is enabled)
    ├── llms-full.txt                 # Full content concatenated (auto-generated, when JSONL is enabled)
    ├── topic_one/
    │   ├── index.md
    │   └── subtopic_a.md
    └── topic_two.md

llms.txt and llms-full.txt

When JSONL output is enabled, the crawler also emits llms.txt and llms-full.txt following the llmstxt.org convention. llms.txt is a markdown manifest (H1 + summary blockquote + ## Pages list of every crawled page with title and URL). llms-full.txt concatenates the full markdown content of every page, with section separators. Both files are regenerated on every crawl from the JSONL source of truth, so resumed crawls produce a complete updated manifest.

Output Format

Each generated Markdown file begins with a YAML frontmatter block carrying page metadata, followed by the converted content:

  • YAML frontmatter (delimited by ---) with title, url (source URL), crawled_at (RFC3339 timestamp), content_hash (SHA-256 of the content, matching the JSONL record), and depth

  • Clean content converted from HTML to GitHub-Flavored Markdown, preserving tables

  • Relative links to other pages (when within the allowed domain)

  • Local image references (if images are enabled)

Example:

---
title: 'Authentication'
url: https://docs.example.com/api/auth
crawled_at: "2026-08-09T12:00:00Z"
content_hash: 9f2b...c1a4
depth: 2
---

# Authentication

...page content as Markdown...

JSONL Output

When enabled, the crawler writes one JSON object per line to a JSONL file. This format is designed for ingestion into RAG pipelines and downstream indexers.

Enable it:

enable_jsonl_output: true
jsonl_output_filename: "pages.jsonl"  # default

The file mixes two record kinds, distinguished by the record_type field:

  • page records, one per crawled page.

  • A single crawl_meta record as the final line, holding the crawl-level summary. Resuming rewrites the file to drop any leftover crawl_meta record before appending a fresh one at close, so a closed file always contains exactly one crawl_meta record.

page record fields (from PageJSONL):

Field

Description

record_type

Always "page"

url

Final absolute URL of the page

title

Page title

content

Full markdown content

headings

Array of headings extracted from the page

links

Array of links found in the content

images

Array of image URLs found in the content

content_hash

SHA-256 hash of the content (used for incremental crawling)

crawled_at

Timestamp of when the page was crawled

depth

Crawl depth from the start URL

crawl_meta record fields (from CrawlMetaJSONL):

Field

Description

record_type

Always "crawl_meta"

site_key

Site key from the config

allowed_domain

The crawled domain

crawl_started_at

Crawl start timestamp

crawl_ended_at

Crawl end timestamp

total_pages

Number of pages recorded in this crawl

The output file is written to each site's output directory. Both the enable flag and filename can be overridden per site.

Auto Content Detection

When you set content_selector: "auto" for a site, the crawler automatically detects the documentation framework and applies the appropriate content selector.

Supported Frameworks

Detection recognizes 30+ documentation generators and hosted platforms, checked in three tiers of decreasing trust: the <meta name="generator"> tag, structural DOM signatures (attributes, ids, classes), and asset path patterns. Covered families include Docusaurus, VitePress, VuePress, Starlight/Astro, Nextra, Fumadocs, Mintlify, GitBook, MkDocs (Material, ReadTheDocs theme, and plain), Sphinx (furo, pydata, book, RTD, and classic themes), Antora, Docsy, hugo-book, Geekdoc, just-the-docs, mdBook, rustdoc, pkg.go.dev, Javadoc, Doxygen, TypeDoc, Writerside, ReadMe.com, Intercom, and Docus.

Every detected selector is validated against the live page before it is trusted: if it matches nothing or captures too little text, the crawler falls back instead of extracting empty content. Client-rendered shells (Docsify, Swagger UI, Redoc, Scalar, Document360, and generic empty-body SPAs) are recognized and reported as needing JavaScript rendering rather than silently producing an empty crawl.

Fallback Behavior

If no known framework is detected (or the detected selectors do not match the page), the crawler uses Mozilla's Readability algorithm to extract the main content. This works well on classic server-rendered docs, but can drop code blocks on some modern sites, so doc-scraper add's preview reports code-block fidelity before you commit a config.

Example Usage

sites:
  pytorch_docs:
    start_urls:
      - "https://pytorch.org/docs/stable/"
    allowed_domain: "pytorch.org"
    allowed_path_prefix: "/docs/stable/"
    content_selector: "auto"  # Auto-detect framework
    max_depth: 0

Parallel Site Crawling

Crawl multiple documentation sites concurrently with shared resource management. The orchestrator coordinates multiple crawlers while respecting global rate limits and semaphores.

Usage

# Crawl specific sites in parallel
./doc-scraper crawl -sites pytorch_docs,tensorflow_docs,langchain_docs

# Crawl all configured sites
./doc-scraper crawl --all-sites

# Resume parallel crawl
./doc-scraper crawl -sites pytorch_docs,tensorflow_docs --resume

Resource Sharing

When running parallel crawls, the following resources are shared across all site crawlers:

  • Global semaphore: Limits total concurrent requests across all sites

  • HTTP client: Shared connection pooling

  • Rate limiter: Respects per-host delays

Each site still maintains its own:

  • BadgerDB store for state persistence

  • Output directory for crawled content

  • Per-host semaphores for domain-specific limiting

Results Summary

After all sites complete, the orchestrator outputs a summary:

===========================================
Parallel crawl completed in 2m30s
Site Results:
  pytorch_docs: SUCCESS - 1500 pages in 1m20s
  tensorflow_docs: SUCCESS - 2000 pages in 2m15s
  langchain_docs: FAILED - 0 pages in 3s
    Error: initial fetch failed for start URL (see logs)
-------------------------------------------
Total: 3 sites (2 success, 1 failed), 3500 pages processed
===========================================

Unknown or misspelled site keys are rejected before the crawl starts, so they never appear as a FAILED row in this summary. For example, crawl -sites pytorch_docs,typo_key exits immediately (non-zero) with:

Invalid site keys: site 'typo_key' not found. Available sites: [pytorch_docs tensorflow_docs langchain_docs]

The FAILED rows in the summary are for sites that exist in the config but errored during the crawl itself.

Watch Mode

Watch mode enables scheduled periodic re-crawling of documentation sites. The scheduler tracks the last run time for each site and automatically triggers crawls when the configured interval has elapsed.

Usage

# Watch a single site with 24-hour interval
./doc-scraper watch -site pytorch_docs -interval 24h

# Watch multiple sites
./doc-scraper watch -sites pytorch_docs,tensorflow_docs -interval 12h

# Watch all configured sites weekly
./doc-scraper watch --all-sites -interval 7d

Interval Format

The interval supports standard Go duration format plus day units:

  • 30m - 30 minutes

  • 1h - 1 hour

  • 24h - 24 hours

  • 7d - 7 days

  • 1d12h - 1 day and 12 hours

State Persistence

Watch mode persists state to <state_dir>/watch_state.json, tracking:

  • Last run time for each site

  • Success/failure status

  • Pages processed

  • Error messages (if any)

This allows the scheduler to resume correctly after restarts, only running sites when their interval has elapsed.

Example Output

INFO Starting watch mode for 2 sites with interval 24h0m0s
INFO Watch schedule:
INFO   pytorch_docs: last run 2024-01-15T10:30:00Z (success, 1500 pages), next run 2024-01-16T10:30:00Z
INFO   tensorflow_docs: never run, will run immediately
INFO Running crawl for 1 due sites: [tensorflow_docs]
...
INFO Next crawl: pytorch_docs in 23h45m (at 10:30:00)

Graceful Shutdown

Watch mode handles SIGINT/SIGTERM gracefully: it stops the scheduler and cancels any in-progress crawl, letting the crawler flush its BadgerDB state and partial output first, so the interrupted crawl resumes cleanly on the next run.

Run (JSON Task Spec)

The run command reads a single JSON object from stdin and dispatches the equivalent crawl or watch. It is meant for orchestration agents that would rather build a JSON payload than assemble shell flags. Unknown fields are rejected so typos surface immediately; logs go to stderr and the exit code matches the equivalent flag-driven subcommand.

{
  "command":     "crawl" | "watch",   // required
  "config":      "config.yaml",        // optional, defaults to config.yaml
  "site":        "site_key",           // exactly one of site | sites | all_sites
  "sites":       ["a", "b"],
  "all_sites":   true,
  "resume":      false,                // crawl only
  "incremental": false,                // crawl only (implies resume)
  "full":        false,                // crawl only (mutually exclusive with incremental)
  "interval":    "24h",                // watch only, defaults to 24h
  "loglevel":    "info",               // defaults to info
  "json_logs":   false,                // emit slog records as JSON on stderr
  "pprof":       ""                    // crawl only, e.g. localhost:6060
}

Examples:

echo '{"command":"crawl","site":"pytorch_docs"}' | doc-scraper run
echo '{"command":"crawl","all_sites":true,"incremental":true,"json_logs":true}' | doc-scraper run
echo '{"command":"watch","sites":["pytorch_docs","tensorflow_docs"],"interval":"6h"}' | doc-scraper run

MCP Server Mode

The crawler can run as a Model Context Protocol (MCP) server, enabling integration with AI assistants like Claude Code and Cursor.

Available MCP Tools

Tool

Description

describe_server

Orientation manifest: server identity + sites + recent jobs in one call (call this first)

list_sites

List all configured sites from config file

get_page

Fetch a single URL live over the network and return content as markdown

crawl_site

Start a background crawl for a site (returns job ID)

get_job_status

Check the status of a background crawl job

cancel_crawl

Cancel a running or pending crawl job by job ID

list_pages

Enumerate crawled pages for a site (paginated, metadata only)

read_page

Return a crawled page's markdown from the stored output, without network access

search_docs

Full-text search across crawled docs (BM25, stemming, snippets), without network access

get_freshness

Report how stale a site's latest crawl is, from the crawl-history index

diff_crawl

Report pages added, removed, or changed since a given timestamp

Usage

The MCP server uses the stdio transport, compatible with Claude Desktop, Claude Code, and Cursor.

./doc-scraper mcp-server -config config.yaml

Claude Code Integration

Add to your Claude Code configuration (claude_code_config.json):

{
  "mcpServers": {
    "doc-scraper": {
      "command": "/path/to/doc-scraper",
      "args": ["mcp-server", "-config", "/path/to/config.yaml"]
    }
  }
}

Tool Examples

List available sites:

Tool: list_sites
Result: Returns all configured sites with their domains and crawl status

Fetch a single page:

Tool: get_page
Arguments: { "url": "https://docs.example.com/guide", "content_selector": "article" }
Result: Returns page content as markdown with metadata

Start a background crawl:

Tool: crawl_site
Arguments: { "site_key": "pytorch_docs", "incremental": true }
Result: Returns job ID for tracking progress

Check crawl progress:

Tool: get_job_status
Arguments: { "job_id": "abc-123-def" }
Result: Returns status, pages processed, and completion info

Enumerate crawled pages:

Tool: list_pages
Arguments: { "site_key": "pytorch_docs", "max_results": 50, "offset": 0 }
Result: Returns up to 50 page entries (URL, title, depth, crawled_at, content_length), sorted by URL. Use offset for pagination.

Cancel a running crawl:

Tool: cancel_crawl
Arguments: { "job_id": "abc-123-def" }
Result: Returns cancelled: true/false and the job's current status. Has no effect on jobs already in a terminal state.

Contributing

Contributions are welcome! Please feel free to open an issue to discuss bugs, suggest features, or propose changes.

Pull Request Process:

  1. Fork the repository

  2. Create a feature branch (git checkout -b feature/amazing-feature)

  3. Commit your changes (git commit -m 'Add some amazing feature')

  4. Push to the branch (git push origin feature/amazing-feature)

  5. Open a Pull Request

Please ensure code adheres to Go best practices and includes appropriate documentation.

Privacy Policy

doc-scraper collects nothing: no telemetry, no analytics, no accounts. All output and state stays on your machine, and the only network requests it makes are the crawls and fetches you explicitly ask for. Full policy: PRIVACY.md.

License

This project is licensed under the Apache-2.0 License.

Acknowledgements

Available Tools

11 tools
cancel_crawlA
Idempotent

Cancel a running or pending crawl job by job ID. Has no effect on jobs already in a terminal state.

ParametersJSON Schema
NameRequiredDescriptionDefault
job_idYesThe job ID returned by crawl_site

TDQS

A4.3/5.0
Behavior4/5

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

Annotations already provide idempotentHint=true and destructiveHint=false. The description adds the terminal-state no-op disclosure and clarifies the exact operation scope, giving the agent a precise expectation of side effects beyond what annotations alone convey.

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?

Two sentences with action-first phrasing and no filler. The second sentence earns its place by defining the boundary condition, making the whole description compact yet complete.

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

Completeness5/5

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

For a single-parameter action with full schema coverage and helpful annotations, the description fully states the target states, the no-op condition, and the identifying parameter. No output schema exists, but return values are not necessary for correct invocation.

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 coverage is 100% and the job_id description already explains its origin from crawl_site. The description only repeats 'by job ID' without adding new semantics, so the baseline 3 is appropriate.

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 states a specific verb ('Cancel'), a specific resource ('crawl job'), and the key selector ('by job ID'). The action is unmistakably distinct from siblings like get_job_status and crawl_site, even without naming them.

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?

It explicitly says the tool applies to running or pending jobs and gives a clear when-not condition: no effect on terminal-state jobs. It does not name alternative tools, but the boundary condition is enough to guide correct use.

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

crawl_siteA

Start a background crawl for a configured site. Returns immediately with a job ID.

ParametersJSON Schema
NameRequiredDescriptionDefault
site_keyYesSite key from config file (e.g., 'langchain_py', 'rust_docs')
incrementalNoEnable incremental mode (skip unchanged pages)

TDQS

A4/5.0
Behavior4/5

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

Adds key behavioral context beyond annotations: the crawl runs in the background and the call returns immediately with a job ID, implying the actual work happens asynchronously. This helps the agent know to poll get_job_status later. It also clarifies that the site must already be configured, setting a prerequisite not visible from annotations alone.

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?

Two sentences with no filler: the first states the action and scope, the second states the return behavior. Information is front-loaded and every sentence adds value.

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

Completeness4/5

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

For a simple 2-parameter tool with no output schema, the description is sufficient: it explains the start action, background execution, and the immediate return of a job ID. It could mention how to monitor progress, but the existence of a get_job_status sibling makes that omission minor.

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 100%, so both site_key and incremental are already fully documented. The description does not add any additional meaning for these parameters, so the baseline score of 3 is appropriate.

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?

States a clear action: start a background crawl for a configured site, and explicitly notes it returns a job ID immediately. This distinguishes it from sibling tools like cancel_crawl or get_job_status, which operate on existing crawls.

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 implies when to use it—when you want to kick off a crawl of a configured site—but does not explicitly discuss alternatives or exclusion conditions. An agent must infer that cancel_crawl or diff_crawl serve different purposes, so some routing burden remains on the agent.

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

describe_serverA
Read-onlyIdempotent

Returns server identity, configured sites, and recent crawl jobs in one call. Call this first to orient yourself: it consolidates what would otherwise require list_sites plus several get_job_status calls. The MCP tool list is already advertised by the protocol so it is not duplicated here.

ParametersJSON Schema
NameRequiredDescriptionDefault

No parameters

TDQS

A4.5/5.0
Behavior4/5

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

Annotations already declare readOnlyHint, idempotentHint, and destructiveHint, so the safety profile is covered. The description adds behavioral value by explaining that this is a single combined call and that the MCP tool list is intentionally not duplicated, which clarifies a potential expectation gap.

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?

Three concise sentences, each earning its place: the first states the purpose, the second gives usage guidance, and the third prevents a likely confusion about omissions. Information is front-loaded and there is no redundancy with the annotations or schema.

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

Completeness5/5

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

With no parameters, no output schema, and complete annotations, the description provides the needed orientation: what the tool returns and when to call it. Nothing required to invoke it correctly is missing.

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

Parameters4/5

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

The tool has zero parameters, so there are no parameters to document. The baseline for a 0-parameter tool is 4, and the description appropriately focuses on return content rather than input semantics.

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 opens with a specific verb and resource: it returns server identity, configured sites, and recent crawl jobs. It distinguishes itself from siblings by explicitly noting that it consolidates list_sites plus several get_job_status calls, so an agent can tell it apart without inspecting other tools.

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 gives clear usage context with 'Call this first to orient yourself' and names the alternatives it consolidates (list_sites, get_job_status). It does not explicitly state when not to use it, but the orient-first guidance and consolidation rationale are sufficient for correct selection.

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

diff_crawlA
Read-onlyIdempotent

Return added/removed/changed pages between the latest crawl and the most recent crawl whose crawl_ended_at <= since. Hash-based verdicts from the SQLite history index. Pair with get_freshness: pass the last_crawl_ended_at value as since after running crawl_site with incremental set to true to see exactly what changed.

ParametersJSON Schema
NameRequiredDescriptionDefault
sinceYesRFC3339 timestamp; the most recent crawl whose crawl_ended_at <= since is the baseline (e.g. 2026-05-23T22:00:00Z)
offsetNoPagination offset, 0-based
site_keyYesSite key from config
max_resultsNoMaximum diff entries to return (default 100, max 1000)

TDQS

A4.4/5.0
Behavior4/5

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

Annotations already declare readOnlyHint, idempotentHint, and destructiveHint, so the safety profile is covered. The description adds behavioral context by noting 'hash-based verdicts from the SQLite history index' and precisely defining the baseline selection rule, which goes beyond the structured annotations.

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?

Three sentences, with the core behavior first and the workflow example last. Each sentence carries distinct information (what, how, when) with no filler or repetition.

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

Completeness4/5

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

Given there is no output schema, the description gives a sufficient high-level contract: added/removed/changed pages with hash-based verdicts. It also reduces ambiguity by naming the exact sequence with crawl_site and get_freshness, so an agent can correctly select and chain the tool; minor gaps like response entry shape remain but are not essential for invocation.

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

Parameters4/5

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

Schema covers 100% of parameters, so the baseline is 3. The description adds cross-tool value by explaining where `since` comes from ('pass the last_crawl_ended_at value as since'), which is not present in the input schema; the other parameters are already well documented in the schema.

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?

States a clear action and resource: 'Return added/removed/changed pages between the latest crawl and the most recent crawl whose crawl_ended_at <= since.' This is specific enough to distinguish it from sibling tools like get_freshness, and the title 'Diff crawls over time' reinforces the same function.

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?

Provides a concrete workflow: 'Pair with get_freshness: pass the last_crawl_ended_at value as since after running crawl_site with incremental set to true.' This tells an agent when to call it and how to chain it with siblings, though it does not explicitly state exclusions or when-not-to-use alternatives.

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

get_freshnessA
Read-onlyIdempotent

Return the most recent crawl summary for a site (last_crawl_started_at/ended_at, total_pages, mode, age_seconds) plus output/state dir presence and any running job. Use this to decide whether to query the existing crawl or run crawl_site first.

ParametersJSON Schema
NameRequiredDescriptionDefault
site_keyYesSite key from config (use list_sites to discover available keys)

TDQS

A4.5/5.0
Behavior4/5

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

Annotations already declare readOnlyHint, idempotentHint, and destructiveHint, so the safety profile is covered. The description goes further by disclosing what the tool returns (crawl summary fields, output/state dir presence, running job), which is useful behavioral detail beyond the structured annotations. It accurately describes a read-only inspection operation without contradicting any annotations.

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?

Two dense sentences: the first lists the return content, the second gives the decision context. No filler and the key return information is front-loaded. The description achieves high information density in a compact format.

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

Completeness5/5

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

For a single-parameter read-only tool with no output schema, this description is complete. It states what is returned, how to decide to use the tool, and the annotations cover idempotence and safety. The provided information fully supports correct invocation.

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?

The input schema documents site_key at 100% coverage, including the hint to use list_sites to discover available keys. The description adds no parameter-level details, but with full schema coverage the baseline score of 3 is appropriate. No extra semantics are needed beyond what the schema already provides.

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 opens with a specific verb and resource ('Return the most recent crawl summary for a site') and enumerates the returned fields, making the operation unmistakable. It also injects a decision cue ('Use this to decide whether to query the existing crawl or run crawl_site first') that distinguishes it from the crawl_site sibling. This is a clear, specific purpose statement that differentiates it from other tools.

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

Usage Guidelines5/5

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

It explicitly states when to use the tool: before deciding between querying an existing crawl and running crawl_site. This names the alternative and the condition, satisfying the usage guidance dimension. The tool's role as a freshness check is directly tied to a decision, giving clear context.

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

get_job_statusB
Read-onlyIdempotent

Get the status of a crawl job

ParametersJSON Schema
NameRequiredDescriptionDefault
job_idYesThe job ID returned by crawl_site

TDQS

B3.4/5.0
Behavior2/5

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

Annotations already declare readOnlyHint=true, idempotentHint=true, and destructiveHint=false, so the safety profile is established. The description adds no behavioral context beyond the word 'Get', such as what status values may appear or whether the call reflects updated progress. No contradiction exists.

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, focused sentence with no filler. The verb and object are front-loaded, making it immediately scannable for an agent.

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

Completeness4/5

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

For a one-parameter, read-only status lookup with strong annotations, the definition is mostly sufficient. However, with no output schema, it does not indicate what statuses or fields the response contains, and the absence of usage guidance leaves minor but real ambiguity.

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 100% and the single job_id parameter is adequately documented as the ID returned by crawl_site. The description itself adds no parameter-level meaning beyond what the schema already provides.

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

Purpose4/5

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

The description uses a specific verb ('Get') and resource ('status of a crawl job'), making the tool's purpose clear. It is naturally distinct from siblings like crawl_site or cancel_crawl, though it does not explicitly name or contrast alternatives.

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?

Usage is only implied: checking the status of a crawl job makes sense after calling crawl_site, and the schema's job_id description references crawl_site as the source of the ID. However, the description itself gives no explicit when-to-use or when-not-to-use guidance.

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

get_pageA
Read-onlyIdempotent

Fetch a URL live over the network and return its content as markdown. This is an on-demand fetch independent of any crawl: it does not read the stored crawl output, and it ignores the site's configured content_selector and scope.

ParametersJSON Schema
NameRequiredDescriptionDefault
urlYesThe URL to fetch
content_selectorNoCSS selector for main content (defaults to 'body')

TDQS

A4.5/5.0
Behavior5/5

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

The annotations already provide readOnlyHint, openWorldHint, idempotentHint, and destructiveHint=false. The description adds substantial behavioral detail beyond these: live network access, markdown output, independence from crawl output, and intentional disregard for site-configured content_selector/scope. There is no contradiction with annotations.

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?

Two sentences, front-loaded with action and output, then a compact contrast that disambiguates from crawl-based tools. Every sentence earns its place with no filler.

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

Completeness5/5

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

For a two-parameter read-only tool with rich annotations, 100% schema coverage, and no output schema, the description is sufficient. It explains what, when, and one key quirk, leaving no ambiguity about invocation.

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?

The input schema covers 100% of the parameters, including the content_selector default of 'body'. The description adds no meaningful parameter-level detail; its mention of content_selector refers to site configuration rather than the argument itself. Baseline 3 is appropriate because the schema carries the parameter documentation.

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 states a specific verb and resource: 'Fetch a URL live over the network and return its content as markdown.' It also distinguishes itself from siblings by explicitly noting it does not read stored crawl output, which clearly separates it from read_page and other crawl-related tools.

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?

It provides a clear context: use this for an on-demand, live fetch independent of any crawl, as opposed to reading stored crawl output. It doesn't name an explicit alternative such as read_page, but the contrast with crawl-based reads gives an agent enough guidance to choose correctly.

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

list_pagesA
Read-onlyIdempotent

List crawled pages for a site, paginated and sorted by URL. Returns metadata only (URL, title, depth, crawled_at, content_length). Pass any URL returned here to read_page to get its stored markdown; get_page re-fetches a URL live rather than returning the crawled copy.

ParametersJSON Schema
NameRequiredDescriptionDefault
offsetNoPagination offset, 0-based (default: 0)
site_keyYesSite key from config (use list_sites to discover available keys)
max_resultsNoMaximum pages to return (default: 100, max: 1000)

TDQS

A4.5/5.0
Behavior4/5

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

Annotations already cover read-only, idempotent, and non-destructive behavior. The description adds useful behavioral context: it returns metadata only (URL, title, depth, crawled_at, content_length), is paginated and sorted by URL, and reflects stored crawled pages rather than live fetches. It doesn't discuss errors or partial failures, so it is strong but not exhaustive.

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?

Three focused sentences front-load the core action, state output scope and shape, and route to sibling tools. Every sentence earns its place with no wasted words.

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

Completeness5/5

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

For a read-only listing tool with fully documented parameters and safety annotations, the description covers action, scope, output fields, pagination ordering, and downstream tool usage. The schema supplies the remaining parameter details, so nothing essential is missing.

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 100%, so the baseline applies. The description's mention of pagination and 'for a site' mildly reinforces site_key, offset, and max_results, but it does not add parameter meaning beyond what the schema already documents.

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?

Description uses a specific verb 'List' with a clear resource 'crawled pages for a site' and adds pagination and sort order. It also distinguishes itself from read_page and get_page by explicitly stating that it returns metadata only and that the pages are crawled, not live.

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

Usage Guidelines5/5

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

The description explicitly routes downstream usage: URLs returned here should be passed to read_page for stored markdown, while get_page is for live re-fetching rather than crawled copies. This gives an agent clear when-to-use and when-not-to-use guidance for the relevant siblings.

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

list_sitesA
Read-onlyIdempotent

List all configured sites available for crawling

ParametersJSON Schema
NameRequiredDescriptionDefault

No parameters

TDQS

A4.1/5.0
Behavior3/5

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

Annotations already declare readOnlyHint, idempotentHint, and destructiveHint, so the safety profile is fully covered. The description adds that the tool lists only configured sites, which is useful scoping, but it does not mention return format or pagination. This is acceptable given the simple read-only nature.

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 communicates the action, resource, and scope with no wasted words.

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

Completeness5/5

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

For a parameterless, read-only list tool with comprehensive annotations, the description is sufficiently complete. An agent knows exactly what this tool does and can invoke it without missing information.

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

Parameters4/5

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

The tool has zero parameters, so there is nothing for the description to elaborate on. The baseline of 4 applies here since no parameter documentation is needed.

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 a specific verb ('List') and resource ('all configured sites'), and the phrase 'available for crawling' adds useful context that distinguishes this from sibling tools like list_pages. An agent can immediately tell what the tool does without opening the schema.

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 implies the usage context: this is for discovering which sites are configured and eligible for crawling. However, it does not explicitly state when to use this tool versus alternatives such as crawl_site or list_pages, leaving some room for inference.

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

read_pageA
Read-onlyIdempotent

Return a page's markdown from the stored crawl output, without any network access. This is the counterpart to get_page: read_page serves the crawled copy that already had the site's content_selector applied, while get_page re-fetches the URL live. Use list_pages to discover URLs, then read_page to read them. Large pages are truncated at max_bytes; follow next_offset to read the rest.

ParametersJSON Schema
NameRequiredDescriptionDefault
urlYesURL of a crawled page, as reported by list_pages
offsetNoByte offset into the page content, for reading a truncated page in parts (default: 0)
site_keyYesSite key from config (use list_sites to discover available keys)
max_bytesNoMaximum content bytes to return (default: 102400, max: 1048576)

TDQS

A4.7/5.0
Behavior5/5

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

Beyond the readOnly/idempotent annotations, the description reveals important behavior: no network access, content_selector already applied, truncation at max_bytes, and a next_offset mechanism for reading the rest. These are not derivable from the annotations or schema alone.

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?

Four sentences, each earning its place: core function, sibling distinction, discovery workflow, and pagination behavior. The most important information is front-loaded, and there is no filler or repetition.

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

Completeness5/5

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

For a tool without an output schema, the description adequately covers return format, truncation, and continuation via next_offset. It also provides the surrounding workflow with list_pages and clarifies the live-crawl tradeoff, leaving no critical gap for an agent to call it correctly.

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 100%, and the schema already explains each parameter's meaning. The description's mention of max_bytes truncation and next_offset adds behavioral context, but it does not add new semantic detail about the parameters themselves.

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 opens with a specific verb and resource: 'Return a page's markdown from the stored crawl output, without any network access.' It explicitly contrasts itself with get_page, making the tool's role unambiguous and differentiating it from the most similar sibling.

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

Usage Guidelines5/5

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

It gives clear workflow guidance: 'Use list_pages to discover URLs, then read_page to read them.' It also explains the exact tradeoff versus get_page (crawled copy vs live re-fetch), so an agent knows when to select this tool over the alternative.

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

search_docsA
Read-onlyIdempotent

Full-text search across all crawled documentation, ranked by relevance (BM25 with stemming), with zero network access. Results carry the page URL, its section heading path, and a snippet with match terms marked [like this]; follow up with read_page for the full page. Supports FTS5 syntax: quoted phrases, OR, and trailing * for prefix matching. Searches the stored index only; run crawl_site first for uncrawled sites.

ParametersJSON Schema
NameRequiredDescriptionDefault
limitNoMaximum results to return (default: 10, max: 50)
queryYesSearch terms. Plain words are ANDed; "quoted phrases", OR, and prefix* work too.
site_keyNoLimit results to one site (use list_sites to discover keys). Omit to search all sites.

TDQS

A4.5/5.0
Behavior5/5

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

Beyond the annotations (readOnlyHint, idempotentHint, destructiveHint), the description reveals important behavioral traits: zero network access, BM25 ranking with stemming, result format including URL and section path, FTS5 syntax support, and the stored-index-only scope. This adds substantial context about what happens when calling the tool and what the output contains, with no contradiction to annotations.

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?

Four dense sentences, each earning its place: purpose and ranking, result format and follow-up, query syntax, and index caveat. The most important scoping constraints are front-loaded, and there is no fluff or repetition of schema content.

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

Completeness5/5

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

Given there is no output schema, the description properly explains what results contain (URL, heading path, snippet with highlighted matches) and how to get the full content. It also covers prerequisites (crawl_site), querying syntax, and scope limitations. The agent has enough context to invoke the tool correctly without missing behavioral details.

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?

The schema already describes all three parameters at 100% coverage, including the query syntax, limit bounds, and site_key usage. The description reiterates the FTS5 syntax but does not meaningfully extend parameter-level understanding beyond the schema. Baseline 3 is appropriate because the schema carries the parameter-heavy lifting.

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 opens with a specific verb and resource: 'Full-text search across all crawled documentation'. It further distinguishes the tool from siblings by stating it is ranked by relevance, works offline, and searches only the stored index, which clearly differentiates it from list_pages, get_page, and read_page.

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 gives clear context for when to use the tool: when you need to search crawled documentation. It also provides practical guidance by telling the agent to run crawl_site first for uncrawled sites and to follow up with read_page for the full page. However, it does not explicitly mention when not to use this tool in favor of a sibling like list_pages or get_page, so it falls just short of a 5.

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. Dates show when Glama detected each change.

  1. 11 tool updates
    • First observedcancel_crawl
    • First observedcrawl_site
    • First observeddescribe_server
    • First observeddiff_crawl
    • First observedget_freshness
    • First observedget_job_status
    • First observedget_page
    • First observedlist_pages
    • First observedlist_sites
    • First observedread_page
    • First observedsearch_docs

TDQS

A4.1/5.0
Disambiguation4/5

Each tool has a distinct role, and the get_page/read_page vs list_pages/search_docs boundaries are clearly explained. The only mild overlap is get_freshness and get_job_status, and describe_server partly duplicates list_sites/job status, though it is positioned as an orientation call.

Naming Consistency5/5

All tools use snake_case verb_noun names like list_sites, crawl_site, cancel_crawl, read_page, and search_docs. There is no mixing of styles or vague verbs; even diff_crawl follows the verb-first pattern.

Tool Count5/5

11 tools is right-sized for a documentation crawling and retrieval server. Each tool maps to a meaningful operation without redundant surfaces.

Completeness4/5

The crawl-and-read workflow is fully covered: start/cancel/status, list/read/search stored pages, and diff/freshness for updates. The main gap is that site management is limited to listing pre-configured sites, with no tool to add, remove, or update site definitions, so agents cannot onboard new documentation sources without out-of-band configuration.

Maintenance

ActivityActive
ResponsivenessResponsive

Related MCP Connectors

Related MCP Servers

  • A
    license
    A
    quality
    B
    maintenance
    Local-first MCP server providing semantic search over library docs, fully offline. Single Go binary speaks MCP over stdio against a vector index pinned to the binary version. Like Context7 with the internet turned off. Apache 2.0. Linux + macOS, also available as a container image.
    2
    3
    Apache 2.0
  • A
    license
    A
    quality
    B
    maintenance
    Go MCP server for multi-format document access — PDF, TXT, MD, DOCX, CSV, images. 12 tools including OCR, search, table extraction, and URL fetch. Single binary, no runtime.
    26
    13
    10
    MIT
  • F
    license
    Not graded
    quality
    D
    maintenance
    Documentation crawler MCP server that crawls and indexes documentation sites so that any MCP-compatible AI can search, read, and expand on the content.
    1
    -
  • 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

Latest Blog Posts

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/Sriram-PR/doc-scraper'

If you have feedback or need assistance with the MCP directory API, please join our Discord server