RAGDown
RAGDown is a local MCP server that indexes folders of Markdown notes into a hybrid LanceDB index and exposes tools for agents to search, read, write, and manage those notes.
Search notes (
ragdown_recall): hybrid semantic + keyword search over indexed sections, returning path, line range, heading breadcrumb, tags, similarity, and last-change date; filters bytag,path_prefix,top_k,written_by,format,max_chars.Inject context per turn (
ragdown_context): hook-oriented tool that returns related sections as a<ragdown-context>block, filtered bymin_score/min_ratio, skipping short prompts and slash commands, and never repeating a section for the samesession_id(untilreset).Read notes (
ragdown_read_doc): read a file whole or by line range straight from disk, or resolve a wikilink target (Note,Note#Heading,sub/Note, alias); returns the file hash used for safe edits.Find backlinks (
ragdown_backlinks): documents linking to a note via wikilink, alias, or relative Markdown link, with the linking lines.Browse notes (
ragdown_list): list documents with title, tags, description, last change, provenance, and superseded status; sort by path or recency.Check index status (
ragdown_stats): folder, index size, embedder, primary/reader role, sync state, and optionally every indexed file.Write new notes (
ragdown_remember): save a new Markdown note with frontmatter, tags, session provenance, andsupersedespaths; indexed immediately and never overwrites.Edit or create notes (
ragdown_edit): replace a whole file (requiresbase_hashto avoid clobbering), or append text at the end of a file or a specific heading; also creates new files.Reindex (
ragdown_reindex): sync the index now, orfull: trueto re-embed everything.Operate on Markdown folders: each top-level folder is its own MCP server (
/mcp/<folder>, subfolders narrow scope), off by default until MCP is enabled, and each folder opens as an Obsidian vault with tags, aliases, wikilinks, and backlinks.Run locally with safety controls: local ONNX embedding models (or OpenAI-compatible endpoints), file watching for automatic sync, and
RAGDOWN_READ_ONLYto hide write tools.
Serves as the document format the server indexes: it walks a folder of Markdown files recursively (.md, .markdown and .mdx), splits each file into sections at every heading, embeds each section with its heading breadcrumb, and keeps a local vector plus full-text index in sync as the files change. Agents can then run hybrid searches over the notes and read whole files or line ranges straight from disk, with each hit returning the path, line range and heading breadcrumb.
Supported alongside Markdown as an indexed file type — .mdx documents in the configured folder are walked, chunked by heading and embedded like any other note, so MDX content becomes searchable by agents.
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., "@RAGDownsearch my notes for how to restore the database"
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.
mcp-ragdown
Website: cubicecho.github.io/mcp-ragdown: an overview, a walkthrough and a condensed reference.
An MCP server over folders of Markdown files. Point it at a directory and it embeds every section
into a local LanceDB index and keeps that index in sync as files change. Each top-level folder is
its own MCP server, off until you turn it on, and each one opens as an Obsidian vault. Agents get
search tools, and a hook that calls ragdown_context before each turn adds related documents to the
prompt. The tool layout follows mcp-zeromem, but the memory here is your Markdown files, not
conversation turns.
Everything an agent or a hook does goes through MCP. There is no hook command and no hook HTTP route; the only other routes feed the web UI.
Everything runs locally: the default embedder is granite-embedding-small-english-r2 on ONNX
Runtime. The Docker image has the model baked in; outside Docker it is downloaded once (about
50 MB) on first start. Two others are a setting away — see
Choosing an embedder.
Quick start
docker-compose.yml:
services:
ragdown:
image: vantreeseba/mcp-ragdown:latest
ports:
- '3300:3000'
volumes:
- ~/notes:/docs # one folder per directory in here; add :ro and RAGDOWN_READ_ONLY=true to forbid writes
- ragdown-data:/data # the index, so restarts only diff
environment:
RAGDOWN_TOKEN: ${RAGDOWN_TOKEN}
restart: unless-stopped
volumes:
ragdown-data:export RAGDOWN_TOKEN=$(openssl rand -hex 32) # keep it; clients need it too
docker compose up -d
curl localhost:3300/api/status # "ready": true, and the file and chunk counts~/notes/work is now the folder work. It starts human-only: open http://localhost:3300, turn
MCP on for it under Settings → Folders (or put { "mcp": true } in ~/notes/work/.ragdown.json),
and Copy MCP config there gives you this line:
claude mcp add --transport http ragdown-work http://localhost:3300/mcp/work \
--header "Authorization: Bearer $RAGDOWN_TOKEN"The first index of a large folder takes minutes; searches answer from what is indexed so far. The
web UI lists each folder's files, renders them with their wikilinks and images, and searches them.
To add documents to every turn automatically, see Hooks in min-agent. Images
are published for linux/amd64 and linux/arm64 to Docker Hub and ghcr.io/cubicecho/mcp-ragdown.
Without Docker
npm install
RAGDOWN_DOCS_DIR=~/notes node src/cli.ts stdio # what an MCP client launches
RAGDOWN_DOCS_DIR=~/notes SECURE_LOCAL_NET=true node src/cli.ts serve # HTTP and the web UI on :3000Requires Node 26+, which runs the TypeScript directly.
Related MCP server: Knowledge MCP Server
Claude Code setup
Against the container, the claude mcp add --transport http line above. As a local process:
claude mcp add ragdown -e RAGDOWN_DOCS_DIR=$HOME/notes/work -- node /path/to/mcp-ragdown/src/cli.ts stdiostdio serves one folder: RAGDOWN_DOCS_DIR itself, whatever its .ragdown.json says (you chose
it by launching it). Point it at an Obsidian vault and the whole vault is the folder.
Claude calls ragdown_recall and ragdown_read_doc itself when the documents might help; the server's
instructions tell it to.
For related documents on every prompt without Claude asking, Claude Code's mcp_tool hooks call
ragdown_context on the server you added. In .claude/settings.json (or ~/.claude/settings.json):
{
"hooks": {
"UserPromptSubmit": [
{ "hooks": [ { "type": "mcp_tool", "server": "ragdown", "tool": "ragdown_context",
"input": { "prompt": "${prompt}", "session_id": "claude:${session_id}", "format": "claude-code" } } ] }
],
"SessionStart": [
{ "matcher": "compact", "hooks": [ { "type": "mcp_tool", "server": "ragdown", "tool": "ragdown_context",
"input": { "prompt": "", "session_id": "claude:${session_id}", "reset": true } } ] }
]
}
}serveris the name you gaveclaude mcp add.format: claude-codeis needed: Claude Code (2.1.292, where this was tried) calls the tool either way but only passes the result on to the model as a hook'sadditionalContext.The
SessionStarthook is for compaction, which keeps the session id and drops the sections Claude was given:resetlets them be sent again. Claude Code skipsmcp_toolhooks on theSessionStartat launch, which needs no reset.Claude Code caps a hook's output at 10,000 characters;
RAGDOWN_HOOK_MAX_CHARSdefaults to 6,000.
Folders
serve treats every top-level directory of RAGDOWN_DOCS_DIR as a folder, found as it
appears; the directories inside one are subfolders. All folders share one index, one model and
one watcher, but each is its own MCP server at /mcp/<folder>, named ragdown-<folder>, and sees
only its own documents: paths in and out are relative to it (backups.md, not work/backups.md), and
ragdown_read_doc refuses one that leaves it.
A folder's settings live in .ragdown.json at its root, and the web UI edits them:
{ "title": "Work notes", "mcp": true, "hook": { "top_k": 6, "min_score": 0.75 } }titleis shown in the web UI and the server's instructions; it defaults to the directory name.mcpdefaults to false. A folder is human-only until someone turns it on: indexed and searchable in the web UI, but no endpoint, no recall hits and no hook context./mcp/<folder>answers a human-only folder with the same 404 as a missing one.hookholds the folder's ownragdown_contextdefaults: any oftop_k,min_score,min_ratioandmax_chars. One it leaves out is the server'sRAGDOWN_HOOK_*value, and an argument on the call still wins over both. It is read on each call, so an edit applies without a restart.
There is no endpoint over every folder: bare /mcp is a 404 that says to pick one. Markdown loose
in RAGDOWN_DOCS_DIR, outside every folder, is not indexed; the log and the web UI list it.
The web UI creates, edits, renames and deletes folders, and Copy MCP config on each one gives the
claude mcp add line and the JSON mcpServers entry for it. Under RAGDOWN_READ_ONLY a folder's
title, MCP switch and search defaults can still change — they are settings, not documents — but
creating, renaming and deleting cannot.
New document (the + beside Upload in a folder's list, or the button in an empty folder) asks
for a title and an optional subfolder, created if missing. It starts beside the open document, writes
# <title> to <subfolder>/<title>.md, and opens it in the editor. A name that is already taken
offers to open that document instead.
Edit on an open document swaps the preview for a Markdown source editor (CodeMirror, loaded on
first use), with a Write/Preview switch and Ctrl/Cmd+S to save. It edits the source, not rich
text, so wikilinks, embeds and front matter come back exactly as written. A save is made against
the version that was opened: if the file changed on disk meanwhile — in Obsidian, by an agent, by
git pull — the editor says so and asks whether to save over it or discard the edit, rather than
quietly losing either. Leaving with unsaved changes asks first.
A folder keeps an agent focused, not out: one token opens every folder with MCP on.
Subfolders
/mcp/<folder>/<subfolder...> narrows a folder's server to a subfolder, following the folder's MCP
setting:
claude mcp add --transport http work-alpha http://localhost:3300/mcp/work/projects/alpha \
--header "Authorization: Bearer $RAGDOWN_TOKEN"ragdown_recallandragdown_contextsearch only files under it, andpath_prefixnarrows further inside it. Paths are relative to the subfolder.ragdown_rememberwrites into the subfolder itself; on a folder's own endpoint it writes into<folder>/<RAGDOWN_NOTES_DIR>.ragdown_statscounts the subfolder's files and chunks, andragdown_context's per-session memory is kept separately for each endpoint.ragdown_reindexstill syncs everything.
A missing subfolder, a file, a symlink or a dot-folder is a 404.
Obsidian
Open a folder as an Obsidian vault and both see the same documents:
.obsidian/,.trash/and every other dot-folder are skipped, as is.ragdown.json.Tags from frontmatter (
tags: [a, b]ortags: a, b) and inline#tagsin the text (not in code) are indexed per document.ragdown_recall'stagfilter matches a tag and the tags nested under it:projectfinds#project/alpha. Every hit lists its document's tags.Aliases from frontmatter are found by keyword search and by wikilinks.
Wikilinks.
ragdown_read_doctakes a link target as well as a path —Note,Note#Heading,sub/Note, an alias — and resolves it the way Obsidian does, within the folder: an exact path, then a document whose name matches (the one beside the linking document, then the shortest path), then an alias. A heading narrows the text to that section. The web UI follows[[links]], shows![[embeds]]and images, lists the documents that link to the open one, completes[[in the editor with the folder's documents, and rewrites the links to a document when it is renamed or moved.
Tags and aliases get their own columns; they are not added to the text that is embedded, which was measured and made retrieval worse.
Hooks in min-agent
A min-agent hook calls a tool on a connected MCP server, so ragdown needs nothing beyond its MCP endpoint. Add a server under Settings → MCP:
{
"id": "ragdown",
"label": "Notes",
"transport": "http",
"url": "http://localhost:3300/mcp/work", // a folder with MCP on
// Stored as-is: min-agent does not expand env vars. Leave empty with SECURE_LOCAL_NET=true.
"headers": { "Authorization": "Bearer <RAGDOWN_TOKEN>" },
// The model has ragdown_recall; the context tool is for the hook only.
"hiddenTools": ["ragdown_context"],
"hooks": [
{ "id": "notes", "on": "beforeTurn", "tool": "ragdown_context", "inject": true, "maxTokens": 800,
"args": { "prompt": "{{prompt}}", "session_id": "min-agent:{{session.id}}", "max_chars": 3000 } }
]
}Before each turn the hook sends the user's message. ragdown_context answers with the sections
whose cosine similarity reaches RAGDOWN_HOOK_MIN_SCORE, wrapped in <ragdown-context>, or with
empty text, which min-agent treats as nothing to add. It skips:
prompts under 12 characters and slash commands;
sections it already returned for the same
session_id, so a long chat pays for each document once.
max_chars of 3,000 keeps the block near min-agent's 800-token cap, so a section is not cut in the
middle. A hook that fails or takes longer than min-agent's 3 seconds only loses that turn's documents.
Tools
Tool | What it does |
| For hooks: the sections related to a |
| Hybrid search. Returns path, line range, heading breadcrumb, tags, similarity and the day the file last changed for each hit, and says when an agent wrote the document. Takes |
| Reads a file, or a line range of one, straight from disk. Never clipped. Also takes a wikilink target ( |
| The documents that link to a |
| Browses rather than searches. Returns a count, then one line per document: its path, title, tags, the day it last changed, the tool and session that wrote it if an agent did, what supersedes it, and its frontmatter |
| Folder, index size, embedder, role (primary or reader), whether a sync is running, and the last sync. |
| Writes a new document (with frontmatter) under |
| Rewrites a document at a |
| Changes part of a document at a |
| Renames or moves a document, or a subfolder with everything in it, from |
| Deletes a document or a subfolder at |
| Syncs now; |
The write tools (ragdown_remember, ragdown_write, ragdown_edit, ragdown_move,
ragdown_delete, ragdown_reindex) are not listed when RAGDOWN_READ_ONLY=true.
Every tool an agent is offered costs it context on every turn, used or not: the twelve descriptions
together are about 15,000 characters. RAGDOWN_DISABLED_TOOLS leaves out the ones you name.
ragdown_context,ragdown_stats,ragdown_reindex is a fair set to drop for an agent that only reads
and writes documents: the first is for hooks, and the other two are for whoever runs the server.
Keep ragdown_context on a server a hook calls.
Configuration
Only RAGDOWN_DOCS_DIR is required. See .env.example.
Variable | Default | |
| — | For |
|
| The index. Deleting it only costs a rebuild. |
|
| Model cache, shared by every folder. |
|
| A local model — |
| OpenAI | For |
| half the cores | ONNX Runtime threads for the local model. |
|
| Watch the folder; without a watcher, sync on start and on |
|
| Hide the write tools, and refuse uploads, deletes and folder changes other than settings. |
| none | Tools to leave out, by name and comma-separated, such as |
|
| Where |
|
| Characters per hit in text output. Every cut names the |
|
|
|
| the embedder's own |
|
|
|
|
|
|
|
|
|
|
|
|
|
| — |
|
|
|
|
Under serve, the embedder, watching, the characters per hit and the four ragdown_context
defaults can also be changed while the server runs, under Settings → Server in the web UI. What
is saved there is kept in .ragdown-server.json in the docs directory and wins over the variable of
the same setting, at once and after every restart, until it is cleared there; the variable is the
value it goes back to. A new embedder is loaded first (so one that cannot load changes nothing),
then the index is rebuilt with it in place. With no minimum score saved or set, the floor follows
whichever embedder is in use. The docs directory, read-only and the token stay variables only, and
stdio reads the variables alone.
Commands and HTTP
node src/cli.ts <command>: stdio (the MCP server a client launches) or serve (HTTP, what the
image runs). Searching, indexing and stats are MCP tools, not commands.
serve exposes these routes:
Route | Auth | |
| none | Liveness and index stats. |
| bearer | Streamable HTTP MCP, stateless, for a folder with MCP on. 404 otherwise, and for bare |
| bearer | Each folder's |
| bearer | Create a folder: JSON |
| bearer | JSON |
| bearer | Delete a folder and everything in it. 400 unless |
| bearer | JSON |
| bearer | Delete one of the |
| bearer | The indexed files, of one folder or all: |
| bearer | One indexed file's text, read from disk, with its tags, aliases and |
| bearer | Upload a file: JSON |
| bearer | Delete a Markdown file. 404 when it is not there. |
| bearer | Hybrid search in one folder, human-only ones included. |
| bearer | A wikilink target, resolved from the document |
| bearer |
|
| bearer | The documents in the same folder that link to |
| bearer | Any file inside a folder — an image, a PDF — as raw bytes, sandboxed and |
| none | The web UI from |
Every path in /api includes the folder: work/notes/a.md. Writes answer after the index has
synced, so the next GET /api/docs already shows them, and uploads, deletes, and creating,
renaming or deleting a folder, and changing the server settings, are a 403 under RAGDOWN_READ_ONLY. They are for the web UI; agents
write with ragdown_remember, ragdown_write and ragdown_edit.
The web UI asks for the token once and keeps it in the browser's local storage. Its static files
hold no documents, so they need none; everything it shows comes from the bearer routes. It is built by
npm run build (the image does this) and only serve serves it.
Upgrading from 4.x
5.0 splits the docs directory into folders, and nothing is migrated for you:
Move loose Markdown into a folder. Files directly in
RAGDOWN_DOCS_DIRare no longer indexed byserve; the log and the web UI list them.Turn MCP on for each folder agents should reach, under Settings → Folders or with
"mcp": truein its.ragdown.json. Every folder starts human-only.Point clients at
/mcp/<folder>./mcpno longer serves anything. A 4.x scope URL/mcp/<folder>/<sub>keeps working once<folder>has MCP on.RAGDOWN_NOTES_DIRis relative to each folder:notesnow means<folder>/notes.
stdio is unchanged in use: it serves RAGDOWN_DOCS_DIR as one folder. Both modes rebuild their
index once on first start, for the new tag and alias columns.
Design
Chunks follow headings. Every heading starts a section, and the section's breadcrumb
(Backups › Restore) is part of what gets embedded. That is how a paragraph that only says "run it
twice" is found by a question about restoring backups. A section longer than 1,500 characters
(about 400 tokens, inside the window of every embedder here) is packed paragraph by paragraph. Fenced code blocks are
never split. Line numbers refer to the original file, so a hit is always one ragdown_read_doc
call away from its surroundings.
Hybrid retrieval. Dense cosine search finds a paragraph that answers the question in different words. BM25 full-text search finds an exact error string or flag name that an embedding blurs. Both read the chunk with its breadcrumb in front, so a table of settings is still findable by the name of the service its heading names and never repeats. Indexing the body alone cost 22 points of top-1 recall on a benchmark of 210 questions, and left the fused ranking below dense search on its own. Both return a pool, and reciprocal rank fusion (k = 60) merges them. Ranking uses the fused score. Filtering uses cosine similarity, because a fused score is not comparable across queries.
Choosing an embedder. RAGDOWN_EMBEDDER picks one of three local models. They were measured on
the same benchmark — 30 generated runbooks, 210 questions with a known answering section, identical
chunks throughout — so only the model differs. Top-1 is how often the right section ranked first;
the timings are one 8-thread laptop CPU indexing that corpus and embedding one query.
| Model | Dim | Top-1 | Recall@5 | Index | Per query |
|
| granite-embedding-small-english-r2 | 384 | 85.7% | 100% | 3.6 s | 9 ms |
|
| bge-small-en-v1.5 | 384 | 82.9% | 100% | 3.5 s | 9 ms |
|
| embeddinggemma-300m | 768 | 91.0% | 100% | 33 s | 260 ms |
|
granite-small is the default because it costs what bge-small costs — same dimensions, same
index, 9 ms a query — and was ahead of it on every measure here, though on 210 questions that gap
alone is not significant (p = 0.41). embeddinggemma is a real jump and a significant one
(p < 0.001), but 260 ms is paid on every search, and a hook searches every turn. Being the default
is also why granite-small is the model baked into the Docker image; the other two download on
first start. gte-small, snowflake-arctic-embed-s, mxbai-embed-xsmall, bge-base, granite's 149M
model and arctic-embed-m were measured too and beat the default on nothing — bigger was not better.
Changing the model rebuilds the index, and RAGDOWN_HOOK_MIN_SCORE moves with it. Cosine
is on each model's own scale, not a shared one. On the same corpus, the weakest on-topic question
and the strongest unrelated prompt ("weather in Paris", "a recipe for carbonara") scored:
on-topic (10th percentile) | unrelated (worst case) | |
| 0.86 | 0.75 |
| 0.70 | 0.60 |
| 0.68 | 0.53 |
The last column of the table above is the value that separates the two for each model, and it is
what RAGDOWN_HOOK_MIN_SCORE defaults to for that model. Borrow another model's number and the
hook either injects a pasta recipe into every prompt or drops the documents that answer the question:
a value left at 0.7 from bge-small lets granite-small answer "tell me a joke" with four
unrelated documents at 0.72. A floor at or under the model's unrelated score is flagged in the web UI's
settings, whether the variable or a folder's own settings set it, and the variable is also logged
at start. An openai:
model has no measured scale, so it gets the default model's number as a guess: set the variable.
A second, relative gate under that one. RAGDOWN_HOOK_MIN_RATIO drops a hit scoring less than
0.95 of the best hit for the same prompt, whatever MIN_SCORE let through. An absolute floor
answers "is this on topic at all"; the ratio answers "is this as on topic as the thing I already
found", and a chunk far below the best one is a distractor that costs accuracy, not just tokens.
Being a ratio it also survives a change of embedder, which MIN_SCORE does not. On the same
benchmark (granite-small, min_score 0.8, top_k 4, 210 questions plus 10 unrelated prompts):
ratio | recall | chunks/prompt | off-topic chunks |
| 84.3% | 1.00 | 0.16 |
| 94.8% | 1.62 | 0.67 |
| 99.0% | 2.67 | 1.68 |
| 99.0% | 3.17 | 2.18 |
| 99.0% | 3.99 | 3.00 |
Recall is flat from 0.96 down, so the default sits one step below the knee rather than on it: 0.95 keeps everything an ungated hook found while still cutting a fifth of the injected chunks.
Superseding a document. A document whose frontmatter lists supersedes: hides the documents it names from
search and from hook context. Nothing is deleted or rewritten — the old file stays on disk and
ragdown_read_doc still opens it, saying which document replaced it, and the web UI
marks it superseded and links to the replacement. But a fact that changed stops coming back as
confident prose next to its replacement.
---
title: "Embedder"
date: 2026-09-15
supersedes: ["2025-04-02-embedder.md"]
created_by: ragdown_remember
---Paths are relative to the document's own folder, the way a Markdown link is, and one that climbs out of
the docs folder is ignored: frontmatter is data from a file, not a path the server should follow.
ragdown_remember writes the field for you from its supersedes argument and refuses a path that
names no document, so an agent that gets it wrong hears about it instead of believing it replaced
something. It also records created_by: ragdown_remember and the session_id, so a later reader —
person or model — can tell an agent's document from one the user wrote.
Who wrote a document. created_by and session are indexed with the document. Every search hit
from a document with created_by says an agent wrote it, in ragdown_recall and in the block
ragdown_context injects, so text an agent saved earlier does not come back looking like the user's
own. written_by: agent or user limits ragdown_recall and ragdown_list to one or the other,
and ragdown_list's session_id finds what one conversation wrote. Both fields are frontmatter:
what a file says about itself, which anyone who can edit the file can change, not proof.
ragdown_remember and ragdown_write write them on a document they create, and ragdown_write keeps
them when it replaces one. A document the user wrote stays the user's however an agent edits it.
What a document is, before reading it. A frontmatter description (one line, or a YAML block)
is indexed as metadata and shown by ragdown_list, so an agent browsing a folder can choose what to
read from more than a title. Like tags and aliases it is never added to the embedded text.
The index is derived data. meta.json records the embedder and chunker version, and a
mismatch drops and rebuilds the index instead of migrating it. Syncs are diffs:
Files whose size and mtime are unchanged are skipped without being read.
A content hash decides whether a changed file is re-embedded.
Files gone from disk are dropped.
A file's chunks are replaced with one delete and one add. Syncs never overlap: changes that arrive
during a sync share one follow-up. The watcher debounces for 750 ms and falls back to polling
every 60 s where recursive fs.watch fails.
One primary per index. Several Claude Code windows on the same folder must not all index it.
The process that binds <data dir>/primary.sock is the primary: it indexes and watches. The others
are readers. They search the same LanceDB table (which sees the primary's commits) and forward syncs to the primary. Holding the socket is the lock. A crashed primary
leaves a socket nobody listens on, and the next process takes it over; readers retry every 30 s.
Searches never wait for indexing. The first index of a large folder takes minutes, so until it
finishes, searches answer from whatever is indexed so far. ragdown_stats shows syncing.
Why TypeScript, not Rust. The plan allowed a Rust/napi port if it paid for itself. It does not. Measured on an i9-13900H:
Model load (warm cache) | ~300 ms |
Query embedding | ~23 ms |
First index, 105 files / 1,919 chunks | 180 s (~11 chunks/s) |
Re-sync with nothing changed | ~40 ms |
Almost all indexing time is the ONNX Runtime forward pass, which is native C++. Tokenizing takes
about 1 ms a chunk, and raw onnxruntime-node without transformers.js measured the same
throughput. A Rust port would call the same kernels. Two changes did help, and neither depends on
the language:
Batch chunks by length. A batch is padded to its longest member.
Use half the cores. Four threads beat eight on this P/E-core CPU.
For faster indexing, switch the embedder to a GPU-backed openai:<model> endpoint.
Development
npm run typecheck && npm run lint && npm test
npm run build # the web UI, into web/dist
npm run dev:web # Vite on :5173, proxying /api to a `serve` on :3000The web UI in web/ is React, TanStack Query and Router, Tailwind and shadcn components from the
cubeui registry (npx shadcn add @cubeui/<name> from web/).
The tests run against real LanceDB, the real socket, and MCP over the SDK's in-memory transport and
real HTTP. They use the hash embedder, so they need no model download.
The numbers quoted in Design come from scripts/bench — the
embedder comparison, the threshold and drop-off sweeps, the RRF check, and the retrieval and
answer-accuracy runs against a synthetic corpus. They are too slow for CI and some need a local LLM,
so they are run by hand; that README says how, and what each one found.
Available Tools
11 toolsragdown_backlinksDocuments linking hereARead-only
The documents that link to a document — by [[wikilink]], alias, or relative Markdown link — each with the lines the links are on. Use it to find what depends on or refers to a document, e.g. before changing or superseding it.
| Name | Required | Description | Default |
|---|---|---|---|
| path | Yes | Path of the document relative to the documents root |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations declare readOnlyHint=true and openWorldHint=false, and the description adds real value beyond that: it specifies that results include the line numbers where links occur and clarifies the link-resolution semantics (wikilink, alias, Markdown). It does not discuss limits, ordering, or whether unresolved links appear, so it is 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.
Is the description appropriately sized, front-loaded, and free of redundancy?
Two sentences, no filler: the first defines the output precisely, the second gives the motivating scenario. Everything is front-loaded and each sentence earns its place.
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 single-parameter read tool with read-only annotations and no output schema, the description covers what the tool returns (documents plus link line numbers) and why one would call it. Missing only edge-case behavior such as whether non-existent paths error or return empty results.
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 description coverage is 100% with a single required 'path' parameter already documented as relative to the documents root. The description adds no further parameter detail (e.g. absolute vs relative handling, trailing separators), so the baseline 3 applies.
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?
States a specific resource (the documents that link to a given document) and enumerates exactly which link syntaxes count ([[wikilink]], alias, relative Markdown link), which no sibling does. An agent can distinguish it from ragdown_context or ragdown_read_doc immediately.
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?
Gives an explicit use case — 'find what depends on or refers to a document, e.g. before changing or superseding it' — which is genuine when-to-use context tied to a workflow. It stops short of naming a sibling alternative or a when-not condition, so it is strong but not complete routing guidance.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
ragdown_contextContext for a promptARead-only
For hooks that run before a turn: the documents related to a user prompt, as a ready-to-inject block, or empty text when nothing is similar enough. Unlike ragdown_recall it filters by min_score, skips short prompts and slash commands, and never returns a section twice for the same session_id. reset: true first forgets what that session was given; call it with an empty prompt after the client compacts or clears its context. format: 'claude-code' wraps the block as the JSON a Claude Code UserPromptSubmit hook adds to the prompt.
| Name | Required | Description | Default |
|---|---|---|---|
| reset | No | Forget the sections already returned for session_id before answering, because the client no longer has them | |
| top_k | No | Default RAGDOWN_HOOK_TOP_K | |
| format | No | 'text' is the block itself. 'claude-code' is the block as a Claude Code hook's additionalContext, for an mcp_tool hook on UserPromptSubmit, which does not pass plain text on to the model | text |
| prompt | Yes | The user's prompt, verbatim | |
| max_chars | No | Most characters in the block. Default RAGDOWN_HOOK_MAX_CHARS | |
| min_ratio | No | Lowest share of the best hit's similarity a hit may have and still be included; 0 keeps every hit above min_score. Default RAGDOWN_HOOK_MIN_RATIO | |
| min_score | No | Lowest cosine similarity to include. Default RAGDOWN_HOOK_MIN_SCORE | |
| session_id | No | Stable id of the conversation; sections already returned for it are skipped |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations cover the safety profile (readOnlyHint, not openWorld), but the description discloses non-obvious stateful behavior they cannot: per-session dedup ('never returns a section twice for the same session_id'), the reset-forgets-sections semantics, skip rules for short prompts/slash commands, and the empty-string return condition. It stops short of error/failure behavior or the exact shape of the returned block, 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.
Is the description appropriately sized, front-loaded, and free of redundancy?
Three dense sentences with no filler, and the primary purpose is front-loaded before the sibling comparison. The reset/format guidance reads as compressed notes rather than prose, which slightly hurts readability but not information density.
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?
With no output schema, the description carries the return-value burden and does so (block vs. empty text). Given 8 parameters fully documented in the schema, the annotations covering safety, and the description covering purpose, routing, statefulness, and return semantics, an agent has everything needed to call it correctly.
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 baseline is 3; the description nonetheless adds real meaning the schema lacks for reset (what is forgotten, and the empty-prompt idiom after compaction) and format (why 'claude-code' exists — the hook does not pass plain text through). The remaining numeric knobs are left entirely to the schema 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?
Names a specific verb and resource (retrieve documents related to a prompt) plus the exact artifact returned (a ready-to-inject <ragdown-context> block or empty text) and the execution context (pre-turn hooks). It explicitly distinguishes itself from the sibling ragdown_recall, so an agent can route without opening either schema.
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?
Explicitly states when to use it ('for hooks that run before a turn') and names the alternative plus the selecting conditions ('Unlike ragdown_recall it filters by min_score, skips short prompts and slash commands'). It also gives concrete invocation guidance for reset ('call it with an empty prompt after the client compacts or clears its context') and when to pick the claude-code format.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
ragdown_deleteDelete a document or subfolderADestructive
Permanently delete a document, or a subfolder, from the documents folder and the index. There is no trash and no undo. Call ragdown_backlinks first: links to a deleted document are left as they are, pointing at nothing. When a document is out of date rather than unwanted, prefer ragdown_remember with supersedes, which keeps the old text readable. A subfolder that holds anything is deleted only with recursive: true. Pass base_hash to delete a document only if it is still the version you read.
| Name | Required | Description | Default |
|---|---|---|---|
| path | Yes | Path of the document or subfolder relative to the documents root, e.g. 'ops/pg.md' or 'ops' | |
| base_hash | No | For a document: the hash ragdown_read_doc returned. If the file changed since, nothing is deleted | |
| recursive | No | For a subfolder: also delete everything in it — documents, attachments and any other files |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already flag destructiveHint=true and idempotentHint=false, but the description adds the decisive operational facts: no trash, no undo, backlinks are left dangling rather than fixed, and base_hash causes a no-op if the file changed. That is exactly the context an agent needs before invoking an irreversible 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?
Six tight sentences, front-loaded with the irreversible nature of the operation, and each sentence carries distinct routing or safety information. Slightly dense, but nothing is padding.
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?
With no output schema and a fully documented input schema, the description covers everything an agent must know: destructive outcome, missing safety net, sibling alternatives, and the conditional-delete semantics. Nothing material is left unspecified.
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 parameters are already documented; the description nonetheless adds meaning the schema lacks, namely that recursive is required for any non-empty subfolder and that base_hash exists as an optimistic-concurrency guard tied to the version previously read.
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?
States a specific verb and resource: permanently delete a document or subfolder from the documents folder and the index. It names the exact scope and distinguishes itself from siblings such as ragdown_remember and ragdown_backlinks by explaining what those are for instead.
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?
Explicit routing: call ragdown_backlinks first to inspect incoming links, use ragdown_remember with supersedes when a document is merely out of date rather than unwanted, and use recursive: true only for a subfolder that holds content. Both the when and the when-not are spelled out with named alternatives.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
ragdown_editEdit a documentADestructive
Change an existing document, or create one at a path you choose. By default text replaces the whole file (frontmatter included), which for an existing document needs base_hash: the hash ragdown_read_doc returned, so you never overwrite a version you have not read. A document this creates gets created_by: ragdown_edit in its frontmatter, so the hash returned is of the file as written, not of text. append: true adds text at the end of the document, or with heading at the end of that section, leaving the rest as it is. If the file changed since base_hash, nothing is written: read it again and redo the edit.
| Name | Required | Description | Default |
|---|---|---|---|
| path | Yes | Path of a Markdown file relative to the documents root, e.g. 'projects/alpha.md' | |
| text | Yes | The document's new Markdown, or with append, what to add | |
| append | No | ||
| heading | No | With append: add to the end of the section under this heading | |
| base_hash | No | The hash from ragdown_read_doc; required to replace an existing document | |
| session_id | No | Stable id of the conversation, recorded in the frontmatter of a document this creates |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already flag destructiveHint=true, but the description goes further: it discloses that replacement includes frontmatter, that base_hash guards against overwriting unread versions, that created documents get created_by: ragdown_edit, and that a hash mismatch results in no write at all. That is real behavioral context beyond the annotations.
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?
Purpose is front-loaded in the first clause, and the remaining sentences each cover a distinct behavior (replace, create tagging, append, conflict). It is dense and slightly run-on, but no sentence is filler.
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 destructive mutation tool with no output schema, the description covers the safety profile, the concurrency contract, the append/replace distinction, and even what the returned hash refers to. Nothing an agent needs in order to call it correctly is missing.
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 83% so the baseline is 3, and the description adds genuine meaning on top: base_hash is the hash ragdown_read_doc returned and is required for replacement, heading only applies with append, and session_id is recorded in created-document frontmatter. It clarifies interactions the schema documents only in isolation.
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?
States a specific verb and resource up front ('Change an existing document, or create one at a path you choose'), covering both the edit and create modes. It names ragdown_read_doc as the source of base_hash, so an agent can distinguish it from sibling read tools without opening a schema.
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?
Gives explicit conditional guidance: default whole-file replace requires base_hash for existing documents, append:true with heading appends to a section, and a stale hash means nothing is written. It does not, however, clarify when to prefer this over the overlapping ragdown_remember for creation, leaving one routing ambiguity.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
ragdown_listList documentsARead-only
Browse the documents rather than search them. Returns total and documents: every document's path, title, tags and last change, its description when its frontmatter has one, and for one an agent wrote, created_by and its session. Optionally under a subfolder, with a tag, by who wrote it, or from one session. sort: 'recent' puts the most recently changed first.
| Name | Required | Description | Default |
|---|---|---|---|
| tag | No | Only documents with this tag; 'project' also matches 'project/alpha' | |
| sort | No | path | |
| limit | No | ||
| session_id | No | Only documents written in this conversation, as ragdown_remember recorded it | |
| written_by | No | Only documents an agent wrote ('agent': frontmatter has created_by) or only the user's own ('user') | |
| path_prefix | No | Only documents under this subfolder, relative to the documents root, e.g. 'projects/' |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnlyHint=true and openWorldHint=false, and the description usefully adds what each returned document carries, including conditional fields (description when frontmatter has one, created_by and session for agent-written docs). This is real context beyond the annotations, though pagination/limit behavior and the default result volume are not 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?
Three dense sentences with the browse-vs-search framing front-loaded, then return shape, then filters and sort. Every clause carries information, though the long return-value sentence is slightly unpacked.
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?
With no output schema, the description correctly takes on the burden of describing the return payload, which it does well. It omits limit/pagination behavior and default sort output, leaving a modest gap for a 6-parameter listing tool.
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 67%, and the description compensates by explaining the filter semantics as a group (subfolder, tag, author, session) and clarifying sort: 'recent' puts the most recently changed first. It does not touch limit or default sort ordering beyond what the enum/default already imply.
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?
States a specific verb and resource ('Browse the documents') and immediately frames the scope against the alternative mode ('rather than search them'), which routes an agent away from ragdown_recall. The return contents (total and documents with path, title, tags, last change) make the tool's output concrete.
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?
Gives clear context for use — browsing versus searching — and enumerates the optional filter axes (subfolder, tag, who wrote it, session). It implies the search alternative without naming ragdown_recall explicitly, and states no exclusions, 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.
ragdown_moveMove or rename a document or subfolderA
Rename or move a document, or a subfolder with everything in it, to a new path inside the documents folder. Every link that pointed at what moved ([[wikilinks]] and relative Markdown links, in any document) is rewritten to still point at it, and the result lists the documents that were updated. Never overwrites: if something is already at to, nothing moves. Use this rather than ragdown_edit plus a delete, which would break the links.
| Name | Required | Description | Default |
|---|---|---|---|
| to | Yes | Its whole new path, name included: 'db/postgres.md', not 'db/'. Missing subfolders on the way are created. A document keeps a Markdown extension | |
| from | Yes | Path of the document or subfolder relative to the documents root, e.g. 'ops/pg.md' or 'ops' |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations declare the safety profile (readOnly=false, destructive=false, openWorld=false), and the description adds substantial behavior beyond that: incoming [[wikilinks]] and relative Markdown links in any document are rewritten, the result enumerates updated documents, missing parent folders are created, and nothing is overwritten. These are exactly the side effects an agent must anticipate before mutating paths.
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?
Purpose, side effects, safety guarantee, and alternative are each delivered in one front-loaded clause with no filler or repetition of the title.
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?
No output schema exists, but the description states what the result contains (the documents that were updated), and annotations plus the description together cover side effects and safety. Nothing needed to invoke this correctly is missing.
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 description coverage is 100%, and the schema already explains both `from` and `to` semantics including the extension and auto-created folders, so the baseline is 3. The description only adds the constraint that the destination is "inside the documents folder," which is mild extra value.
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?
States a specific verb+resource with scope: "Rename or move a document, or a subfolder with everything in it, to a new path inside the documents folder." It distinguishes itself from ragdown_edit and ragdown_delete by naming them, so an agent can route 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.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Gives an explicit replace-this condition: "Use this rather than ragdown_edit plus a delete, which would break the links." It also states the failure condition up front ("Never overwrites: if something is already at `to`, nothing moves"), so when-to-use and when-not are both covered.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
ragdown_read_docRead a documentARead-only
Read a Markdown file from the documents folder, whole or by line range, straight from disk. Never clipped. Use it to see the context around a ragdown_recall hit, or to follow a [[wikilink]] in a document. The result's hash is the whole file's, for ragdown_edit's base_hash.
| Name | Required | Description | Default |
|---|---|---|---|
| path | Yes | Path relative to the documents root, as returned by ragdown_recall, or a wikilink target as written inside [[...]] ('Note', 'Note#Heading', 'sub/Note'); a heading narrows the text to that section | |
| end_line | No | ||
| start_line | No |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnlyHint=true and openWorldHint=false, so safety is covered. The description adds real behavioral value beyond that: 'straight from disk, never clipped' (no truncation), and the crucial detail that the returned hash is the whole file's hash and serves as ragdown_edit's base_hash. It does not describe pagination or the exact return shape, but the hash contract is a meaningful disclosure.
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?
Three tight sentences, front-loaded with the core action, then usage, then the hash contract. Every sentence earns its place and nothing is redundant.
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-only tool with an explicit safety annotation and no output schema, the description covers purpose, routing, and the one non-obvious output detail (the whole-file hash). It is only slightly short on line-range edge-case behavior, which is the sole remaining gap.
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 description coverage is only 33% (path is documented; start_line/end_line are bare). The description compensates by stating the tool reads 'whole or by line range', confirming the line-range intent, but it gives no semantics for inclusivity, defaults, or ordering of start_line/end_line. The path semantics are fully carried by the schema, so a baseline 3 is appropriate.
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?
States a specific verb and resource ('Read a Markdown file from the documents folder'), plus the scope options ('whole or by line range'). It distinguishes itself from siblings by naming ragdown_recall and ragdown_edit directly, so an agent knows exactly where this sits in the toolset.
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?
Gives two explicit use cases: seeing context around a ragdown_recall hit, and following a [[wikilink]] inside a document. That routes the agent well against ragdown_recall. It stops short of stating when NOT to use it or naming a read alternative, so it is clear context without exclusions.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
ragdown_recallSearch documentsARead-only
Hybrid (semantic + keyword) search over the user's Markdown documents. Returns the most relevant sections with file path, line range, heading breadcrumb and cosine similarity (above ~0.8 is usually on topic) and the day the file last changed. A section of a document an agent wrote says so. Use it before answering anything the documents may cover; follow up with ragdown_read_doc for the surrounding text.
| Name | Required | Description | Default |
|---|---|---|---|
| tag | No | Only search documents with this tag (frontmatter tags or inline #tags); 'project' also matches 'project/alpha' | |
| query | Yes | What to look for, as a question or keywords | |
| top_k | No | ||
| format | No | text | |
| max_chars | No | text format: characters per hit before it is clipped (0 = never) | |
| written_by | No | Only documents an agent wrote ('agent': frontmatter has created_by) or only the user's own ('user') | |
| path_prefix | No | Only search files under this subfolder, relative to the documents root, e.g. 'projects/' |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With readOnlyHint/openWorldHint already covering the safety profile, the description adds real behavioral context: what a hit contains, how to interpret the cosine similarity (~0.8 means on topic), that provenance is signaled when a section was written by an agent, and that results are clipped. Missing details are minor (ordering, dedup, empty-result behavior).
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?
Three sentences, front-loaded with the search mechanism before return shape and usage routing. The middle sentence is dense but each clause (path, line range, breadcrumb, similarity, last-changed) earns its place by substituting for a missing output schema.
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 7-parameter read tool with no output schema and no annotations carrying behavioral detail, the description supplies return fields and usage routing well. It leaves gaps on default result count, the text vs json format switch, and handling of empty results.
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 71%, and the description adds essentially no parameter guidance — tag, path_prefix, written_by, top_k, format and max_chars are documented only in the schema. The similarity-threshold hint is useful interpretation but does not map to an input parameter. Baseline 3 for moderate coverage with no compensating description.
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?
States a specific verb and resource — hybrid semantic + keyword search over the user's Markdown documents — and immediately distinguishes itself from the browsing siblings (list, read_doc, backlinks). An agent can identify the retrieval tool 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.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Explicitly says when to use it ('before answering anything the documents may cover') and names a follow-up tool (ragdown_read_doc for surrounding text). It gives no when-not conditions or contrast against similar retrieval siblings such as ragdown_context, so it stops 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.
ragdown_reindexReindex documentsA
Bring the index up to date with the folder now. Changes are normally picked up automatically within a second; use this after bulk edits made while no server was running, or full: true to re-embed everything.
| Name | Required | Description | Default |
|---|---|---|---|
| full | No |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare this is a non-read-only, non-destructive, closed-world mutation, so the safety bar is partly covered. The description adds genuinely useful context beyond that: normal automatic pickup latency (within a second) and the fact that full: true re-embeds everything, which signals a materially heavier operation. It stops short of stating whether the call blocks or how long a full re-embed takes, so it is not a 5.
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?
Two tight sentences with zero filler, front-loading the core action and then the usage conditions. Every clause earns its place: the automatic-indexing note prevents unnecessary calls and the full flag guidance drives correct invocation.
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 single-optional-boolean, no-output-schema mutation tool, the description covers what it does, when to invoke it, and what the lone parameter changes. Nothing an agent needs to call it correctly is missing.
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 description coverage is 0%, so the description must carry the meaning of 'full' on its own, and it largely does: full: true re-embeds everything versus the implied incremental default that only picks up changes. The default value (false) and the incremental-path behavior are only implied rather than stated outright, keeping this from a 5.
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 states a specific action on a specific resource: bringing the folder's index up to date. It is immediately distinguishable from siblings like ragdown_recall, ragdown_edit, or ragdown_stats, which do not touch the index. No ambiguity about what the tool accomplishes.
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 explicitly frames the default behavior (changes are normally picked up automatically within a second), tells the agent when NOT to call it, and then names the two triggering conditions: bulk edits made while no server was running, or full: true to re-embed everything. This is a textbook when/when-not/alternative formulation.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
ragdown_rememberWrite a documentA
Save something worth keeping (a decision, a fix, a how-to) as a new Markdown document in the documents folder, indexed immediately so later searches find it. Never overwrites an existing file. When this document replaces an earlier one, pass that document's path as supersedes so searches stop returning the old version.
| Name | Required | Description | Default |
|---|---|---|---|
| name | No | File name under the documents folder, without .md; defaults to <date>-<title-slug> | |
| tags | No | ||
| title | Yes | ||
| content | Yes | Markdown body; the title and date go in frontmatter | |
| session_id | No | Stable id of the conversation, recorded in the document's frontmatter | |
| supersedes | No | Paths of documents this one replaces, as returned by ragdown_recall. They stay on disk and ragdown_read_doc still opens them, but search and hook context skip them. Use it when a fact changed, not when you are merely writing about the same topic. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations declare openWorldHint=false and non-destructive write. The description goes beyond that with three real behavioral facts: content is indexed immediately for later search, existing files are never overwritten, and superseding changes what search returns. It doesn't cover failure modes or name-collision handling, but the added context is substantial against a low annotation bar.
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?
Three sentences, front-loaded with the write action, then the non-overwrite guarantee, then the conditional supersede workflow. No filler, no restatement of the title.
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?
No output schema exists, but this is a write tool whose result is largely confirmed by the immediate-indexing statement. With 6 parameters, the description covers purpose, safety, indexing and the one non-obvious parameter; only edge cases like name collisions or failed writes are left unaddressed.
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 description coverage is 67%, so the schema already documents name, content, session_id and supersedes in detail. The description restates the supersedes intent ('searches stop returning the old version') but adds no syntax or semantics the schema lacks, so it stays at the baseline for a partially-covered schema.
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?
Names a specific verb and resource ('Save something worth keeping ... as a new Markdown document in the documents folder') and immediately qualifies the scope with the indexing behavior. The 'Never overwrites an existing file' clause effectively separates it from ragdown_edit without the agent needing to open a schema.
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?
Gives concrete when-to-use cues ('a decision, a fix, a how-to') and a conditional rule for the supersedes case ('When this document replaces an earlier one'). It never states when NOT to use it versus ragdown_edit or ragdown_recall, so it stops short of full routing guidance.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
ragdown_statsIndex statusBRead-only
The documents folder, index size (files, chunks), embedder, whether this process is the indexing primary, and the last sync. include_files lists every indexed file.
| Name | Required | Description | Default |
|---|---|---|---|
| include_files | No |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnlyHint=true and openWorldHint=false, so the safety profile is covered. The description adds that include_files enumerates every indexed file, hinting at a potentially large response, but it never warns about output size or cost, so the addition is modest.
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?
Two tight sentences with no filler. Slight deduction because it opens with a bare list of returned fields rather than a framing clause like 'Returns index status', which would front-load the purpose more cleanly.
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?
With no output schema, the description usefully enumerates the return contents, which is what an agent needs from a status tool. It is adequate for a read-only one-parameter tool, missing only guidance on when to prefer it over other index-inspecting siblings.
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 description coverage is 0% and the sole parameter include_files has no schema description at all, so the description carries the full burden. It does define the parameter's effect ('lists every indexed file'), which is exactly the compensation needed for a single-parameter tool.
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 enumerates exactly what the tool reports (documents folder, index size, embedder, primary status, last sync), so the resource is unambiguous even though it never states a verb like 'reports'. It does not distinguish itself from siblings such as ragdown_list or ragdown_reindex, which is the only real gap.
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?
There is no statement of when to call this versus alternatives like ragdown_list or ragdown_reindex, and no prerequisites or context given. Usage is only inferable from the fact that it reports index state.
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.
9 tool updates
v5.16.0- Changed
ragdown_backlinks1 field changed- changed
Input schema / properties / path / descriptionPrevious value: -"Path of the note relative to the notes root"New value: +"Path of the document relative to the documents root"
- Changed
ragdown_context2 fields changed- added
Input schema / properties / formatAdded value: +{ + "default": "text", + "description": "'text' is the block itself. 'claude-code' is the block as a Claude Code hook's additionalContext, for an mcp_tool hook on UserPromptSubmit, which does not pass plain text on to the model", + "enum": [ + "text", + "claude-code" + ], + "type": "string" +} - added
Input schema / properties / resetAdded value: +{ + "default": false, + "description": "Forget the sections already returned for session_id before answering, because the client no longer has them", + "type": "boolean" +}
- Added
ragdown_delete - Changed
ragdown_edit4 fields changed- changed
Input schema / properties / base_hash / descriptionPrevious value: -"The hash from ragdown_read_doc; required to replace an existing note"New value: +"The hash from ragdown_read_doc; required to replace an existing document" - changed
Input schema / properties / path / descriptionPrevious value: -"Path of a Markdown file relative to the notes root, e.g. 'projects/alpha.md'"New value: +"Path of a Markdown file relative to the documents root, e.g. 'projects/alpha.md'" - added
Input schema / properties / session_idAdded value: +{ + "description": "Stable id of the conversation, recorded in the frontmatter of a document this creates", + "type": "string" +} - changed
Input schema / properties / text / descriptionPrevious value: -"The note's new Markdown, or with append, what to add"New value: +"The document's new Markdown, or with append, what to add"
- Changed
ragdown_list4 fields changed- changed
Input schema / properties / path_prefix / descriptionPrevious value: -"Only notes under this subfolder, relative to the notes root, e.g. 'projects/'"New value: +"Only documents under this subfolder, relative to the documents root, e.g. 'projects/'" - added
Input schema / properties / session_idAdded value: +{ + "description": "Only documents written in this conversation, as ragdown_remember recorded it", + "type": "string" +} - changed
Input schema / properties / tag / descriptionPrevious value: -"Only notes with this tag; 'project' also matches 'project/alpha'"New value: +"Only documents with this tag; 'project' also matches 'project/alpha'" - added
Input schema / properties / written_byAdded value: +{ + "description": "Only documents an agent wrote ('agent': frontmatter has created_by) or only the user's own ('user')", + "enum": [ + "agent", + "user" + ], + "type": "string" +}
- Added
ragdown_move - Changed
ragdown_read_doc1 field changed- changed
Input schema / properties / path / descriptionPrevious value: -"Path relative to the notes root, as returned by ragdown_recall, or a wikilink target as written inside [[...]] ('Note', 'Note#Heading', 'sub/Note'); a heading narrows the text to that section"New value: +"Path relative to the documents root, as returned by ragdown_recall, or a wikilink target as written inside [[...]] ('Note', 'Note#Heading', 'sub/Note'); a heading narrows the text to that section"
- Changed
ragdown_recall3 fields changed- changed
Input schema / properties / path_prefix / descriptionPrevious value: -"Only search files under this subfolder, relative to the notes root, e.g. 'projects/'"New value: +"Only search files under this subfolder, relative to the documents root, e.g. 'projects/'" - changed
Input schema / properties / tag / descriptionPrevious value: -"Only search notes with this tag (frontmatter tags or inline #tags); 'project' also matches 'project/alpha'"New value: +"Only search documents with this tag (frontmatter tags or inline #tags); 'project' also matches 'project/alpha'" - added
Input schema / properties / written_byAdded value: +{ + "description": "Only documents an agent wrote ('agent': frontmatter has created_by) or only the user's own ('user')", + "enum": [ + "agent", + "user" + ], + "type": "string" +}
- Changed
ragdown_remember3 fields changed- changed
Input schema / properties / name / descriptionPrevious value: -"File name under the notes folder, without .md; defaults to <date>-<title-slug>"New value: +"File name under the documents folder, without .md; defaults to <date>-<title-slug>" - changed
Input schema / properties / session_id / descriptionPrevious value: -"Stable id of the conversation, recorded in the note's frontmatter"New value: +"Stable id of the conversation, recorded in the document's frontmatter" - changed
Input schema / properties / supersedes / descriptionPrevious value: -"Paths of notes this one replaces, as returned by ragdown_recall. They stay on disk and ragdown_read_doc still opens them, but search and hook context skip them. Use it when a fact changed, not when you are merely writing about the same topic."New value: +"Paths of documents this one replaces, as returned by ragdown_recall. They stay on disk and ragdown_read_doc still opens them, but search and hook context skip them. Use it when a fact changed, not when you are merely writing about the same topic."
3 tool updates
v5.9.0- Added
ragdown_backlinks - Added
ragdown_edit - Added
ragdown_list
2 tool updates
v5.2.0- Changed
ragdown_read_doc1 field changed- changed
Input schema / properties / path / descriptionPrevious value: -"Path relative to the notes root, as returned by ragdown_recall"New value: +"Path relative to the notes root, as returned by ragdown_recall, or a wikilink target as written inside [[...]] ('Note', 'Note#Heading', 'sub/Note'); a heading narrows the text to that section"
- Changed
ragdown_recall2 fields changed- changed
Input schema / properties / path_prefix / descriptionPrevious value: -"Only search files under this folder, relative to the notes root, e.g. 'projects/'"New value: +"Only search files under this subfolder, relative to the notes root, e.g. 'projects/'" - added
Input schema / properties / tagAdded value: +{ + "description": "Only search notes with this tag (frontmatter tags or inline #tags); 'project' also matches 'project/alpha'", + "type": "string" +}
2 tool updates
v4.0.0- Added
ragdown_context - Changed
ragdown_remember2 fields changed- added
Input schema / properties / session_idAdded value: +{ + "description": "Stable id of the conversation, recorded in the note's frontmatter", + "type": "string" +} - added
Input schema / properties / supersedesAdded value: +{ + "description": "Paths of notes this one replaces, as returned by ragdown_recall. They stay on disk and ragdown_read_doc still opens them, but search and hook context skip them. Use it when a fact changed, not when you are merely writing about the same topic.", + "items": { + "type": "string" + }, + "type": "array" +}
5 tool updates
v1.0.0- First observed
ragdown_read_doc - First observed
ragdown_recall - First observed
ragdown_reindex - First observed
ragdown_remember - First observed
ragdown_stats
TDQS
Scored across 11 tools
Most tools target distinct operations (search, read, list, move, delete, stats). The two fuzzy pairs are recall vs context (both search, but context is clearly scoped to hook injection with min_score/session dedup) and remember vs edit (both can create, but remember never overwrites while edit takes a base_hash). Descriptions resolve these well enough that misselection is unlikely.
Every tool uses the consistent ragdown_ snake_case prefix with a short action-oriented suffix. Mixing nouns (context, backlinks, stats) with verbs (recall, edit, move) is a minor stylistic quirk, but the pattern is entirely predictable and readable.
11 tools is well within the ideal 3-15 range for a document store plus RAG index. Each tool earns its place: search, context, read, browse, links, stats, and a full write lifecycle.
Full lifecycle coverage: create (remember/edit), read (read_doc/list), update (edit with base_hash), delete (delete), plus move with automatic link rewriting. Search (recall), hook context, backlinks, stats, and reindex round out the domain with no obvious dead ends.
Maintenance
Related MCP Connectors
Markdown notes and whiteboards your AI agent can read and write.
Personal context for every AI: search, read, and write back to your private Markdown library.
Markdown notes in folders, with files, that your AI assistant can read, write and organise.
Markdown notes in folders, with files, that your AI assistant can read, write and organise.
Related MCP Servers
- AlicenseNot gradedqualityAmaintenanceTurns a folder of Markdown notes into an agent-native knowledge base, providing long-term memory with provenance, token-budgeted retrieval, and safe write-back with versioning.MIT
- AlicenseNot gradedqualityBmaintenanceEnables AI agents to search, read, create, and maintain a local-first knowledge base using hybrid retrieval and Markdown note management through the Model Context Protocol.8 npm1MIT
- AlicenseAqualityCmaintenanceEnables local-first hybrid knowledge retrieval from authorized Markdown and plain-text files, combining full-text and vector search with reranking and traceable source references via a single search tool.1MIT
- AlicenseNot gradedqualityCmaintenanceEnables AI agents to semantically search and retrieve content from a local Markdown vault. Provides tools for fetching specific notes and finding related ones via embeddings and wikilinks.MIT