【从0开发一个 Agent】第七章:实现 MCP (Model Context Protocol)
在上一章中,我们成功实现了 Tool Calling,让 AI 长出了“手脚”。但很快你就会发现一个工程痛点:如果你的企业有 100 个内部系统(如 Jira、Slack、内部数据库),难道你要在 AI 后端的 tools.ts 里手写 100 个工具函数吗?当外部 API 发生变更时,难道你要修改 AI 的核心代码吗?
本章,我们将引入 AI 时代的“USB 接口”——MCP (Model Context Protocol)。通过 MCP,你的 Agent 将具备“即插即用”的无限扩展能力。
1. 为什么传统的 Tool 不够用?
传统的 Tool Calling 存在严重的耦合问题:
- 集成成本高:每个工具的参数校验、API 鉴权、错误处理都需要在 AI 应用内部硬编码。
- 缺乏标准化:不同工具的输入输出格式各异,大模型需要消耗大量 Token 去理解这些非标准格式。
- 上下文割裂:当工具变多时,一次性将所有工具的定义(JSON Schema)塞给大模型,会迅速耗尽上下文窗口。
MCP 的诞生就是为了解决这些问题。 它由 Anthropic 提出,本质上是一个开放标准协议。它定义了 AI 模型、工具(API)、数据源之间进行标准化交互的规范。
2. MCP 的核心架构与生命周期
MCP 遵循经典的 Client-Server 架构。
MCP 生命周期解析:
- 初始化 (Initialization):Agent 启动时,MCP Client 与各个 MCP Server 建立连接(支持 STDIO、SSE、Streamable HTTP 等传输协议)。
- 能力发现 (Capability Discovery):Client 自动拉取 Server 暴露的 Tools、Resources 和 Prompts,并动态注册到 Agent 中。
- 动态注入 (Dynamic Injection):用户的提问与这些动态获取的工具定义一起下发给 LLM。
- 协议化执行 (Protocol Execution):LLM 决策后,Client 将请求通过标准化协议路由至对应的 Server 执行。
3. 代码实现:构建 MCP 客户端
在我们的 Next.js 项目中,我们需要在后端维护一个 MCP Client 实例,用于连接外部的 MCP Server。
首先安装 MCP 官方 SDK:
npm install @modelcontextprotocol/sdk
在 src/lib/mcp/client.ts 中封装 MCP 客户端:
// src/lib/mcp/client.ts
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
import { SSEClientTransport } from "@modelcontextprotocol/sdk/client/sse.js";
export async function getMCPTools() {
const client = new Client({
name: "ai-agent-enterprise",
version: "1.0.0",
});
// 连接到本地的 MCP Server (例如一个旅游助手或文件系统服务)
const transport = new SSEClientTransport(new URL("http://localhost:3001/sse"));
await client.connect(transport);
// 动态获取所有可用工具
const { tools } = await client.listTools();
return { client, tools };
}
设计思考:
这里我们使用了 SSE (Server-Sent Events) 传输协议。如果你的 MCP Server 是本地脚本,可以使用 STDIO;如果是部署在云端的微服务,推荐使用最新的 Streamable HTTP 协议。
4. 将 MCP 工具无缝接入 AI SDK
MCP 返回的工具格式与 Vercel AI SDK 的 tool 格式不完全一致,我们需要一个转换层。
在 /api/chat/route.ts 中,我们在每次对话前动态拉取 MCP 工具:
// src/app/api/chat/route.ts
import { streamText, tool } from 'ai';
import { z } from 'zod';
import { getMCPTools } from '@/lib/mcp/client';
import { getCurrentTime } from '@/lib/ai/tools'; // 上一章的本地工具
export async function POST(req: Request) {
const { messages } = await req.json();
// 1. 获取本地工具
const localTools = { getCurrentTime };
// 2. 动态获取并转换 MCP 工具
const { client, tools: mcpToolsRaw } = await getMCPTools();
const mcpTools = Object.fromEntries(
mcpToolsRaw.map((mcpTool) => [
mcpTool.name,
tool({
description: mcpTool.description,
parameters: z.object({}) as any, // 实际项目中需根据 mcpTool.inputSchema 动态生成 Zod Schema
execute: async (args) => {
// 通过 MCP Client 调用远程工具
const result = await client.callTool({ name: mcpTool.name, arguments: args });
return result.content;
},
}),
])
);
// 3. 合并本地工具与 MCP 工具
const allTools = { ...localTools, ...mcpTools };
const result = streamText({
model: openai('gpt-4o'),
messages,
tools: allTools,
maxSteps: 5,
});
return result.toDataStreamResponse();
}
5. Agent 自动发现与调用展示
当 MCP Server 上线新工具时,无需重启或修改 AI Agent 的任何代码,Agent 会在下一次对话时自动发现它。
在前端,我们在上一章实现的 MessageBubble 中已经具备了工具状态渲染能力。当 Agent 调用 MCP 工具时,UI 会自动展示:
正在调用工具: search_flights
工具调用完成
这种无缝的体验正是 MCP 带来的工程红利。
6. 测试验证
验证清单:
- 启动一个测试用的 MCP Server(如官方的
@modelcontextprotocol/server-filesystem)。 - 在 AI 聊天中输入:“帮我列出当前目录下的文件”。
- 观察后端日志,确认 MCP Client 成功发起了 tools/call 请求。
- 确认前端正确展示了工具调用状态,并返回了真实的文件列表。
7. 常见问题与踩坑分析
问题 1:MCP 工具参数校验失败
原因:MCP 返回的是 JSON Schema,而 AI SDK 强依赖 Zod。
解决:可以使用 zod-to-json-schema 的反向库,或者编写一个通用的 jsonSchemaToZod 转换函数,在运行时动态生成 Zod 校验规则。
问题 2:每次请求都建立 MCP 连接,导致延迟极高
原因:在 API Route 内部每次实例化 new Client() 会带来巨大的握手开销。
解决:在生产环境中,应将 MCP Client 作为全局单例(Singleton)或使用连接池(Connection Pool)进行管理,复用 SSE/HTTP 长连接。
本章总结
- 我们理解了传统 Tool Calling 的耦合痛点,以及 MCP 作为“AI 时代 USB 接口”的核心价值。
- 掌握了 MCP Client-Server 架构及其动态发现、协议化执行的生命周期。
- 在 Next.js 中实现了 MCP 客户端,并将 MCP 工具动态转换、无缝接入 Vercel AI SDK。
- 实现了本地工具与远程 MCP 工具的混合调度。
至此,你的 Agent 已经具备了连接万物、无限扩展的能力。
但真正的企业级任务往往是复杂的。比如“分析昨天的 GitHub Issue 并生成日报”,这需要多个步骤和多个工具的组合。从下一章开始,我们将引入 Memory(长期记忆)与 RAG(检索增强生成),让 Agent 拥有真正的“企业大脑”。
更多推荐



所有评论(0)