项目概述
本章构建一个完整的 代码审查 MCP 工具集(Code Review MCP Server),这是 MCP 在实际工程中最常见的应用场景之一。这个 Server 将整合多种能力:
Code Review MCP Server ╔═══════════════════════════════════════════════════════════════╗ ║ ║ ║ Tools(工具) Resources(资源) ║ ║ ┌─────────────────┐ ┌─────────────────────────┐ ║ ║ │ git_diff │ │ git://repo/diff/HEAD │ ║ ║ │ git_log │ │ git://repo/file/{path} │ ║ ║ │ run_tests │ │ analysis://report │ ║ ║ │ lint_code │ └─────────────────────────┘ ║ ║ │ analyze_deps │ ║ ║ │ check_security │ Prompts(模板) ║ ║ └─────────────────┘ ┌─────────────────────────┐ ║ ║ │ full_code_review │ ║ ║ Sampling │ review_pr_checklist │ ║ ║ ┌─────────────────┐ │ explain_test_failure │ ║ ║ │ AI 代码质量评估 │ └─────────────────────────┘ ║ ║ └─────────────────┘ ║ ╚═══════════════════════════════════════════════════════════════╝
项目结构
code-review-mcp/ ├── src/ │ ├── index.ts # 入口:注册所有能力 │ ├── tools/ │ │ ├── git.ts # Git 操作工具 │ │ ├── testing.ts # 测试运行工具 │ │ ├── linting.ts # 代码检查工具 │ │ └── security.ts # 安全扫描工具 │ ├── resources/ │ │ └── git-resources.ts # Git 相关资源 │ ├── prompts/ │ │ └── review-prompts.ts # 代码审查提示词模板 │ └── utils/ │ ├── exec.ts # Shell 命令执行工具 │ └── security.ts # 安全检查工具 ├── package.json └── tsconfig.json
核心工具实现
工具层:Shell 命令安全执行
// src/utils/exec.ts import { exec } from "child_process"; import { promisify } from "util"; const execAsync = promisify(exec); export interface ExecResult { stdout: string; stderr: string; exitCode: number; } /** * 安全执行 Shell 命令 * 注意:只接受预定义命令白名单,禁止直接拼接用户输入 */ export async function safeExec( command: string, args: string[], // 参数独立传递,不拼接 options: { cwd?: string; timeout?: number } = {} ): Promise<ExecResult> { // 命令白名单,只允许这些命令 const ALLOWED_COMMANDS = new Set([ "git", "npm", "npx", "node", "eslint", "tsc", "vitest", "jest", ]); if (!ALLOWED_COMMANDS.has(command)) { throw new Error(`命令 "${command}" 不在允许列表中`); } // 转义参数,防止 shell 注入 const sanitizedArgs = args.map(arg => arg.replace(/[;&|`$(){}[\]\\]/g, "\\") ); const fullCommand = [command, ...sanitizedArgs].join(" "); try { const { stdout, stderr } = await execAsync(fullCommand, { cwd: options.cwd || process.cwd(), timeout: options.timeout || 30000, // 默认 30 秒超时 maxBuffer: 1024 * 1024 * 10, // 10MB 输出上限 }); return { stdout: stdout.trim(), stderr: stderr.trim(), exitCode: 0 }; } catch (err: any) { return { stdout: (err.stdout || "").trim(), stderr: (err.stderr || err.message).trim(), exitCode: err.code || 1, }; } }
Git 工具实现
// src/tools/git.ts import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; import { z } from "zod"; import { safeExec } from "../utils/exec.js"; export function registerGitTools(server: McpServer, repoPath: string) { // ─── git diff ─────────────────────────────────────────────── server.tool( "git_diff", `获取 Git diff 输出,显示代码变更内容。 可以比较:工作区变更、暂存区变更、两个提交之间的差异、特定文件的变更。 返回标准 diff 格式,包含文件路径、变更行数和具体内容。`, { base: z.string().default("HEAD").describe("比较基准(提交 SHA、分支名、HEAD)"), target: z.string().optional().describe("目标(默认为工作区)"), file: z.string().optional().describe("限制到特定文件(可选)"), stat: z.boolean().default(false).describe("只显示统计摘要,不显示详细内容"), }, async ({ base, target, file, stat }) => { const args = ["diff"]; if (stat) args.push("--stat"); args.push(base); if (target) args.push(target); if (file) args.push("--", file); const result = await safeExec("git", args, { cwd: repoPath }); if (result.exitCode !== 0) { return { content: [{ type: "text", text: `Git diff 失败:${result.stderr}` }], isError: true, }; } const output = result.stdout || "(没有变更)"; return { content: [{ type: "text", text: output }] }; } ); // ─── git log ─────────────────────────────────────────────── server.tool( "git_log", "获取 Git 提交历史,返回提交 SHA、作者、日期和提交信息", { limit: z.number().int().min(1).max(100).default(10).describe("最多返回的提交数"), branch: z.string().optional().describe("查看特定分支的历史"), author: z.string().optional().describe("按作者过滤"), since: z.string().optional().describe("起始时间(如 '2 weeks ago')"), }, async ({ limit, branch, author, since }) => { const args = [ "log", `--max-count=${limit}`, "--pretty=format:%H|%an|%ad|%s", "--date=short", ]; if (branch) args.push(branch); if (author) args.push(`--author=${author}`); if (since) args.push(`--since=${since}`); const result = await safeExec("git", args, { cwd: repoPath }); if (result.exitCode !== 0) { return { content: [{ type: "text", text: `Git log 失败:${result.stderr}` }], isError: true, }; } // 格式化为可读表格 const lines = result.stdout.split("\n").filter(Boolean); const formatted = lines.map(line => { const [sha, author, date, message] = line.split("|"); return `${sha.slice(0, 8)} ${date} [${author}] ${message}`; }).join("\n"); return { content: [{ type: "text", text: formatted || "(没有提交记录)" }] }; } ); }
测试运行工具
// src/tools/testing.ts export function registerTestingTools(server: McpServer, repoPath: string) { server.tool( "run_tests", `运行项目的测试套件并返回测试结果。 支持 npm test、vitest、jest 等测试框架。 返回通过/失败统计、失败的测试名称和错误信息。 警告:这会实际执行代码,请确保只在受信任的仓库中使用。`, { pattern: z.string().optional().describe("测试文件匹配模式(如 '**/*.test.ts')"), coverage: z.boolean().default(false).describe("是否生成覆盖率报告"), }, async ({ pattern, coverage }) => { const args = ["test", "--run"]; if (coverage) args.push("--coverage"); if (pattern) args.push(pattern); const result = await safeExec("npx", ["vitest", ...args], { cwd: repoPath, timeout: 120000, // 测试可能耗时较长,设 2 分钟 }); const output = result.stdout + (result.stderr ? `\n${result.stderr}` : ""); const passed = result.exitCode === 0; return { content: [{ type: "text", text: `测试${passed ? "通过" : "失败"} (exit ${result.exitCode})\n\n${output}`, }], isError: !passed, }; } ); }
Resources 实现
// src/resources/git-resources.ts import { ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js"; export function registerGitResources(server: McpServer, repoPath: string) { // 静态资源:HEAD diff 摘要(代码审查的起点) server.resource( "git-head-diff", "git://repo/diff/HEAD", { name: "HEAD 变更摘要", description: "当前工作区相对于 HEAD 的变更统计", mimeType: "text/plain", }, async (uri) => { const result = await safeExec("git", ["diff", "HEAD", "--stat"], { cwd: repoPath, }); return { contents: [{ uri: uri.toString(), mimeType: "text/plain", text: result.stdout || "(无变更)", }], }; } ); // 动态资源:按路径读取任意文件 server.resource( "repo-file", new ResourceTemplate("git://repo/file/{path}", { list: undefined }), { name: "仓库文件", description: "读取仓库中任意文件的当前内容" }, async (uri, variables) => { const filePath = path.join(repoPath, variables.path as string); const resolved = path.resolve(filePath); const base = path.resolve(repoPath); if (!resolved.startsWith(base)) { throw new Error("路径超出仓库范围"); } const content = await fs.readFile(resolved, "utf-8"); return { contents: [{ uri: uri.toString(), mimeType: getMimeType(resolved), text: content, }], }; } ); }
Prompts 实现
// src/prompts/review-prompts.ts export function registerReviewPrompts(server: McpServer) { server.prompt( "full_code_review", "启动完整的代码审查流程:获取 diff → 运行测试 → 代码质量分析 → 生成审查报告", { scope: z.enum(["staged", "head", "branch"]) .default("head") .describe("审查范围"), focus: z.array( z.enum(["bugs", "security", "performance", "style", "tests"]) ).default(["bugs", "security"]).describe("审查重点"), }, ({ scope, focus }) => { const focusDesc = focus.join("、"); return { messages: [{ role: "user", content: { type: "text", text: `请对当前代码变更进行系统的代码审查。 审查范围:${scope === "staged" ? "暂存区变更" : scope === "head" ? "最新提交" : "当前分支所有变更"} 重点关注:${focusDesc} 请按以下步骤进行: 1. 使用 git_diff 工具获取代码变更 2. 使用 run_tests 工具检查测试是否通过 3. 使用 lint_code 工具检查代码风格 4. 综合分析,生成详细的审查报告 审查报告应包含: - **变更摘要**:改动了哪些文件,主要变更内容 - **问题列表**:按严重程度排列(Critical / Warning / Info) - **测试覆盖**:测试结果和覆盖情况 - **安全检查**:是否存在安全风险(重点关注:${focus.includes("security") ? "是" : "否"}) - **改进建议**:具体的修改方案 请开始审查。`, }, }], }; } ); }
Server 入口文件
// src/index.ts — 整合所有模块 import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import * as path from "path"; import { registerGitTools } from "./tools/git.js"; import { registerTestingTools } from "./tools/testing.js"; import { registerGitResources } from "./resources/git-resources.js"; import { registerReviewPrompts } from "./prompts/review-prompts.js"; // 从环境变量获取仓库路径 const REPO_PATH = path.resolve(process.env.REPO_PATH || process.cwd()); // 创建 Server const server = new McpServer({ name: "code-review-mcp", version: "1.0.0", }); // 注册所有能力 registerGitTools(server, REPO_PATH); registerTestingTools(server, REPO_PATH); registerGitResources(server, REPO_PATH); registerReviewPrompts(server); // 启动 async function main() { const transport = new StdioServerTransport(); await server.connect(transport); process.stderr.write(`Code Review MCP Server 已启动\n仓库路径:${REPO_PATH}\n`); } main().catch(err => { process.stderr.write(`启动失败:${err.message}\n`); process.exit(1); });
Claude Desktop 配置
{
"mcpServers": {
"code-review": {
"command": "node",
"args": ["/path/to/code-review-mcp/dist/index.js"],
"env": {
"REPO_PATH": "/path/to/your/project"
}
}
}
}
完整工作流演示
配置好 Server 后,在 Claude Desktop 中的一次完整代码审查对话如下:
用户:我刚提交了一些代码修改,请帮我做代码审查 Claude:好的,我来用代码审查工具进行系统分析。 ► 调用 git_diff(base="HEAD~1", target="HEAD") ► 调用 run_tests() ► 调用 lint_code() 分析结果: 变更摘要:修改了 3 个文件 - src/auth.ts (+45/-12 行) - 新增 JWT 验证 - src/routes/user.ts (+8/-2 行) - 使用新认证 - tests/auth.test.ts (+32/-0 行) - 新增测试 ⚠ 警告(2 个): 1. auth.ts:28 - JWT secret 从环境变量读取,但缺少非空检查 2. user.ts:15 - 缺少对过期 token 的错误处理 ℹ 建议(1 个): 1. 建议增加 token 刷新逻辑的测试覆盖 ✓ 测试全部通过(18/18) ✓ Lint 无错误
教程总结
至此,你已掌握 MCP 协议开发的全部核心知识:从协议原理(第 1-2 章)、基础开发(第 3-4 章)、三大能力实现(第 5-7 章)、安全与质量(第 8-9 章)到完整实战(第 10 章)。MCP 是快速演进的技术,建议持续关注
modelcontextprotocol.io 官方文档和 github.com/modelcontextprotocol 仓库的最新动态。