MCP 协议实战:让 AI Agent 真正连上你的业务系统

发布时间:2026/8/6 12:15:41
MCP 协议实战:让 AI Agent 真正连上你的业务系统 MCP 协议实战让 AI Agent 真正连上你的业务系统MCP 正在成为 AI Agent 接入外部工具的HTTP 时刻。但读文档和实际落地之间隔着一个真实的业务系统。背景上个月给出海电商团队搭一个运营数据 Agent需求很朴素自然语言查订单、分析 SKU 周转、对比各站点 GMV。传统做法是写个 RAG SQL GeneratorPrompt 里拼一堆 Schema 让 LLM 猜怎么查。但有两个根本问题Schema 膨胀出海业务跨多国多站点表结构随站点定制Prompt 塞不下安全风险让 LLM 直接拼 SQL白名单纯靠 Prompt 约束 防君子不防小人我需要一种方式让 Agent 像调用 HTTP API 一样调内部服务 ——MCP (Model Context Protocol)恰好是这个抽象。插一句这让我想起早期门户时代的「频道模板系统」。CMS 后台给编辑一个结构化表单编辑填参数不用碰 HTML。MCP 对 Agent 做的事本质上一样 —— 给 LLM 一个结构化的工具契约让它不用碰底层实现。技术方案MCP 是什么两句话说清楚MCP 是 Anthropic 提出的开放协议定义了两个角色MCP Server暴露 Tools / Resources / Prompts 的服务端MCP Client调用这些能力的一方通常是 AI Agent Host如 Claude Desktop、Cursor、自建 Agent通信走 JSON-RPC 2.0支持stdio和Server-Sent Events (SSE)两种传输。对业务系统集成来说SSE 是最实用的方案 —— 不需要 Agent 和 Server 在同一台机器上。架构设计┌──────────────┐ SSE(HTTP) ┌──────────────┐ gRPC ┌──────────────┐ │ AI Agent │ ◄──────────────► │ MCP Server │ ◄──────────► │ 业务服务 │ │ (Claude/自建) │ │ (Node.js) │ │ (订单/BI等) │ └──────────────┘ └──────────────┘ └──────────────┘MCP Server 充当翻译层把 Agent 的工具调用请求翻译成内部 RPC把返回结果格式化成 Tool Result。选型理由不用改现有服务MCP Server 作为独立 Sidecar 部署业务服务零侵入SSE 模式天然支持跨机器Agent 在云端MCP Server 在内网过一层 API Gateway 就行TypeScript 生态modelcontextprotocol/sdk官方 SDK和现有 Node 技术栈匹配实施步骤Step 1创建 MCP Server 项目mkdir order-mcp-server cd order-mcp-server npm init -y npm install modelcontextprotocol/sdk express cors npm install -D typescript types/node tsxStep 2定义 Tool 清单按业务需求定义了 3 个 Tool// tools/schema.ts export const TOOLS: ToolDefinition[] [ { name: query_orders, description: 按站点、日期范围、订单状态查询订单列表, parameters: { type: object, properties: { site: { type: string, enum: [US, SEA, ME, LATAM], description: 站点代码 }, startDate: { type: string, description: 开始日期格式 YYYY-MM-DD }, endDate: { type: string, description: 结束日期格式 YYYY-MM-DD }, status: { type: string, enum: [PENDING, SHIPPED, DELIVERED, CANCELED] }, limit: { type: number, default: 20 } }, required: [site, startDate, endDate] } }, { name: get_sku_metrics, description: 查询 SKU 的周转天数、库存深度、近 30 天销量, parameters: { type: object, properties: { skuCode: { type: string, description: SKU 编码 }, site: { type: string, enum: [US, SEA, ME, LATAM] } }, required: [skuCode, site] } }, { name: compare_gmv, description: 对比多个站点的 GMV支持同比/环比, parameters: { type: object, properties: { sites: { type: array, items: { type: string }, description: 站点列表 }, compareType: { type: string, enum: [yoy, mom], description: 同比/环比 } }, required: [sites] } } ];注意Tool 的description就是 Agent 的API 文档。写得越精确Agent 调用越准确。这里踩过一个坑 —— 最早compare_gmv没限制sites数组长度Agent 一次传 12 个站点后端 SQL 直接超时。Step 3实现 SSE MCP Server// server.ts import { McpServer } from modelcontextprotocol/sdk/server/mcp.js; import { SSEServerTransport } from modelcontextprotocol/sdk/server/sse.js; import express from express; import { TOOLS } from ./tools/schema; import { OrderService } from ./services/order; const app express(); const transportMap new Mapstring, SSEServerTransport(); // SSE endpoint — 建立长连接 app.get(/sse, async (req, res) { const transport new SSEServerTransport(/messages, res); const mcpServer new McpServer({ name: order-mcp-server, version: 1.0.0 }); // 注册 Tools for (const tool of TOOLS) { mcpServer.tool( tool.name, tool.description, tool.parameters, async (args: any) { return await OrderService.handleToolCall(tool.name, args); } ); } transportMap.set(transport.sessionId, transport); await mcpServer.connect(transport); res.on(close, () transportMap.delete(transport.sessionId)); }); // POST endpoint — 接收 JSON-RPC 消息 app.post(/messages, express.json(), async (req, res) { const sessionId req.query.sessionId as string; const transport transportMap.get(sessionId); if (!transport) { res.status(404).end(); return; } await transport.handlePostMessage(req, res); }); app.listen(3001, () console.log(MCP Server running on :3001));关键点SSE 模式下需要维护sessionId → transport映射。每次请求带sessionIdquery param 来路由到正确的长连接。Step 4在 Agent 侧配置 MCP Client以 Claude Desktop 为例编辑claude_desktop_config.json{ mcpServers: { order-service: { url: https://internal-api.your-company.com/mcp/sse, headers: { Authorization: Bearer your-api-token } } } }自建 AgentWorkBuddy 等则需要在 Agent 侧实现 MCP Client 的 SSE transport。SDK 提供了标准客户端接入代码如下import { Client } from modelcontextprotocol/sdk/client/index.js; import { SSEClientTransport } from modelcontextprotocol/sdk/client/sse.js; const transport new SSEClientTransport( new URL(https://internal-api/mcp/sse) ); const client new Client({ name: my-agent, version: 1.0.0 }); await client.connect(transport); // 获取可用工具列表 const tools await client.listTools(); // 调工具 const result await client.callTool({ name: compare_gmv, arguments: { sites: [US, SEA], compareType: mom } });踩坑记录坑 1SSE 重连风暴现象Agent 掉线后重连短时间内创建了 200 个 SSE 连接服务 OOM。根因Claude Desktop 的重连退避算法默认最大值太小30s突发重连时瞬间打满连接池。解决Server 侧加maxConnectionsPerClient限制我们设 5旧连接加 60s TTL超时主动断Agent 侧reconnect.backoff调到最大 120s// 连接数限制 const MAX_PER_CLIENT 5; const clientConnectionCount new Mapstring, number(); app.get(/sse, (req, res) { const clientId req.headers[x-client-id] as string || req.ip; const count clientConnectionCount.get(clientId) || 0; if (count MAX_PER_CLIENT) { res.status(429).json({ error: Too many connections }); return; } clientConnectionCount.set(clientId, count 1); res.on(close, () { const c clientConnectionCount.get(clientId) || 1; clientConnectionCount.set(clientId, Math.max(0, c - 1)); }); // ... rest of handler });坑 2Tool Result 太大导致上下文爆炸现象query_orders一次返回 500 条订单每条含完整地址、物流轨迹、备注。Agent 收到后 Token 直接飙到上限后续对话全丢。解决Tool 返回做摘要化只返回关键字段列表类结果限制行数大量数据走Resource 模式而非 Tool Result。Agent 拿到 Resource URI 后按需读取// Tool 返回精简版附带 Resource URI async function queryOrders(args: QueryOrdersArgs) { const orders await OrderService.query(args); return { content: [{ type: text, text: JSON.stringify({ total: orders.total, summary: orders.items.slice(0, 20).map(o ({ id: o.id, site: o.site, status: o.status, amount: o.amount, createdAt: o.createdAt })), _more: mcp-resource://orders/detail?ids${orders.itemIds.join(,)} }) }] }; }坑 3认证 token 泄露到 LLM现象有一天翻 Agent 日志发现一次 Tool 调用的错误信息里包含了服务端返回的Authorization header expired: Bearer sk-xxx...。这个信息构成了 LLM 的上下文如果后续对话持续token 有概率被泄露。解决MCP Server 层的错误信息做脱敏处理不透露任何 credential/token/secretsasync function handleToolCall(toolName: string, args: any) { try { return await callInternalService(toolName, args); } catch (err: any) { // 永远不要让原始错误信息进入 Agent 上下文 return { isError: true, content: [{ type: text, text: Service temporarily unavailable, please retry }] }; } }实际内网的错误信息打全量日志到 ELKAgent 只看脱敏后的消息。总结MCP 的意义不在于技术本身有多复杂它就是个 JSON-RPC over SSE而在于标准化了 Agent ↔ 工具之间的契约。这跟当年互联网从各种自定义二进制协议收敛到 HTTP 的逻辑一样标准化降低集成成本、催生生态。几个关键收获Tool description 是最重要的文档写得不精确 Agent 调不对SSE 连接的生命周期管理是线上最大的坑重连策略、连接池、TTL 都要提前设计Tool Result 的粒度决定了 Token 消耗宁可多分几个小 Tool也别用一个巨型 Tool 塞所有数据安全审计要覆盖 Tool→Agent 的返回路径任何错误信息都可能成为 LLM 的上下文目前在出海业务侧MCP Server 已经接了订单、库存、物流三个域。下一步打算把多站点的 BI 指标也通过 MCP 暴露让运营能把「东南亚站上月 GMV 环比为什么掉了 15%」这种问题直接问 Agent。作者lotusxyhf互联网老兵转型出海 AI Agent 赛道。从门户到 AI踩过的坑比写过的代码还多。欢迎评论区交流。