compendio-mcp
Compendio MCP is a local RAG retrieval server that gives AI agents three tools to efficiently find and read project documentation.
docs_overview() — Get a compact map of the documentation corpus: counts by type/module and one line per document, useful for orienting or resolving filenames to indexed paths.
search_docs(query, filters) — Run hybrid lexical+semantic search in natural language, with optional filters for type, module, tags, result count, and including excluded-status docs.
read_doc(path, section?) — Read a single document section (or whole doc if small), with frontmatter; unknown paths return the 3 closest matches instead of failing.
Works fully locally with SQLite + CPU embeddings, no API keys or network calls at query time.
Automatically syncs on server startup and throttled on tool calls, with manual
syncand fullindexrebuild options.Multilingual and diacritic-insensitive; optional strict documentation convention enforcement and status-based exclusion.
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@compendio-mcpsearch docs for configuration options"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
The problem
Your agent doesn't know your documentation. So it does what it can: grep, then cat a 400-line file to answer a question that lived in one paragraph. Three files later the context window is full of noise and the answer is still a guess.
Attaching the whole docs/ folder doesn't fix it — it just moves the waste earlier. Neither does keyword search: nobody writes questions using the exact words the document uses.
Related MCP server: docs-mcp
What Compendio does
Compendio indexes your markdown documentation and gives any AI agent three tools to find and read exactly what it needs.
🔍 Hybrid retrieval, not grep — keyword search finds the exact term, semantic search finds the paraphrase. Compendio runs both and merges the results.
✂️ Token-frugal by design — orient for ~10 tokens per document, search for a handful of fragments, read a single section. Never the whole corpus.
🔒 100% local — one SQLite file, embeddings on CPU, zero network calls at query time. No API keys, no Docker, no services, nothing leaves your machine.
♻️ Stays current — a running server picks up your documentation edits on its own. No watcher process, no manual rebuild loop.
🗣️ Multilingual — index documentation in any language. The embeddings model is multilingual and search is diacritic-insensitive. See Multilingual.
🧩 Zero configuration — works on any folder of
.mdfiles. No required frontmatter, no config file. An optional documentation convention is there if your team already has a taxonomy to enforce.
Requirements
Node.js ≥ 22.12.
Nothing else.
Quick start
1. Install it.
npm install -g compendio-mcpTo update Compendio later, run that same command again — it always pulls the latest published version.
2. Register it as an MCP server in your client, pointed at your project root.
Claude Code (.mcp.json at the repo root or {USER_FOLDER} .claude.json to global install):
{
"mcpServers": {
"compendio": {
"command": "compendio",
"args": ["serve"]
}
}
}Claude Desktop (~/Library/Application Support/Claude/claude_desktop_config.json on macOS, %APPDATA%\Claude\claude_desktop_config.json on Windows — or Settings → Developer → Edit Config):
{
"mcpServers": {
"compendio": {
"command": "compendio",
"args": ["serve"]
}
}
}OpenCode (opencode.json):
{
"mcp": {
"compendio": {
"type": "local",
"command": ["compendio", "serve"],
"enabled": true
}
}
}VS Code / Copilot (.vscode/mcp.json):
{
"servers": {
"compendio": {
"type": "stdio",
"command": "compendio",
"args": ["serve"]
}
}
}Cursor (.cursor/mcp.json):
{
"mcpServers": {
"compendio": {
"command": "compendio",
"args": ["serve"]
}
}
}Codex (.codex/config.toml):
[mcp_servers.compendio]
command = "npx"
args = ["compendio-mcp", "serve"]
enabled = true
startup_timeout_sec = 60Windsurf (~/.codeium/windsurf/mcp_config.json):
{
"mcpServers": {
"compendio": {
"command": "compendio",
"args": ["serve"]
}
}
}Zed (settings.json, or Settings → AI → MCP Servers → Add Custom Server):
{
"context_servers": {
"compendio": {
"command": "compendio",
"args": ["serve"],
"env": {}
}
}
}Cline (MCP Servers icon → Configure → Configure MCP Servers; the CLI reads ~/.cline/mcp.json):
{
"mcpServers": {
"compendio": {
"command": "compendio",
"args": ["serve"]
}
}
}Gemini CLI (.gemini/settings.json in the project, or ~/.gemini/settings.json):
{
"mcpServers": {
"compendio": {
"command": "compendio",
"args": ["serve"]
}
}
}3. Build the index once, from the project root:
compendio indexThat's it. With no config file, Compendio auto-discovers top-level folders that contain Markdown files — no hidden docs/ default needed. Add .compendio/ to your .gitignore.
Why this step exists. The server also indexes on startup, so strictly speaking you could skip it — but the first run downloads and caches the embeddings model (tens of MB), and whoever triggers it waits. Running it here pays that cost in your terminal, with a progress bar, instead of inside your agent's first tool call. From then on everything is offline, and the index keeps itself up to date (below).
Windows note. Some MCP clients can't spawn the
compendio.cmdshim directly. If the server fails to start withENOENT, use"command": "npx"with"args": ["compendio-mcp", "serve"].
Configuration
Entirely optional — Compendio works with no config file at all. Create compendio.config.json at your project root only to override what you need:
{
"docsDir": ["docs"],
"exclude": ["INDEX.md"],
"db": ".compendio/compendio.db",
"embeddings": { "provider": "local", "model": "Xenova/multilingual-e5-small" },
"chunk": { "minTokens": 100, "maxTokens": 480 },
"search": { "k": 5 },
"sync": { "throttleMs": 30000 },
"convention": {
"mode": "loose",
"excludedStatuses": [],
"frontmatterFields": { "type": "type", "module": "module", "status": "status" }
}
}Key | What it's for |
| One or more explicit documentation roots, relative to the project root. Always an array — there is no single-string form. Omit it or set |
| Entries to skip when indexing: an exact path, a bare filename (matched anywhere), or a directory prefix (e.g. |
| Where the SQLite index file is written |
| Default number of fragments returned per search |
| Fragment size bounds, in tokens |
| Minimum time between automatic sync passes, in ms (30000 = 30 s). A floor, not a timer — gates only |
| Optional documentation taxonomy — see below |
Declaring only part of the convention block merges with the defaults field by field; it never wipes the siblings you didn't mention. frontmatterFields maps type/module/status onto non-standard frontmatter keys (e.g. { "status": "estado" } reads a Spanish document's estado: field as status).
Every numeric key (search.k, chunk.minTokens, chunk.maxTokens, sync.throttleMs) is honored only when it is a finite number greater than 0 — search.k must additionally be a whole number. Anything else, including a quoted number like "480", falls back to the default exactly as an absent key would, and the fallback is reported: on stderr for every CLI command, and in docs_overview's response for an MCP client. An unrecognized key under embeddings, chunk or convention.frontmatterFields (a typo such as maxtokens) is reported the same way. A config with nothing wrong reports nothing.
Multiple documentation roots
Declare more than one root to index several folders — adr/, rfcs/, a spec directory — as one searchable corpus:
{ "docsDir": ["docs", "openspec"], "exclude": ["INDEX.md", "openspec/changes/archive"] }Every document path is prefixed with its root's alias — the directory's own name, so docs/x.md and openspec/specs/y.md both read as the real project-relative path. This holds with a single explicit root and with discovered roots too: openspec/specs/y.md, not specs/y.md. search_docs, docs_overview, read_doc and the generated INDEX.md all use this prefixed shape; passing a path back to read_doc exactly as returned always resolves.
Declared roots may not collide: two roots resolving to the same directory, one nested inside another (in either declaration order), or two roots sharing the same directory name (and therefore the same alias) are all rejected before anything is indexed. A root that is declared but cannot be read (a typo, or a folder only some checkouts have) is reported and skipped — the run continues on the remaining roots, and only throws if every declared root fails. Removing a root from docsDir deletes its documents on the next sync pass, same as deleting the files themselves would.
In discovery mode, Compendio rescans top-level folders on every index, sync, and serve sync pass, selects those with .md files anywhere below them, skips symlinked content and generated/internal folders such as .git, .compendio, node_modules, dist, build, and coverage, and writes INDEX.md at the project root. Discovery fails closed: malformed top-level config JSON, unreadable candidate trees, traversal/read failures, or a previously indexed discovered root that disappears or becomes a symlink/junction before sync abort before mutating the index. A previously indexed root that is still a readable directory is still traversed even after its last Markdown file is deleted, so legitimate deletions are reconciled normally. The symlink checks use lstat/realpath at scan/traversal time, but they are not a kernel-level sandbox; a filesystem race between check and read remains out of scope. --dir <path> (below) is explicit mode: it replaces the whole declared/discovered root set with that one directory and writes INDEX.md inside it.
Works with your SDD framework
Spec-driven development frameworks keep their planning artifacts in Markdown, which is exactly what Compendio indexes. Point docsDir at the folders your framework writes to:
Framework | Config | Indexes |
| Feature specs, plans and tasks under | |
|
| |
|
| |
| PRD, architecture, sharded epics and stories | |
Framework + your own docs |
| Both, as one searchable corpus |
Hidden directories such as .specify/ and .kiro/ are indexed normally, both as declared roots and in discovery mode — dot-prefixed entries inside a root are what gets skipped, not the root itself. So with no config file at all, a Spec Kit or Kiro project already indexes.
Three things worth knowing before you copy a line:
A root's alias is its directory name, not the path you declared.
.kiro/specsis aliasedspecs, so its documents come back asspecs/auth/design.md. That also means it collides with a top-levelspecs/root and cannot be combined with Spec Kit's — declare.kiroinstead, which is what the table does.BMAD's output folder is configurable.
docsis the default; BMAD v6 readsoutput_folderfrom its own config, so declare whatever yours is set to.Templates are noise.
.specify/templates/holds placeholder scaffolding, not project knowledge. Add"exclude": [".specify/templates", ".specify/scripts"]if you would rather they stayed out of search results.
Documentation convention (optional)
Two modes, selected by convention.mode:
loose(default, zero-config) — never rejects a file for missing metadata. The title comes from the first H1 (falling back to a humanized filename), the module is inferred from the folder, andtype/statusare read from frontmatter when present and left absent otherwise.strict(opt-in) — a linter: every document needs an H1 and non-emptytype/module/status, validated against the lists your project declares. Files that fail are skipped and reported, never breaking the run.
{
"convention": {
"mode": "strict",
"types": ["functional", "adr", "api", "qa", "guide"],
"statuses": ["draft", "current", "deprecated"],
"excludedStatuses": ["draft", "deprecated"]
}
}excludedStatuses hides documents from search by lifecycle state — drafts and deprecated pages stop polluting results. See docs/documentation-convention.md for the full convention this repository's own docs follow.
MCP tools
Designed as progressive disclosure: orient cheaply → search cheaply → read only what is needed.
1. docs_overview() — the corpus map. Counts by type and module, plus one line per document. Roughly 10 tokens per document.
2. search_docs({ query, type?, module?, tags?, k?, include_excluded? }) — the top k fragments (5 by default, at most 2 per document), each with path, section, excerpt and score. type is an open, project-defined string, not a fixed list.
3. read_doc({ path, section? }) — one section, or the whole document. A large document with sections (above ~6,000 estimated tokens) returns a compact outline of its H2/H3 headings instead of the body, so the agent reads only the sections it needs. A path that doesn't exist returns the 3 most similar paths instead of an error, so the agent self-corrects instead of retrying blind.
CLI
Command | What it does |
| Starts the MCP server over stdio |
| Full rebuild of the index |
| Runs one incremental sync pass from the terminal — syncs only the documents whose content changed, with live progress. See Incremental sync |
| Hybrid search with filters: |
| Map of the indexed corpus |
| Generates or updates one combined |
| Measures retrieval quality against a goldenset |
Global option -C, --root <dir>: project root. Add --lexical to index, sync or search to skip embeddings entirely. --dir <path> on index/index-md replaces the configured docsDir with that one directory — it does not add to it, and the index it produces still has the prefixed path shape (<dirname>/x.md). sync has no --dir: under an incremental pass, dropping a root this way would delete its documents rather than merely skip them (see compendio sync --help).
How it works
docs/**/*.md
│
├─▶ split into fragments at heading boundaries, then bounded to maxTokens
│
├─▶ index each fragment twice ─┬─ full-text (keywords)
│ └─ embeddings (meaning)
│
└─▶ one file: .compendio/compendio.dbAt query time both indexes are searched independently and their rankings are merged with Reciprocal Rank Fusion — a rank-based merge with no weights to tune blindly. The agent gets back the smallest set of relevant fragments.
Compendio is the retrieval half of RAG. It never calls an LLM and generates nothing: it finds the right paragraphs and gets out of the way.
If the embeddings model is unavailable, Compendio doesn't crash — it degrades to keyword-only search and says so in its responses.
Incremental sync
Documentation changes while you work, and Compendio keeps up on its own — or on request. There are four ways the index gets refreshed:
Trigger | What happens |
Server startup ( | One incremental sync pass, started before the transport connects. The first tool call waits for it, so nothing is ever answered against a cold index |
Any MCP tool call ( | One incremental sync pass — but only if 30 s have elapsed since the last one ( |
| One incremental sync pass, run manually from the terminal, with live progress. |
| Full rebuild from scratch: the index is dropped and recreated |
It is not a timer. There is no background interval and no file watcher. Syncing is driven by your agent's tool calls, or by you running compendio sync, and the throttle is a floor between the two automatic triggers, not a schedule: a server nobody is querying does not sync, and a burst of ten calls in one second still triggers at most one pass. Concurrent calls join the pass already running instead of starting a second one.
Each incremental sync pass compares content hashes against what's already indexed, so only new, changed and deleted documents do any work — an unchanged corpus costs nothing. Inside serve, if a pass fails it's logged to stderr and the tool still answers against the current index; compendio sync has no such fallback, so the same failure exits the process non-zero.
When you need the full rebuild. compendio index is the only command that reindexes: it drops and recreates the whole database, and it is the authoritative one. Reach for it after a large restructuring, if you suspect the index has drifted, or — the case that surprises people — after changing chunk.minTokens/chunk.maxTokens. An incremental sync pass, whether automatic or run manually via compendio sync, fingerprints a document by its content hash alone, so a document you haven't edited keeps its old fragment boundaries no matter what the config now says. Only a full reindex (compendio index) applies new chunking to unchanged files — see compendio sync --help for the same caveat at the point you're most likely to need it.
Multilingual
Write your documentation in whatever language your team works in. Compendio doesn't care:
The contract is English, the corpus doesn't have to be. Tool parameters (
path,type,module,tags,section), response fields and tool descriptions are English, so any agent reads them without friction. That is independent of what language your documents are written in: frontmatter keys are stripped before indexing, and the FTS5 tokenizer carries no language-specific stemmer.Non-English frontmatter keys map back. If your documents use
estado:instead ofstatus:,convention.frontmatterFieldstranslates them.Accents are handled properly. Search is diacritic-insensitive, so validación and validacion match. Accent-sensitive search silently loses results.
The embeddings model is multilingual (
Xenova/multilingual-e5-small), so single-language and mixed-language corpora index and retrieve alike.
The reference corpus and evaluation set shipped in ejemplos/ are Spanish — deliberately, as proof that an English codebase and tool contract retrieve non-English documentation without loss.
How much does semantics add over grep?
Measured with compendio eval on the example corpus (ejemplos/: 11 documents, 29 chunks, no config file — the zero-config path itself) and its goldenset of 22 real questions:
mode | recall@5 | MRR | failures |
hybrid | 1.00 | 0.943 | 0 |
keyword-only | 0.95 | 0.856 | 1 |
Keyword search is already strong when the question uses the corpus terminology.
The gap opens on paraphrases and synonyms: «¿Qué endpoint hay que llamar para crear un lead?» falls out of the top 5 without embeddings, and the semantic leg recovers it. Questions with zero word overlap with the matching document are solved only by semantics.
Speed: with the model warm, hybrid search answers in 5–20 ms.
compendio eval reproduces this table at any time — it's also the instrument for tuning chunking and k without guessing.
Architecture
Hexagonal: the core knows nothing about SQLite, transformers.js, or the filesystem.
src/
├── domain/ # pure, no dependencies: model, chunking, ranking, convention policy
├── application/ # use cases
├── infrastructure/ # adapters: SQLite, markdown parsing, filesystem, embeddings
├── composition.ts # composition root — start here to see the whole app
├── cli.ts # input adapter: commander
└── server.ts # input adapter: MCP server (stdio)Every external dependency sits behind a port in src/domain/ports.ts. Swapping the vector store or the embeddings provider is a local change in one adapter, not a rewrite.
Development
npm install
npm run build # compiles to dist/
npm test # vitest: domain, adapters and integration
npm run typecheck # tsc --noEmit
npm run dev -- ... # CLI without compiling (tsx)Integration tests use a deterministic embeddings provider (no downloads) against the real ejemplos/ corpus.
Try the CLI against the bundled example corpus without installing the package:
node dist/cli.js --root ejemplos index
node dist/cli.js --root ejemplos search "¿cuándo se considera duplicado un lead?"This repository ships a .mcp.json that serves the ejemplos/ corpus, so you can try the tools from Claude Code with zero configuration.
License
MIT © Raúl García Barciela
Available Tools
3 toolsdocs_overviewDocumentation mapA
Map of the documentation corpus: counts by type and module, plus one line per document ([type] path — summary (status)). Use it to enumerate what exists, or to pick filter values for search_docs. When a user names a .md file but its exact indexed path is uncertain, use this to find that path before calling read_doc. For a specific question, call search_docs first — it answers in one call, while orienting here lists the whole corpus before you can read anything.
| Name | Required | Description | Default |
|---|---|---|---|
No parameters | |||
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
The description discloses that the tool returns counts and lines with a specific format, but it does not explicitly state whether the tool is read-only or has no side effects. Since no annotations are provided, the description carries the full burden for behavioral transparency, and this aspect is not fully addressed.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is concise and well-structured, presenting the tool's purpose, its output format, and use cases in a clear and efficient manner without unnecessary verbosity.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Given the simplicity of the tool (no parameters, no output schema), the description provides all necessary context: what the tool does, what it returns, when to use it, and how it relates to the sibling tools. No additional information is needed.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
The tool has zero parameters, so the schema requires no explanation. According to the rule, a baseline of 4 is appropriate when there are no parameters to describe.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description clearly states that the tool provides a map of the documentation corpus, including counts by type and module and one line per document. It explicitly distinguishes itself from sibling tools by explaining that it is used to enumerate what exists, pick filter values for search_docs, or find a file path before calling read_doc.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description provides explicit guidance on when to use this tool versus the alternatives: use it to enumerate what exists, pick filter values for search_docs, or find a path before read_doc, while directing specific questions to search_docs first.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
read_docRead a documentA
Reads one section of a document, along with its frontmatter. Built for sections, not whole files: pass section whenever you can. Omitting it returns the entire document when it is small or cannot usefully be split into sections; a large document with sections returns its outline instead (H2 and H3 headings, each with an estimated token size), so call again with one of those headings as section. To read or summarize a whole document, start from its outline and request the sections you need. When a user names a section in a .md document, pass that named section here after locating the indexed path with docs_overview if necessary. If the path does not exist, it responds with the 3 closest matching paths instead of failing.
| Name | Required | Description | Default |
|---|---|---|---|
| path | Yes | Document path, relative to the docs directory | |
| section | No | Heading (or part of it) of the section to read, e.g. 'Business rules', or a heading from read_doc's outline. Use the section field of a search_docs result. Sections name a region of a document, not a single fragment: a large section returns all of its parts joined. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
The description discloses key behaviors beyond the schema: it returns an outline for large documents, estimates token sizes, returns the 3 closest matching paths on failure, and joins all parts of a large section. Since no annotations are provided, the description carries the full burden and does so well, though it doesn't mention any side effects or permissions (which are likely irrelevant for a read operation).
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is dense but well-organized, front-loading the core behavior and then covering edge cases. It's longer than strictly necessary, but every sentence adds operational value—no filler or repetition of schema details.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a read tool with no output schema and no annotations, the description covers all essential context: what it returns, how to handle large documents, how to use it with sibling tools, and failure behavior. An agent has enough information to call it correctly in all documented scenarios.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100%, so the schema already documents both parameters. The description adds value by explaining the section parameter's role in the outline workflow and how it relates to search_docs results, and clarifies that sections name a region, not a single fragment. This goes beyond the schema's basic descriptions.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description clearly states the tool reads one section of a document along with its frontmatter, and explicitly distinguishes it from whole-file reading. It names the sibling tools (docs_overview, search_docs) and explains when to use them, making the purpose unambiguous.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description provides explicit guidance: pass section whenever possible, omit it for small documents, and use the outline for large documents. It also explains how to handle user-named sections in .md files and what to do if the path doesn't exist, covering both normal and edge-case usage.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
search_docsDocumentation searchA
Hybrid search (lexical BM25 + semantic) in natural language over the project's documentation, with metadata filters. Entry point for any question about what the project does or why — behaviour, business rules, the exact text of a user-facing message, limits, endpoints, deployment steps, or the reasoning behind a decision. For a content or project question, this is the cheapest first probe; a filename alone does not reliably identify an indexed path, so resolve named .md files with docs_overview before calling read_doc. Source code remains the authority on current behaviour, while these docs are the only record of intent. The top result carries a full-length excerpt, centred on the part of the document that matched, which usually answers outright; the rest carry short ones, centred on their own match, enough to tell whether the top result is the right one. Each result has path, title, section, excerpt and score; section names the document region the fragment came from — a document with no headings reports one region for the whole file. A '…' at either end of an excerpt marks content omitted there — that is the signal to call read_doc with its path and section. If the project declares convention.excludedStatuses, documents in those statuses are left out unless include_excluded is set; if it declares none, no document is excluded by status.
| Name | Required | Description | Default |
|---|---|---|---|
| k | No | Number of results (5 by default) | |
| tags | No | Filter by tags (matching one is enough) — same caveat: omit unless docs_overview showed them. | |
| type | No | Filter by document type — project-defined, and absent entirely in many projects. Omit it unless docs_overview showed you the value; never infer it from directory names or paths. | |
| query | Yes | Natural-language query | |
| module | No | Filter by module — same caveat as type: omit unless docs_overview showed the value. | |
| include_excluded | No | Include documents whose status is listed in convention.excludedStatuses (no effect if the project declares no exclusions) |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations provided, the description carries the full burden and does so thoroughly: it explains the hybrid search method, metadata filtering, excluded status behavior, the shape of each result, excerpt-length differences, and the meaning of the '…' marker as a signal to call read_doc.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is longer than average but well structured and front-loaded with purpose. Some ideas are repeated or phrased redundantly, but every sentence carries useful operational guidance, so the verbosity is justified.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Despite having no output schema, the description fully explains the result structure, excerpt behavior, filtering semantics, and the relationship to docs_overview and read_doc. This gives an agent enough context to use the tool correctly without additional documentation.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
The schema already covers all parameters with descriptions, so the baseline is high. The description adds valuable caveats beyond the schema, such as omitting tags/type/module unless docs_overview showed valid values, never inferring type from directory names, and the no-effect behavior of include_excluded when no exclusions are declared.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description clearly states that this is a hybrid lexical+semantic natural-language search over project documentation, and frames it as the entry point for project content questions. It also distinguishes itself from siblings by telling the agent to use docs_overview first for named files and read_doc for full excerpts.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
It gives explicit when-to-use guidance: it is the cheapest first probe for content/project questions, and it instructs resolving named .md files with docs_overview before read_doc. It also explains when to call read_doc based on excerpt truncation, and how excludedStatuses affects results.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
1 tool update
v1.5.1- Changed
read_doc1 field changed- changed
Input schema / properties / section / descriptionPrevious value: -"Heading (or part of it) of the section to read, e.g. 'Business rules'. Use the section field of a search_docs result. Sections name a region of a document, not a single fragment: a large section returns all of its parts joined."New value: +"Heading (or part of it) of the section to read, e.g. 'Business rules', or a heading from read_doc's outline. Use the section field of a search_docs result. Sections name a region of a document, not a single fragment: a large section returns all of its parts joined."
3 tool updates
v0.1.0- First observed
docs_overview - First observed
read_doc - First observed
search_docs
TDQS
Scored across 3 tools
Each tool has a distinct role: search_docs for finding relevant content, docs_overview for enumerating the corpus, and read_doc for retrieving specific sections. The descriptions explicitly cross-reference when to use one over the other, leaving no real ambiguity.
read_doc and search_docs follow a clear verb_noun pattern, while docs_overview is a noun-phrase exception. All names are lowercase snake_case, so the set is still readable and mostly predictable.
Three tools is an ideal size for a documentation-corpus server: enumerate, search, and read cover the full exploration workflow without redundancy. Every tool earns its place.
The read-side lifecycle is fully covered: search to locate, overview to enumerate, and read to retrieve content. There are no obvious gaps for a read-only documentation server.
Maintenance
Related MCP Connectors
Versioned documentation registry and semantic search for AI tools and coding assistants.
Open-source Obsidian for MDX - edit local docs with agent assistance
Provide your AI coding tools with token-efficient access to up-to-date technical documentation for…
Personal context for every AI: search, read, and write back to your private Markdown library.
Related MCP Servers
- AlicenseNot gradedqualityBmaintenanceEnables semantic search over local Markdown documentation using hybrid retrieval combining embeddings, keyword search, and graph traversal with automatic file watching and zero-configuration setup.2MIT
- AlicenseNot gradedqualityDmaintenanceGives AI agents full-text search over any Markdown/MDX documentation folder.9 npmMIT
- AlicenseNot gradedqualityBmaintenanceProvides local technical documentation with hybrid search (semantic + BM25) for AI agents, ensuring access to up-to-date framework docs.MIT
- AlicenseAqualityBmaintenanceIndexes local documents (PDF, Word, Markdown, text) into a SQLite database for AI agents to search and retrieve bounded, source-located passages. Runs fully locally with optional OCR, preserving privacy.5MIT