rustdoc-mcp
An MCP server that gives AI assistants ground-truth Rust API documentation, parsing rustdoc's JSON output from your actual dependency graph with custom TF-IDF full-text search.
rustdoc-mcp is a Model Context Protocol server that gives AI coding assistants accurate Rust API documentation. Ask an LLM about a crate's API and it will confidently invent method names, argument orders, and trait bounds that were never real. rustdoc-mcp closes that gap by serving the compiler's own output: it runs cargo rustdoc against the crates in your workspace, parses the resulting JSON, and answers questions about real signatures, types, and trait implementations — straight from the analysis rustdoc itself renders its HTML from, not scraped web pages.
The trust boundary
Most tools that feed Rust docs to a model scrape docs.rs HTML or lean on the model's training data. Both drift from the truth: training data goes stale the moment a crate publishes a new version, and HTML is a lossy rendering built for human eyes. rustdoc-mcp reads rustdoc's JSON output, which preserves the full item tree — signatures, generic bounds, trait impls, re-exports — as structured data. Every answer is generated from the exact versions pinned in your Cargo.lock, with the feature flags your build actually enables, so the documentation describes the code you compile against rather than whatever a registry happens to publish.
Generating docs on demand
Documentation is generated lazily and cached. The first query for a crate shells out to cargo +nightly rustdoc, parses the result, and stores the parsed index in an in-memory LRU cache; later queries are a map lookup. A background worker runs a detection loop on a five-second interval and pre-generates docs for the crates most likely to be asked about, so the first real query rarely pays the cold-start cost. Concurrent requests for the same not-yet-generated crate collapse onto a single shared future — ten tools asking about tokio at once trigger one cargo invocation, not ten.
A nightly cargo bug, sidestepped
Generating docs for a crate that is only a dev-dependency of the workspace trips a panic in nightly cargo's feature resolver — did not find features for (pkg, NormalOrDev). The workaround: instead of running cargo rustdoc --package X from the workspace root, rustdoc-mcp locates the crate's unpacked source in the registry cache through cargo metadata and runs rustdoc from inside that directory, avoiding the resolver path that panics.
Knowing when to regenerate
Regeneration is the hard half of caching: rebuild too eagerly and every query waits on cargo; rebuild too rarely and the docs quietly lie. rustdoc-mcp fingerprints each crate with a digest and regenerates only when that digest changes, and it treats the two kinds of crate differently. A workspace member is code you're actively editing, so its digest hashes Cargo.toml and every .rs file under src/, walked in sorted order so the result is identical across machines. An external dependency is immutable once locked, so hashing its source would be wasted work — its digest keys on the version plus the SHA-256 checksum already sitting in Cargo.lock, where a matching checksum is a guarantee of identical source. Both digests fold in a hash of rustc --version, so a toolchain upgrade invalidates every cached crate at once.
Search
Beyond direct lookups by path like tokio::sync::Mutex, rustdoc-mcp runs full-text search over a crate's item names and documentation, ranked by relevance. The index is a hand-rolled TF-IDF inverted index, and the interesting part is the tokenizer, because identifiers don't tokenize like prose. A search for BackgroundWorker should match an item named exactly that, but also background_worker and a doc comment that mentions "the background worker." A character-level state machine splits on CamelCase, snake_case, and hyphen boundaries and emits both the parts and the whole — HttpServer indexes as http, server, and httpserver — then stems each token so parsing and parses collapse to one term. Item names are weighted twice as heavily as doc text, and multi-word queries carry a quadratic coverage penalty, so an item matching both words of cache invalidation outranks one matching only the far more common invalid.
Building that index for a large crate isn't free, so the finished index is serialized to disk and reloaded on the next run, invalidated by a plain mtime comparison against the source JSON.
The standard library
Standard-library documentation is always available when the rust-docs-json component is installed: rustdoc-mcp reads the pre-generated JSON straight from the nightly sysroot, so std::vec::Vec or core::mem::swap resolve with no workspace configured at all. The sysroot is read-only, so the compiled search indices for stdlib crates are cached separately under the user cache directory, in a subdirectory keyed by the exact rustc version — a toolchain upgrade builds a fresh cache without clobbering the old one.
The tool surface
The server exposes four tools. set_workspace points it at a project and resolves the dependency graph through cargo metadata. inspect_crate lists the known crates or drills into one crate's module structure. inspect_item resolves a path or bare name to a single item and renders it at one of three detail levels — signature only, signature plus docs, or plus members and trait impls. search runs the full-text query above. The detail levels matter for a token-budget-bound consumer: a model usually wants the signature, not a trait's entire method list.
What's rough
It's early, and the sharp edges are real. The nightly requirement is a hard dependency rather than a convenience — there is no stable-toolchain path, because rustdoc's JSON output simply doesn't exist on stable. Generating docs for a large dependency tree on a cold cache is genuinely slow, which is the whole reason the background worker exists to hide it. And rustdoc JSON is itself an unstable format whose version bumps regularly, so a toolchain far newer or older than the pinned parser is its own distinct way for a lookup to fail.