Skip to main content
Glama

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

yesstatus/superseded_by, default-excluded archives, honest no-results

zilliztech/claude-context

hybrid BM25+dense, AST chunking, Milvus

no

shinpr/mcp-local-rag

semantic+keyword, LanceDB, PDF/DOCX/MD

no

Zackriya/MCP-Markdown-RAG

markdown, heading chunking, Milvus

no

proofgeist/obsidian-notes-rag

sqlite-vec + Ollama, graph-aware

no

patakuti/local-knowledge-rag-mcp

pgvector

no

其余——仅本地运行、按标题分块、SQLite 存储、增量索引——在该领域只是基本门槛,而不是差异化优势。它们列在下面的功能表中,而不是宣传里。

Related MCP server: recall-mcp

什么时候你并不需要它

诚实版本,因为这个领域已经变了:

  • 对于符号的精确匹配,你的智能体已经有 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-mcp

npx 会在每次启动时重新解析包,并在首次使用时下载。这对 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 脚本与各子命令一一对应(setupinitserveindexsearchscaffold)。从源代码检出搭建项目时,请传入 --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,drafts

MCP 工具在 search_notes 上以可选的 exclude 数组暴露同样的功能,因此你可以在对话中要求 “搜索,但忽略 archive 文件夹”

某些文件夹(默认是 archiveplans —— 见 defaultExclude)会在每次查询时被跳过,而不仅仅是你传入 --exclude 时。要为某一次查询仍然搜索它们,请传入 --all(CLI)或 searchAll: true(工具参数)。同样的标志也会重新纳入在 frontmatter 中标记为 superseded/archived 的文档,这些文档在默认搜索中同样被隐藏。

两条检索轨道,以及标签

稠密嵌入恰恰在规范文档里满是的东西上表现薄弱:缩写、错误码、函数名、版本号。因此每次查询都会运行两条轨道并合并结果。

  • 语义轨道按余弦相似度对所有分块排序,并应用阈值。

  • 词汇轨道是一种全文检索(FTS5),仅限于你查询中的稀有词。“稀有”是相对你自己的索引衡量的:一个词若出现在至多 max(5, lexicalMaxDocFreq × total chunks) 个分块中,并且不超过其中一半,就符合条件。对每个词都运行 FTS 会让常见词的匹配淹没结果;稀有性门槛正是保持精确的关键。

每条命中上的标签会告诉你它由哪条轨道放入:

label

meaning

[semantic 0.712]

found by meaning, cosine score 0.712

[both 0.712]

found by both tracks — the strongest signal

[exact match]

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 会解析到配置文件旁边),因此各实例互不干扰。解析顺序:

  1. 绝对路径始终优先。

  2. CLAUDE_PROJECT_DIR,Claude Code 会将它设置为其启动的服务器和钩子环境中的项目根目录。

  3. 从工作目录向上查找,直到第一个存在该路径的祖先目录。这就是让 --config .rag/config.json 在没有设置自身环境变量的客户端上也能工作的原因。

  4. 否则,使用工作目录。

完全不使用 --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,因为它不依赖任何东西。相对路径也可以通过上述目录向上查找来工作——但该路径是经过构造设计和单元测试验证的,并未针对那些客户端进行测试。如果你在其中之一上运行它,欢迎提交报告。

为每个实例设置各自的 serverNametoolDescription:模型会读取该描述来决定是否调用此工具,因此要描述这个知识库里有什么,而不是这个工具做什么。

保持索引最新

索引是一种构建产物: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 文件即可。

配置参考

字段

默认值

备注

vaultPath

—(必需)

要索引的文件夹;绝对路径,或相对于配置文件

includeGlobs

["**/*.md"]

excludeGlobs

["**/node_modules/**"]

ollamaUrl

http://localhost:11434

embedModel

bge-m3

任何 Ollama 嵌入模型;更改它会强制重建

indexPath

index.db

相对于配置文件;SQLite(WAL 模式)

topK

8

默认结果数量

serverName

docs-search

客户端中显示的 MCP 服务器名称

toolDescription

generic

告诉模型这个知识库包含什么

调优检索

五个可选字段控制返回哪些结果(括号内为默认值):

  • defaultExclude["archive", "plans"])——每次查询都会跳过的路径子字符串。调用者可以在单次查询中通过 searchAll: true(CLI:--all)选择重新包含这些路径;同样的标志也会重新包含在 frontmatter 中标记为 supersededarchived 的文档。

  • minScore0.55)——绝对余弦下限。低于该值时,结果会被丢弃,工具会以被拒绝的最佳得分回答 "No relevant results",而不是返回噪声。如何选择该值,请参阅上面的校准报告。

  • relativeCutoff0.88)——丢弃得分低于最佳幸存得分这一比例的结果。

  • lexicalMaxDocFreq0.01)——查询词必须有多罕见才会触发词汇检索轨道:如果它出现在最多 max(5, ratio × total chunks) 个块中,则符合条件;如果它出现在超过一半的语料库中,则永远不符合。提高该值可让更多词通过(更多精确匹配,更多噪声);降低该值可让该轨道保留给真正不常见的标识符。

  • pathBoosts{})——按路径子字符串的仅排序乘数,例如 {"reference/": 1.15, "decisions/": 1.1},用于让精选文档优先于笔记。乘数永远不会覆盖阈值,也永远不会改变报告的分数。

minScorerelativeCutoff 出厂时为 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

A
license - permissive license
Not graded
quality - not tested
C
maintenance

Maintenance

Maintainers
Response time
Release cycle
Releases (12mo)
Commit activity

Resources

Unclaimed servers have limited discoverability.

Looking for Admin?

If you are the server author, to access and configure the admin panel.

Related MCP Servers

  • F
    license
    Not graded
    quality
    D
    maintenance
    Enables managing and searching markdown notes with semantic search, question answering, and note generation, and provides an MCP server for GitHub Copilot integration.
    4
  • A
    license
    A
    quality
    D
    maintenance
    Turns a local folder of notes and documents into a searchable knowledge base for AI assistants via MCP, enabling semantic search, reading, and adding notes entirely on-device.
    4
    9
    MIT
  • F
    license
    Not graded
    quality
    C
    maintenance
    Enables AI agents to list, search, read, and append to Markdown notes through MCP tool calls, making it easy to interact with a second brain folder.

View all related MCP servers

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.

View all MCP Connectors

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/andreaselmi/docs-rag-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server