关于 rag-mcp
rag-mcp 是面向 Agent 的本地语料检索 MCP 服务,使用本地嵌入和向量存储搜索 Markdown 文档,并为结果附上来源引用。它还支持增量索引,并限制检索范围在配置的语料目录内。
主要功能
- 01本地嵌入与向量检索
- 02结果附来源、标题和分块索引
- 03增量更新并清理过期分块
- 04按 note 或 handoff 类型筛选
- 05限制语料目录并返回结构化错误
适用场景
- 01在本地 Markdown 文档中查找依据
- 02检索结果需追溯到来源和标题
- 03增量维护不断更新的文档索引
MCP 相关
MCP 服务通过 stdio 暴露 `search_knowledge(query, k, doc_class=None)` 工具,搜索配置的本地向量存储并返回带引用的文本片段。
配置
本地服务的配置在各客户端中通用,复制后粘贴到客户端的 MCP 配置文件即可。
工具
本地服务需要在你的电脑上运行后才能读取工具,维护者暂未提供工具列表。
详细介绍
rag-mcp
一个精简、诚实的基于语料库的 RAG MCP 检索工具。它只有一个工具:
search_knowledge(query, k, doc_class=None),用于嵌入查询、对本地语料库进行向量搜索,并返回带引文的段落(来源 + 标题 + 分块索引),以便追溯答案。
专为适配 mcp-factory 清单模型而构建。 完全本地运行,$0(无需付费的嵌入 API)。
为什么可以安全地用于真实语料库
- 带引文——每条命中结果都包含
source+heading+chunk_index。 - 限定授权范围——结果仅限于已配置的语料库根目录;拒绝越出该目录的来源(绝对路径、
..路径遍历)。 - 故障时平稳降级——存储不可用或为空时会返回结构化错误,不会抛出导致调用 Agent 崩溃的异常。
- 有界——
k被限制在[1, 20];拒绝空查询。 - 依赖版本固定(
requirements.txt)。
技术栈
| 层 | 选型 |
|---|---|
| 嵌入 | 本地 ONNX all-MiniLM-L6-v2(384 维、CPU、$0)——默认。可选择启用 bge-large-en-v1.5(1024 维、512 token 上下文),通过 RAG_MCP_EMBEDDER=bge 配置;详见 CUTOVER.md。 |
| 向量存储 | ChromaDB 嵌入式 PersistentClient(零基础设施) |
| 服务 | mcp Python SDK 2.x,stdio 传输,协议修订版 2026-07-28 |
协议修订版
固定使用 mcp==2.0.0,这是首个实现 MCP 协议修订版 2026-07-28 的 SDK 版本。服务通过同一个 stdio 连接同时支持两个时期的协议——由客户端的首个帧决定:
| 客户端以何种方式发起连接 | 协商的修订版 | 备注 |
|---|---|---|
单次请求 _meta 信封(或 server/discover 探测) | 2026-07-28 | 无状态的单次请求信封;不需要 initialize |
经典的 initialize 握手 | 2025-11-25 | 握手时期的能力上限到此为止——这是预期行为,并非降级 |
2026-07-28 无法通过 initialize 握手达成;这是通过 server/discover 或内联 _meta 版本标记达成的“现代”修订版。时期选择会自动进行,并且以连接为单位——服务端没有相应开关。
tests/test_protocol_version.py 会对两条路径进行端到端断言,因此,如果依赖回滚导致服务悄然退回较旧的修订版,CI 会失败,而不是静默通过。
快速开始
python -m venv .venv && .venv/Scripts/python -m pip install -r requirements.txt
# Ingest a corpus (markdown). Incremental by default: only files whose content
# changed since the last run are re-embedded.
python -m rag_mcp.cli ingest path/to/docs --db ./store.chroma
# Force a rebuild in place (ignore the manifest, re-embed everything)
python -m rag_mcp.cli ingest path/to/docs --db ./store.chroma --full
# One-off query (corpus root = the auth scope)
python -m rag_mcp.cli query "your question" --db ./store.chroma --corpus path/to/docs -k 5
# Same, restricted to one doc_class ("note" or "handoff" -- see "Filtering by
# document class" below)
python -m rag_mcp.cli query "your question" --db ./store.chroma --corpus path/to/docs --doc-class note
# Run as an MCP server (stdio); configure via env first
# RAG_MCP_CORPUS_ROOT, RAG_MCP_DB_PATH, RAG_MCP_COLLECTION, RAG_MCP_EMBEDDER
python run_server.py # operational entrypoint (referenced by mcp.yaml)
python -m rag_mcp # same server, via the packaged console entry point
rag-mcp # after `pip install jaimenbell-rag-mcp` -- console script
保持索引最新(增量导入)
默认采用增量导入。存储目录中的清单会记录每个文件解码文本的 SHA-256;每次运行只会重新嵌入实际发生变化的内容,并清理仅靠 upsert 无法清除的内容(已删除或重命名笔记的分块,以及变短笔记的尾部分块)。
在一个包含 2808 个文件、大小为 26.6 MiB 的在线语料库上测得(bge、CPU):
| 运行 | 耗时 |
|---|---|
| 无变更时运行一次 | 约 0.7 秒(遍历 + 读取 + 对所有文件计算哈希) |
| 全量重新嵌入 | 约 2 小时 33 分钟(50,109 个分块,约 5.5 个分块/秒) |
因此,频繁调度才切实可行:reingest.bat 计划每 15 分钟运行一次,而不是每天凌晨 03:00 运行一次;原先的安排会导致 03:05 写入的笔记在 search_knowledge 中近 24 小时不可见。
只有在运行标识一致时才会信任清单——包括嵌入器、嵌入维度、集合和分块参数。更改其中任何一项,都会重新嵌入所有文件,因此切换嵌入器绝不会出现悄无声息地仅应用一部分的情况。清单缺失、损坏或不匹配,或者存储为空但清单存在时,都会退化为全量重建;不会出现误跳过内容的情况。
快照去重
清单中的跳过机制使用的是整个文件的哈希,因此无法识别真正影响检索的重复内容:每日快照系列(fleet-health-2026-07-23.md 及类似文件)会在哈希仍然变化的文件中,逐字重复前一天的段落。在实际知识库中进行测量后发现,一条 ## RED Bots 状态行在连续十五个文件中有五种不同的取值,并以字节完全相同的副本挤占前 10 名结果,把解释该状态行的文档挤到了第 16 名。
因此,摄取过程还会在分块级别去重,但仅限于带日期的系列,且只与紧邻的前一份快照进行比较。首次出现的内容始终会被嵌入,并保留自己的日期作为 source;之后逐字重复的内容不会被嵌入,而是将其日期追加到保留内容的 repeat_dates 元数据中,search_knowledge 会将其作为 snapshot_date / also_unchanged_on / snapshots_covered 返回。因此,仍然可以回答“日期 X 的内容是什么”——这正是要对系列进行去重,而不是将其排除的原因。如果某个取值发生变化,之后又恢复原状,则仍会保留,因为它是新事实,而非重复内容。
范围有限,并在 rag_mcp/snapshots.py 中通过规则明确说明:文件名以 -YYYY-MM-DD 结尾;至少有 3 个此类文件共享同一目录和文件名前缀;且标题相同、内容逐字节一致。在实际语料库中,这涉及 2,814 个文件中的 316 个,并会将 50,428 个分块中的 842 个合并(系列分块的 17.5%,整个语料库的 1.67%),不会影响任何普通笔记。使用 --no-snapshot-dedupe 禁用。
--full 会原地重建(忽略清单,但保留存储);--clean 会先删除存储。两者仍会写入清单,因此下一次运行会很快。每周运行的 reingest-clean.bat 仍作为双重保险的重置方式。
作为 MCP 服务
通过 mcp.yaml 注册(使用 mcp-factory 的 Manifest 加载器进行验证)。工具为
search_knowledge(query, k, doc_class=None);它会读取由 RAG_MCP_* 环境变量配置的存储。
按文档类别筛选
每个分块的元数据都包含一个 doc_class,在摄取时设置。当文档的 YAML frontmatter 中包含 type: handoff 或 tags 条目 handoff(不区分大小写)时,类别为 "handoff";或者——由于文档的 frontmatter 是可选的,而作为会话镜像的文档往往没有 frontmatter,因此也会用此规则标记——当文件直接位于 handoff_mirror_dir 下(默认值为 context/;可通过 ingest() 的
handoff_mirror_dir=/handoff_mirror_basenames= 参数,或 CLI 的
--handoff-mirror-dir/--handoff-mirror-basename 标志进行配置),且文件名为 handoff.md /
active.md / resume.md,或符合带边界的“handoff”文件名标记(例如
handoff-2026-09-03.md、morning-dispatch-handoff.md)时,也会设置为该类别——绝不会仅因文件名中包含子字符串而匹配,因此仅仅提及该词的标题(handoff-skill-redesign-spec.md)仍为 "note"。
其他所有文档默认均为 "note"。传入 doc_class 可将查询限定在某一类别,例如,
从知识查询中排除 Agent 自身的会话/交接记录:
from rag_mcp.search import search_knowledge
search_knowledge(
"what did we decide about X", k=5, store=store, corpus_root=root, doc_class="note",
)
doc_class 是经过验证、区分大小写的枚举——"note" 或 "handoff"(参见
rag_mcp.search.ALLOWED_DOC_CLASSES)——也可以省略以不进行筛选。超出该集合的值(大小写错误、拼写错误或任何其他类型)会返回结构化的 invalid_doc_class 错误,其格式与 invalid_query 相同。语法有效但在当前存储中没有匹配项的 doc_class,仍会以空结果软失败,且 ok: true。
增量摄取运行会将 doc_class(以及其他元数据架构变更)回填到已嵌入且内容未变的分块中,而无需重新嵌入——参见
ingest.CURRENT_METADATA_VERSION。完全早于此功能的存储,只需进行一次增量运行,每个分块就会获得正确的 doc_class,无需执行完整的 --clean 重建。
测试
python -m pytest # 272 passed
此计数为收集到的测试总数,CI 会根据实时 pytest 摘要强制校验
(scripts/check_readme_counts.py,已接入 .github/workflows/ci.yml)。其中
一个测试受平台限制——tests/test_reindex_handle_release.py 中的
WINDOWS_ONLY 仅在 Windows 上运行——因此 Linux CI 对同一组 272 个测试
报告 271 个通过、1 个跳过;门禁会将通过数与跳过数之和与此处声明的计数进行比较,
而非仅比较通过数,因此这一预期的跳过不会被视为偏差。
目录结构
rag_mcp/
chunking.py heading-scoped, overlapping markdown chunks
store.py VectorStore (Chroma) + Embedder protocol (MiniLM default + BgeEmbedder opt-in + offline HashEmbedder)
ingest.py idempotent ingest pipeline with source/heading/chunk-index metadata; incremental by default
manifest.py per-file content hashes -> skip unchanged files, prune stale chunks
search.py search_knowledge: cited, auth-scoped, fail-soft, bounded
server.py MCP stdio server exposing search_knowledge
config.py env-driven Config
cli.py ingest + query CLI
__main__.py console entrypoint (`python -m rag_mcp` / `rag-mcp` script); fails loud on missing config
run_server.py operational MCP entrypoint (referenced by mcp.yaml)
mcp.yaml manifest (mcp-factory model)
商业支持
由 Jaimen Bell 维护。如需生产环境 MCP 集成、自定义服务或 Agent 可靠性方面的工作,请访问 jaimenbell.dev。
正在构建自己的 MCP 服务?MCP Starter Kit 提供模板、构建指南,以及发布此项目过程中的打包经验分享。
mcp-name: io.github.jaimenbell/rag-mcp
评论
一个开放协议,规定了 AI 应用如何以统一方式连接外部工具与数据源。客户端负责与模型对话,MCP 服务负责提供工具、资源和提示模板,两者通过标准消息通信,同一个服务可以被不同客户端复用。