Skip to main content
Glama

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 :3000

Requires 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 stdio

stdio 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 } } ] }
    ]
  }
}
  • server is the name you gave claude mcp add.

  • format: claude-code is 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's additionalContext.

  • The SessionStart hook is for compaction, which keeps the session id and drops the sections Claude was given: reset lets them be sent again. Claude Code skips mcp_tool hooks on the SessionStart at launch, which needs no reset.

  • Claude Code caps a hook's output at 10,000 characters; RAGDOWN_HOOK_MAX_CHARS defaults 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 } }
  • title is shown in the web UI and the server's instructions; it defaults to the directory name.

  • mcp defaults 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.

  • hook holds the folder's own ragdown_context defaults: any of top_k, min_score, min_ratio and max_chars. One it leaves out is the server's RAGDOWN_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.

Bare /mcp is a 404 that says to pick a folder, unless the root is turned on: see Every folder at once. 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.

Every folder at once

With RAGDOWN_ROOT_MCP=true, or Serve every folder at /mcp under Settings → Server, bare /mcp is one MCP server, named ragdown, over every folder with MCP on. It saves setting an agent up once per folder, and it is the only place an agent can make a folder.

claude mcp add --transport http ragdown http://localhost:3300/mcp \
  --header "Authorization: Bearer $RAGDOWN_TOKEN"
  • The tools are the same, each with a folder argument, and paths stay relative to that folder (folder: "work", path: "backups.md"). ragdown_read_doc, ragdown_backlinks, ragdown_remember, ragdown_write, ragdown_edit, ragdown_move and ragdown_delete need one. ragdown_recall, ragdown_list, ragdown_context and ragdown_stats cover every folder when it is left out, and each result then names its folder: [work] backups.md:7-9.

  • A folder is still its own: wikilinks resolve inside it, a move stays inside it, and its hook settings apply to a ragdown_context call that names it. A call that names none uses the server's defaults and keeps its own per-session memory.

  • A human-only folder is not there: it is not searched, listed or counted, and naming it answers as a folder that does not exist would.

  • ragdown_folders lists the folders. ragdown_create_folder makes one, with MCP on and "created_by": "ragdown_create_folder" in its .ragdown.json, which the web UI shows as Made by an agent. Agents cannot rename or delete a folder.

Each folder's own /mcp/<folder> endpoint is unchanged, with no folder argument: an agent that should see one folder is still pointed at that one.

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_recall and ragdown_context search only files under it, and path_prefix narrows further inside it. Paths are relative to the subfolder.

  • ragdown_remember writes into the subfolder itself; on a folder's own endpoint it writes into <folder>/<RAGDOWN_NOTES_DIR>.

  • ragdown_stats counts the subfolder's files and chunks, and ragdown_context's per-session memory is kept separately for each endpoint.

  • ragdown_reindex still 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] or tags: a, b) and inline #tags in the text (not in code) are indexed per document. ragdown_recall's tag filter matches a tag and the tags nested under it: project finds #project/alpha. Every hit lists its document's tags.

  • Aliases from frontmatter are found by keyword search and by wikilinks.

  • Wikilinks. ragdown_read_doc takes 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

ragdown_context

For hooks: the sections related to a prompt as a <ragdown-context> block, or empty text. Filters by similarity, skips short prompts and slash commands, and never repeats a section for the same session_id until reset: true forgets what that session was given. format: claude-code wraps the block as a Claude Code hook's additionalContext. Takes top_k, min_score, min_ratio and max_chars to override the folder's hook settings and the RAGDOWN_HOOK_* defaults.

ragdown_recall

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 top_k, path_prefix, tag, written_by: agent|user, format: text|json and max_chars.

ragdown_read_doc

Reads a file, or a line range of one, straight from disk. Never clipped. Also takes a wikilink target (Note#Heading); see Obsidian. Returns the text as it is in the file, under one line: path:start-end of total (hash …). The hash is the first 12 characters of the file's SHA-256, which is what base_hash takes; the line also says when another document supersedes this one.

ragdown_backlinks

The documents that link to a path — by wikilink, alias or relative Markdown link — with the lines the links are on. Links in code are not links.

ragdown_list

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 description. Takes path_prefix, tag, written_by: agent|user, session_id, sort: path|recent and limit.

ragdown_stats

Folder, index size, embedder, role (primary or reader), whether a sync is running, and the last sync.

ragdown_remember

Writes a new document (with frontmatter) under RAGDOWN_NOTES_DIR and indexes it before returning. Never overwrites a file, and returns the path it chose. supersedes lists the documents this one replaces, which search then skips; session_id is recorded as provenance.

ragdown_write

Rewrites a document at a path, or creates one, which gets created_by: ragdown_write (and the session_id) in its frontmatter. text replaces the whole file, which for an existing document needs base_hash — the hash ragdown_read_doc gave — so an agent never overwrites a version it has not read. append: true adds text at the end instead, or with heading at the end of that section. A file that changed since base_hash is not written. Returns the new hash, so the next change needs no read.

ragdown_edit

Changes part of a document at a path without sending the rest: each of edits swaps one exact passage (old_text) for another (new_text). A passage must match exactly, and once unless replace_all: true. The edits are made in order, and if any does not apply nothing is written. base_hash is optional, since the match already proves the passage is current. A passage that is not found is answered with the nearest one the document does have, to copy from. Returns how many passages were replaced and the new hash.

ragdown_move

Renames or moves a document, or a subfolder with everything in it, from from to to, and rewrites every wikilink and relative Markdown link that pointed at what moved. Returns the documents whose links it updated. Never overwrites what is already at to.

ragdown_delete

Deletes a document or a subfolder at path, for good: there is no trash. A subfolder that holds anything needs recursive: true, and then goes with its attachments too. Links to a deleted document are left as they are. With base_hash (the hash ragdown_read_doc gave), a document is deleted only if it has not changed since.

ragdown_reindex

Syncs now; full: true re-embeds everything.

ragdown_folders

At /mcp only: the folders with MCP on, one line each with its name, title and how many documents it holds.

ragdown_create_folder

At /mcp only: makes an empty folder from a name and an optional title, with MCP on. Never overwrites, and the name follows the web UI's rules.

The write tools (ragdown_remember, ragdown_write, ragdown_edit, ragdown_move, ragdown_delete, ragdown_reindex, ragdown_create_folder) are not listed when RAGDOWN_READ_ONLY=true. At /mcp every tool but ragdown_reindex also takes a folder: see Every folder at once.

Every tool an agent is offered costs it context on every turn, used or not: the twelve descriptions together are about 15,000 characters, and /mcp adds two tools and a folder argument to those. 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

RAGDOWN_DOCS_DIR

—

For serve, the directory holding the folders; for stdio, the one folder. Walked recursively. Dot-folders and node_modules are skipped, and symlinks are not followed. Indexes .md, .markdown and .mdx.

RAGDOWN_DATA_DIR

~/.cache/ragdown/<hash of docs dir>

The index. Deleting it only costs a rebuild. serve and stdio on the same directory keep separate ones.

RAGDOWN_MODELS

~/.cache/ragdown/models

Model cache, shared by every folder.

RAGDOWN_EMBEDDER

granite-small

A local model — granite-small, bge-small or embeddinggemma — or openai:<model> (any OpenAI-compatible /embeddings endpoint, such as Ollama or llama.cpp), or hash (tests only). See Choosing an embedder.

RAGDOWN_EMBEDDING_URL / _API_KEY

OpenAI

For openai:<model>.

RAGDOWN_THREADS

half the cores

ONNX Runtime threads for the local model.

RAGDOWN_WATCH

true

Watch the folder; without a watcher, sync on start and on ragdown_reindex only.

RAGDOWN_ROOT_MCP

false

serve only. Serve every folder with MCP on as one server at /mcp, where agents can also make folders. See Every folder at once.

RAGDOWN_READ_ONLY

false

Hide the write tools, and refuse uploads, deletes and folder changes other than settings.

RAGDOWN_DISABLED_TOOLS

none

Tools to leave out, by name and comma-separated, such as ragdown_stats,ragdown_reindex. They are neither listed nor callable. See Tools.

RAGDOWN_NOTES_DIR

notes

Where ragdown_remember writes, relative to each folder (a subfolder endpoint writes into the subfolder). A relative path inside the folder, not a dot-folder.

RAGDOWN_TEXT_LIMIT

2000

Characters per hit in text output. Every cut names the ragdown_read_doc call that returns the rest.

RAGDOWN_MAX_PER_DOCUMENT

0

Most sections of one document a search returns and ragdown_context injects; the rest are filled from other documents. 0 is no cap. See Design.

RAGDOWN_FOLLOW_LINKS

0

How many of a search's best hits have the wikilinks in their own section followed: each linked document's two closest sections are placed right after the hit, within its folder and under the search's filters. 0 is off. See Design.

RAGDOWN_QUERY_LOG

false

Log every search, hook call and ragdown_read_doc to query-log.jsonl in the data dir, for the web UI's Settings → Usage. Never sent anywhere. See Design.

RAGDOWN_HOOK_TOP_K

4

ragdown_context: most sections per prompt.

RAGDOWN_HOOK_MIN_SCORE

the embedder's own

ragdown_context: lowest cosine similarity returned. Defaults to the MIN_SCORE of the embedder in use (0.8 for the default one), so leave it unset unless you measured a better number.

RAGDOWN_HOOK_MIN_RATIO

0.95

ragdown_context: lowest share of the best hit's similarity a hit may have and still be injected; 0 disables it. Being a ratio, it carries across embedders as MIN_SCORE does not. See Design.

RAGDOWN_HOOK_MAX_CHARS

6000

ragdown_context: most characters per prompt.

PORT

3000

serve only. The HTTP port.

HTTP_KEEP_ALIVE_TIMEOUT_MS

75000

serve only. How long an idle client connection is kept open. Above the 60 s nginx and ALB hold theirs; raise it behind a proxy that holds longer. 0 never closes one.

RAGDOWN_TOKEN

—

serve only. The bearer token /mcp, /mcp/<folder> and the web UI's /api routes require.

SECURE_LOCAL_NET

false

serve only. Skip the token on a trusted network. serve refuses to start with neither.

Under serve, the embedder, watching, the /mcp root, the characters per hit, the sections per document, the query log 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

GET /api/status

none

Liveness and index stats. ready: false while the model loads. settings holds the non-secret tuning values (RAGDOWN_EMBEDDER, RAGDOWN_WATCH, RAGDOWN_ROOT_MCP, RAGDOWN_TEXT_LIMIT, RAGDOWN_MAX_PER_DOCUMENT, RAGDOWN_FOLLOW_LINKS, RAGDOWN_QUERY_LOG, RAGDOWN_HOOK_*) for the web UI's Settings page: the ones in effect, the ones saved there, and what the env says.

/mcp/<folder>[/<subfolder...>]

bearer

Streamable HTTP MCP, stateless, for a folder with MCP on. 404 otherwise. See Folders.

/mcp

bearer

The same over every folder with MCP on, while RAGDOWN_ROOT_MCP is on. 404 otherwise. See Every folder at once.

GET /api/folders

bearer

Each folder's name, title, mcp, mcp_path, files and chunks, with created_by on one an agent made, and loose_files: the Markdown outside every folder.

POST /api/folders

bearer

Create a folder: JSON { name, title?, mcp? }. 201; 409 when it exists, 400 for a bad name.

PATCH /api/folders/<name>

bearer

JSON { title?, mcp?, hook?, name? }: change its settings, or rename it with name (which re-indexes it). hook takes any of top_k, min_score, min_ratio and max_chars; null takes the folder's own value away. Unknown keys in .ragdown.json are kept.

DELETE /api/folders/<name>?confirm=<name>

bearer

Delete a folder and everything in it. 400 unless confirm repeats the name.

PATCH /api/settings

bearer

JSON { embedder?, watch?, root_mcp?, text_limit?, max_per_document?, follow_links?, query_log?, hook? }: save server settings over their variables and apply them at once. null gives a value back to its variable. 400 for a value that is not valid or an embedder that cannot load, 409 when this process is not the one that owns the index. Answers with the new settings.

GET /api/query-log

bearer

The query log summed up for Settings → Usage: enabled, and a summary of the searches, the gaps (queries whose best hit was under the minimum score), the hook's injection rate, and the documents read most with how many reads followed a search that showed them. DELETE empties the log, read-only or not.

DELETE /api/loose?name=

bearer

Delete one of the loose_files: a Markdown file directly in the docs directory. 404 for any other name.

GET /api/docs?folder=

bearer

The indexed files, of one folder or all: path, folder, title, description, tags, aliases, created_by, session, superseded_by, mtime_ms, size, chunks.

GET /api/doc?path=

bearer

One indexed file's text, read from disk, with its tags, aliases and hash (SHA-256 of the bytes on disk). 404 for a file the index does not hold.

POST /api/doc

bearer

Upload a file: JSON { path, text, overwrite? }, body up to 4 MiB. Only .md, .markdown or .mdx inside an existing folder, somewhere the indexer reads (no .., dot-folders, node_modules or symlinked folders); subfolders are created. 201 when created, 200 when overwritten, 409 for an existing file without overwrite: true. An edit sends base_hash, the hash it was opened at, in place of overwrite: 409 with code: "changed" if the file has changed or gone since. Saved over a CRLF file, the text keeps CRLF. The answer carries the new hash.

DELETE /api/doc?path=

bearer

Delete a Markdown file. 404 when it is not there.

GET /api/search?folder=&q=&tag=&top_k=

bearer

Hybrid search in one folder, human-only ones included. top_k defaults to 10, at most 50. Each hit has path, title, heading, start_line, end_line, similarity, text, tags, mtime_ms and created_by.

GET /api/resolve?from=&link=

bearer

A wikilink target, resolved from the document from within its folder: { path, anchor? } or 404.

POST /api/move

bearer

{ from, to }: rename or move a document within its folder. Every link in the folder that pointed at it — wikilinks and relative Markdown links, its own included — is rewritten to follow it, with the shortest target that still resolves. Answers with the documents it updated.

GET /api/backlinks?path=

bearer

The documents in the same folder that link to path, with the linking lines. The UI shows them under the preview.

GET /api/file?path=

bearer

Any file inside a folder — an image, a PDF — as raw bytes, sandboxed and nosniff. Never a dot-path or a symlink out.

GET /*

none

The web UI from web/dist, with index.html for any other path.

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:

  1. Move loose Markdown into a folder. Files directly in RAGDOWN_DOCS_DIR are no longer indexed by serve; the log and the web UI list them.

  2. Turn MCP on for each folder agents should reach, under Settings → Folders or with "mcp": true in its .ragdown.json. Every folder starts human-only.

  3. Point clients at /mcp/<folder>. /mcp serves nothing unless RAGDOWN_ROOT_MCP is on (6.2 and later: Every folder at once). A 4.x scope URL /mcp/<folder>/<sub> keeps working once <folder> has MCP on.

  4. RAGDOWN_NOTES_DIR is relative to each folder: notes now 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.

A query that names something leans on BM25. A query holding a quoted command, a snake_case key, a dotted or slashed path, a flag, a camelCase name, or a word mixing letters and digits is fused by score instead: each side min-max normalised over its own pool, BM25 weighted 0.8. Rank fusion gives the dense side an equal vote, and the dense side ranks a chunk about the name's topic above the one chunk that holds it. On three generated corpora the chunk holding an identifier came first 26–36% of the time under RRF and 77–85% routed, and top-1 over all 270 questions rose 4–6 points, with no question type worse. Plain prose (up-to-date, e.g., 10:30) is not routed. Weighting every query towards BM25, or fusing every query by score, gained 1–3 points and cost table-cell questions up to 10.

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.

RAGDOWN_EMBEDDER

Model

Dim

Top-1

Recall@5

Index

Per query

MIN_SCORE

granite-small (default)

granite-embedding-small-english-r2

384

85.7%

100%

3.6 s

9 ms

0.8

bge-small

bge-small-en-v1.5

384

82.9%

100%

3.5 s

9 ms

0.7

embeddinggemma

embeddinggemma-300m

768

91.0%

100%

33 s

260 ms

0.6

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)

granite-small

0.86

0.75

bge-small

0.70

0.60

embeddinggemma

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

1.00

84.3%

1.00

0.16

0.98

94.8%

1.62

0.67

0.96

99.0%

2.67

1.68

0.95 (default)

99.0%

3.17

2.18

0 (off)

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.

A quoted name passes both gates. The hook re-sorts by cosine, so better fusion alone barely reaches it: a chunk that holds the exact command a prompt quotes can sit below MIN_SCORE, since everything around the name is prose the prompt does not share. So the best-ranked candidate holding a name the prompt quotes — whole, case-insensitively — is injected first whatever its cosine, and the gated hits follow it. Only one, and only a strong name: a plain word in backticks (`typescript`) or an unquoted version number does not count. On the three corpora the identifier's chunk was injected for 4–10% of identifier prompts before and 100% after; the 270 questions were unchanged, for about 0.15 more chunks per prompt, and prompts quoting names no document holds got nothing more.

An alias passes both gates too. A document's frontmatter aliases are in its first chunk's lexical text and nowhere in the embedding, so a query that calls a document by an alias found the right section first for under a third of questions. Search adds one rank-1 RRF share to every candidate of a document whose alias the query holds — whole, case-insensitively, three characters or more — and the hook injects the best-ranked such candidate whatever its cosine, as it does for a quoted name. On three generated corpora, each document given a codename alias, the right section came first for 83–85% of alias questions (98–99% in the top 8) and the hook injected it for 86–89%, against 32–38%; questions by title were unchanged. A title needs no such help: it is in every chunk's breadcrumb, embedded and indexed. A query routed to score fusion is not boosted.

Sections per document, off by default. A long document on the topic can fill every result with its own sections. RAGDOWN_MAX_PER_DOCUMENT caps them: past the cap, a document's sections move behind every other document's, so a short result fills from the next-best documents and a corpus with too few still fills it. The hook applies the cap again after its cosine sort. On three generated corpora a cap of 2 cut the hook's off-target sections by about a fifth (2.31–2.40 to 1.83–1.89 a prompt), at about 2 points of the right section in the top 8 (88.9–89.3% to 86.7%). It did not find more of the documents a question spread over several needed, and nor did maximal marginal relevance at λ 0.9–0.5, so neither is on by default (diversity.ts). Turn the cap on when one document crowds out the rest.

Following links, off by default. A question can need a document it does not describe: "what is the first command of the procedure the outage was recovered with?" matches the incident, and the incident links the procedure. RAGDOWN_FOLLOW_LINKS follows the wikilinks in the own section of a search's best hits: each linked document's two sections closest to the query are placed right after the hit that links it, with that hit's score and their own cosine. They obey every filter of the search, and a link resolves within the hit's folder. On four generated corpora, following the first hit took the procedure's steps from 0% to 37–43% of the top 8 and an upstream service's section from 0% to 63–83% of the top 4 and 93–100% of the top 8, at 3–6 points of top 4 on the other questions and none of first place or top 8. Raising the linked documents' scores instead cost first place and found nothing. The hook gains nothing either way: it gates on each chunk's own cosine, which a linked section's is not near (links.ts). Turn it on for a vault whose notes lean on their links.

The query log, off by default. Search is tuned on generated corpora; RAGDOWN_QUERY_LOG (or Settings → Server) records how it does on real ones. Each ragdown_recall, ragdown_context and web search is a JSON line in query-log.jsonl in the data dir — the query, its filters, each hit's path, lines, similarity and score, the latency, and for the hook what every gate dropped — and each ragdown_read_doc is one more. It holds queries, never document text, stays on the server, and rotates at 10 MB to one .1 file. Settings → Usage sums it up: the gaps, the share of prompts the hook injected into, and the reads that followed a search showing the same document within 10 minutes. HTTP MCP has no session, so that last one is a hint that a hit was used, not proof.

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:

  1. Files whose size and mtime are unchanged are skipped without being read.

  2. A content hash decides whether a changed file is re-embedded.

  3. 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 :3000

The 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

12 tools
ragdown_contextContext for a promptA
Read-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.

NameRequiredDescriptionDefault
resetNoForget the sections already returned for session_id before answering, because the client no longer has them
top_kNoDefault RAGDOWN_HOOK_TOP_K
formatNo'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 modeltext
promptYesThe user's prompt, verbatim
max_charsNoMost characters in the block. Default RAGDOWN_HOOK_MAX_CHARS
min_ratioNoLowest 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_scoreNoLowest cosine similarity to include. Default RAGDOWN_HOOK_MIN_SCORE
session_idNoStable id of the conversation; sections already returned for it are skipped

TDQS

A4.6/5.0
Behavior4/5

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.

Conciseness4/5

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.

Completeness5/5

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.

Parameters4/5

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.

Purpose5/5

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.

Usage Guidelines5/5

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 subfolderA
Destructive

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.

NameRequiredDescriptionDefault
pathYesPath of the document or subfolder relative to the documents root, e.g. 'ops/pg.md' or 'ops'
base_hashNoFor a document: the hash ragdown_read_doc returned. If the file changed since, nothing is deleted
recursiveNoFor a subfolder: also delete everything in it — documents, attachments and any other files

TDQS

A4.8/5.0
Behavior5/5

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.

Conciseness4/5

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.

Completeness5/5

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.

Parameters4/5

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.

Purpose5/5

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.

Usage Guidelines5/5

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 documentA
Destructive

Change part of an existing document without sending the rest of it: each edit swaps one exact passage (old_text) for another (new_text). old_text must match the document character for character, as ragdown_read_doc returned it, and be there exactly once unless replace_all is true, so include enough of the surrounding text to make it unique. Edits are made in order, each on the result of the one before, and if any of them does not apply nothing is written. An empty new_text deletes the passage. A passage that is not found is answered with the nearest one the document does have.

NameRequiredDescriptionDefault
pathYesPath of a Markdown file relative to the documents root, e.g. 'projects/alpha.md'
editsYes
base_hashNoThe hash from ragdown_read_doc. Optional: if the file changed since, nothing is written

TDQS

A4.6/5.0
Behavior5/5

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

Annotations only declare destructiveHint=true and readOnlyHint=false; the description adds substantially more: edits are applied in order on the prior result, the operation is atomic ('if any of them does not apply nothing is written'), empty new_text deletes, and a missing passage returns the nearest match instead of silently failing. That is real behavioral context an agent cannot get from 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.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Four sentences, purpose first, then matching rules, then ordering/atomicity, then edge cases. No filler; every sentence carries operational information.

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

Completeness4/5

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

With no output schema, the description usefully covers failure behavior (nearest-passage response, all-or-nothing writes), but it never says what a successful edit returns — notably the updated hash implied by the base_hash parameter — leaving the agent to infer the success payload.

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

Parameters4/5

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

Schema coverage is 67%, and the description compensates: it explains the character-for-character match requirement, the exactly-once uniqueness constraint, the 'include enough surrounding text' remedy, and the delete-by-empty-new_text convention. It does not address the optional base_hash precondition, which only the schema documents.

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

Purpose5/5

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

States a specific verb+resource ('Change part of an existing document') and the exact mechanism ('each edit swaps one exact passage (old_text) for another (new_text)'). This clearly distinguishes it from the sibling full-document writer ragdown_write and from ragdown_read_doc, which it names as the source of old_text.

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

Usage Guidelines4/5

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

Tells the agent how to obtain old_text ('as ragdown_read_doc returned it') and the condition under which replace_all is needed, and 'without sending the rest of it' implicitly routes the agent away from a full rewrite. It stops short of explicitly naming ragdown_write as the alternative or stating when not to use this tool.

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

ragdown_listList documentsA
Read-only

Browse the documents rather than search them. Returns one line per document: its path, title, tags and last change, who wrote it and in which session when an agent did, and its description when its frontmatter has one. Optionally under a subfolder, with a tag, by who wrote it, or from one session. sort: 'recent' puts the most recently changed first.

NameRequiredDescriptionDefault
tagNoOnly documents with this tag; 'project' also matches 'project/alpha'
sortNopath
limitNo
session_idNoOnly documents written in this conversation, as ragdown_remember recorded it
written_byNoOnly documents an agent wrote ('agent': frontmatter has created_by) or only the user's own ('user')
path_prefixNoOnly documents under this subfolder, relative to the documents root, e.g. 'projects/'

TDQS

A4.7/5.0
Behavior5/5

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 substantial behavioral detail beyond annotations: one line per document, exact fields returned (path, title, tags, last change, author, session, frontmatter description), and what 'recent' sorting does. This is valuable given no output schema exists.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is a single tight paragraph front-loaded with purpose and usage contrast, followed by return format, optional filters, and sort behavior. Every sentence carries information; nothing is wasted.

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

Completeness5/5

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

With no output schema, the description thoroughly explains the return shape (one line per document and which fields appear). It covers filtering options, sorting semantics, and the safe-read nature is covered by annotations. The only minor gap is limit/pagination, which the schema already constrains.

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

Parameters4/5

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

Schema description coverage is 67%. The description accounts for five of six parameters (tag, path_prefix, written_by, session_id, sort) and clarifies sort='recent' behavior that the schema's bare enum does not explain. It omits 'limit' entirely, and adds little beyond the schema for parameters that already have descriptions, so it is good but not exhaustive.

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

Purpose5/5

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

The description states a specific verb and resource ('Browse the documents') and immediately distinguishes it from search. It also lists the exact return format, so an agent knows this is a listing tool, not a retrieval or search tool.

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

Usage Guidelines4/5

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

The opening 'Browse the documents rather than search them' gives clear when-to-use context and implies a search alternative, but it does not name a specific sibling tool or state explicit exclusions. The optional filters are framed as conditions, which helps selection.

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 names the documents that were updated. Never overwrites: if something is already at to, nothing moves. Use this rather than ragdown_write plus a delete, which would break the links.

NameRequiredDescriptionDefault
toYesIts whole new path, name included: 'db/postgres.md', not 'db/'. Missing subfolders on the way are created. A document keeps a Markdown extension
fromYesPath of the document or subfolder relative to the documents root, e.g. 'ops/pg.md' or 'ops'

TDQS

A4.7/5.0
Behavior5/5

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

Annotations only tell the agent it is not read-only, not destructive, and closed-world; the description adds substantive behavior beyond that: wikilink and relative Markdown links across any document are rewritten, nothing is overwritten on collision, and the result names the updated documents. These are the side effects an agent must know before calling a move tool.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Three sentences, front-loaded with what moves and where, then the link-rewrite consequence, then the alternative. No filler and every clause carries decision-relevant information.

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

Completeness5/5

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

With no output schema, the description compensates by stating what the result contains ('names the documents that were updated'), and it covers the collision case and the link side effects. Nothing needed to invoke it correctly is missing.

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

Parameters3/5

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

Schema description coverage is 100%, and both params already document their path format, extension rules, and subfolder creation in the schema. The description adds only the destination-scoping phrase and the no-overwrite rule, which is baseline for a fully documented two-parameter schema.

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

Purpose5/5

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

States a precise verb pair (rename/move) and resource (document or subfolder with its contents) and scopes the destination ('to a new path inside the documents folder'). It also explicitly contrasts itself with the naive ragdown_write + delete approach, so an agent can distinguish it from siblings without opening any schema.

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

Usage Guidelines5/5

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

Gives an explicit when-to-use and a named alternative with the reason it is wrong: 'Use this rather than ragdown_write plus a delete, which would break the links.' It also states the no-overwrite precondition, which tells the agent what to expect when a destination already exists.

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 documentA
Read-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 first line is 'path:start-end of total_lines (hash …)', the hash being the whole file's, for base_hash; the text follows from the second line, exactly as it is in the file.

NameRequiredDescriptionDefault
pathYesPath 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_lineNo
start_lineNo

TDQS

A4.3/5.0
Behavior5/5

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

Annotations cover only read-safety (readOnlyHint=true, openWorldHint=false), and the description adds genuinely new behavior: reads unclipped directly from disk and returns a header line 'path:start-end of total_lines (hash …)' where the hash is the whole file's base_hash, with raw file text from line two. That return contract is exactly what an agent needs and is absent from structured fields.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Front-loads verb, resource and scope in the first sentence, then use cases, then output format. Three dense sentences, all load-bearing; the header/hash explanation is slightly run-on but is necessary since there is no output schema.

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

Completeness4/5

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

With no output schema, the description carries the return-format burden and does so explicitly. Combined with path semantics for recall hits and wikilinks, an agent has enough to call it correctly; the only shortfall is line-range boundary semantics. Not requiring auth or error behavior is acceptable for a read-only disk tool.

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

Parameters3/5

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

Schema coverage is only 33%: path is documented in the schema, but start_line and end_line are bare. The description's 'whole or by line range' explains the intent of the two undocumented integers but not whether the range is inclusive, 1-based, or what happens on out-of-range values, so it only partially compensates.

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

Purpose5/5

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

States a specific verb and resource ('Read a Markdown file from the documents folder') plus scope ('whole or by line range, straight from disk'). An agent can distinguish this from ragdown_recall (search) and ragdown_context without opening the schema.

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

Usage Guidelines4/5

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

Gives two concrete triggers: inspecting context around a ragdown_recall hit and following a [[wikilink]]. It names the sibling workflow that leads here but never states when not to use it (e.g., vs. ragdown_context or ragdown_list), so guidance is strong but not exhaustive.

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

ragdown_recallSearch documentsA
Read-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.

NameRequiredDescriptionDefault
tagNoOnly search documents with this tag (frontmatter tags or inline #tags); 'project' also matches 'project/alpha'
queryYesWhat to look for, as a question or keywords
top_kNo
formatNotext
max_charsNotext format: characters per hit before it is clipped (0 = never)
written_byNoOnly documents an agent wrote ('agent': frontmatter has created_by) or only the user's own ('user')
path_prefixNoOnly search files under this subfolder, relative to the documents root, e.g. 'projects/'

TDQS

A4.1/5.0
Behavior4/5

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.

Conciseness4/5

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.

Completeness4/5

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.

Parameters3/5

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.

Purpose5/5

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.

Usage Guidelines4/5

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.

NameRequiredDescriptionDefault
fullNo

TDQS

A4.7/5.0
Behavior4/5

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.

Conciseness5/5

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.

Completeness5/5

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.

Parameters4/5

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.

Purpose5/5

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.

Usage Guidelines5/5

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.

NameRequiredDescriptionDefault
nameNoFile name under the documents folder, without .md; defaults to <date>-<title-slug>
tagsNo
titleYes
contentYesMarkdown body; the title and date go in frontmatter
session_idNoStable id of the conversation, recorded in the document's frontmatter
supersedesNoPaths 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

A4.2/5.0
Behavior4/5

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.

Conciseness5/5

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.

Completeness4/5

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.

Parameters3/5

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.

Purpose5/5

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.

Usage Guidelines4/5

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 statusB
Read-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.

NameRequiredDescriptionDefault
include_filesNo

TDQS

B3.4/5.0
Behavior3/5

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.

Conciseness4/5

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.

Completeness4/5

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.

Parameters4/5

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.

Purpose4/5

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.

Usage Guidelines2/5

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.

ragdown_writeRewrite or append to a documentA
Destructive

Rewrite or append to an existing document, or create one at a path you choose. To change part of a document, use ragdown_edit, which sends only the passages that change. 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_write 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.

NameRequiredDescriptionDefault
pathYesPath of a Markdown file relative to the documents root, e.g. 'projects/alpha.md'
textYesThe document's new Markdown, or with append, what to add
appendNo
headingNoWith append: add to the end of the section under this heading
base_hashNoThe hash from ragdown_read_doc; required to replace an existing document
session_idNoStable id of the conversation, recorded in the frontmatter of a document this creates

TDQS

A4.7/5.0
Behavior5/5

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

Annotations only say readOnly=false and destructive=true; the description goes well beyond by disclosing that default text replaces the whole file including frontmatter, that base_hash prevents overwriting an unread version, that a created document gets created_by stamped, and that a changed file results in a no-op. This is exactly the destructive-mutation context an agent needs.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Front-loaded with purpose and the edit-vs-write routing, and every sentence carries operational content. It is on the dense side, but the length is justified by the number of behavioral facts it must convey for a destructive mutation.

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

Completeness4/5

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

For a 6-parameter destructive tool with no output schema and no annotations beyond hints, the description covers conflict handling, hash semantics, frontmatter side effects, and append behavior. Only minor gaps remain, such as the exact shape of the returned hash on create vs replace.

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

Parameters4/5

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

Schema coverage is 83%, so baseline is 3, but the description adds real meaning: base_hash origins from ragdown_read_doc and why it is required, the append+heading interaction (end of document vs end of section), and session_id's role via frontmatter recording of created documents.

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

Purpose5/5

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

States specific verbs (rewrite, append, create) and the resource (a document) with scope, and explicitly distinguishes itself from sibling ragdown_edit by naming it. An agent can pick between the two 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.

Usage Guidelines5/5

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

'To change part of a document, use ragdown_edit' gives an explicit alternative plus the condition that selects it. It also states when base_hash is required and what happens on conflict, leaving little to inference.

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. 2 tool updatesv6.6.0
    • Changedragdown_edit7 fields changed
      • removedInput schema / properties / append
        Removed value: -{
        -  "default": false,
        -  "type": "boolean"
        -}
      • changedInput schema / properties / base_hash / description
        Previous value: -"The hash from ragdown_read_doc; required to replace an existing document"New value: +"The hash from ragdown_read_doc. Optional: if the file changed since, nothing is written"
      • addedInput schema / properties / edits
        Added value: +{
        +  "items": {
        +    "additionalProperties": false,
        +    "properties": {
        +      "new_text": {
        +        "description": "What replaces it; empty to delete the passage",
        +        "type": "string"
        +      },
        +      "old_text": {
        +        "description": "The passage to replace, exactly as it is in the document",
        +        "minLength": 1,
        +        "type": "string"
        +      },
        +      "replace_all": {
        +        "default": false,
        +        "description": "Replace every occurrence of old_text rather than require one",
        +        "type": "boolean"
        +      }
        +    },
        +    "required": [
        +      "old_text",
        +      "new_text"
        +    ],
        +    "type": "object"
        +  },
        +  "minItems": 1,
        +  "type": "array"
        +}
      • removedInput schema / properties / heading
        Removed value: -{
        -  "description": "With append: add to the end of the section under this heading",
        -  "type": "string"
        -}
      • removedInput schema / properties / session_id
        Removed value: -{
        -  "description": "Stable id of the conversation, recorded in the frontmatter of a document this creates",
        -  "type": "string"
        -}
      • removedInput schema / properties / text
        Removed value: -{
        -  "description": "The document's new Markdown, or with append, what to add",
        -  "minLength": 1,
        -  "type": "string"
        -}
      • changedInput schema / required
        Previous value: -[
        -  "path",
        -  "text"
        -]New value: +[
        +  "path",
        +  "edits"
        +]
    • Addedragdown_write
  2. 9 tool updatesv5.16.0
    • Changedragdown_backlinks1 field changed
      • changedInput schema / properties / path / description
        Previous value: -"Path of the note relative to the notes root"New value: +"Path of the document relative to the documents root"
    • Changedragdown_context2 fields changed
      • addedInput schema / properties / format
        Added 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"
        +}
      • addedInput schema / properties / reset
        Added value: +{
        +  "default": false,
        +  "description": "Forget the sections already returned for session_id before answering, because the client no longer has them",
        +  "type": "boolean"
        +}
    • Addedragdown_delete
    • Changedragdown_edit4 fields changed
      • changedInput schema / properties / base_hash / description
        Previous 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"
      • changedInput schema / properties / path / description
        Previous 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'"
      • addedInput schema / properties / session_id
        Added value: +{
        +  "description": "Stable id of the conversation, recorded in the frontmatter of a document this creates",
        +  "type": "string"
        +}
      • changedInput schema / properties / text / description
        Previous 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"
    • Changedragdown_list4 fields changed
      • changedInput schema / properties / path_prefix / description
        Previous 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/'"
      • addedInput schema / properties / session_id
        Added value: +{
        +  "description": "Only documents written in this conversation, as ragdown_remember recorded it",
        +  "type": "string"
        +}
      • changedInput schema / properties / tag / description
        Previous value: -"Only notes with this tag; 'project' also matches 'project/alpha'"New value: +"Only documents with this tag; 'project' also matches 'project/alpha'"
      • addedInput schema / properties / written_by
        Added value: +{
        +  "description": "Only documents an agent wrote ('agent': frontmatter has created_by) or only the user's own ('user')",
        +  "enum": [
        +    "agent",
        +    "user"
        +  ],
        +  "type": "string"
        +}
    • Addedragdown_move
    • Changedragdown_read_doc1 field changed
      • changedInput schema / properties / path / description
        Previous 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"
    • Changedragdown_recall3 fields changed
      • changedInput schema / properties / path_prefix / description
        Previous 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/'"
      • changedInput schema / properties / tag / description
        Previous 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'"
      • addedInput schema / properties / written_by
        Added value: +{
        +  "description": "Only documents an agent wrote ('agent': frontmatter has created_by) or only the user's own ('user')",
        +  "enum": [
        +    "agent",
        +    "user"
        +  ],
        +  "type": "string"
        +}
    • Changedragdown_remember3 fields changed
      • changedInput schema / properties / name / description
        Previous 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>"
      • changedInput schema / properties / session_id / description
        Previous 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"
      • changedInput schema / properties / supersedes / description
        Previous 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. 3 tool updatesv5.9.0
    • Addedragdown_backlinks
    • Addedragdown_edit
    • Addedragdown_list
  4. 2 tool updatesv5.2.0
    • Changedragdown_read_doc1 field changed
      • changedInput schema / properties / path / description
        Previous 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"
    • Changedragdown_recall2 fields changed
      • changedInput schema / properties / path_prefix / description
        Previous 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/'"
      • addedInput schema / properties / tag
        Added value: +{
        +  "description": "Only search notes with this tag (frontmatter tags or inline #tags); 'project' also matches 'project/alpha'",
        +  "type": "string"
        +}
  5. 2 tool updatesv4.0.0
    • Addedragdown_context
    • Changedragdown_remember2 fields changed
      • addedInput schema / properties / session_id
        Added value: +{
        +  "description": "Stable id of the conversation, recorded in the note's frontmatter",
        +  "type": "string"
        +}
      • addedInput schema / properties / supersedes
        Added 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"
        +}
  6. 5 tool updatesv1.0.0
    • First observedragdown_read_doc
    • First observedragdown_recall
    • First observedragdown_reindex
    • First observedragdown_remember
    • First observedragdown_stats

TDQS

A4.2/5.0

Scored across 12 tools

Disambiguation4/5

Each tool targets a distinct action (recall=search, list=browse, backlinks=referrers, context=hook injection), and descriptions carefully differentiate close pairs like recall vs context and write vs edit. The only mild overlap is remember vs write, since both create documents, though descriptions clarify remember never overwrites and is intent-specific.

Naming Consistency5/5

Every tool uses the same ragdown_ prefix with snake_case, and verb-based names (read_doc, write, edit, move, delete, list, recall, remember) dominate. The few noun names (backlinks, context, stats) still fit the uniform convention.

Tool Count5/5

12 tools is well within the ideal 3-15 range and each one earns its place in a document-RAG workflow. No redundant or filler tools are present.

Completeness5/5

Full lifecycle coverage exists: create (remember/write), read (read_doc/list), update (edit/write), delete, plus move with link rewriting, search (recall), backlinks, reindex, and stats. This covers the domain thoroughly with no obvious dead ends.

Maintenance

ActivityMaintained
ResponsivenessUnresponsive

Related MCP Connectors

Related MCP Servers

  • A
    license
    Not graded
    quality
    B
    maintenance
    Enables 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.
    0
    1
    MIT
  • A
    license
    Not graded
    quality
    B
    maintenance
    Lets AI agents retrieve source-linked passages from a local folder of Markdown notes or an Obsidian vault, optionally following the explicit links between notes, and can also fetch passages from pinned public GitHub files you configure. It returns bounded evidence packets with original text, file paths and hashes so agents can answer from your material rather than generating content themselves.
    1
    MIT
  • A
    license
    Not graded
    quality
    C
    maintenance
    Enables AI assistants to search, read, and navigate a folder of Markdown notes as a knowledge base, with full-text search, backlinks, tags, metadata, and note resources for conversation attachment.
    1
    MIT