mcpField
R
rag-mcp

rag-mcp

本地服务
github.com/jaimenbell记忆与知识11 周前更新

关于 rag-mcp

rag-mcp 是面向 Agent 的本地语料检索 MCP 服务,使用本地嵌入和向量存储搜索 Markdown 文档,并为结果附上来源引用。它还支持增量索引,并限制检索范围在配置的语料目录内。

主要功能

  1. 01本地嵌入与向量检索
  2. 02结果附来源、标题和分块索引
  3. 03增量更新并清理过期分块
  4. 04按 note 或 handoff 类型筛选
  5. 05限制语料目录并返回结构化错误

适用场景

  1. 01在本地 Markdown 文档中查找依据
  2. 02检索结果需追溯到来源和标题
  3. 03增量维护不断更新的文档索引

MCP 相关

MCP 服务通过 stdio 暴露 `search_knowledge(query, k, doc_class=None)` 工具,搜索配置的本地向量存储并返回带引用的文本片段。

配置

本地服务的配置在各客户端中通用,复制后粘贴到客户端的 MCP 配置文件即可。

mcp.json · 通用配置
{
"mcpServers": {
"jaimenbell-rag": {
"command": "uvx",
"args": [
"jaimenbell-rag-mcp"
]
}
}
}
启动命令uvx jaimenbell-rag-mcp

本地服务需要在你的电脑上安装运行环境(如 Node.js 18+)。各客户端的配置文件位置不同,请参考客户端文档。

安装包
pypijaimenbell-rag-mcp0.1.0

工具

还没有获取到工具列表

本地服务需要在你的电脑上运行后才能读取工具,维护者暂未提供工具列表。

详细介绍

rag-mcp

CI

一个精简、诚实的基于语料库的 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

评论

支持 Markdown

第一次接触 MCP?

从协议是什么,到在客户端里装上第一个服务,这几条回答覆盖了大多数入门问题。

一个开放协议,规定了 AI 应用如何以统一方式连接外部工具与数据源。客户端负责与模型对话,MCP 服务负责提供工具、资源和提示模板,两者通过标准消息通信,同一个服务可以被不同客户端复用。