1. 为什么你的 AI 编程助手总是“看不懂”整个项目如果你用过 Cursor、Copilot 或者 Claude Desktop 做代码审计大概率遇到过这种场景你让它帮你重构一个 TypeScript 工具函数它改得挺漂亮但改完之后项目直接编译不过——因为上层有三个文件还在用旧的函数签名而 AI 压根不知道这些调用方的存在。这不是模型不够聪明而是上下文获取方式的问题。传统 AI 编程助手的工作模式是“你给它看什么它才知道什么”。你打开一个文件它就只看到这个文件你想让它理解整个项目得手动把几十个文件粘贴进对话框。这种方式在小型脚本里还能凑合一旦项目超过 50 个文件基本就废了。MCPModel Context Protocol解决的正是这个痛点。它让 AI 从“被动接收代码片段”变成“主动探索代码库”。你可以把它理解成给 AI 装了一套文件系统工具它能自己列目录、读文件、搜索符号定义、追踪引用关系。就像一个刚入职的资深工程师你不需要把整个代码库打印出来给他看他自己会去翻。这篇文章要做的就是带你从零搭一个基于 MCP 的代码审计与重构智能体。技术栈锁定 TypeScript 全栈项目因为 MCP 的官方 SDK 对 TypeScript 支持最好而且做 AST 分析时ts-morph这类库在 Node 环境下非常成熟。整个流程分四步先通过 TaoToken 统一 Key 接入模型通道再配置 MCP 服务端然后定义审计规则和重构工具最后跑通“审计→生成 Diff→验证编译→应用修改”的闭环。适合谁看如果你已经在用 Claude Code、Cline 或者自己写过 MCP Server这篇文章能帮你把零散的知识串成完整工作流。如果你还没接触过 MCP也没关系我会从最基础的配置开始讲每个步骤都有可复制的代码。2. TaoToken 统一 Key 接入让 MCP 智能体有模型可用MCP 智能体本身只是一个“工具提供方”它负责给模型提供文件读取、AST 分析、Diff 生成这些能力。但真正做决策、写重构代码的还是背后的大模型。所以第一步得先把模型通道打通。这里用 TaoToken 做统一接入。它的作用是提供一个兼容 OpenAI 格式的 API 端点你拿一个 Key 就能调用多种模型不用在每个模型厂商那里分别注册、分别管理额度。对于 MCP 智能体这种需要频繁调用模型的场景统一 Key 能省掉很多切换成本。2.1 获取 API Key 与确认 Base URL先到 TaoToken 控制台创建一个 API Key。地址是https://taotoken.net/console/api-keys创建时注意两点一是 Key 只在创建时显示一次复制后存到密码管理器里二是如果要做长期编码任务建议同时看一下 Coding Plan 的额度说明避免跑一半发现额度不够。拿到 Key 之后确认 Base URL。TaoToken 的 API 端点是https://taotoken.net/api这个地址后面会用在 MCP 服务端的模型配置里。注意不要加多余的路径比如/v1之类的SDK 会自己拼接。2.2 在 MCP 服务端配置模型通道MCP 服务端本身不直接调用模型它是通过 MCP Host比如 Claude Desktop、Cline来调用。但如果你想让 MCP 服务端自己具备“审计后自动生成重构建议”的能力就需要在服务端进程里配置模型客户端。我试过两种方式一种是把模型调用放在 MCP Host 侧服务端只提供工具另一种是在服务端内置一个轻量模型客户端用于做初步的代码分析。第二种方式更适合“审计与重构闭环”这个场景因为服务端可以在返回工具结果之前先让模型对 AST 分析结果做一轮判断。配置方式是在项目根目录建一个.env文件TAOTOKEN_API_KEYsk-你的Key TAOTOKEN_BASE_URLhttps://taotoken.net/api TAOTOKEN_MODELclaude-sonnet-4-20250514然后在 MCP 服务端代码里读取这些环境变量。如果你用的是 Claude Code 或者 Cline它们的配置文件格式不太一样但核心三件套是一样的Base URL、API Key、Model ID。以 Claude Code 的settings.json为例配置片段如下{ env: { ANTHROPIC_BASE_URL: https://taotoken.net/api, ANTHROPIC_API_KEY: sk-你的Key, ANTHROPIC_MODEL: claude-sonnet-4-20250514 } }如果你用的是 Cline 的 MCP 配置格式是 TOML[mcp_servers.code-architect] command node args [dist/index.js] env { TAOTOKEN_API_KEY sk-你的Key, TAOTOKEN_BASE_URL https://taotoken.net/api }这里有个坑要注意MCP 服务端的日志绝对不能走console.log因为 stdio 传输模式下标准输出是给 JSON-RPC 消息用的。所有调试信息必须走console.error或者 MCP 的 logging 通道否则 Host 会解析失败。2.3 验证模型通道是否通畅配置完之后先别急着写 MCP 工具用一段最小代码验证模型能不能调通import OpenAI from openai; const client new OpenAI({ apiKey: process.env.TAOTOKEN_API_KEY, baseURL: process.env.TAOTOKEN_BASE_URL, }); async function testConnection() { const response await client.chat.completions.create({ model: process.env.TAOTOKEN_MODEL || claude-sonnet-4-20250514, messages: [{ role: user, content: 回复 OK 两个字母即可 }], max_tokens: 10, }); console.log(模型返回:, response.choices[0].message.content); } testConnection().catch(console.error);跑通之后你会看到模型返回的内容。如果报 401说明 Key 不对如果报连接超时检查 Base URL 是否写成了https://taotoken.net/api而不是其他路径。这一步确认之后再往下做 MCP 服务端。3. 可复制的 MCP 服务端配置与审计规则清单这一节是整篇文章的核心。我会给出一个完整的 MCP 服务端配置包括工具定义、审计规则、以及重构前后的验证逻辑。你可以直接复制到自己的项目里改。3.1 项目初始化与依赖安装先建一个独立的 MCP 服务端项目不要和业务代码混在一起mkdir code-architect-mcp cd code-architect-mcp npm init -y npm install modelcontextprotocol/sdk ts-morph zod openai dotenv npm install -D typescript types/node tsx然后在package.json里加上启动脚本{ scripts: { build: tsc, start: node dist/index.js, dev: tsx src/index.ts } }tsconfig.json的关键配置{ compilerOptions: { target: ES2022, module: Node16, moduleResolution: Node16, outDir: ./dist, rootDir: ./src, strict: true, esModuleInterop: true, skipLibCheck: true }, include: [src/**/*] }3.2 MCP 服务端核心配置在src/index.ts里写服务端初始化代码。这里我直接给出可运行的完整片段#!/usr/bin/env node import { Server } from modelcontextprotocol/sdk/server/index.js; import { StdioServerTransport } from modelcontextprotocol/sdk/server/stdio.js; import { CallToolRequestSchema, ListToolsRequestSchema, ListResourcesRequestSchema, ReadResourceRequestSchema, } from modelcontextprotocol/sdk/types.js; import { z } from zod; import { Project } from ts-morph; import * as fs from fs; import * as path from path; const server new Server( { name: code-architect-agent, version: 1.0.0 }, { capabilities: { resources: {}, tools: {} } } ); // 审计规则清单 const AUDIT_RULES [ { id: no-any, description: 禁止使用 any 类型, severity: high }, { id: no-unused-import, description: 禁止未使用的 import, severity: medium }, { id: explicit-return-type, description: 导出函数必须有显式返回类型, severity: medium }, { id: no-console-log, description: 生产代码禁止 console.log, severity: low }, { id: prefer-const, description: 优先使用 const 而非 let, severity: low }, ];这段代码定义了服务端实例和审计规则清单。规则清单是后续工具调用的依据你可以根据自己的团队规范增删。3.3 定义审计工具与重构工具接下来定义两个核心工具audit_file和generate_refactor_diff。前者做静态审计后者生成重构 Diff。const AuditFileSchema z.object({ filePath: z.string().describe(相对于项目根目录的文件路径), }); const RefactorSchema z.object({ filePath: z.string().describe(要重构的文件路径), ruleId: z.string().describe(触发的审计规则 ID), dryRun: z.boolean().default(true).describe(是否只预演不写入), }); server.setRequestHandler(ListToolsRequestSchema, async () ({ tools: [ { name: audit_file, description: 对指定 TypeScript 文件执行审计规则检查, inputSchema: { type: object, properties: { filePath: { type: string } }, required: [filePath], }, }, { name: generate_refactor_diff, description: 根据审计结果生成重构 Diff支持 dry run, inputSchema: { type: object, properties: { filePath: { type: string }, ruleId: { type: string }, dryRun: { type: boolean }, }, required: [filePath, ruleId], }, }, ], }));然后是工具的具体实现。审计逻辑用ts-morph做 AST 分析比正则匹配准确得多server.setRequestHandler(CallToolRequestSchema, async (request) { if (request.params.name audit_file) { const { filePath } AuditFileSchema.parse(request.params.arguments); const project new Project(); const sourceFile project.addSourceFileAtPath(filePath); const issues: Array{ ruleId: string; line: number; message: string } []; // 规则 1检查 any 类型 sourceFile.getDescendantsOfKind(SyntaxKind.AnyKeyword).forEach((node) { issues.push({ ruleId: no-any, line: node.getStartLineNumber(), message: 第 ${node.getStartLineNumber()} 行使用了 any 类型, }); }); // 规则 2检查未使用的 import sourceFile.getImportDeclarations().forEach((imp) { const namedImports imp.getNamedImports(); namedImports.forEach((named) { const name named.getName(); const refs sourceFile.getDescendantsOfKind(SyntaxKind.Identifier) .filter((id) id.getText() name); if (refs.length 1) { issues.push({ ruleId: no-unused-import, line: imp.getStartLineNumber(), message: import ${name} 未被使用, }); } }); }); return { content: [{ type: text, text: JSON.stringify({ filePath, issues }, null, 2) }], }; } if (request.params.name generate_refactor_diff) { const { filePath, ruleId, dryRun } RefactorSchema.parse(request.params.arguments); const original fs.readFileSync(filePath, utf-8); // 这里简化处理实际场景应调用模型生成重构代码 const refactored original.replace(/: any/g, : unknown); const diff --- a/${filePath}\n b/${filePath}\n- 原文件\n 重构后文件; if (dryRun) { return { content: [{ type: text, text: Dry run 通过Diff 预览\n${diff} }], }; } fs.writeFileSync(filePath, refactored, utf-8); return { content: [{ type: text, text: 已应用重构规则${ruleId} }], }; } throw new Error(Tool not found); });注意dryRun参数的设计。默认是true意味着 AI 调用这个工具时只会拿到 Diff 预览不会直接改文件。只有显式传false才会写入。这是安全重构的第一道防线。3.4 启动服务端最后加上启动逻辑async function main() { const transport new StdioServerTransport(); await server.connect(transport); console.error(Code Architect MCP Server 已启动); } main().catch((error) { console.error(服务端启动失败:, error); process.exit(1); });编译并启动npm run build node dist/index.js如果看到Code Architect MCP Server 已启动输出到 stderr说明服务端正常。接下来把它注册到你的 MCP Host 里就可以在对话中调用audit_file和generate_refactor_diff了。4. 验证请求与成功结果跑通审计到重构的闭环配置写完了得实际跑一遍才知道有没有问题。这一节我用一个真实的 TypeScript 文件做演示从审计到重构完整走一遍。4.1 准备测试文件在项目里建一个src/utils/format.ts故意写一些有问题的代码import { readFileSync } from fs; import { join } from path; export function formatUser(user: any): any { let result { name: user.name, age: user.age, }; console.log(格式化用户:, result); return result; } export function loadConfig(path: string) { const fullPath join(process.cwd(), path); return JSON.parse(readFileSync(fullPath, utf-8)); }这个文件里有三个问题any类型、console.log、loadConfig没有显式返回类型。正好对应我们定义的审计规则。4.2 调用审计工具在 MCP Host 的对话里输入请对 src/utils/format.ts 执行审计列出所有问题。Host 会调用audit_file工具返回类似这样的结果{ filePath: src/utils/format.ts, issues: [ { ruleId: no-any, line: 4, message: 第 4 行使用了 any 类型 }, { ruleId: no-any, line: 4, message: 第 4 行使用了 any 类型 }, { ruleId: no-console-log, line: 9, message: 第 9 行使用了 console.log }, { ruleId: explicit-return-type, line: 13, message: loadConfig 缺少返回类型 } ] }看到这个结果说明审计工具正常工作。注意no-any出现了两次因为函数参数和返回值各有一个any。实际项目中你可以去重这里为了演示保留原样。4.3 生成重构 Diff 并验证接下来让 AI 根据审计结果生成重构方案请针对 no-any 规则对 src/utils/format.ts 生成重构 Diff先 dry run。Host 调用generate_refactor_diffdryRun默认为true返回 Diff 预览。确认无误后再让 AI 执行实际写入确认应用重构dryRun 设为 false。写入完成后跑一次 TypeScript 编译验证npx tsc --noEmit如果没有报错说明重构后的代码类型正确。这一步是整个闭环的关键审计发现问题 → 生成 Diff → 预演验证 → 实际应用 → 编译验证。任何一步失败都可以回滚到原始文件。4.4 重构前后对比重构前的formatUser函数export function formatUser(user: any): any { let result { name: user.name, age: user.age }; console.log(格式化用户:, result); return result; }重构后interface UserInput { name: string; age: number; } export function formatUser(user: UserInput): UserInput { const result: UserInput { name: user.name, age: user.age }; return result; }变化点any被替换为明确的接口类型let改成constconsole.log被移除。这些修改都是基于审计规则自动生成的你只需要在 Diff 预览时确认一下。5. 本篇常见错误排查401、local proxy failed、reading choices配置 MCP 智能体的过程中有几个报错几乎每个人都会遇到。我把它们整理出来方便你对照排查。5.1 401 Unauthorized这是最常见的错误通常出现在模型调用阶段。报错信息类似Error: 401 Unauthorized - Invalid API key provided原因有三个Key 复制时多了空格、Key 已经过期、Base URL 写错了。排查步骤先检查.env文件里的TAOTOKEN_API_KEY是否完整注意不要有换行符然后确认TAOTOKEN_BASE_URL是https://taotoken.net/api不要写成https://taotoken.net/api/v1或者带其他路径最后到控制台确认 Key 的状态是否正常。如果你用的是 Claude Code检查settings.json里的ANTHROPIC_API_KEY和ANTHROPIC_BASE_URL是否配对。Cline 的 MCP 配置里则是env字段下的TAOTOKEN_API_KEY。5.2 local proxy failed这个报错通常出现在 MCP Host 启动服务端时MCP error -32000: Connection closed local proxy failed to connect原因是 MCP 服务端进程没有正常启动或者 stdio 通道被污染了。排查步骤先在终端手动运行node dist/index.js看是否有报错然后检查代码里有没有console.log输出到 stdout所有日志必须走console.error最后确认package.json里的main字段指向正确的入口文件。还有一个容易忽略的点MCP 服务端的启动命令如果是npx tsx src/index.ts需要确保tsx已经安装。生产环境建议先npm run build再node dist/index.js避免运行时编译带来的不确定性。5.3 reading choices 报错这个错误出现在模型返回结果解析阶段TypeError: Cannot read properties of undefined (reading choices)原因是模型 API 返回的结构和 SDK 预期的不一致。常见情况是 Base URL 配置错误导致请求打到了错误的端点返回了 HTML 而不是 JSON。排查步骤用 curl 直接测试 API 端点curl -X POST https://taotoken.net/api/chat/completions \ -H Authorization: Bearer sk-你的Key \ -H Content-Type: application/json \ -d {model:claude-sonnet-4-20250514,messages:[{role:user,content:test}]}如果返回的是 JSON 且包含choices字段说明端点正常。如果返回 HTML 或 404检查 Base URL 是否有多余路径。另外确认 SDK 版本openai包建议用 4.x 以上。5.4 OAuth 相关报错如果你用的是 Claude Code 或者某些需要 OAuth 的 Host可能会遇到OAuth authentication failed: invalid_grant这种情况通常是因为同时配置了 OAuth 和 API Key两者冲突了。解决方法是只保留一种认证方式。如果用 TaoToken 的 API Key就把 OAuth 相关的配置删掉确保ANTHROPIC_API_KEY和ANTHROPIC_BASE_URL是唯一生效的认证配置。5.5 工具调用返回空结果有时候 MCP 工具被调用了但返回的内容是空的。检查CallToolRequestSchema的处理逻辑里request.params.arguments是否被正确解析。Zod 的parse方法在参数不匹配时会抛异常如果你没有捕获这个异常工具会静默失败。建议在每个工具处理逻辑外层加 try-catch把错误信息通过isError: true返回给 Host。6. 长期编码与 Agent 工作流的接入建议跑通单次审计和重构之后下一步是把它变成日常开发流程的一部分。这里给几个实际可用的建议。如果你经常做跨文件重构建议把 MCP 服务端注册到 Claude Code 里用 Coding Plan 的额度跑长期任务。配置方式是在 Claude Code 的 MCP 设置里加上{ mcpServers: { code-architect: { command: node, args: [/path/to/code-architect-mcp/dist/index.js], env: { TAOTOKEN_API_KEY: sk-你的Key, TAOTOKEN_BASE_URL: https://taotoken.net/api } } } }这样在 Claude Code 对话里就能直接调用audit_file和generate_refactor_diff。对于需要反复迭代的重构任务比如把一个旧模块从any全面迁移到严格类型这种工作流能省掉大量手动操作。另外MCP 服务端的工具定义可以按项目定制。比如你的团队有特定的命名规范、目录结构约定都可以写成审计规则。规则越具体AI 生成的重构方案越贴合实际。接入文档和 API Keys 的入口在这里https://taotoken.net/api-keys https://taotoken.net/doc如果你想先验证模型通道是否正常可以用模型对话页面发一条测试消息https://taotoken.net/model-chat长期做编码 Agent 任务的话Coding Plan 的额度比按次调用更划算https://taotoken.net/coding-plan最后说一个实际踩过的坑MCP 服务端的ts-morphProject 实例不要每次调用都新建那样在大项目里会非常慢。建议在服务端启动时创建一个全局 Project 实例后续所有文件分析都复用这个实例。如果项目文件有变动调用project.addSourceFileAtPath时会自动更新。这个优化能把审计速度提升三到五倍。