本文是「从零搭建私人 RAG 实战」专栏的第五篇。上一篇《从零搭建私人 RAG 实战:用户问题向量化与语义检索》完成了用户问题的向量化,并从 BADR、FADR 向量表中检索相关知识片段;本篇继续介绍 Claude Code 如何通过本地规则、Skill 和 MCP 调用 CorpRAG,实现知识库的按需查询。
在完成文档切分、向量化和 MCP Server 开发后,我们已经具备了一个可以被外部调用的知识库服务。
不过,服务能够运行,并不代表 AI 客户端能够稳定、准确地使用它。
从"服务可用"到"客户端按需调用",中间还需要解决几个问题:
- Claude Code 如何发现并连接 MCP Server?
- 用户提出问题后,客户端如何判断该查询哪个知识库?
- 如何通过文档类型缩小检索范围?
- MCP 返回的知识片段如何参与大模型的最终回答?
- 如何避免把完整的内部文档长期放入模型上下文?
本文以 Claude Code 调用 CorpRAG 为例,介绍"客户端配置---规则路由---工具发现---向量检索---答案生成"的完整链路。
本文只讨论客户端接入方式和通用调用机制。示例不包含任何真实的内部决策、业务规则或知识库正文。
一、完整调用链
整个流程可以概括为:
text
用户
│
│ 在 Claude Code 中提出问题
▼
Claude Code
│
│ 根据 CLAUDE.local.md 判断是否需要查询知识库
│ 命中决策场景 → 加载 Skill
▼
Skill(如 decision-guide)
│
│ 识别决策类型(FADR/BADR/混合)
│ 将宽泛问题改写为具体查询
│ 调用 MCP 工具
▼
MCP 工具调用
│
│ mcp__corprag__fr_query({ question, docType })
▼
CorpRAG MCP Server
│
│ 参数校验 + 知识库路由
▼
向量检索
│
│ 问题向量化 → 查询对应向量表 → 返回相关片段
▼
MCP Server
│
│ 格式化片段、来源和文档类型
▼
Claude Code
│
│ 将用户问题、检索结果和当前上下文交给大模型组合
▼
最终回答
这里涉及两种不同模型的职责:
- Embedding 模型负责把问题转换为向量,用于查找语义相近的知识片段;
- Claude 大模型负责理解用户意图、选择工具、阅读检索结果并组织最终答案。
MCP Server 不直接替代大模型回答问题,它提供的是与当前问题相关的知识上下文。
二、Claude Code 如何连接 MCP Server
项目通过根目录下的 .mcp.json 声明 MCP 服务:
json
{
"mcpServers": {
"corprag": {
"command": "node",
"args": ["<CorpRAG 项目路径>/dist/stdio.js"],
"cwd": "<CorpRAG 项目路径>"
}
}
}
1. 服务名称
corprag 是 Claude Code 识别该 MCP Server 时使用的名称。
服务端注册的工具(如 fr_query)在 Claude Code 运行时中显示为:
text
mcp__corprag__fr_query
名称可以拆成三部分:
text
mcp__<服务名称>__<工具名称>
因此,规则文档中描述的逻辑名称 corprag.fr_query 与 Claude Code 运行时展示的完整名称 mcp__corprag__fr_query 指向的是同一个工具。
2. STDIO 传输
Claude Code 根据配置启动一个子进程,执行编译后的 MCP Server 入口文件。入口文件中通过 StdioServerTransport 建立通信:
ts
const server = createServer();
const transport = new StdioServerTransport();
await server.connect(transport);
在这种模式下:
- Claude Code 是 MCP Client;
- CorpRAG 是 MCP Server;
- 双方通过标准输入和标准输出传递 MCP 协议消息;
- 服务端运行日志应写入标准错误,避免干扰协议消息。
STDIO 适合本地知识库场景,不需要额外监听网络端口,也不必将知识库服务暴露到公网。
3. 工作目录
cwd 指定 MCP Server 启动时的工作目录。当服务需要读取环境变量、本地数据或相对路径时,正确的工作目录很重要。显式配置 cwd 可以减少不同启动环境带来的路径差异。
修改 .mcp.json 后,需要让 Claude Code 重新加载 MCP 配置,再检查当前会话是否已发现 corprag 服务及其工具。
三、工具注册机制
CorpRAG 不会为每个系统手工注册工具,而是通过配置驱动的方式动态注册。
ts
for (const system of SYSTEMS) {
const toolName = `${system}_query`;
server.registerTool(toolName, { /* ... */ });
}
SYSTEMS 配置定义了当前项目需要接入的知识库系统。例如,配置中只包含 fr 时,注册的工具为 fr_query。每个工具都接受相同的参数结构:
json
{
"question": "需要查询的具体问题",
"docType": "FADR"
}
| 参数 | 说明 |
|---|---|
question |
要进行语义检索的具体问题 |
docType |
文档类型,用于限定检索范围 |
docType 在服务端通过 Schema 约束为 FADR 或 BADR:
- FADR:技术架构与实现决策
- BADR:业务架构、业务规则与合规决策
底层允许省略 docType 以同时检索两类文档,但客户端规则通常要求每次调用都显式传入。这样做有三个好处:
- 缩小检索范围;
- 减少无关片段干扰;
- 让调用过程更容易观察和排查。
四、从工具参数到向量检索
当 Claude Code 发起如下调用时:
json
{
"question": "当前技术方案需要满足哪些约束?",
"docType": "FADR"
}
MCP Server 内部经历以下步骤。
第一步:确定系统
调用的是 fr_query,目标系统为 fr。系统路由避免了在所有数据中进行无边界检索。
第二步:确定文档类型
docType 决定查询哪一类文档。例如系统为 fr、文档类型为 FADR 时,对应的向量表为 fr_fadr_kb。
第三步:将问题转换为向量
使用与建库时相同的 Embedding 模型将问题转换为向量。如果建库和查询使用了不同模型,它们产生的向量不在同一个语义空间中,检索质量无法保证。
第四步:执行相似度检索
ts
store.similaritySearch(question, topK)
topK 表示返回多少个相关片段。向量检索不是简单的关键词匹配,即使用户的表达与文档原文不同,只要语义相近,也可能召回相关内容。
第五步:格式化结果
每个检索结果保留以下字段返回给客户端:
content:知识片段正文source:片段来源docType:文档类型
来源和文档类型可以帮助模型区分检索证据,也方便排查错误召回。
五、为什么只配置 MCP 还不够
完成 .mcp.json 后,Claude Code 可以发现 MCP 工具,但不一定会在正确的时间使用正确的工具。
例如,用户提出一个宽泛的问题:
text
请为当前需求提供实现建议。
如果缺少调用规则,AI 可能出现以下情况:
- 不查询知识库,直接依赖通用经验回答;
- 选择了错误的工具(如用
rebuild_kb代替查询); - 调用工具时没有传
docType; - 问题同时涉及两类文档,却只查询了一类;
- 已经开始修改代码,之后才查询项目约束;
- 因工具显示名称不同(
mcp__corprag__fr_queryvscorprag.fr_query),错误判断 MCP 工具不存在。
因此,连接 MCP Server 之后,还需要给 AI 定义一套稳定的调用规则。
六、通过 CLAUDE.local.md 定义调用入口
CLAUDE.local.md 适合保存只在本地生效、不希望提交到仓库的规则。
在 frodo 的实际项目中,CLAUDE.local.md 没有直接内联所有查询规则,而是采用了一层间接委托:
markdown
# frodo 本地决策入口(按需查询)
## 强制路由
收到任务后,若命中以下决策场景,必须在调用其他 Agent / Skill 前,
先加载并执行 decision-guide:
- 技术决策(FADR):前端架构、组件设计、状态管理、性能优化、构建配置等
- 业务决策(BADR):业务规则、交易流程、合规约束、权限控制、渠道差异等
- 混合决策:同时包含技术实现与业务目标
执行入口:.claude/skills/decision-guide/SKILL.md
这种设计的好处是:
CLAUDE.local.md只负责"触发判断",不承载具体查询逻辑;- 具体的识别规则、查询参数、冲突处理全部封装在 Skill 中;
- 多个项目可以复用同一套 Skill,只需在本地规则中配置触发条件。
规则层应该覆盖的内容
一套完整的本地调用规则至少应该覆盖以下五部分:
1. 触发条件
明确什么情况下需要查询知识库,什么情况下不需要。简单的 Bug 修复、样式微调、文案修改等可以直接按常规流程执行,无需查询。
2. 系统路由
判断问题属于哪个系统,选择对应工具。如果无法判断,应先询问用户,而不是随意查询某个知识库。
3. 文档类型路由
规则需要说明什么情况下选择 FADR,什么情况下选择 BADR。具体触发关键词由项目内部定义。
4. 调用顺序
建议固定为:
text
理解用户问题
→ 判断是否需要查询知识库
→ 是 → 加载 Skill
→ Skill 识别类型并改写问题
→ 调用 MCP 工具
→ 整理返回结果
→ 再执行后续任务
"先查询约束,再执行任务"是关键。如果 AI 已经根据通用经验形成了完整方案,知识库就只能变成事后补充,而不能真正约束执行过程。
5. 降级边界
允许降级,但必须先确认工具确实无法提供结果。仅在以下情况允许降级:
- 已实际调用工具,但 MCP Server 返回错误;
- 调用成功,但没有检索到相关片段;
- 已检查当前会话工具列表,确认对应工具不存在。
不能仅仅因为规则中写的是 corprag.fr_query 而运行时显示的是 mcp__corprag__fr_query,就判断工具不可用。
七、通过 Skills 封装可复用查询流程
CLAUDE.local.md 解决的是"什么情况下触发",而 Skill 解决的是"触发后怎么做"。
实际项目中,decision-guide Skill 封装了完整的查询流程:
text
接收任务
↓
识别是否属于决策场景
├─ 否 → 按常规路由继续
└─ 是
↓
识别 FADR / BADR / 混合类型
↓
将问题改写为具体、单一的决策问题
↓
按类型调用 corprag.fr_query
├─ FADR → docType: "FADR"
├─ BADR → docType: "BADR"
└─ 混合 → 拆分并分别查询
↓
提取适用决策、约束和禁止项
↓
处理冲突并形成执行建议
↓
路由到对应 Agent/Skill 或输出结论
这几种配置承担不同职责:
| 方式 | 主要职责 |
|---|---|
.mcp.json |
声明 MCP Server 在哪里、如何启动 |
CLAUDE.local.md |
定义何时触发知识库查询 |
| Skills | 将多步骤查询流程封装为可复用能力 |
三层架构可以概括为:
text
连接层:.mcp.json
触发层:CLAUDE.local.md
流程层:Skills
如果希望 Claude 在日常对话中自动判断是否查询,CLAUDE.local.md 更直接;如果希望将一套固定步骤作为能力复用,Skills 更合适。二者可以同时使用。
八、两类文档同时相关时如何处理
某些问题可能同时涉及 FADR(技术决策)和 BADR(业务决策)。
此时不建议省略 docType 进行一次混合查询,而应该先把问题拆分。例如,用户提出:
text
请结合项目已有约束,给出当前需求的实现方案。
客户端可以分别构造两个查询:
json
{
"question": "当前需求涉及哪些技术架构约束?",
"docType": "FADR"
}
以及:
json
{
"question": "当前需求涉及哪些业务规则和合规约束?",
"docType": "BADR"
}
两次调用完成后,再由大模型合并结果。合并时按优先级处理:先满足业务目标和合规要求,再在技术架构允许范围内选择实现方案。
这种方式比一次混合检索更容易控制:
- 每次查询的目标明确;
- 两类结果不会互相挤占
topK; - 可以分别判断某类文档是否有结果;
- 便于记录调用日志和分析召回质量。
九、一次完整调用是怎样发生的
假设用户在 Claude Code 中提出一个涉及技术决策的问题:
text
请查询 fr 系统的已有约束,并给出当前需求的处理建议。
1. Claude Code 读取 CLAUDE.local.md
命中技术决策触发场景,加载 decision-guide Skill。
2. Skill 识别决策类型
判断问题属于 FADR(技术架构与实现决策)。
3. Skill 改写问题并调用 MCP
将宽泛问题改写为具体查询:
json
{
"question": "当前需求涉及哪些技术架构约束?",
"docType": "FADR"
}
运行时对应的工具名为 mcp__corprag__fr_query。
4. CorpRAG 执行向量检索
text
fr_query
→ fr_fadr_kb
→ Embedding
→ similaritySearch
→ Top K 片段
5. MCP Server 返回结构化文本
将片段正文、来源和类型格式化后返回给 Claude Code。
6. Claude 生成最终回答
Claude 应完成以下工作:
- 根据检索结果回答当前问题;
- 不把无关片段强行加入答案;
- 区分知识库内容与模型推断;
- 检索结果不足时明确说明,而不是补写不存在的内部规则。
这就是完整的 RAG 闭环:
text
检索负责提供依据,大模型负责结合问题组织答案。
十、为什么不在会话启动时加载全部文档
一种简单方案是在 Claude Code 启动时直接导入全部知识文档。这种方式在文档很少时可以工作,但随着内容增长,会出现几个问题。
1. 占用上下文窗口
大量与当前任务无关的内容长期驻留在上下文中,挤占代码和问题分析空间。
2. 增加注意力干扰
模型同时面对大量规则,不一定比只提供少量相关片段效果更好。
3. 增加 Token 消耗
如果每轮对话都携带完整文档,会产生不必要的输入成本。
4. 增加信息暴露范围
一次性加载全部内容,会让与当前任务无关的内部信息也进入会话上下文。按系统、按类型、按问题检索,可以遵循最小必要原则。
5. 不利于知识更新
文档更新后,旧会话中已经加载的内容可能过期;按需检索则可以访问当前索引。
更合理的方式是:
text
内部文档
→ 建立向量索引
→ 按系统和类型查询
→ 只返回相关片段
→ 临时注入当前上下文
CLAUDE.local.md 只保存触发规则,Skills 封装查询流程,真实知识正文始终在向量库中按需检索。
十一、常见问题与排查方法
1. Claude Code 中看不到 corprag 工具
依次检查:
.mcp.json是否位于正确的项目目录;- Node.js 命令是否可执行;
- MCP Server 入口文件是否已经构建;
cwd是否正确;- 服务端是否把普通日志错误地写入标准输出;
- 修改配置后是否重新加载了 Claude Code 会话。
2. MCP Server 可以启动,但知识库不存在
MCP Server 启动成功只说明客户端与服务端已经建立连接。向量表仍然需要提前创建。例如,要查询 fr_fadr_kb,必须先完成该表对应文档的加载、切分和向量化。
3. 工具可以调用,但召回结果不准确
可以从以下方面排查:
question是否足够具体;- 工具是否选择正确(如用
fr_query还是rebuild_kb); docType是否选择正确;- 建库与查询是否使用相同的 Embedding 模型;
- 文档切分粒度是否合理;
topK是否合适;- 知识库本身是否包含相关内容。
4. Claude 选择了错误的系统或文档类型
这通常不是 MCP 连接问题,而是客户端规则不够明确。需要在规则或 Skill 中补充:
- 触发条件(什么情况下需要查询);
- 系统识别方式;
- 文档类型映射;
- 混合问题拆分方式;
- 调用顺序;
- 无法判断时是否先询问用户;
- 查询失败后的降级条件。
5. 检索结果包含不应对外展示的信息
RAG 检索和答案输出是两个不同的权限环节。系统不仅要控制"能不能查",还要控制"查到后能不能展示"。不要把"工具调用成功"视为"内容可以公开"的依据。
十二、总结
AI 客户端调用 MCP 服务,不只是增加一份 .mcp.json 配置。一条稳定的知识调用链需要同时具备:
- 服务连接:Claude Code 启动并连接 CorpRAG;
- 工具发现 :客户端识别
fr_query等工具; - 触发判断 :
CLAUDE.local.md决定何时需要查询知识库; - 流程封装:Skill 将识别、改写、查询、处理封装为可复用流程;
- 类型路由 :通过
docType限定知识范围; - 向量检索:将问题转换为向量并返回相关片段;
- 答案组合:Claude 将检索结果与当前问题组合成最终回答;
- 安全治理:控制查询权限、输出范围、日志内容和公开信息。
MCP 解决的是客户端与工具之间的连接问题,RAG 解决的是知识检索问题,而 CLAUDE.local.md 与 Skills 解决的是 AI 在什么条件下、按照什么流程使用这些能力的问题。