JohnXu22786/docs-retriever
doctrove: versioned library documentation retrieval MCP server for coding agents — zero runtime dependencies, installable as a dsh plugin bundle.
Listed
0
Tools
Bundle verified
What it does
Doctrove: versioned library documentation retrieval MCP server for coding agents — catalog lookup, version selection and focus-ranked doc extraction, with zero runtime dependencies and a dsh bundle.
Best for
- Coding agents that need version-specific library API documentation before generating or changing code.
- Teams maintaining a local or self-hosted documentation catalog with ranked, traceable extracts and offline fallback.
Not ideal for
- General web research or repositories not represented in the configured documentation index.
- Workflows that need complete documentation browsing rather than focused excerpts selected from catalog entries.
README
doctrove — Versioned library documentation retrieval (agent toolset)
doctrove is a versioned documentation retrieval plugin for coding agents: it maintains a “library documentation catalog index”, letting agents fetch accurate, versioned, traceable API documentation snippets on demand while writing code — instead of guessing API usage from training memory — and thereby avoiding “APIs that don’t exist in the docs”, “outdated signatures”, and “fabricated parameters”.
-
Zero runtime dependencies: uses only Node.js built-ins (
fetch,node:test), runs without installing any package; - Standard MCP stdio server: any MCP-capable client (dsh, Claude Code, Codex, opencode, etc.) can connect;
-
Built for dsh: ships a dsh bundle (
cordis.patch.yml+ a self-built bridge plugin), one-step integration viadsh plugin add, tools automatically appear in the model’s tool list (mcp__doctrove__*); -
Versioned: every entry carries multiple documentation volumes, supporting “latest stable / exact version / prefix version (
4→ 4.21.x)” selection; - Scored, ranked results: entry retrieval and documentation snippets both carry 0–1 relevance scores and hit signals, so the model can verify “why it ranked first”;
-
Offline-capable: ships with a built-in local demo index (
data/index.json), runs without networking or remote sources; - Self-hostable remote index: the index is an open JSON format that can be hosted on any static hosting (a zero-dependency hosting script is included);
-
Smart caching: TTL + LRU in-memory cache, remote index and query results expire automatically per configuration,
--no-cachedisables it in one shot; -
Graceful degradation: when the remote index is unreachable, falls back to the local index automatically, results are tagged with
sourceso the agent can tell data provenance.
Quick start
Method A: connect directly from any MCP client
# Requires Node.js >= 18.17; no arguments means offline mode (built-in index)
node src/entry.js
Example config line using the official dsh bridge (also applies to Claude Code / Codex MCP config):
# dsh: insert into $DSH_HOME/profiles/<profile>/cordis.patch.yml
- insert:
- id: mcp-doctrove
name: '@deepseek-ai/dsh-mcp-client'
config:
serverName: doctrove
transport: stdio
command: node
args: ['/absolute/path/src/entry.js']
Once connected, the model sees 3 tools: catalog_lookup, catalog_releases, doc_extract
(generic MCP clients see the bare names; in the dsh scenario they carry the mcp__doctrove__ prefix, see below).
Method B: install as a dsh plugin bundle (recommended)
This plugin is declared as a dsh bundle (the dsh.bundle field in package.json). Run the following in the plugin checkout directory:
dsh plugin --profile web add .
- On first use it automatically initializes the
webprofile and adds this package todsh.profile.bundles; - The
doctrove/bridgeplugin defined incordis.patch.ymlspawns this MCP server inside the dsh process, and after the handshake registers all tools intoctx.tools, no manual config changes needed; - Offline-capable: the built-in index is loaded by default; to use a remote index, configure
args: ['--index-url', ...]on the bridge line (see below); - Uninstall:
dsh plugin --profile web remove doctrove.
Installing in DSH
dsh plugin --profile demo add github:JohnXu22786/docs-retriever
-
demois a dsh profile: it is created automatically on first use, and the package is added todsh.profile.bundles; - The
cordis.patch.ymlinside the package defines thedoctrove/bridgeplugin, which starts this MCP server within the dsh process and registers all tools intoctx.toolsafter the handshake — no manual configuration needed; - Offline-capable out of the box: the built-in index is loaded by default; configure
args: ['--index-url', ...]on the bridge line to use a remote index (see below); - Removal:
dsh plugin --profile demo remove doctrove
After installing, restart dsh, then in a session you can simply say:
“Write an Express 5 endpoint with
:idroute params and a JSON response — look up the exact route-param syntax first”
The corresponding tool call chain: mcp__doctrove__catalog_lookup (confirm express) →
mcp__doctrove__doc_extract (id=express, focus=route parameters).
Note: dsh enables no MCP servers by default (every server command is trusted code executed outside the sandbox), and this plugin’s bundle line is the “enable” action itself; only install plugins you trust.
dsh integration notes (how the pluginized harness loads it)
dsh uses the Cordis plugin framework, and the composition unit is a bundle: an npm package + a patch layer. The loading chain is:
package.json(dsh.bundle.patch → ./cordis.patch.yml)
└─ one line in cordis.patch.yml: name: 'doctrove/bridge'
└─ src/bridge/plugin.js(Cordis plugin, inject: ['tools'])
├─ spawns src/entry.js with Node itself(MCP server subprocess, stdio)
├─ completes the initialize / tools/list handshake
└─ registers each tool as mcp__doctrove__<toolname> into ctx.tools
-
Tool interface: the model-visible tool names are
mcp__<serverName>__<raw tool name>,serverNamedefaults todoctrove; -
Events/skills: this plugin registers no events or skills; it exposes capabilities only through the
ctx.toolstool interface (read-only tools, no side effects); -
Lifecycle: the handshake and registration happen during the plugin’s
apply; on unload the subprocess is killed and all tools are deregistered automatically (registered viactx.effectcleanup, no leftovers after hot reload/unload); -
Two bridge options: the self-built bridge
doctrove/bridgebundled with this package (zero-dependency, works out of the box) and the official@deepseek-ai/dsh-mcp-clientconfig line (seeexamples/overlay-for-dsh.yml.example); tool naming and behavior are identical — pick either one, don’t enable both; -
Environment variables: dsh filters credential-like variables from MCP subprocess environments; the self-built bridge’s subprocess inherits the host environment,
so
DOCTROVE_INDEX_URLand similar pass through, and can also be set explicitly withenv:on the bridge line.
Common dsh issues
| Symptom | Fix |
|---|---|
| Tools missing from the list | Check that the cordis.patch.yml line took effect (dsh --profile <name> --dump-config to inspect layers), confirm no startup log errors |
| Want a remote index | Configure args: ['--index-url', 'https://your-index-url'] on the bridge line (directory root), or env: { DOCTROVE_INDEX_URL: '...' }
|
| Want looser caching | Configure args: ['--cache-ttl', '3600'] on the bridge line; use --no-cache for testing/debugging |
| pnpm >=10 rejects git-installed prepare scripts | This plugin is pure JS with no build script, so it is not affected; install from checkout or tarball |
Tool list (3 tools, all read-only)
| Tool | Purpose | Main parameters |
|---|---|---|
catalog_lookup |
Search the doc catalog by name/description, return candidates with scores and hit signals |
query (required), limit
|
catalog_releases |
List available and recommended versions of an entry |
id (required) |
doc_extract |
Extract doc snippets for an entry/version/focus (relevance-ranked) |
id (required), version, focus, maxSections
|
catalog_lookup
Search the doc catalog. When unsure of a library’s canonical id, call this first, then use the returned id with doc_extract.
// request
{ "query": "express", "limit": 5 }
// response (structuredContent summary)
{
"results": [{
"id": "express", "name": "Express", "summary": "Minimal web framework for Node.js",
"score": 1.0, "matches": ["exact name match"],
"versions": ["5.1.0", "4.21.2"], "latest": "5.1.0", "source": "local:.../data/index.json"
}],
"total": 1, "sources": ["local:.../data/index.json"]
}
catalog_releases
View an entry’s version list and recommended version, useful for checking whether a target version is available (doc_extract supports the same version syntax).
{ "id": "express" }
// → { "id": "express", "name": "Express", "latest": "5.1.0",
// "versions": ["5.1.0", "4.21.2"], "source": "local:..." }
doc_extract
Extract documentation. focus describes one concept at a time (e.g. “route parameters”); split cross-concept questions into multiple calls
to avoid diluted results; version defaults to the latest stable release.
{ "id": "express", "version": "5", "focus": "wildcard" }
// → {
// "id": "express", "name": "Express", "version": "5.1.0",
// "releaseKind": "prefix", "releaseNote": "prefix match 5.x → latest 5.x release",
// "sections": [{ "heading": "Wildcard routes", "score": 0.5, "matches": ["heading hit: 1 word"], ... }],
// "source": "local:..."
// }
Errors are always structured isError results, with error.code taking one of: validation / not-found / version /
network / timeout / internal, and message carrying actionable hints (e.g. candidate versions when the requested one is unavailable).
Parameter-validation failures are likewise folded into isError (rather than the protocol-level -32602), so the model sees a structured error code in one call and can self-correct.
Scoring and ranking algorithm
Entry retrieval (catalog_lookup)
Score = signal-tier score + popularity fine-tuning, both capped at 1.0:
| Signal | Base score | Notes |
|---|---|---|
| Exact name match (case-insensitive) | 1.0 | name or id exactly equals the query |
| Exact alias match | 0.95 | e.g. query expressjs hits an alias |
| Name prefix match | 0.90 | e.g. query expr
|
| Alias prefix match | 0.85 | |
| Name token overlap | 0.60–0.83 | proportional to hit tokens; ceiling deliberately below the alias-prefix tier to keep tier order invariant |
| Summary token overlap | 0.30–0.50 | when the name is completely unrelated |
- Popularity fine-tuning =
(1 − raw) × min(0.1, log₁₀(popularity)/100), applied only within the headroom of the current signal tier, so “exact > alias > prefix > token overlap” can never be inverted by popularity; - Tokenization: English by word, Chinese per character (space-less languages);
- Ties are broken by popularity, descending (stable sort).
Doc snippet ranking (doc_extract focus)
- Snippet score =
(2 × heading hit words + body hit words) / (2 × query words); - Heading hits count double the body; zero-hit snippets are filtered out; truncated past
maxSections; - Without
focus, snippets return in the index’s original order.
Version selection (catalog_releases / doc_extract version)
latest / default → latest stable release (or latest prerelease when no stable exists);
exact version → unique match (a v/V prefix is tolerated; build metadata such as +build.2 does not participate in comparison);
prefix (5 / 5.1 / 5.1.x / 5.1.*) → latest release matching the prefix;
prerelease identifiers compare per semver rules (rc.10 > rc.9);
no match → version error with a candidate list attached.
Caching strategy
- One TTL + LRU in-memory cache per process (default 256 entries, 600 s lifetime), caching: remote index fetches and query results; the local index itself is parsed once per process (static data);
- TTL is configurable:
--cache-ttl <sec>(0–86400, 0 = disabled),--no-cacheis a shortcut for disabled; -
Failure cooldown (negative caching): after a remote index fetch fails, a 30-second cooldown kicks in during which the plugin falls back to local
and does not repeat the network request (avoiding a timeout wait on every query while the source is down); after the cooldown it retries automatically and heals itself once the source recovers.
The cooldown timing is independent of the cache TTL (a
--cache-ttlshorter than the cooldown does not cut it short); it does not apply under--no-cache/--cache-ttl 0(every failure then really retries); - LRU evicts by access order; cache stats (hits/misses/evictions) are printed to stderr at exit with
--debug; - Local-index cold start is free (synchronous read); after the first remote fetch, all queries hit the cache.
Offline mode and remote index
Offline mode (default)
Without --index-url the plugin is fully offline: it uses the built-in data/index.json (3 demo entries:
Express 5.1/4.21 dual versions, Zod 3.24/3.23, Day.js 1.11, including a version-difference demo).
The built-in index can be replaced with your own (--local-index <path>), see the format below.
Remote index
The index is an open JSON format hostable on any static HTTP service (GitHub Pages, object storage, intranet file servers all work):
index URL(--index-url / DOCTROVE_INDEX_URL,the URL of the directory containing index.json)
└─ <url>/index.json ← fetched by the plugin along this path
Minimal local hosting (zero dependencies, supports ETag conditional requests; by default listens on the local loopback only — change host yourself to expose on LAN):
node scripts/serve-index.mjs [dir] [port] # default ./data, port 8730
node src/entry.js --index-url http://localhost:8730
Relationship between remote and local: remote first, local as fallback. When the remote fetch fails (offline/timeout/non-2xx/invalid format),
the plugin degrades to the local index and keeps serving; every entry and result carries a source tag so the model can judge data freshness.
Index format specification
{
"format": "doctrove-index@1", // required, versioned format identifier
"updatedAt": "2026-08-16T00:00:00.000Z",
"entries": [{
"id": "express", // required, canonical id (globally unique)
"name": "Express", // required, display name
"summary": "Minimal web framework for Node.js",
"aliases": ["expressjs"], // search aliases (array of strings)
"homepage": "https://expressjs.com",
"popularity": 1200, // popularity weight (scoring fine-tuning)
"versions": ["5.1.0", "4.21.2"], // required, available versions
"volumes": { // required, version → documentation volume
"5.1.0": {
"summary": "highlights of this version (optional)",
"sections": [{ // required, doc snippets (elements must be non-array objects)
"heading": "Route handlers", // snippet title (2x ranking weight)
"path": "https://expressjs.com/en/5x/api.html#app.METHOD", // provenance link (optional)
"body": "snippet body (may include code examples)"
}]
}
}
}]
}
Validation rules: format must be doctrove-index@1; entries must be an array; id/name non-empty and id unique;
aliases must be an array of strings; every version in versions must have a matching volumes volume,
and a volume’s sections must be a valid array of objects.
Invalid indexes are rejected (remote sources report network and degrade to local; local sources report config and exit).
Configuration reference
Precedence: command line > environment variables > config file > defaults.
| Setting | CLI | Environment variable | Config file key | Default |
|---|---|---|---|---|
| Remote index URL | --index-url <url> |
DOCTROVE_INDEX_URL |
indexUrl |
none (offline) |
| Local index path | --local-index <path> |
DOCTROVE_LOCAL_INDEX |
localIndex |
built-in data/index.json
|
| Cache lifetime (s) |
--cache-ttl <sec> / --no-cache
|
DOCTROVE_CACHE_TTL |
cacheTtl |
600 |
| Remote timeout (ms) | --timeout-ms <ms> |
DOCTROVE_TIMEOUT_MS |
timeoutMs |
15000 |
| Debug logging | --debug |
DOCTROVE_DEBUG |
debug |
false |
| Config file | --config <path> |
DOCTROVE_CONFIG |
— | none |
The config file is JSON (example: examples/doctrove.config.example.json). All configuration is read-only:
the plugin performs no writes and persists no local state. Empty-string environment variables count as unset (defaults apply);
cacheTtl: 0 is a valid value (cache disabled).
Testing
node --test # 105 cases: scoring/versions/cache/config/JSON-RPC/engine/e2e/index hosting
Coverage: scoring-ranking boundaries (tier order can never be inverted by popularity), version selection (latest/exact/prefix/prerelease/ build metadata), multi-source merge and degradation self-healing, failure cooldown, cache TTL/LRU, config precedence plus invalid values and empty strings, MCP protocol (uninitialized gate, version negotiation, error folding, conflicting messages), subprocess-level end-to-end (handshake + 3 tools + error paths + graceful exit), index hosting (ETag/304/traversal protection/symlink escape/malformed encodings).
Directory structure
src/
entry.js CLI entry: config → assembly → stdio MCP session
core/ config (layered config), errors (unified error model), version
vault/ttl.js TTL + LRU in-memory cache
catalog/ scoring (scoring/ranking), releases (version selection), store (catalog hub)
supply/provider.js data sources: LocalSource / RemoteSource + index validation
protocol/ jsonrpc / engine (MCP session engine) / transport (stdio line protocol)
tools/ registry (registry + parameter validation), definitions (3 tools)
bridge/ plugin.js (dsh Cordis plugin), client.js (MCP stdio client)
data/index.json built-in offline index (demo data, replaceable)
scripts/serve-index.mjs zero-dependency index hosting script
test/ 105 test cases
License
MIT (see LICENSE).
Frequently Asked QuestionsFAQ
Use the verified command dsh plugin --profile default add github:JohnXu22786/docs-retriever in a DSH-enabled shell. The command resolves the public package metadata and keeps the plugin attached to the catalog identity shown on this page.
Compatibility follows the bundle and profile status shown above. If a profile is not detected, keep the plugin disabled there and check the repository documentation before enabling it in production.
The GitHub link and activity metadata are the source of truth for releases and maintenance. Revisit this page after a new release to confirm the catalog has observed the latest version.