用最小可用 MCP Server 把企业知识接入 Cursor:让 AI 真正理解你的内部文档、API 和代码上下文
为什么通用 AI 编程助手不够用
很多团队第一次把 AI 编程助手接进开发流程,都会遇到同一类问题:
- 它能写通用 CRUD,但看不懂你们的内部术语
- 它知道 REST,却不知道你们网关的鉴权头怎么传
- 它会重构代码,却不知道哪些模块受合规约束不能动
- 它能猜数据库字段,却不知道真实字段来自哪份内部文档
- 它给排障建议,却拿不到最近一次接口变更记录
根本原因不是模型不聪明,而是上下文缺失。
通用 AI 编程助手默认只能看到:
- 你当前打开的代码片段
- 公开互联网知识
- 你临时粘进去的说明
但企业开发真正依赖的是另一层信息:
- 内部文档:架构说明、约定、SOP、发布流程
- API 平台:接口定义、错误码、示例请求、版本状态
- 私有代码库:模块边界、历史实现、隐式约束
- 变更记录:谁改了什么、为什么改、何时上线
如果没有这一层,AI 的表现常常是“语言很流畅,结论很危险”。
所以真正可落地的做法不是让模型“更会猜”,而是让它按权限读取企业上下文。这正是 MCP(Model Context Protocol)的价值:把你已有的知识源,通过统一协议暴露给 AI 工具调用,而不是把所有敏感内容直接塞进提示词。
这篇教程要做什么
我们会实现一个最小可用 MCP Server,暴露 3 类能力:
- 文档检索:从内部知识库按关键词返回相关片段
- 接口查询:按服务名 / 路径查询 API 定义
- 变更记录:按模块或文件查询最近变更历史
然后把它接到 Cursor,让 AI 在以下场景里真正可用:
- 基于内网文档生成代码
- 根据真实接口定义补全调用逻辑
- 遇到线上问题时回看最近变更做排障
- 重构前先理解模块边界和历史约束
同时我们还会补上落地时最容易忽略的部分:
- MCP 协议结构
- 工具定义方式
- 权限控制
- 审计日志
- 提示词边界
- 如何把本地 Skill 封装成团队复用能力
- 推荐目录结构
- 调试方法和常见坑
为了让例子足够清晰,下面用 Node.js + TypeScript 做服务端示例。
先理解 MCP:不是“喂更多提示词”,而是“提供可调用能力”
很多人一开始会把 MCP 理解成“更高级的插件”。从工程角度看,它更像一个标准化的上下文接入层。
一个 MCP Server 通常做几件事:
- 声明自己有哪些工具(tools)
- 告诉客户端这些工具的入参结构
- 在工具被调用时执行真实逻辑
- 返回结构化结果给模型
客户端(比如 Cursor)负责:
- 把用户意图和当前任务交给模型
- 让模型决定是否调用某个工具
- 将工具结果再喂回模型继续推理
这和“把一堆文档直接塞进 system prompt”最大的区别是:
- 按需读取:模型需要时才调用,而不是每次都携带全部上下文
- 可控权限:你可以只开放可读接口,不开放原文全量下载
- 可审计:每次调用都可记录谁查了什么
- 可演进:后面可以继续加代码搜索、工单查询、发布记录等能力
你可以把它理解成:
提示词负责约束行为,MCP 负责提供事实。
这两者必须同时存在。
我们的最小可用方案
为了控制复杂度,先做一个单进程 MCP Server,背后接 3 个假数据源:
docs/:内部 Markdown 文档apis/apis.json:接口定义changes/changes.json:变更记录
后面你可以很容易把它换成真实系统:
- 文档检索 → Confluence / 语雀 / Wiki / 向量库
- 接口查询 → Apifox / Swagger / 内部 API 平台
- 变更记录 → GitLab API / GitHub API / Jira / 发布系统
推荐目录结构
先给一个适合落地的目录,后面代码就按这个组织:
text enterprise-mcp/ ├─ package.json ├─ tsconfig.json ├─ src/ │ ├─ server.ts │ ├─ tools/ │ │ ├─ searchDocs.ts │ │ ├─ getApiSpec.ts │ │ └─ getChangeLog.ts │ ├─ data/ │ │ ├─ docs.ts │ │ ├─ apis.ts │ │ └─ changes.ts │ ├─ security/ │ │ ├─ auth.ts │ │ ├─ policy.ts │ │ └─ audit.ts │ └─ skill/ │ └─ code-review-checklist.md ├─ docs/ │ ├─ payment-refund-flow.md │ └─ order-state-machine.md ├─ apis/ │ └─ apis.json ├─ changes/ │ └─ changes.json └─ dist/
这个结构有几个好处:
tools/只管工具定义和返回结果data/只管读取数据源security/集中处理鉴权、权限、审计skill/单独放团队沉淀下来的规则和方法论
不要把所有逻辑堆到一个 server.ts 里,后面一接真实系统就会失控。
第一步:初始化项目
bash mkdir enterprise-mcp cd enterprise-mcp npm init -y npm install @modelcontextprotocol/sdk zod npm install -D typescript tsx @types/node npx tsc --init
把 package.json 的 scripts 补上:
{ "scripts": { "dev": "tsx src/server.ts", "build": "tsc", "start": "node dist/server.js" } }
tsconfig.json 可使用这种最小配置:
{ "compilerOptions": { "target": "ES2022", "module": "NodeNext", "moduleResolution": "NodeNext", "outDir": "dist", "rootDir": "src", "strict": true, "esModuleInterop": true, "skipLibCheck": true }, "include": ["src"] }
第二步:准备示例数据
文档示例
docs/payment-refund-flow.md
md
支付退款流程
退款接口由 payment-service 提供。 调用前必须带上 X-Tenant-Id 和 X-Operator-Id。 退款状态机:PENDING -> PROCESSING -> SUCCESS | FAILED。 如果订单已结算,退款必须先走风控校验。
docs/order-state-machine.md
md
订单状态机
订单状态包含 CREATED、PAID、FULFILLED、CANCELLED。 禁止从 CREATED 直接跳转到 FULFILLED。 payment-service 回调成功后才能进入 PAID。
接口定义示例
apis/apis.json
[ { "service": "payment-service", "method": "POST", "path": "/v1/refunds", "description": "创建退款单", "headers": ["Authorization", "X-Tenant-Id", "X-Operator-Id"], "requestExample": { "orderId": "ORD-1001", "amount": 99.8, "reason": "duplicate_payment" }, "responseExample": { "refundId": "RF-9001", "status": "PENDING" } } ]
变更记录示例
changes/changes.json
[ { "module": "payment-service", "file": "src/refund/createRefund.ts", "commit": "a1b2c3d", "author": "alice", "date": "2025-01-12T10:30:00Z", "summary": "退款创建前新增风控校验,未通过时直接拒绝" }, { "module": "order-service", "file": "src/order/stateMachine.ts", "commit": "d4e5f6g", "author": "bob", "date": "2025-01-10T08:00:00Z", "summary": "修复 CREATED 状态可误进入 FULFILLED 的问题" } ]
第三步:实现数据访问层
先把文件读取和简单检索封装起来,后面替换成真实数据源时不用动工具层。
src/data/docs.ts
ts import fs from "node:fs"; import path from "node:path";
const DOCS_DIR = path.resolve(process.cwd(), "docs");
export type DocHit = { file: string; snippet: string; };
export function searchDocs(query: string, limit = 5): DocHit[] { const files = fs.readdirSync(DOCS_DIR).filter((f) => f.endsWith(".md")); const q = query.toLowerCase(); const hits: DocHit[] = [];
for (const file of files) { const fullPath = path.join(DOCS_DIR, file); const content = fs.readFileSync(fullPath, "utf8"); const lower = content.toLowerCase(); const idx = lower.indexOf(q); if (idx >= 0) { const start = Math.max(0, idx - 40); const end = Math.min(content.length, idx + q.length + 120); hits.push({ file, snippet: content.slice(start, end).replace(/\n+/g, " ") }); } }
return hits.slice(0, limit); }
src/data/apis.ts
ts import fs from "node:fs"; import path from "node:path";
export type ApiSpec = { service: string; method: string; path: string; description: string; headers: string[]; requestExample: Record<string, unknown>; responseExample: Record<string, unknown>; };
const API_FILE = path.resolve(process.cwd(), "apis/apis.json");
export function getApiSpec(service?: string, apiPath?: string): ApiSpec[] { const raw = fs.readFileSync(API_FILE, "utf8"); const specs = JSON.parse(raw) as ApiSpec[];
return specs.filter((item) => { const serviceOk = service ? item.service === service : true; const pathOk = apiPath ? item.path === apiPath : true; return serviceOk && pathOk; }); }
src/data/changes.ts
ts import fs from "node:fs"; import path from "node:path";
export type ChangeItem = { module: string; file: string; commit: string; author: string; date: string; summary: string; };
const CHANGE_FILE = path.resolve(process.cwd(), "changes/changes.json");
export function getChangeLog(module?: string, file?: string, limit = 10): ChangeItem[] { const raw = fs.readFileSync(CHANGE_FILE, "utf8"); const items = JSON.parse(raw) as ChangeItem[];
return items .filter((item) => { const moduleOk = module ? item.module === module : true; const fileOk = file ? item.file === file : true; return moduleOk && fileOk; }) .sort((a, b) => +new Date(b.date) - +new Date(a.date)) .slice(0, limit); }
这里故意没有上向量检索、全文索引、数据库,原因很简单:
先跑通协议和调用链,再升级检索质量。
很多团队一上来就想接 ES、接 embedding、接权限中心,最后连第一条工具调用都没通。
第四步:设计工具定义
MCP 工具不是随便起个名字就行,工具定义会直接影响模型是否会正确调用。
好的工具定义要满足三点:
- 名称明确:看名字就知道用途
- 描述具体:说明什么时候该调用
- 参数收敛:别让模型自由发挥太多
我们实现 3 个工具:
search_docsget_api_specget_change_log
src/tools/searchDocs.ts
ts import { z } from "zod"; import { searchDocs } from "../data/docs.js";
export const searchDocsTool = { name: "search_docs", description: "检索内部技术文档,适用于查询业务流程、字段约定、状态机、内部术语和开发规范。", inputSchema: { query: z.string().min(2).describe("要检索的关键词或短语"), limit: z.number().int().min(1).max(10).optional().describe("返回结果数量,默认 5") }, async handler(args: { query: string; limit?: number }) { const hits = searchDocs(args.query, args.limit ?? 5); return { content: [ { type: "text", text: JSON.stringify({ hits }, null, 2) } ] }; } };
src/tools/getApiSpec.ts
ts import { z } from "zod"; import { getApiSpec } from "../data/apis.js";
export const getApiSpecTool = { name: "get_api_spec", description: "查询内部 API 定义,适用于生成接口调用代码、排查请求参数问题、核对鉴权头和响应结构。", inputSchema: { service: z.string().optional().describe("服务名,例如 payment-service"), path: z.string().optional().describe("接口路径,例如 /v1/refunds") }, async handler(args: { service?: string; path?: string }) { const specs = getApiSpec(args.service, args.path); return { content: [ { type: "text", text: JSON.stringify({ specs }, null, 2) } ] }; } };
src/tools/getChangeLog.ts
ts import { z } from "zod"; import { getChangeLog } from "../data/changes.js";
export const getChangeLogTool = { name: "get_change_log", description: "查询模块或文件的最近变更记录,适用于排障、回归分析、评估重构风险。", inputSchema: { module: z.string().optional().describe("模块名,例如 payment-service"), file: z.string().optional().describe("文件路径,例如 src/refund/createRefund.ts"), limit: z.number().int().min(1).max(20).optional().describe("返回数量,默认 10") }, async handler(args: { module?: string; file?: string; limit?: number }) { const items = getChangeLog(args.module, args.file, args.limit ?? 10); return { content: [ { type: "text", text: JSON.stringify({ items }, null, 2) } ] }; } };
注意这里有一个实践细节:
描述里要写“适用于什么场景”,而不只是“这个工具做什么”。
模型在决定调哪个工具时,对场景语义非常敏感。
第五步:补上权限控制和审计日志
只要开始接企业数据,权限和审计就不是可选项。
最小可用版至少要解决这几个问题:
- 谁能调用 MCP Server
- 谁能调用哪些工具
- 哪些参数属于敏感查询
- 每次查询是否有日志可追溯
一个简单但实用的权限模型
先不要追求大而全 RBAC,最小版本可以这样分层:
developer:可查文档、接口、普通变更记录senior:额外可查指定模块历史admin:可查全部工具并查看审计日志
src/security/auth.ts
ts export type UserContext = { userId: string; role: "developer" | "senior" | "admin"; };
export function getUserContext(): UserContext { const userId = process.env.MCP_USER_ID || "local-dev"; const role = (process.env.MCP_USER_ROLE || "developer") as UserContext["role"]; return { userId, role }; }
src/security/policy.ts
ts import type { UserContext } from "./auth.js";
export function assertToolAllowed(user: UserContext, toolName: string, args: Record<string, unknown>) { if (toolName === "search_docs") return; if (toolName === "get_api_spec") return;
if (toolName === "get_change_log") { if (user.role === "developer" && args.module === "finance-core") { throw new Error("permission denied: developer cannot access finance-core changes"); } return; }
throw new Error(unknown tool: ${toolName});
}
src/security/audit.ts
ts import fs from "node:fs"; import path from "node:path";
type AuditEvent = { time: string; userId: string; role: string; tool: string; args: Record<string, unknown>; ok: boolean; error?: string; };
const AUDIT_FILE = path.resolve(process.cwd(), "audit.log");
export function writeAudit(event: AuditEvent) { fs.appendFileSync(AUDIT_FILE, JSON.stringify(event) + "\n", "utf8"); }
这套实现很简单,但已经能覆盖大部分内部试点阶段需求。
权限控制的几个底线
- 不要让工具直接暴露底层数据库查询语句
- 不要允许模型自由拼接 shell 命令去查内网
- 不要返回整份敏感文档原文,优先返回摘要和片段
- 不要把 access token 放进模型可见的文本结果里
- 不要让“查询所有内容”成为默认能力
记住一句话:
模型可以聪明,但权限必须笨一点。
第六步:组装 MCP Server
下面把工具、权限和审计串起来。
src/server.ts
ts import { Server } from "@modelcontextprotocol/sdk/server/index.js"; import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import { ListToolsRequestSchema, CallToolRequestSchema } from "@modelcontextprotocol/sdk/types.js"; import { zodToJsonSchema } from "zod-to-json-schema"; import { searchDocsTool } from "./tools/searchDocs.js"; import { getApiSpecTool } from "./tools/getApiSpec.js"; import { getChangeLogTool } from "./tools/getChangeLog.js"; import { getUserContext } from "./security/auth.js"; import { assertToolAllowed } from "./security/policy.js"; import { writeAudit } from "./security/audit.js";
const server = new Server( { name: "enterprise-context-mcp", version: "0.1.0" }, { capabilities: { tools: {} } } );
const tools = [searchDocsTool, getApiSpecTool, getChangeLogTool];
server.setRequestHandler(ListToolsRequestSchema, async () => { return { tools: tools.map((tool) => ({ name: tool.name, description: tool.description, inputSchema: zodToJsonSchema(tool.inputSchema) })) }; });
server.setRequestHandler(CallToolRequestSchema, async (request) => { const user = getUserContext(); const tool = tools.find((t) => t.name === request.params.name);
if (!tool) {
throw new Error(tool not found: ${request.params.name});
}
const args = (request.params.arguments ?? {}) as Record<string, unknown>;
try { assertToolAllowed(user, tool.name, args); const result = await tool.handler(args as never); writeAudit({ time: new Date().toISOString(), userId: user.userId, role: user.role, tool: tool.name, args, ok: true }); return result; } catch (error) { const message = error instanceof Error ? error.message : String(error); writeAudit({ time: new Date().toISOString(), userId: user.userId, role: user.role, tool: tool.name, args, ok: false, error: message }); throw error; } });
const transport = new StdioServerTransport(); await server.connect(transport);
如果你用到了 zod-to-json-schema,记得安装:
bash npm install zod-to-json-schema
这里的关键点不是代码量,而是结构:
ListTools:告诉客户端有哪些工具和参数CallTool:接收调用并执行- 调用前:先过权限校验
- 调用后:无论成功失败都写审计日志
这就是最小但完整的闭环。
第七步:在 Cursor 中配置 MCP Server
不同版本的 Cursor 配置位置可能略有差异,但核心思路是一致的:把你的 MCP Server 注册进去,让 Cursor 可以通过 stdio 启动它。
一个典型配置长这样:
{ "mcpServers": { "enterprise-context": { "command": "node", "args": ["/absolute/path/to/enterprise-mcp/dist/server.js"], "env": { "MCP_USER_ID": "alice", "MCP_USER_ROLE": "developer" } } } }
如果你开发阶段直接跑 TypeScript,也可以先这样配:
{ "mcpServers": { "enterprise-context": { "command": "npx", "args": ["tsx", "/absolute/path/to/enterprise-mcp/src/server.ts"], "env": { "MCP_USER_ID": "alice", "MCP_USER_ROLE": "developer" } } } }
配置完成后,重启 Cursor 或刷新 MCP 服务列表。
如何验证是否接通
你可以直接在 Cursor 里问:
- “查询内部文档里和退款流程相关的约束,并总结成 3 条实现注意事项”
- “查看 payment-service 的退款接口定义,帮我生成 TypeScript 调用代码”
- “查一下 payment-service 最近变更,分析这次退款失败是否可能和风控校验有关”
如果服务已接通,模型通常会自动触发工具调用,而不是凭空编故事。
第八步:给模型加“提示词边界”,别让它越权发挥
很多团队把 MCP 接通后,马上遇到第二个问题:
模型拿到了工具,但还是会补脑。
所以你需要在 Cursor 的项目规则、团队规则或系统提示中明确边界。
下面给一个可直接改造的提示词模板:
text 你是团队内部的 AI 编程助手。
工作边界:
- 涉及内部业务规则、接口定义、状态机、变更历史时,优先调用 MCP 工具获取事实,不要基于常识猜测。
- 如果工具结果不足以支撑结论,明确说“信息不足”,并指出还需要什么信息。
- 不要编造不存在的内部接口、字段、状态和值班流程。
- 生成代码时,优先遵守查询到的内部文档和 API 约束。
- 涉及重构建议时,先查询变更记录,再评估风险点。
- 不输出任何访问令牌、密钥、数据库连接串等敏感信息。
- 当用户请求超出权限范围时,直接说明无法访问,而不是尝试绕过。
这个边界非常重要,因为:
- 工具解决“事实来源”
- 提示词解决“行为约束”
缺一不可。
第九步:实战示例 1 —— 基于内部 API 生成调用代码
假设你在 Cursor 里输入:
帮我写一个调用 payment-service 创建退款单的 TypeScript 函数,要求带上正确请求头,并处理 PENDING 状态。
模型理想的动作顺序应该是:
- 调用
get_api_spec(service=payment-service, path=/v1/refunds) - 如果需要,再调用
search_docs(query=退款流程) - 根据真实接口定义生成代码
一个可运行的生成结果可以长这样:
ts type CreateRefundInput = { baseUrl: string; token: string; tenantId: string; operatorId: string; orderId: string; amount: number; reason: string; };
type CreateRefundResponse = { refundId: string; status: "PENDING" | "PROCESSING" | "SUCCESS" | "FAILED"; };
export async function createRefund(input: CreateRefundInput): Promise<CreateRefundResponse> {
const response = await fetch(${input.baseUrl}/v1/refunds, {
method: "POST",
headers: {
"Content-Type": "application/json",
"Authorization": Bearer ${input.token},
"X-Tenant-Id": input.tenantId,
"X-Operator-Id": input.operatorId
},
body: JSON.stringify({
orderId: input.orderId,
amount: input.amount,
reason: input.reason
})
});
if (!response.ok) {
const text = await response.text();
throw new Error(create refund failed: ${response.status} ${text});
}
const data = (await response.json()) as CreateRefundResponse;
if (data.status === "PENDING") {
console.log(refund created and waiting for processing: ${data.refundId});
}
return data; }
如果没有 MCP,这段代码大概率会漏掉 X-Tenant-Id 或 X-Operator-Id。
这就是企业上下文接入最直观的价值。
第十步:实战示例 2 —— 用变更记录做排障
假设线上报错:“退款创建后直接失败”。
你在 Cursor 里提问:
最近退款逻辑是不是改过?结合变更记录和文档,帮我分析这次失败最可能的原因。
模型应该:
- 调
get_change_log(module=payment-service) - 调
search_docs(query=退款 风控 校验) - 综合输出判断
一个靠谱的分析结果大概会是:
- 最近在
createRefund.ts增加了“退款前风控校验” - 文档中明确写了“订单已结算时,退款必须先走风控校验”
- 如果当前请求没有补齐风控要求字段,或调用链没经过风控服务,可能会直接失败
- 建议先检查失败请求是否满足结算态退款的前置条件
这种回答的价值不在“语言像人”,而在于它引用了真实变更和真实文档。
第十一步:实战示例 3 —— 让 AI 在重构前先理解边界
很多人最怕的一件事,是让 AI 直接重构核心代码。
正确姿势不是直接说“帮我重构”,而是让它先查上下文:
在不破坏订单状态机约束的前提下,评估
stateMachine.ts的重构方案,并指出哪些状态跳转不能改。
模型先查文档、再查变更,然后才给建议。这样它更容易输出这类结论:
CREATED -> FULFILLED是明确禁止跳转- 最近刚修过一个 CREATED 误入 FULFILLED 的 bug
- 重构时应先把状态校验提炼为纯函数,并补回归测试
- 不建议在第一步重构中改动状态常量值或事件命名
这就是“AI 参与重构”和“AI 胡乱改代码”的分水岭。
第十二步:把本地 Skill 封装成团队可复用能力
很多团队接完 MCP,只把它当成“查资料工具”,其实它还能把团队经验沉淀成可复用 Skill。
所谓 Skill,你可以理解成:
- 一组固定工作流
- 一套提示模板
- 一组工具调用顺序
- 一份团队共识清单
例如你可以把“代码评审检查单”做成一个本地 Skill。
src/skill/code-review-checklist.md
md
Code Review Checklist
适用场景:涉及支付、订单状态机、退款、风控链路的代码变更。
执行步骤:
- 先调用 get_change_log 查看相关模块最近 5 次变更。
- 再调用 search_docs 检索业务流程与状态机约束。
- 如果涉及接口调用,调用 get_api_spec 核对请求头、字段和响应。
- 输出 review 结果时,必须包含:
- 风险点
- 是否违反文档约束
- 是否需要补测试
- 是否存在回滚风险
在团队里,Skill 的价值很实际:
- 新人和老人的 AI 使用质量更一致
- 好的工作流不再只存在某个人脑子里
- AI 从“问答玩具”变成“流程执行器”
如果你想进一步扩展,可以参考这些站内资产思路:
- code-context-mcp:适合补代码库语义检索和架构理解
- StackGuide MCP:适合加项目规则执行、代码评审和交付编排
- context-ops-mcp:适合陌生 TypeScript SaaS 项目的上下文摸底
- Open Context:适合把项目文档读写能力接进 MCP
- llm-context.py:适合高效整理和裁剪要发给模型的上下文
- EGC:适合做跨会话持久记忆,减少上下文丢失
我的建议是:
- MCP 先解决“取事实”
- Skill 再解决“怎么干”
不要反过来。
第十三步:调试方法,别一上来就在 Cursor 里盲猜
MCP 问题排查最好分三层。
1. 先单测数据层
先在 Node 里直接调用数据函数,确认数据能读出来。
例如临时写个 src/debug.ts:
ts import { searchDocs } from "./data/docs.js"; import { getApiSpec } from "./data/apis.js"; import { getChangeLog } from "./data/changes.js";
console.log(searchDocs("退款")); console.log(getApiSpec("payment-service", "/v1/refunds")); console.log(getChangeLog("payment-service"));
运行:
bash npx tsx src/debug.ts
如果这层都读不出数据,就不要急着开 Cursor。
2. 再看工具层返回结构
重点检查:
name是否唯一description是否足够清晰inputSchema是否正确- 返回内容是否是模型容易理解的结构化文本
经验上,返回 JSON 字符串通常比散乱文本更稳定。
3. 最后再测 Cursor 集成
如果 Cursor 里不触发工具,优先检查:
- 配置文件路径是否正确
command/args是否能本地启动- 环境变量是否传进去了
- Server 是否成功列出了 tools
- 提示词是否明确要求“先查工具再回答”
常见坑总结
坑 1:工具定义太宽,模型不会用
错误示例:
query_internal_systemfetch_context
这种名字太抽象,模型不知道什么时候该调。
更好的方式是:
search_docsget_api_specget_change_log
坑 2:返回内容太长,反而污染上下文
不要把整份文档原文直接吐给模型。
建议:
- 默认返回片段
- 限制条数
- 摘要优先
- 真要看全文,再单独设计受控工具
坑 3:把权限放在客户端做
客户端提示词只能约束,不能防守。
真正的权限控制必须在 MCP Server 里做。
坑 4:把“审计”理解成日志打印
控制台打一句 console.log 不叫审计。
至少要记录:
- 谁调的
- 调了哪个工具
- 入参是什么
- 成功还是失败
- 失败原因是什么
- 时间戳
坑 5:没有给模型设边界,导致它继续脑补
你以为接了工具就安全了,其实不是。
如果提示里没明确“事实不足就说不知道”,模型还是可能顺手补全。
坑 6:一开始就接太多系统
最容易失败的做法是第一周就想接:
- 文档系统
- Git 平台
- 工单系统
- 发布平台
- 监控平台
- 向量库
- 权限中心
正确顺序是:
- 先跑通 1 个工具
- 再扩到 3 个核心工具
- 再补权限和审计
- 最后优化检索质量和团队流程
一个更适合真实团队的演进路线
如果你准备正式落地,我建议按下面 4 个阶段推进。
阶段 1:个人可用
目标:我自己在 Cursor 里能查到内部文档和接口定义。
交付物:
- 本地 MCP Server
- 2~3 个工具
- 基本提示词边界
阶段 2:小组试点
目标:3~10 人团队可稳定使用。
交付物:
- 简单角色权限
- 审计日志
- 团队 Skill 模板
- 常见问题手册
阶段 3:平台化
目标:多个项目复用。
交付物:
- 统一身份认证
- 真实 API 平台接入
- 文档检索优化
- Git / 发布 / 工单联动
阶段 4:流程化
目标:让 AI 不只是查资料,而是参与研发流程。
交付物:
- PR 审查 Skill
- 排障 Skill
- 重构评估 Skill
- 交付前检查 Skill
这样做的好处是,每一步都能出价值,不会陷入“大平台永远在建设中”。
小结
如果只靠通用 AI 编程助手,它最多理解“通用编程知识”,很难安全理解你的企业上下文。
真正能落地的方案是:
- 用 MCP Server 把内部知识变成可调用工具
- 用明确的工具定义让模型按需取事实
- 用服务端权限控制和审计日志守住边界
- 用提示词规则约束模型不要脑补
- 再把本地 Skill 沉淀成团队可复用能力
这篇教程里,我们实现了一个最小可用版本,虽然只有文档检索、接口查询和变更记录 3 个能力,但已经足够覆盖很多高频场景:
- 代码生成
- 接口联调
- 线上排障
- 重构评估
你完全可以从这个骨架开始,逐步换成真实数据源,把它变成你们团队的企业上下文入口。
如果你准备继续往前走,下一步优先做这三件事:
- 把文档检索从关键词匹配升级到语义检索
- 把变更记录从本地 JSON 升级到 Git 平台 API
- 把团队 Skill 固化成评审、排障、重构三类标准流程
做到这一步,AI 才不是“会写代码的聊天机器人”,而是真正接入你团队工作方式的开发助手。