docs-rag-mcp
docs-rag-mcp
它并不是一个在你文档之上的更好搜索引擎,而是对智能体被允许相信之物的过滤器: 被取代的决策会消失,阈值会说出“我不知道”而不是凭空编造,而且你始终能看到某条结果为什么被收录。
一个 MCP 服务器,将某个 markdown 文档文件夹以单一搜索工具 search_notes 暴露给任何 MCP 客户端(Claude Code、Claude Desktop、Codex、Cursor、Zed…)。一切都在你的机器上运行:嵌入通过 Ollama 完成,索引时和查询时都不会有任何数据离开你的计算机。
npx -y docs-rag-mcp init # guided questions -> config.json
npx -y docs-rag-mcp index # builds the index正在编写你要搜索的文档? 你如何组织 markdown 文件,决定了它被找到的难易程度。请参阅 AUTHORING.md —— 一份关于如何编写易于检索的文档的简短指南。花五分钟读完,就能改善每一次搜索。
它真正与众不同的地方
这个工具所做的大部分事情,其他本地 RAG 服务器也能做到。真正罕见的部分是文档生命周期:索引知道某份文档已被取代,并据此行动。
一份在 frontmatter 中标记为
status: superseded的文档默认不再被返回。当它确实被返回时——因为你明确要求了历史记录——它会带有[superseded → reference/auth.md; 2026-03-01]标签返回,因此接替它的文档会与它一起出现。低于
minScore时,工具会返回 “没有相关结果(最佳得分 0.41,阈值 0.55)”,而不是把找到的最接近的噪声交出去。拿到错误匹配的智能体会把它当作事实;人类则会犹豫。阈值就是这种犹豫所在。每条命中都会显示它为什么在这里:
[semantic 0.712]、[both 0.712]、[exact match]。不是一个你必须信任的黑盒。
其他人都做文件新鲜度——重新同步、重新索引、监视变化。没有人做真相新鲜度。这才是这个工具的全部意义。
approach | handles document lifecycle? | |
docs-rag-mcp | dense + lexical, SQLite, Ollama | yes — |
| hybrid BM25+dense, AST chunking, Milvus | no |
| semantic+keyword, LanceDB, PDF/DOCX/MD | no |
| markdown, heading chunking, Milvus | no |
| sqlite-vec + Ollama, graph-aware | no |
| pgvector | no |
其余——仅本地运行、按标题分块、SQLite 存储、增量索引——在该领域只是基本门槛,而不是差异化优势。它们列在下面的功能表中,而不是宣传里。
Related MCP server: Notes RAG MCP Server
什么时候你并不需要它
诚实版本,因为这个领域已经变了:
对于符号的精确匹配,你的智能体已经有 grep,而且 grep 更快、不需要索引。函数名、错误码、配置键:不要为这些构建向量索引。
在文档只有几十份以下时,智能体式搜索就够了。 智能体读取文件树、grep、打开看起来相关的内容。这样可行。
Anthropic 曾在 Claude Code 中内置带向量数据库的 RAG,随后又移除了它(2025 年 5 月),因为智能体式搜索表现更好。Cursor、Windsurf、Cline 等也走了同样的路。假装不是这样是不诚实的。
经得起这些考验、也正是它存在的原因是:grep 只能找到你叫得出名字的东西。 当文档称其为“token refresh window”、而你叫它“session expiry”时,grep 一无所获,语义搜索则返回该文档。而在一个冗长、多层级的话料库上,语义检索比让智能体遍历文件树花费更少的往返次数和更少的 token——不是“更好的结果”,而是更便宜的结果。
因此它的适用场景狭窄而具体:一份冗长、多层级、由 markdown 编写的决策、规范和 ADR 语料库,其中包含被取代的材料,而你需要智能体不复活一个已死的决策。
前置条件
Node.js ≥ 22.5 — 索引使用内置的
node:sqlite模块,因此无需构建原生依赖。根据你的 Node 版本,你可能会在 stderr 上看到一行警告(ExperimentalWarning: SQLite is an experimental feature);它无害。Ollama 且带有嵌入模型。 构建索引以及每次搜索都需要该模型——查询会在运行时即时嵌入,因此只要 MCP 服务器在使用,Ollama 就必须在运行,而不仅仅是在索引期间。
# install Ollama from https://ollama.com, then:
ollama pull bge-m3安装
发布出来的包无需克隆、无需构建:
npx -y docs-rag-mcp init # guided questions -> writes config.json
npx -y docs-rag-mcp index # builds the index如果你打算使用自动重新索引钩子,请改为全局安装:
npm i -g docs-rag-mcpnpx 会在每次启动时重新解析包,并在首次使用时下载。这对 MCP 服务器无关紧要,因为它每个会话只启动一次;但该钩子被设计为零秒退出,并在每次文件编辑后运行——全局安装可以完全消除这一开销。docs-rag scaffold 会检测全局安装,并自行写入更短的命令形式。
更喜欢手工编辑配置?把 config.example.json 复制为 config.json 并设置 vaultPath。其他一切都有合理的默认值。
git clone https://github.com/andreaselmi/docs-rag-mcp
cd docs-rag-mcp
yarn install
yarn setup # -> config.json
yarn index
yarn build # compiles to dist/yarn 脚本与各子命令一一对应(setup→init、serve、index、search、scaffold)。从源代码检出搭建项目时,请传入 --local:它会在生成的文件中写入 node /abs/path/dist/server.js,而不是一条会解析到已发布包、而非你当前工作树的 npx 命令。
命令
docs-rag init interactive wizard, writes config.json
docs-rag scaffold <dir> give a project its own scoped instance
docs-rag index build or update the index
docs-rag search "question" query the index from the terminal
docs-rag serve run the MCP server on stdio
docs-rag hook Claude Code hook entry point (auto re-index)每个命令都接受 --config <path>。
在终端中测试检索
docs-rag search "how do we handle authentication"你会看到匹配的章节,每一节都带有它被返回的原因:
[semantic 0.712] reference/auth.md › Auth > How the client refreshes the token
[both 0.688] decisions/2026-01-session-length.md › Session length [2026-01-14]
[exact match] reference/errors.md › Error codes > ERR_TOKEN_EXPIRED这正是 MCP 客户端将使用的检索逻辑——请先在这里验证。
要为单次查询跳过某些文件夹,请传入 --exclude(逗号分隔的路径片段,不区分大小写):
docs-rag search "how do we handle auth" --exclude archive,draftsMCP 工具在 search_notes 上以可选的 exclude 数组暴露同样的功能,因此你可以在对话中要求 “搜索,但忽略 archive 文件夹”。
某些文件夹(默认是 archive、plans —— 见 defaultExclude)会在每次查询时被跳过,而不仅仅是你传入 --exclude 时。要为某一次查询仍然搜索它们,请传入 --all(CLI)或 searchAll: true(工具参数)。同样的标志也会重新纳入在 frontmatter 中标记为 superseded/archived 的文档,这些文档在默认搜索中同样被隐藏。
两条检索轨道,以及标签
稠密嵌入恰恰在规范文档里满是的东西上表现薄弱:缩写、错误码、函数名、版本号。因此每次查询都会运行两条轨道并合并结果。
语义轨道按余弦相似度对所有分块排序,并应用阈值。
词汇轨道是一种全文检索(FTS5),仅限于你查询中的稀有词。“稀有”是相对你自己的索引衡量的:一个词若出现在至多
max(5, lexicalMaxDocFreq × total chunks)个分块中,并且不超过其中一半,就符合条件。对每个词都运行 FTS 会让常见词的匹配淹没结果;稀有性门槛正是保持精确的关键。
每条命中上的标签会告诉你它由哪条轨道放入:
label | meaning |
| found by meaning, cosine score |
| found by both tracks — the strongest signal |
| lexical only. No score shown on purpose: the cosine value is not the reason this hit is here, and printing it would suggest otherwise |
仅词汇命中数量有限(2 个槽位),并且总是排在语义命中之后,因此稀有词匹配可以为答案作补充,但永远不会接管答案。
嵌入模型
Ollama 上可用的任何嵌入模型都可以——在配置中设置 embedModel。默认是 bge-m3,阈值也随附并针对它调校。
现在更改 embedModel 会强制完全重建。 索引会记录是哪个模型构建了它;用不同模型打开会被拒绝,而不是悄悄用不兼容的向量计算得分。早期版本会静默混合它们,并在没有任何错误的情况下返回错误结果。
有些模型需要在输入上加任务前缀(nomic-embed-text 需要 search_query: / search_document:)。这些模型记录在一个小注册表中,会自动为你应用。如果你选择的模型不在注册表中,docs-rag index 会明确说出来——搜索仍然可用,但没有人验证过该模型的前缀约定或阈值。
校准报告
每次索引运行结束时,你会得到一行类似这样的输出:
Calibration: background noise p99 = 0.421 over 500 random pairs -> suggested minScore 0.45 (in use: 0.55, from the model registry).它会从你自己的话料库中随机抽取成对的分块,这些分块按定义不相关,并报告它们仍然达到的相似度得分。这就是模型的噪声底:任何得分低于它的结果,都与两份互不相干的文档无法区分。
把它当作下界,而不是照抄的设置。如果建议值远高于你配置的 minScore,说明你的阈值正在放行噪声。如果远低于,你可以放心地更严格。配置值始终优先——报告绝不会覆盖你的选择,它只告诉你它测量到了什么。
每个项目一个实例(推荐)
你通常希望每个项目有一个独立的知识库。你并不需要为每个项目复制一份这个工具——安装一次,然后给每个项目各自的配置,并在项目作用域注册:
docs-rag scaffold /path/to/some-project # asks a few questions (or pass flags)对于该项目,它会写入:
some-project/.rag/config.json— 它的配置(vaultPath是项目根目录;索引落在旁边的.rag/index.db)。在已搭建过的项目上重新运行 scaffold 会保留此文件:调校过的pathBoosts和阈值会保留,只有你在该次运行中作为标志显式传入的键才会被覆盖。some-project/.mcp.json— 一个项目作用域的 MCP 注册。其中已有的服务器会被保留。由于该命令不包含任何机器特定路径,此文件可以提交:任何克隆仓库的人无需手工安装任何内容,就能获得对其文档的搜索。将
.rag/index.db*和旧的.rag/index.json*追加到项目的.gitignore。使用
--hook时:some-project/.claude/settings.local.json,一个 Claude Code 钩子,会在每次 markdown 编辑后在后台重新索引(见下文)。
然后:
docs-rag index --config /path/to/some-project/.rag/config.json非交互式,可跨多个仓库脚本化:
docs-rag scaffold /path/to/proj --name proj-docs --include "**/docs/**/*.md" \
--desc "What's in this project's docs" --hook --yes--config 如何解析
每个命令都接受 --config。配置文件“携带”它自己的索引(相对 indexPath 会解析到配置文件旁边),因此各实例互不干扰。解析顺序:
绝对路径始终优先。
CLAUDE_PROJECT_DIR,Claude Code 会将它设置为其启动的服务器和钩子环境中的项目根目录。从工作目录向上查找,直到第一个存在该路径的祖先目录。这就是让
--config .rag/config.json在没有设置自身环境变量的客户端上也能工作的原因。否则,使用工作目录。
完全不使用 --config 时,同样的向上查找会寻找 .rag/config.json,因此在脚手架项目内的任何位置运行 docs-rag search "…" 都能直接生效。
不要自己把 ${CLAUDE_PROJECT_DIR} 写入 .mcp.json 的 args:Claude Code 不会在那里展开变量,因此它会被原样传递。
接入 MCP 客户端
Claude Code
项目作用域是 docs-rag scaffold 所配置的。如果你想要的是在 每次 会话中都存在的知识库,则在用户作用域下注册:
claude mcp add work-docs -s user -- npx -y docs-rag-mcp serve --config ~/vaults/work.json用 claude mcp list 检查是否已连接,然后问类似 “search_notes:我们为什么选择 X?” 的问题。
Codex CLI、Cursor、Zed 以及其他 MCP 客户端
该服务器是标准 stdio MCP,因此任何支持该协议的客户端都可以运行它。在 Codex CLI 的 ~/.codex/config.toml 中:
[mcp_servers.docs-search]
command = "npx"
args = ["-y", "docs-rag-mcp", "serve", "--config", "/absolute/path/to/.rag/config.json"]Cursor 和 Zed 在自己的 MCP 设置中使用相同的命令和参数。
这里应使用绝对路径的 --config,因为它不依赖任何东西。相对路径也可以通过上述目录向上查找来工作——但该路径是经过构造设计和单元测试验证的,并未针对那些客户端进行测试。如果你在其中之一上运行它,欢迎提交报告。
为每个实例设置各自的 serverName 和 toolDescription:模型会读取该描述来决定是否调用此工具,因此要描述这个知识库里有什么,而不是这个工具做什么。
保持索引最新
索引是一种构建产物:index.db,一个 WAL 模式的 SQLite 数据库,带有 -wal 和 -shm 伴生文件。编辑文档后,重新运行 docs-rag index——它是增量的,只会重新嵌入 mtime 发生变化的文件。正在运行的 MCP 服务器会自动拾取每次重新索引,无需重启。过期的索引会用过期内容作答——比未命中更糟。
自动重新索引(仅限 Claude Code,可选启用)
使用 --hook 搭建项目时,Claude Code PostToolUse 钩子 会在 Claude 写入或编辑该项目中的 markdown 文件时在后台重新运行增量索引器——合并为最多每 30 秒运行一次,不会遗漏任何编辑。在 Claude Code 之外所做的编辑仍需要手动运行。
该钩子位于项目的 .claude/settings.local.json(个人设置,不提交);删除 PostToolUse 条目即可禁用它。如果后台运行无法工作——最常见的原因是 Ollama 没有运行——你在会话中每个片段只会收到一次警告,而不是每次保存都收到,详细信息会写入 .rag/hook.log。其他 MCP 客户端不会运行 Claude Code 钩子:在这些客户端中,请手动重新索引。
升级现有索引
升级后的第一次 docs-rag index 是一次一次性的完整重新嵌入,而不是通常的亚秒级空操作:schema 新增了模型标识表和 FTS5 表,旧向量无法迁移。如果启用了钩子,它会在你第一次编辑时在后台启动,因此预计第一次运行需要几分钟而不是一秒。
从原始 index.json 升级:它会被重命名为 index.json.bak,并从零重建。对结果满意后,删除 .bak 文件即可。
配置参考
字段 | 默认值 | 备注 |
| —(必需) | 要索引的文件夹;绝对路径,或相对于配置文件 |
|
| |
|
| |
|
| |
|
| 任何 Ollama 嵌入模型;更改它会强制重建 |
|
| 相对于配置文件;SQLite(WAL 模式) |
|
| 默认结果数量 |
|
| 客户端中显示的 MCP 服务器名称 |
| generic | 告诉模型这个知识库包含什么 |
调优检索
五个可选字段控制返回哪些结果(括号内为默认值):
defaultExclude(["archive", "plans"])——每次查询都会跳过的路径子字符串。调用者可以在单次查询中通过searchAll: true(CLI:--all)选择重新包含这些路径;同样的标志也会重新包含在 frontmatter 中标记为superseded/archived的文档。minScore(0.55)——绝对余弦下限。低于该值时,结果会被丢弃,工具会以被拒绝的最佳得分回答 "No relevant results",而不是返回噪声。如何选择该值,请参阅上面的校准报告。relativeCutoff(0.88)——丢弃得分低于最佳幸存得分这一比例的结果。lexicalMaxDocFreq(0.01)——查询词必须有多罕见才会触发词汇检索轨道:如果它出现在最多max(5, ratio × total chunks)个块中,则符合条件;如果它出现在超过一半的语料库中,则永远不符合。提高该值可让更多词通过(更多精确匹配,更多噪声);降低该值可让该轨道保留给真正不常见的标识符。pathBoosts({})——按路径子字符串的仅排序乘数,例如{"reference/": 1.15, "decisions/": 1.1},用于让精选文档优先于笔记。乘数永远不会覆盖阈值,也永远不会改变报告的分数。
minScore 和 relativeCutoff 出厂时为 bge-m3 调优。如果使用其他模型,请先运行 docs-rag index 并阅读校准行,再信任默认值。
工作原理
大约 2,000 行 TypeScript,没有构建魔法,没有框架。检索路径本身——分块、嵌入、排序——约占其中 500 行,你可以从头到尾阅读。
src/chunk.ts——按标题拆分 markdown,保留标题面包屑,从嵌入文本中剔除代码围栏,拆分过大的章节。src/frontmatter.ts——解析status/date/superseded_by,并将它们从被索引的文本中剥离。src/embed.ts+src/models.ts——Ollama 的/api/embed,以及按模型区分的前缀注册表。src/store.ts——SQLite 模式、版本管理、模型标识、FTS5 镜像。src/index-docs.ts——遍历 vault,分块,嵌入,增量写入。src/calibrate.ts——建议的minScore背后的噪声底线测量。src/lexical.ts——稀有度门控:哪些查询词值得进行精确搜索。src/search.ts——嵌入查询,按余弦排序,应用阈值,排除和增强,合并词汇检索轨道。src/server.ts——通过 stdio 暴露search_notes的 MCP 服务器。src/scaffold.ts——每个项目的实例生成器。src/hook.ts——自动重新索引钩子(防抖、锁、失败通知)。src/cli.ts——docs-rag可执行文件;只有一个子命令表,别无其他。
刻意不在范围内
两个轨道的倒数排名融合。RRF 会丢弃绝对分数,而
minScore——“我不知道”的阈值——正是建立在绝对分数之上的。保持轨道分离可以保留这一承诺。单个实例中的多个 vault(改用多个配置)。
可插拔的嵌入提供商——仅支持 Ollama,以保持仅本地的承诺。
面向人类的 UI、协作、多文档综合。它的存在是为了让处理你代码的代理了解你的决策,而不是作为你的团队阅读文档的地方。
许可证
MIT——参见 LICENSE。
This server cannot be deployed
Maintenance
Related MCP Connectors
Serve a folder of Markdown notes as an MCP server: hybrid search, reading, and sourced answers.
Search, read, and write your Apple Notes from ChatGPT/Claude via a local Mac agent + MCP relay.
Search and reason over your Obsidian-style Markdown vault, right from ChatGPT.
- TaprootOAuthcom.taproothq
Persistent memory layer for AI tools. Save and recall notes across Claude and other MCP clients.
Related MCP Servers
- AlicenseNot gradedqualityBmaintenanceEnables Claude Code to search and retrieve from a local knowledge base of markdown notes using hybrid semantic+keyword search, keeping data entirely offline.6 npmMIT
- FlicenseNot gradedqualityAmaintenanceEnables semantic search over personal markdown notes by indexing them into a vector database and exposing search, reindex, and status tools via MCP.-
- AlicenseNot gradedqualityCmaintenanceProvides a search_docs tool for MCP clients to perform local semantic search over indexed files using embeddings, enabling grounded answers from personal notes.MIT
- AlicenseNot gradedqualityAmaintenanceProvides hybrid search (BM25 + vector) and staleness reporting for local markdown notes, exposing search and indexing tools to AI assistants via MCP.14 npmMIT