1. 项目概述为什么我们需要深入理解 MCP如果你最近在折腾 AI 应用开发特别是想把不同的工具、数据源和模型能力“粘合”起来构建一个更智能的 Agent智能体那你大概率已经听过 MCP 这个词了。它不是什么新出的服务器型号也不是某个加密协议而是Model Context Protocol的缩写一个由 Anthropic 公司牵头推出的开放协议。简单来说MCP 想解决一个很实际的问题如何让 AI 应用比如 Claude Desktop、Cursor 里的 AI 助手安全、标准化地访问外部工具和数据而无需开发者每次都去写一堆定制化的、脆硬的集成代码。我最初接触 MCP 是因为想给团队内部的 AI 助手接入公司内部的 Jira 看板和 Confluence 文档库。传统做法要么是调用不稳定的 API 包装层要么就是给 AI 一个权限过高的账号安全和稳定性都让人头疼。MCP 的出现相当于定义了一套“插座”和“插头”的标准。任何符合 MCP 标准的工具称为MCP Server都可以被任何支持 MCP 的 AI 应用称为MCP Client比如 Claude Desktop即插即用。这极大地降低了集成成本也让 AI 的能力边界得以灵活扩展。所以这个“完整指南”的目标很明确我们不只停留在“知道 MCP 是什么”而是要彻底吃透它。从协议的核心思想拆解开始到自己动手搭建一个能提供特定服务比如查询天气、管理待办事项的 MCP Server最后再探讨如何将这种能力集成到一个自主运行的 AI Agent 系统中。整个过程我会结合我踩过的坑和实战心得让你不仅能复现更能理解背后的设计哲学和最佳实践。2. MCP 协议深度解析不只是 API更是会话模型很多人会把 MCP 简单地理解成另一种 RPC远程过程调用协议比如 gRPC 或 JSON-RPC 的变种。这其实低估了它的价值。MCP 的核心创新在于它采用了一种资源Resources与工具Tools为中心的声明式模型并且设计为围绕SSEServer-Sent Events的双向通信这更贴合 AI 与外部世界交互的异步、流式特性。2.1 核心概念资源、工具与提示词模板要理解 MCP必须搞清楚三个核心概念它们构成了 Server 向 Client 暴露能力的全部内容。资源Resources你可以把它想象成“只读的数据源”。一个资源有一个唯一的uri如file:///path/to/doc.md或jira://issue/PROJ-123一个mimeType描述其内容格式以及一个name和description供 AI 理解。Client 可以通过read_resource请求来获取资源的内容。例如一个“今日头条新闻”资源其内容就是最新的新闻列表文本。资源的关键在于它是静态或快照式的AI 读取它但不直接修改它。工具Tools工具代表了“可执行的动作”。每个工具有一个name、description和定义输入参数的inputSchema基于 JSON Schema。Client 通过call_tool请求来调用它。例如“创建 Jira 工单”就是一个工具它需要输入project,summary,description等参数。工具是 AI 与外界交互产生“副作用”如创建记录、发送消息的主要方式。提示词模板Prompts这是一个非常实用的设计。它允许 Server 预定义一些复杂的、多步骤的提示词框架。Client 可以获取这些模板get_prompt并传入参数来实例化成一个完整的提示词直接用于与 AI 模型的对话。比如一个“代码审查”提示词模板可以接受code和language参数生成一个结构化的审查请求。这相当于把最佳实践“固化”下来在 Client 侧复用。为什么这样设计传统 API 集成需要 AI 模型去理解复杂的 API 文档和参数结构。而 MCP 通过description和结构化的inputSchema将这些信息以一种对 AI 更友好的方式暴露出来。AI 只需要根据自然语言描述选择正确的工具或资源并生成符合 Schema 的参数即可大大降低了幻觉和调用错误。2.2 通信模型基于 SSE 的会话流MCP 没有采用传统的 HTTP 请求-响应模式而是基于Server-Sent Events (SSE)。这是一个关键区别。在 SSE 模型中Client 与 Server 建立一条长期连接通信以 Server 向 Client 推送事件流text/event-stream的形式进行。连接建立后流程通常是这样的初始化InitializationClient 发送initialize请求Server 回复其支持的协议版本、能力如支持哪些资源、工具以及一个唯一的serverId。列表ListingClient 随后会发送list_系列的请求如list_resources、list_tools、list_prompts来获取 Server 提供的所有能力的元数据。会话Session此后Client 可以在整个会话中随时发送read_resource、call_tool、get_prompt等请求。Server 处理请求并返回结果。通知Notifications这是 SSE 的优势所在。Server 可以主动向 Client 推送notifications例如当一个被监听的资源如日志文件内容发生变化时Server 可以主动推送resources/updated事件Client 从而可以及时获取最新内容实现近乎实时的数据同步。实战心得选择 SSE 而非 WebSocket刚开始我疑惑为什么不用更全双工的 WebSocket。实践后发现对于 MCP 这种以 Server 向 Client 推送状态变化为主、Client 发起操作请求为辅的场景SSE 更简单轻量。它基于 HTTP兼容性更好内置了重连机制并且大多数编程语言都有成熟库。我们只需要关心事件格式MCP 定义的标准 JSON-RPC 消息而不必处理底层的帧协议。2.3 协议格式与安全考量MCP 的消息格式遵循 JSON-RPC 2.0 规范每个消息包含jsonrpc,id,method,params等标准字段。这保证了协议的广泛兼容性和可调试性。安全是 MCP 设计中的重中之重主要体现在无默认网络传输MCP 规范本身不规定传输层。Server 和 Client 通常通过stdio标准输入输出或SSH进行通信。这意味着 Server 默认只运行在本地或受信任的远程主机上极大地缩小了攻击面。你几乎不会看到一个 MCP Server 默认监听一个 TCP 端口。显式权限模型Client如 Claude Desktop在首次连接一个 Server 时会向用户清晰展示这个 Server 提供了哪些资源、工具和提示词并请求用户授权。用户可以看到“这个 Server 想访问你的文件系统”或“拥有发送邮件的权限”从而做出明确选择。这比直接给 AI 一个万能密钥要安全得多。上下文隔离每个工具调用、资源读取都在独立的请求中完成Server 可以实现严格的参数校验和操作审计。注意正因为 MCP Server 通常通过 stdio 运行你在开发时可能会遇到进程管理的问题。例如如果 Server 崩溃需要 Client 有能力重启它。在集成到 Agent 系统时需要设计稳健的子进程管理机制。3. 动手构建你的第一个 MCP Server理解了协议最好的巩固方式就是动手写一个。我们以构建一个“待办事项Todo管理” MCP Server 为例。它将提供一个资源列出所有待办项和两个工具添加待办项、标记完成。我们将使用官方推荐的TypeScript SDK来开发这是目前最成熟、文档最全的方案。3.1 环境准备与项目初始化首先确保你的环境有 Node.js建议 18 版本和 npm。# 创建一个新目录并初始化项目 mkdir mcp-server-todo cd mcp-server-todo npm init -y # 安装 MCP TypeScript SDK 和必要的类型定义 npm install modelcontextprotocol/sdk npm install --save-dev typescript types/node tsx # 初始化 TypeScript 配置 npx tsc --init修改生成的tsconfig.json确保设置合适的编译选项例如module: ESNext,target: ES2022, 和outDir: ./dist。3.2 核心 Server 类实现接下来创建src/server.ts文件开始编写 Server 逻辑。import { Server } from modelcontextprotocol/sdk/server/index.js; import { StdioServerTransport } from modelcontextprotocol/sdk/server/stdio.js; import { CallToolRequestSchema, ListResourcesRequestSchema, ListToolsRequestSchema, ReadResourceRequestSchema, } from modelcontextprotocol/sdk/types.js; // 一个简单的内存存储实际项目中可替换为数据库 interface TodoItem { id: number; title: string; completed: boolean; } class TodoMcpServer { private server: Server; private todos: TodoItem[]; private nextId: number; constructor() { this.server new Server( { name: todo-mcp-server, version: 1.0.0, }, { capabilities: { resources: {}, // 声明我们支持资源 tools: {}, // 声明我们支持工具 }, } ); this.todos []; this.nextId 1; this.setupRequestHandlers(); this.setupErrorHandlers(); } private setupRequestHandlers() { // 1. 处理列出所有资源的请求 this.server.setRequestHandler(ListResourcesRequestSchema, async () { return { resources: [ { uri: todo:///items, mimeType: application/json, name: 所有待办事项, description: 获取当前所有的待办事项列表包括已完成和未完成的。, }, ], }; }); // 2. 处理读取特定资源的请求 this.server.setRequestHandler(ReadResourceRequestSchema, async (request) { if (request.params.uri todo:///items) { return { contents: [ { uri: request.params.uri, mimeType: application/json, // 将待办列表以 JSON 字符串形式返回 text: JSON.stringify(this.todos, null, 2), }, ], }; } throw new Error(Resource not found: ${request.params.uri}); }); // 3. 处理列出所有工具的请求 this.server.setRequestHandler(ListToolsRequestSchema, async () { return { tools: [ { name: add_todo, description: 添加一个新的待办事项。, inputSchema: { type: object, properties: { title: { type: string, description: 待办事项的标题, }, }, required: [title], }, }, { name: complete_todo, description: 根据 ID 标记一个待办事项为已完成。, inputSchema: { type: object, properties: { id: { type: number, description: 要标记为完成的待办事项的 ID, }, }, required: [id], }, }, ], }; }); // 4. 处理调用工具的请求 this.server.setRequestHandler(CallToolRequestSchema, async (request) { const { name, arguments: args } request.params; switch (name) { case add_todo: { const title args?.title; if (typeof title ! string || !title.trim()) { throw new Error(Title is required and must be a non-empty string.); } const newTodo: TodoItem { id: this.nextId, title: title.trim(), completed: false, }; this.todos.push(newTodo); return { content: [ { type: text, text: 待办事项添加成功ID: ${newTodo.id}, 标题: ${newTodo.title}, }, ], }; } case complete_todo: { const id args?.id; if (typeof id ! number) { throw new Error(ID is required and must be a number.); } const todo this.todos.find((t) t.id id); if (!todo) { throw new Error(未找到 ID 为 ${id} 的待办事项。); } if (todo.completed) { return { content: [ { type: text, text: 待办事项 ID: ${id} 已经是完成状态。, }, ], }; } todo.completed true; return { content: [ { type: text, text: 成功将待办事项 ID: ${id} 标记为完成。, }, ], }; } default: throw new Error(Unknown tool: ${name}); } }); } private setupErrorHandlers() { this.server.onerror (error) { console.error([Server Error], error); }; process.on(SIGINT, async () { await this.server.close(); process.exit(0); }); } async run() { const transport new StdioServerTransport(); await this.server.connect(transport); console.error(Todo MCP Server running on stdio...); } } // 启动服务器 const server new TodoMcpServer(); server.run().catch(console.error);代码解析与注意事项能力声明在Server构造函数的capabilities中我们明确声明了此 Server 支持resources和tools。这符合 MCP 的声明式哲学Client 在初始化时就能知道你能做什么。URI 设计我们为资源定义了一个 URItodo:///items。todo是自定义的 scheme///items是路径。这是一种常见的模式用于标识资源的类型和位置。输入验证在call_tool处理中我们对参数进行了严格的类型和有效性检查。这是必须的因为 AI 生成的参数可能不准确健全的校验能防止 Server 崩溃或产生不可预期的行为。错误处理我们使用throw new Error()来返回错误。MCP SDK 会将其捕获并格式化为标准的 JSON-RPC 错误响应返回给 Client。同时我们也监听了进程信号以便优雅关闭。3.3 编译、运行与在 Claude Desktop 中测试首先在package.json中添加启动脚本{ scripts: { build: tsc, start: node dist/server.js, dev: tsx watch src/server.ts } }使用npm run dev可以在开发模式下运行依赖tsx。但为了在 Claude Desktop 中测试我们需要一个稳定的可执行文件。编译运行npm run build生成dist/server.js。配置 Claude Desktop找到 Claude Desktop 的配置文件夹。在 macOS 上通常是~/Library/Application Support/Claude/claude_desktop_config.json在 Windows 上是%APPDATA%\Claude\claude_desktop_config.json。编辑这个 JSON 文件添加我们的 MCP Server 配置{ mcpServers: { todo: { command: node, args: [/ABSOLUTE/PATH/TO/YOUR/mcp-server-todo/dist/server.js] } } }重要提示必须使用绝对路径。node命令也需要在系统 PATH 中或者你也可以直接指向 node 的绝对路径如/usr/local/bin/node。重启 Claude Desktop保存配置并完全重启 Claude Desktop 应用。测试重启后当你新建一个对话时Claude 应该会提示“已连接至待办事项服务器”。你可以尝试对它说“帮我看看现在的待办事项有哪些”触发read_resource或者说“添加一个待办事项写 MCP 博客”触发call_tool“add_todo”。你可以在对话中看到 Claude 调用工具的过程和结果。踩坑记录权限与路径第一次配置时最常见的问题是路径错误或权限不足。确保command中的node在 Claude Desktop 的运行环境中可访问。有时 GUI 应用的环境变量与终端不同使用绝对路径最保险。你的脚本文件有可执行权限在 Unix 系统上。如果看到连接失败可以查看 Claude Desktop 的日志文件位置在配置文件夹内来获取详细的错误信息。4. 进阶构建一个实用的“天气查询” MCP Server内存待办事项只是个玩具。我们再来构建一个更有实用价值的 Server天气查询。这个例子将展示如何集成第三方 API并处理更复杂的参数和错误。4.1 设计资源、工具与选择 API资源我们可以提供一个weather://current/{city}资源返回指定城市的当前天气快照JSON 格式。工具提供一个get_weather工具参数为city城市名和可选的units单位制如metric或imperial。API 选择我们将使用 OpenWeatherMap 的免费 API。你需要去其官网注册一个免费账户获取 API Key。4.2 集成第三方 API 与错误处理创建src/weather-server.ts。import { Server } from modelcontextprotocol/sdk/server/index.js; import { StdioServerTransport } from modelcontextprotocol/sdk/server/stdio.js; import { CallToolRequestSchema, ListResourcesRequestSchema, ListToolsRequestSchema, ReadResourceRequestSchema, } from modelcontextprotocol/sdk/types.js; import fetch from node-fetch; // 需要安装: npm install node-fetch interface WeatherData { city: string; temperature: number; feels_like: number; humidity: number; description: string; icon: string; timestamp: number; } class WeatherMcpServer { private server: Server; private apiKey: string; constructor(apiKey: string) { if (!apiKey) { throw new Error(OpenWeatherMap API key is required.); } this.apiKey apiKey; this.server new Server( { name: weather-mcp-server, version: 1.0.0, }, { capabilities: { resources: {}, tools: {}, }, } ); this.setupHandlers(); } private setupHandlers() { // 列出资源 this.server.setRequestHandler(ListResourcesRequestSchema, async () ({ resources: [ { uri: weather://current/*, // 使用通配符表示需要具体城市 mimeType: application/json, name: 当前天气, description: 获取指定城市的当前天气信息。URI 格式: weather://current/{城市名}例如 weather://current/Beijing, }, ], })); // 读取资源 - 实现从 URI 解析城市并调用 API this.server.setRequestHandler(ReadResourceRequestSchema, async (request) { const uri request.params.uri; const match uri.match(/^weather:\/\/current\/(.)$/); if (!match) { throw new Error(Invalid weather resource URI. Expected format: weather://current/{city}); } const city decodeURIComponent(match[1]); const weather await this.fetchWeather(city); return { contents: [{ uri, mimeType: application/json, text: JSON.stringify(weather, null, 2), }], }; }); // 列出工具 this.server.setRequestHandler(ListToolsRequestSchema, async () ({ tools: [ { name: get_weather, description: 查询指定城市的当前天气。, inputSchema: { type: object, properties: { city: { type: string, description: 城市名称例如 London 或 北京。支持中文和英文。, }, units: { type: string, description: 单位制。可选值: metric(摄氏度), imperial(华氏度)。默认为 metric。, enum: [metric, imperial], default: metric, }, }, required: [city], }, }, ], })); // 调用工具 this.server.setRequestHandler(CallToolRequestSchema, async (request) { if (request.params.name ! get_weather) { throw new Error(Unknown tool: ${request.params.name}); } const args request.params.arguments as { city: string; units?: string }; if (!args || !args.city) { throw new Error(City parameter is required.); } const city args.city; const units args.units || metric; try { const weather await this.fetchWeather(city, units); const unitSymbol units metric ? °C : °F; return { content: [{ type: text, text: **${weather.city}** 当前天气 - 温度: ${weather.temperature} ${unitSymbol} (体感 ${weather.feels_like} ${unitSymbol}) - 湿度: ${weather.humidity}% - 状况: ${weather.description} - 更新时间: ${new Date(weather.timestamp).toLocaleString()} , }], }; } catch (error: any) { // 将 API 错误转化为用户友好的信息 let errorMessage 获取天气信息失败。; if (error.message.includes(404)) { errorMessage 未找到城市 ${city}请检查名称是否正确。; } else if (error.message.includes(401)) { errorMessage API 密钥无效请检查服务器配置。; } else if (error.message.includes(429)) { errorMessage API 调用频率超限请稍后再试。; } throw new Error(errorMessage); } }); } private async fetchWeather(city: string, units: string metric): PromiseWeatherData { const url https://api.openweathermap.org/data/2.5/weather?q${encodeURIComponent(city)}units${units}appid${this.apiKey}; const response await fetch(url); if (!response.ok) { throw new Error(HTTP ${response.status}: ${response.statusText}); } const data: any await response.json(); return { city: data.name, temperature: data.main.temp, feels_like: data.main.feels_like, humidity: data.main.humidity, description: data.weather[0].description, icon: data.weather[0].icon, timestamp: data.dt * 1000, }; } async run() { const transport new StdioServerTransport(); await this.server.connect(transport); console.error(Weather MCP Server running...); } } // 从环境变量获取 API Key const apiKey process.env.OPENWEATHER_API_KEY; if (!apiKey) { console.error(错误请设置 OPENWEATHER_API_KEY 环境变量。); process.exit(1); } const server new WeatherMcpServer(apiKey); server.run().catch(console.error);关键点与避坑指南环境变量管理API Key 等敏感信息绝不能硬编码在代码中。我们通过process.env从环境变量读取。在 Claude Desktop 配置中你需要确保这个环境变量被设置。一种更安全的方式是使用args传递一个配置文件的路径。URI 模式匹配在read_resource处理器中我们使用正则表达式从 URI 中提取城市参数。这使得资源 URI 是动态的、可寻址的。友好的错误处理在call_tool中我们捕获了fetchWeather可能抛出的错误如网络错误、API 错误并将其转换为对最终用户或 AI更友好的自然语言描述。这是提升用户体验的关键。资源与工具的互补注意我们既提供了weather://current/{city}资源也提供了get_weather工具。它们的区别在于资源更适合被 AI 以“读取数据”的方式消费返回的是原始结构化数据JSONAI 可以进一步解析和推理。工具更适合完成一个具体的“任务”返回的是格式化好的、面向人类阅读的自然语言文本摘要。 在实际设计中你可以根据使用场景决定暴露为资源、工具或两者都提供。5. 将 MCP Server 能力集成到自主 AI Agent 中到目前为止我们的 MCP Server 都是被 Claude Desktop 这样的“通用 Client”调用。但在更复杂的自动化场景中我们可能需要构建一个自主运行的AI Agent它能主动规划、决策并调用 MCP Server 提供的工具。这里我们以使用LangChain JS/TS框架为例展示如何集成。5.1 架构设计Agent 作为 MCP Client在这个架构中你的 AI Agent 系统将扮演 MCP Client 的角色。它需要动态发现与管理 MCP Server启动或连接到一个或多个 MCP Server。获取工具列表通过 MCP 协议获取每个 Server 暴露的工具及其 Schema。将工具“翻译”给 Agent 框架将 MCP 工具描述转化为 LangChain 等框架能理解的Tool对象。供 Agent 调用Agent 根据任务规划选择并调用合适的工具。5.2 使用 LangChain 集成 MCP 工具首先安装 LangChain 相关包npm install langchain/core langchain langchain/openai假设我们已经有一个运行在 stdio 上的 Todo MCP Server。我们需要创建一个 LangChain Tool 来封装对它的调用。// src/langchain-mcp-adapter.ts import { DynamicStructuredTool } from langchain/core/tools; import { z } from zod; import { Client } from modelcontextprotocol/sdk/client/index.js; import { StdioClientTransport } from modelcontextprotocol/sdk/client/stdio.js; import { spawn } from child_process; import path from path; /** * 一个通用的 MCP 工具适配器类 * 负责启动 MCP Server 进程连接 Client并将 MCP 工具转换为 LangChain Tool。 */ export class MCPToolAdapter { private client: Client; private serverProcess; constructor(serverScriptPath: string) { // 1. 启动 MCP Server 子进程 this.serverProcess spawn(node, [serverScriptPath], { stdio: [pipe, pipe, inherit], // 将 Server 的 stderr 继承到当前进程便于调试 }); // 2. 创建 MCP Client 并连接到 Server 的 stdio this.client new Client( { name: langchain-agent-mcp-client, version: 1.0.0, }, { capabilities: {}, // Client 的能力声明这里为空 } ); const transport new StdioClientTransport(this.serverProcess); this.client.connect(transport).catch(console.error); } /** * 获取所有 MCP 工具并转换为 LangChain Tool 数组 */ async getTools(): PromiseDynamicStructuredTool[] { await this.client.initialize(); // 等待初始化完成 const { tools } await this.client.listTools(); // 获取工具列表 const langchainTools: DynamicStructuredTool[] []; for (const mcpTool of tools) { // 根据 MCP Tool 的 inputSchema 构建 Zod Schema const zodSchema this.convertJsonSchemaToZod(mcpTool.inputSchema); const tool new DynamicStructuredTool({ name: mcpTool.name, description: mcpTool.description, schema: zodSchema, func: async (args) { // 调用 MCP 工具 const result await this.client.callTool({ name: mcpTool.name, arguments: args, }); // 将结果转换为字符串返回给 Agent // 注意call_tool 返回的 content 是数组我们需要提取文本 const textContent result.content?.find(c c.type text)?.text; return textContent || JSON.stringify(result.content); }, }); langchainTools.push(tool); } return langchainTools; } // 一个简单的 JSON Schema 到 Zod 的转换器简化版仅处理基本类型 private convertJsonSchemaToZod(schema: any): z.ZodTypeany, any, any { if (schema.type object) { const shape: any {}; for (const [key, prop] of Object.entries(schema.properties || {})) { shape[key] this.convertJsonSchemaToZod(prop); } return z.object(shape); } else if (schema.type string) { return z.string(); } else if (schema.type number) { return z.number(); } else if (schema.type boolean) { return z.boolean(); } else if (schema.type array) { return z.array(this.convertJsonSchemaToZod(schema.items)); } // 默认返回 any return z.any(); } async cleanup() { await this.client.close(); this.serverProcess.kill(); } }代码解析进程管理MCPToolAdapter在构造函数中启动了 MCP Server 的子进程。这是关键一步因为 MCP 通信依赖于 stdio。你需要确保 Server 脚本路径正确。协议连接使用StdioClientTransport将 Client 连接到子进程的 stdin/stdout。动态工具创建getTools方法在运行时查询 Server 有哪些工具并根据其inputSchema动态创建 LangChain 的DynamicStructuredTool。这使得我们的 Agent 能够适配任何符合 MCP 协议的 Server无需为每个 Server 硬编码工具。Schema 转换convertJsonSchemaToZod是一个简化版的转换函数将 MCP 工具使用的 JSON Schema 转换为 LangChain 所需的 Zod Schema。在实际生产中你可能需要一个更完善的转换库来处理所有 JSON Schema 特性。5.3 构建一个简单的任务执行 Agent现在我们可以使用这些工具来构建一个 Agent。// src/agent.ts import { ChatOpenAI } from langchain/openai; import { AgentExecutor, createReactAgent } from langchain/agents; import { MCPToolAdapter } from ./langchain-mcp-adapter.js; import * as dotenv from dotenv; dotenv.config(); async function main() { // 1. 初始化 LLM const llm new ChatOpenAI({ modelName: gpt-4o-mini, // 或 gpt-4-turbo temperature: 0, openAIApiKey: process.env.OPENAI_API_KEY, }); // 2. 创建 MCP 工具适配器并获取工具 const todoServerPath path.resolve(process.cwd(), dist, todo-server.js); const adapter new MCPToolAdapter(todoServerPath); const tools await adapter.getTools(); console.log(Loaded ${tools.length} MCP tools:, tools.map(t t.name)); // 3. 创建 ReAct Agent const agent createReactAgent({ llm, tools, }); // 4. 创建执行器 const agentExecutor new AgentExecutor({ agent, tools, verbose: true, // 打印详细执行过程 }); // 5. 运行一个任务 try { const result await agentExecutor.invoke({ input: 请先帮我添加一个待办事项购买 groceries。然后再添加一个阅读 MCP 文档。最后列出所有现有的待办事项给我看看。, }); console.log(\n--- Agent 执行结果 ---); console.log(result.output); } catch (error) { console.error(Agent 执行出错:, error); } finally { // 6. 清理资源 await adapter.cleanup(); } } main();运行这个 Agent你会看到 LangChain Agent 的思考过程由于verbose: true思考识别出需要调用add_todo工具两次。行动调用add_todo工具并传入正确的参数。观察接收工具返回的成功信息。再思考识别出需要调用“列出待办事项”的功能。注意我们的 Todo Server 只提供了todo:///items资源没有对应的“列出”工具。这时一个更智能的 Agent 需要能够理解“列出所有待办事项”这个用户指令对应于“读取todo:///items资源”。这需要更高级的 Agent 规划能力或者我们可以在 Server 端额外暴露一个list_todos工具来简化操作。集成中的挑战与心得工具与资源的映射Agent 框架通常围绕“工具调用”设计。如果你的 MCP Server 主要提供资源可能需要额外包装一层创建一个“读取某资源”的工具或者教会 Agent 理解资源 URI 的概念。更成熟的做法是使用支持 MCP 原生集成的 Agent 框架正在涌现中。错误传播MCP Server 的错误需要被妥善捕获并转换为 Agent 能理解的格式以便其进行重试或调整策略。性能与生命周期频繁地启动/关闭 MCP Server 进程开销很大。在生产环境中通常采用Server 常驻 Client 连接池的模式。我们的适配器类需要改进为支持连接到一个已运行的 Server例如通过 TCP Socket如果 Server 支持的话。6. 生产环境部署与优化考量当你开发完一个有用的 MCP Server并成功集成到 Agent 后就需要考虑如何将它部署到生产环境供团队或更广泛的用户使用。6.1 部署模式从 Stdio 到 Socket开发时我们使用 stdio部署时则有更多选择进程托管推荐用于桌面集成对于 Claude Desktop 这类场景由桌面应用管理 Server 进程的生命周期是最简单的。你只需要提供一个可执行文件或脚本。常驻服务Socket对于 Agent 后端服务你可能希望 MCP Server 作为一个常驻进程运行监听一个 Unix Domain Socket 或 TCP 端口。这样多个 Agent 实例可以连接同一个 Server共享状态如共享的待办列表。MCP SDK 目前对 Socket 传输的支持还在完善中但你可以基于其底层协议自己实现ServerTransport和ClientTransport。HTTP 桥接一个更通用的模式是开发一个轻量的 HTTP 服务它内部启动 MCP Server 并通过 stdio 与之通信然后将 MCP 的资源/工具暴露为 RESTful API。这样任何能发送 HTTP 请求的客户端都能使用而不仅仅是支持 MCP 的 AI 应用。6.2 安全性强化权限细分在 Server 实现中应根据调用者的上下文如果协议扩展支持进行更细粒度的权限检查。例如一个“文件管理” Server 可以限制工具只能访问特定目录。输入消毒与限流对所有来自 Client 的输入进行严格的消毒防止注入攻击。对工具调用进行限流防止滥用。审计日志记录所有的工具调用和资源访问请求用于安全审计和问题排查。6.3 性能与可观测性连接池如果采用 Socket 模式为 MCP Client 实现连接池避免频繁建立连接的开销。超时与重试为工具调用设置合理的超时并实现重试机制以应对临时性故障。指标暴露在 Server 中集成监控暴露如请求数、耗时、错误率等指标方便接入 Prometheus 等监控系统。构建和集成 MCP Server 的旅程本质上是在为 AI 构建一套标准化、可扩展的“手”和“眼”。它剥离了集成中的复杂性让开发者能更专注于工具本身的价值。从理解协议规范到亲手实现 Server再到将其融入自主 Agent 的决策循环每一步都让我对如何设计 AI 可用的接口有了更深的理解。最大的体会是良好的协议设计能极大降低生态的参与门槛而围绕 MCP 正在形成的工具生态很可能成为下一代 AI 应用的基础设施。如果你正在构建复杂的 AI 应用现在投入时间学习 MCP会是一个非常值得的前期投资。