1. 为什么你的 AI 编程助手总是“看不懂”整个项目
如果你用过 Cursor、Copilot 或者 Claude Desktop 做代码审计,大概率遇到过这种场景:你让它帮你重构一个 TypeScript 工具函数,它改得挺漂亮,但改完之后项目直接编译不过——因为上层有三个文件还在用旧的函数签名,而 AI 压根不知道这些调用方的存在。
这不是模型不够聪明,而是上下文获取方式的问题。传统 AI 编程助手的工作模式是“你给它看什么,它才知道什么”。你打开一个文件,它就只看到这个文件;你想让它理解整个项目,得手动把几十个文件粘贴进对话框。这种方式在小型脚本里还能凑合,一旦项目超过 50 个文件,基本就废了。
MCP(Model 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_KEY=sk-你的Key TAOTOKEN_BASE_URL=https://taotoken.net/api TAOTOKEN_MODEL=claude-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_diff,dryRun默认为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改成const,console.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时会自动更新。这个优化能把审计速度提升三到五倍。