给 Cursor/Claude Code 接上项目大脑:用 MCP 把代码索引、文档和变更记录变成可检索上下文
给 AI 补齐项目上下文,为什么不能只靠 IDE
在小仓库里,Cursor 或 Claude Code 只看当前文件、附近引用和少量历史消息,通常还能给出像样的建议。但一旦进入中大型仓库,这套上下文就开始失效,典型表现有三种:
- 只看见局部,看不见约束:AI 能改出能编译的代码,但不知道你们的接口兼容策略、模块边界、命名规范和历史决策。
- 知道“像什么”,不知道“为什么”:它能根据相似代码补全实现,却不知道某个绕路设计是为了兼容旧客户端,还是规避线上事故。
- 回答像真的,但没法追溯:建议里经常出现“项目里应该有一个 X 工具类”“这里可能会调用某个 Y 服务”,听上去合理,但你很难知道它依据了什么。
这也是很多团队把 AI 用在重命名、补测试还行,一到跨模块重构、线上排障、接口变更评估就不敢放手的根本原因。
问题不在模型本身,而在于它拿到的项目知识过于偶然:当前打开的文件、少量 grep 结果、历史聊天记录,不足以构成团队级知识底座。
MCP(Model Context Protocol)的价值就在这里:把项目里的知识源,按工具能力暴露给 IDE 里的 AI,让它在需要时主动检索,而不是全靠你手工复制粘贴上下文。
这篇教程不讲抽象概念,直接做一个可落地的最小方案:
- 暴露本地代码索引
- 暴露项目文档与 ADR
- 暴露接口 schema
- 暴露 Git 变更记录
- 控制权限边界,默认只读
- 在真实重构/排障任务中验证效果
- 最后评估命中率、幻觉率和团队接入成本
先定知识源:别把所有文件都喂给 AI
很多团队一上来就想“把整个仓库都索引”。这通常不是最优解。真正有效的是先选对知识源,再决定怎么暴露。
我建议按这四类来拆。
1. 代码:解决“实现在哪里”
这是最基础的一层,至少应覆盖:
- 核心业务目录
- 公共库和基础设施代码
- 配置定义、路由、依赖注入入口
- 测试用例中的典型调用方式
代码索引的目标不是把所有源码塞进对话,而是让 AI 能回答这类问题:
- 某个接口最终落到哪个实现类
- 某个字段是从哪里注入、转换、落库的
- 某个功能跨了哪些模块
- 重构时有哪些调用点可能受影响
如果你希望快速得到现成能力,可以考虑这些站内资产:
- hailangx-codex-mcp-server:适合为本地仓库建立索引并做语义检索
- nikondrat-cctx-mcp:适合结构化代码分析和 Git 提交理解
- bgauryy-octocode:适合跨仓库语义检索和流程理解
- edmmy-codebase-bridge-mcp:适合只读检索本地代码并带行号回答
2. README / 设计文档 / ADR:解决“为什么这么做”
代码告诉 AI“怎么实现”,但项目协作里更稀缺的是“为什么不能那样改”。这部分最适合放:
- 模块 README
- 架构设计文档
- ADR(Architecture Decision Record)
- 迁移说明
- 兼容性约束
ADR 特别重要。很多线上事故都不是因为代码写错,而是 AI 或人类开发者不知道某个老逻辑不能碰。
如果你已经有个人或团队文档索引,可以看:
- thiagovasconcelosti-context0-core:通过 MCP HTTP 暴露文档索引访问能力
3. API Schema:解决“接口长什么样”
接口文档是最容易被忽视但最适合结构化检索的一类知识源,包括:
- OpenAPI / Swagger
- GraphQL schema
- Protobuf
- AsyncAPI / 事件定义
- 内部 DTO / JSON schema
有了 schema,AI 在生成客户端调用、服务适配器、参数校验和 mock 数据时会更稳,至少不至于凭空编字段。
如果你的仓库 API 多、上下文容易爆,可以看:
- codeturion-codesurface:适合索引公开 API 并返回精简接口信息
4. Git 变更记录:解决“历史上怎么改过”
变更记录经常是排障和评估影响面的关键证据,建议至少能检索:
- 最近 N 次相关提交
- 某个文件或目录的变更摘要
- 某个关键字关联的提交说明
- 回滚记录、修复记录
这类信息特别适合回答:
- 这个逻辑上个月为什么改过
- 相似事故以前是怎么修的
- 这个 API 变更是不是正在迁移中
最小可用方案:先做 4 个只读工具就够了
很多人一听到 MCP,就想做一个“万能项目助手”。我更建议先做最小集,先让 AI 能查,再考虑能改。
一个最小可用的 MCP Server,我建议先暴露 4 个只读工具:
search_code:按关键字、语义或路径搜索代码read_doc:读取 README、ADR、设计文档get_api_schema:按服务名或路径获取接口定义git_history:查询文件/模块/关键字相关提交
这 4 个工具已经能覆盖大多数“理解仓库、评估修改、辅助排障”的任务。
核心原则就一句话:先把项目知识做成可检索,再考虑让 AI 直接写入仓库。
实战搭建:用 Node.js 写一个最小 MCP Server
下面给一个最小示例。它不是生产级实现,但足够帮你跑通链路。
目录结构可以这样放:
text project-mcp/ package.json server.js docs/ adr/ architecture/ api/ openapi.json repo/ ...你的代码仓库或挂载目录
第一步:初始化依赖
bash mkdir project-mcp && cd project-mcp npm init -y npm install @modelcontextprotocol/sdk fast-glob
如果你的环境里 SDK 包名不同,以实际版本为准。下面示例重点在思路和工具边界。
第二步:实现最小工具集
js import fs from 'node:fs/promises'; import path from 'node:path'; import { execFile } from 'node:child_process'; import { promisify } from 'node:util'; import fg from 'fast-glob'; import { Server } from '@modelcontextprotocol/sdk/server/index.js'; import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
const execFileAsync = promisify(execFile); const ROOT = process.env.PROJECT_ROOT || path.resolve('./repo'); const DOC_ROOT = path.resolve('./docs'); const API_SCHEMA = path.resolve('./api/openapi.json');
function safeJoin(root, target) { const full = path.resolve(root, target); if (!full.startsWith(root)) { throw new Error('path out of root'); } return full; }
async function searchCode(query, limit = 20) { const files = await fg(['/*.{js,ts,tsx,jsx,java,go,py,rb,md,yml,yaml,json}'], { cwd: ROOT, ignore: ['/node_modules/', '/dist/', '/build/', '/.git/**'], dot: false });
const results = []; for (const file of files) { const abs = path.join(ROOT, file); const content = await fs.readFile(abs, 'utf8').catch(() => ''); if (!content) continue;
const lines = content.split('\n');
lines.forEach((line, idx) => {
if (line.toLowerCase().includes(query.toLowerCase())) {
results.push({
file,
line: idx + 1,
snippet: line.trim()
});
}
});
if (results.length >= limit) break;
}
return results.slice(0, limit); }
async function readDoc(docPath) { const abs = safeJoin(DOC_ROOT, docPath); const content = await fs.readFile(abs, 'utf8'); return { path: docPath, content }; }
async function getApiSchema(pointer) { const raw = await fs.readFile(API_SCHEMA, 'utf8'); const schema = JSON.parse(raw);
if (!pointer) return schema.info || schema;
const paths = schema.paths || {}; for (const [route, def] of Object.entries(paths)) { if (route.includes(pointer)) { return { route, def }; } }
return { message: 'not found' }; }
async function gitHistory(target, limit = 10) {
const args = ['log', -${limit}, '--oneline', '--', target || '.'];
const { stdout } = await execFileAsync('git', args, { cwd: ROOT });
return stdout.trim().split('\n').filter(Boolean);
}
const server = new Server( { name: 'project-knowledge-mcp', version: '0.1.0' }, { capabilities: { tools: {} } } );
server.setRequestHandler('tools/list', async () => ({ tools: [ { name: 'search_code', description: 'Search code in local repository and return file/line/snippet', inputSchema: { type: 'object', properties: { query: { type: 'string' }, limit: { type: 'number', default: 20 } }, required: ['query'] } }, { name: 'read_doc', description: 'Read README, ADR or architecture docs under docs/', inputSchema: { type: 'object', properties: { path: { type: 'string' } }, required: ['path'] } }, { name: 'get_api_schema', description: 'Get API schema by route keyword', inputSchema: { type: 'object', properties: { pointer: { type: 'string' } } } }, { name: 'git_history', description: 'Read git history for a file or directory', inputSchema: { type: 'object', properties: { target: { type: 'string' }, limit: { type: 'number', default: 10 } } } } ] }));
server.setRequestHandler('tools/call', async (req) => { const { name, arguments: args = {} } = req.params;
switch (name) {
case 'search_code':
return { content: [{ type: 'text', text: JSON.stringify(await searchCode(args.query, args.limit), null, 2) }] };
case 'read_doc':
return { content: [{ type: 'text', text: JSON.stringify(await readDoc(args.path), null, 2) }] };
case 'get_api_schema':
return { content: [{ type: 'text', text: JSON.stringify(await getApiSchema(args.pointer), null, 2) }] };
case 'git_history':
return { content: [{ type: 'text', text: JSON.stringify(await gitHistory(args.target, args.limit), null, 2) }] };
default:
throw new Error(unknown tool: ${name});
}
});
const transport = new StdioServerTransport(); await server.connect(transport);
这个版本故意保持简单,但已经体现了三个关键点:
- 所有工具默认只读
- 文件访问被限制在根目录内
- 返回结果尽量结构化,方便 AI 引用和追溯
第三步:准备项目知识目录
建议不要让 AI 在全仓里随便扫文档,而是整理出一个稳定目录,例如:
text docs/ README.md architecture/ module-boundary.md auth-flow.md adr/ ADR-001-api-versioning.md ADR-002-cache-invalidation.md api/ openapi.json repo/ services/ libs/ apps/
这样做的好处不是“更整洁”,而是检索入口稳定。当团队每个项目都沿用类似结构,AI 工具链可以复用,接入成本会越来越低。
接到 Cursor 或 Claude Code:关键不是能连上,而是能被正确使用
不同 IDE/Agent 的 MCP 配置方式略有区别,但思路一致:把上面的 server 作为一个本地 MCP 服务注册进去。
一个典型的本地 stdio 配置大概长这样:
{ "mcpServers": { "project-knowledge": { "command": "node", "args": ["/absolute/path/to/project-mcp/server.js"], "env": { "PROJECT_ROOT": "/absolute/path/to/your/repo" } } } }
连上以后,不要立刻让 AI “帮我重构整个支付模块”。先验证三个最小问题:
- 能不能通过
search_code找到正确实现位置 - 能不能通过
read_doc读到 ADR 和模块说明 - 能不能在回答里引用具体文件、行号、路由或提交记录
如果这三件事做不到,说明你的知识源组织方式或工具返回格式有问题,不是模型不聪明。
权限与安全边界:MCP 不是让 AI 拿到你的整台电脑
工程团队对 MCP 最大的顾虑通常不是“不会搭”,而是“太危险”。这个顾虑是对的。
我的建议很明确:第一阶段只上只读工具,不给 shell,不给任意文件系统访问,不给生产凭证。
最小安全策略
1. 工具按用途分级
- L1:只读检索,如代码搜索、读文档、查 schema、看 Git log
- L2:受限生成,如生成 patch 建议、测试草稿、迁移清单
- L3:高风险执行,如改文件、跑命令、访问外网、调用内部服务
团队第一次落地,先只启用 L1,最多加少量 L2。
2. 文件路径白名单
只允许访问:
- 指定仓库根目录
- docs 目录
- API schema 文件
明确禁止:
~/.ssh.env- 云平台凭证目录
- 任何父目录穿越路径
上面的 safeJoin 就是在做这个最基本的防线。
3. 返回结果做“裁剪”而不是全量透传
例如查 Git,不要直接把完整 diff 和敏感提交说明全部扔给模型;查代码,也不要动不动整文件回传。
更好的做法是只返回:
- 文件路径
- 行号范围
- 关键片段
- 文档标题和摘要
- 提交 hash 与首行 message
这样更省 token,也更可审计。
4. 记录工具调用日志
至少要记:
- 谁发起的会话
- 调用了哪个工具
- 输入参数是什么
- 返回了哪些文件/路由/提交
- 最终建议里引用了哪些证据
这样做的价值非常直接:出了问题能追责,效果好了也能沉淀提示词和工具模式。
真实任务演示一:跨模块重构,先让 AI 证明它理解了影响面
假设你要把 UserProfileDTO.phone 改成 mobile,这个字段会影响:
- HTTP API 返回
- 内部 service 映射
- 前端或客户端消费
- 旧版本兼容逻辑
- 文档和测试
如果只靠 IDE 当前文件,AI 很容易只改 DTO 和一两个调用点,然后自信地说“已完成重构”。
更稳的方式是给它一个明确任务模板:
text 请不要直接改代码,先使用 MCP 工具完成以下检查:
- 搜索 UserProfileDTO.phone 的定义与所有引用
- 读取与 API 版本兼容相关的 ADR 或文档
- 查询 /user/profile 相关 schema
- 查看最近与 profile 字段变更相关的提交
- 输出一份影响面分析,按“必须修改 / 可能受影响 / 需要人工确认”分类
- 每个判断都给出依据文件、行号、文档或提交记录
理想的 AI 输出应该像这样组织,而不是直接给 patch:
- 必须修改
repo/services/user/dto/UserProfileDTO.ts:12repo/services/user/mapper/profileMapper.ts:48/user/profile的 OpenAPI response schema
- 可能受影响
- 前端 BFF 的字段透传逻辑
- 报表导出模块中的匿名序列化器
- 需要人工确认
- ADR-001 中提到 v1 客户端兼容三个月,是否仍在生效
这里最重要的不是“AI 找得全不全”,而是它能不能把依据说出来。只要依据可追溯,人类就能快速做二次判断;如果依据说不出来,这种建议就不适合直接进入开发流程。
接着再给第二轮指令:
text 基于上一步的影响面分析,只生成一个最小修改方案:
- 保持 v1 返回 phone 不变
- 在 v2 新增 mobile
- 补充单元测试与 OpenAPI schema
- 如果信息不足,不要猜,明确指出缺失证据
这样得到的修改建议通常会比“直接重构”可信得多,因为它建立在检索证据上,而不是语言模式联想上。
真实任务演示二:线上排障,让 AI 先查变更和约束,再猜原因
再看一个更适合 MCP 的场景:线上某接口在灰度后返回 500,但只影响一部分租户。
这类问题如果直接问 AI:“为什么会 500?”它大概率会列一堆常见原因,价值不高。
更有效的提问方式是:
text 请使用 MCP 工具协助定位以下问题:
- 现象:/billing/summary 在灰度后部分租户返回 500
- 目标:先列出最可能的 3 个根因,并标注证据强弱
- 操作要求:
- 搜索 billing summary 的实现与异常处理
- 读取 billing 模块的架构文档与相关 ADR
- 获取 /billing/summary 的 schema 和最近相关改动
- 查询最近 20 次 billing 目录提交
- 输出“根因假设 -> 证据 -> 建议验证步骤”
如果你的 MCP 结果足够好,AI 的结论会从“泛泛而谈”变成更接近排障会上的材料,例如:
- 租户级开关导致新字段为空,序列化器未处理 null
- 证据:最近提交里新增
taxRegion字段 - 证据:OpenAPI schema 标注 required,但 mapper 中是可空
- 证据:ADR 说明灰度期间旧租户配置可能缺失
- 证据:最近提交里新增
- billing adapter 依赖的新服务只在部分租户路由生效
- 证据:实现代码中按租户路由不同 provider
- 证据:最近 Git 记录修改了 provider fallback
- 异常被统一包装,真实错误被吞掉
- 证据:controller 里 catch 后只抛 500,缺少业务码透出
注意,这里 AI 仍然没有“真相”,但它已经能给出证据化假设,而不是模板化废话。这就是 MCP 的实际价值。
让建议更可信的关键:要求 AI 输出“证据链”
很多团队接入 MCP 后效果仍然一般,不是因为工具没用,而是提示方式没变,还是在让 AI 直接给答案。
我的经验是,把下面这条规则写进团队提示模板里,效果会明显提升:
text 任何修改建议都必须附带证据链:
- 代码依据:文件路径 + 行号 + 片段
- 文档依据:文档名 + 小节标题
- 接口依据:schema 路由 + 字段定义
- 历史依据:提交 hash + message 若任一关键依据缺失,必须标记为“待人工确认”,不得自行补全事实。
这会强迫 AI 从“生成答案”切换成“基于检索构造论证”。
从团队治理视角看,这正是“可追溯、可复用、可审计”的核心:
- 可追溯:每条建议都能回到仓库证据
- 可复用:同样的工具链和提示模板能迁移到其他项目
- 可审计:事后能检查 AI 当时看到了什么、为什么这么建议
怎么评估效果:别只看“感觉更聪明了”
MCP 落地后,如果没有评估方式,最后很容易退化成“开发者主观觉得还行”。我建议至少看三个指标。
1. 命中率:该找到的有没有找到
可以定义成:在一组典型任务里,AI 是否找到了关键证据。
例如准备 10 个任务,每个任务预先标出人工确认的关键证据点:
- 关键实现文件
- 必读 ADR
- 对应 API schema
- 最近相关提交
然后统计:
text 命中率 = AI 找到的关键证据数 / 任务要求的关键证据总数
如果命中率低,优先排查这三件事:
- 索引范围不对
- 文档目录组织混乱
- 工具返回结果太噪,模型挑不出重点
2. 幻觉率:有没有无依据地下结论
幻觉率不要只看“说错了多少”,而要看“有没有假装有依据”。
一个简单标准是:
- 凡是结论中出现“项目中存在某逻辑/某约束/某字段”
- 但没有附文件、文档、schema 或提交证据
- 记为一次无依据断言
可以粗略定义:
text 幻觉率 = 无依据断言数 / 总断言数
团队最该压低的不是普通错误,而是这种“说得很像真的”断言。因为它最容易骗过评审和开发者自己。
3. 落地成本:值不值得团队长期维护
MCP 不是零成本能力,建议评估:
- 初次接入需要多少人天
- 每个仓库需要多少目录整理工作
- 文档和 schema 是否有人持续维护
- 工具失败时开发者是否能自助排查
- IDE 接入是否依赖少数人掌握
一个实用的经验是:
- 第一阶段,用 1 个仓库、4 个只读工具、1 套提示模板验证价值
- 第二阶段,沉淀通用目录规范和指标面板
- 第三阶段,再考虑 patch 生成、自动测试、批量迁移
别反过来。一上来做全自动改代码,通常会在安全、信任和维护上一起翻车。
推荐一条实际可执行的团队落地路径
如果你准备在团队里推,我建议按下面的节奏走。
第 1 周:选一个痛点明显的仓库
标准是:
- 模块多,跨目录调用复杂
- 文档不算完美,但至少有 README/ADR/API schema 中的两类
- 最近确实有重构或排障需求
第 2 周:搭最小 MCP Server
目标不是功能全,而是把下面四个问题答对:
- 实现在哪
- 为什么这么设计
- 接口定义是什么
- 最近谁改过
如果不想从零造轮子,可以优先评估这些能力型资产:
- 代码索引:
hailangx-codex-mcp-server - 文档索引:
thiagovasconcelosti-context0-core - 结构化代码与 Git:
nikondrat-cctx-mcp - 跨仓语义检索:
bgauryy-octocode - API 精简检索:
codeturion-codesurface - 只读代码桥接:
edmmy-codebase-bridge-mcp
第 3 周:固定 5 个标准任务做对照测试
比如:
- 字段重命名影响面分析
- 新增接口参数兼容性检查
- 某线上异常的根因假设输出
- 跨模块调用链梳理
- 根据 ADR 评估某重构是否违反既有约束
每个任务都让“无 MCP”和“有 MCP”各跑一次,比较:
- 找到的证据数量
- 回答中的无依据断言数量
- 人工修正所需时间
第 4 周:沉淀团队规范
最终不是沉淀“一个神奇机器人”,而是沉淀这几样东西:
- 项目知识目录规范
- MCP 工具最小集
- 安全边界规则
- 标准任务提示模板
- 评估指标和复盘方式
这时你的收益才会从“某个会用 AI 的同事效率很高”,变成“团队整体协作质量更稳定”。
小结
只靠 IDE 内置上下文,AI 在中大型仓库里很容易陷入局部视角:看得到代码片段,看不到项目约束;能生成改动,看不出影响面;会给答案,但说不清依据。
MCP 的真正价值,不是把更多 token 塞给模型,而是把项目知识做成按需检索的工具能力。当代码索引、README、ADR、API schema 和 Git 变更都能被结构化查询后,Cursor 或 Claude Code 才有机会从“会补全的助手”变成“会查证据的协作者”。
如果你准备开始,记住这条最实用的路线:
- 先选知识源,不要全量乱喂
- 先做 4 个只读工具,不要急着开放写权限
- 让 AI 先输出影响面和证据链,再给修改建议
- 用命中率、幻觉率和落地成本衡量价值
- 把目录规范、提示模板和审计日志一起沉淀下来
做到这一步,AI 生成的建议才真正具备工程上的三层价值:可追溯、可复用、可审计。
如果你所在团队正卡在“AI 看起来很聪明,但我们不敢信”,MCP 往往就是从演示效果走向工程可用的那条分水岭。
// from this post
// related reading
从零搭建基于 MCP 的开发助手工作流:让 Cursor / Claude Code 先读文档、再改代码、最后产出可审查 PR
这篇教程面向有项目经验的开发者,讲清楚如何把 MCP 接进 Cursor 或 Claude Code,搭建一条“读仓库文档与 issue → 定位问题 → 生成修复方案 → 修改代码 → 跑测试 → 输出 PR 说明”的可复用工作流。
用 MCP 给 Cursor 接入团队知识库:从文档检索到可审计的问答 Skill 实战
本地代码补全只能看见当前仓库,解决不了团队规范、接口约定和历史决策的检索问题。本文带你从 0 到 1 用 MCP 为 Cursor 接入内部文档、代码索引和 API 说明,并封装成可复用、可控、可审计的团队知识增强编程流。
用最小可用 MCP Server 把企业知识接入 Cursor:让 AI 真正理解你的内部文档、API 和代码上下文
只靠通用 AI 编程助手,模型看不到你的内网文档、接口约束和历史变更,生成结果很容易“像对的,但不能用”。这篇教程带你从零实现一个最小可用 MCP Server,在 Cursor 中安全接入企业上下文,完成文档检索、接口查询、变更追溯与团队复用。