去年我们团队接了个内部需求:把两个月的 MCP Demo 代码改造成支撑三个业务方共用的 AI 自动化中台。原以为只是把脚本包一层 HTTP 服务,结果从架构评审到上线,整整折腾了六周。这期间踩的坑让我意识到一个事实:MCP(Model Context Protocol)本身不难,真正难的是从"能跑"走向"能生产"。
如果你也在做 MCP 相关的 Agent 自动化平台,或者正准备把实验性项目推向生产,这篇文章大概能帮你省掉几周的弯路。我会从架构演进、权限沙箱设计、实测踩坑三条线展开,尽量说人话,给可直接参考的方案。
1. Toy Demo 与生产系统的分水岭:不是模型不够强,是工程问题没人管
先对齐一下概念。MCP 是模型上下文协议,它为 AI 模型和外部工具、数据源之间定义了一套标准化的通信方式。你可以把它理解成 AI 领域的 USB-C 接口:过去每个 AI 应用接一套工具就要写一套私有协议,现在工具侧实现一次 MCP Server,所有支持 MCP 的客户端(Claude、Cursor、自研 Agent 框架等)都能直接调用。
Demo 阶段,我们通常做的是跑通一个 MCP Server,注册几个 Tool,然后在本地用 MCP Client 调一下,看到大模型正确调用了工具就完事了。但生产环境完全是另一套要求:
- 多租户隔离:三个业务方共用一个中台,A 业务的数据不能被 B 业务的 Agent 读到。
- 权限模型:大模型拿到工具调用权后,谁能调、能调哪些、参数怎么校验、敏感操作怎么办。
- 稳定性:工具超时、服务重启、消息积压、上下游接口抖动,这些在生产里不是"可能发生",而是"必然发生"。
- 可审计性:Agent 做了什么决策、调用了哪些工具、传了什么参数,全部要能回溯。
- 可观测性:中台内部链路长,一个问题要能从前端请求一路追踪到工具执行结果。
换句话说,Demo 解决的是"这个方向能不能走通",生产落地解决的是"这个系统能不能在无人值守的情况下稳定运行"。大部分团队卡住的并不是模型能力,而是这些工程问题没有提前纳入设计。
1.1 先想清楚:中台到底要承载什么
在动手写架构之前,建议先回答三个问题:
- 谁在用?是内部员工、外部客户,还是多个业务系统?
- 工具从哪里来?自研工具、第三方 SaaS、内部遗留系统,还是动态注册的外部 MCP Server?
- Agent 的决策边界是什么?全自动执行,还是关键步骤需要人工确认?
这三个问题的答案直接决定权限沙箱和中台架构的复杂程度。我见过一个团队在自用阶段做得非常轻,接入三方 MCP Server 时完全放开权限,上线两周后出现了一次数据误删事故——这是第三次提到权限问题的原因,也是后面会花大篇幅讲它的意义所在。
1.2 别急着上微服务
很多读者看到"中台"两个字就容易联想到微服务、K8s、消息队列。但以我实际经验,初期的生产化改造,模块化单体反而更合适。原因很简单:MCP 中台的核心复杂度在协议适配和权限控制,不在弹性扩容。先把模块边界划清楚(协议层、编排层、工具层、权限层),跑通一两个真实场景,再根据压力测试结果决定是否拆分。一上来就拆八个小服务,光是排查一个工具调用的分布式链路就能让你崩溃。
2. MCP 协议的本质:为 Agent 定义一个标准化的 I/O 接口层
网上讲 MCP 的文章很多,但大多数停留在"什么是 Tool、什么是 Resource"的概念层。实际动手做中台,必须吃透的是协议背后的交互模式和数据模型。
2.1 MCP 的核心抽象:不只是远程调用
MCP 协议定义了三种核心原语:
- Tool:可被模型调用的函数,有名称、描述、输入 Schema(JSON Schema)。执行由 Server 完成,结果返回给 Client。
- Resource:提供给模型读取的上下文数据,比如文件内容、数据库记录、API 响应。Resource 是"读",Tool 是"做"。
- Prompt:可复用的提示词模板,服务端下发,客户端渲染后交给模型。
从架构角度看,真正需要重点设计的是 Tool。因为 Tool 承载了"Agent 对现实世界的操作能力",也是权限沙箱的边界所在。
举个例子,一个简单的 MCP Server 注册 Tool 的代码(TypeScript):
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; import { z } from "zod"; const server = new McpServer({ name: "erp-connector", version: "1.0.0", }); server.registerTool( "query_sales_order", "查询销售订单明细,支持按订单号、客户名称、时间范围过滤", { orderNo: z.string().optional().describe("订单号,精确匹配"), customerName: z.string().optional().describe("客户名称,模糊匹配"), startDate: z.string().optional().describe("开始日期,YYYY-MM-DD"), endDate: z.string().optional().describe("结束日期,YYYY-MM-DD"), }, async (params) => { // 在这里做权限校验、参数校验,然后调用真实业务系统 return { records: [], total: 0, }; } );这里有一个很关键但容易被忽略的点:Tool 的 description 和参数 Schema 决定了模型是否正确使用它。模型通过自然语言理解工具用途,如果你的描述写得含糊,或者参数定义不合理,模型就会猜——猜错率高达三成以上。
2.2 传输层选型:stdio 还是 HTTP
MCP 支持两类传输方式:stdio(标准输入输出)和 HTTP/SSE(Streamable HTTP)。
Demo 阶段几乎都用 stdio,因为本地启动子进程最简单。但生产环境,stdio 有三个痛点:
- 进程生命周期管理:服务端进程由客户端拉起,客户端退出时未必能正确回收子进程,时间长了会有僵尸进程。
- 横向扩展受限:stdio 模式天然是一对一的,不方便做负载均衡。
- 跨网络部署困难:MCP Server 和客户端必须同一台机器,这在中台场景下基本不可用。
生产环境建议直接上 Streamable HTTP。我们当时的做法是:每个工具域(比如"ERP 域""CRM 域")独立部署成一个 MCP Server,对外暴露 HTTP 端点,中台作为 MCP Client 统一接入。这样每个 Server 的扩容、更新、故障隔离都独立,不是所有服务混在一个进程里互相影响。
| 维度 | stdio | HTTP/SSE |
|---|---|---|
| 部署形态 | 本地子进程 | 独立服务 |
| 进程管理 | 客户端负责,容易残留 | 服务自身管理 |
| 横向扩展 | 差 | 好 |
| 跨域访问 | 不支持 | 支持 |
| 适合场景 | 本地调试、单机工具 | 生产中台 |
2.3 每次工具调用都是一次协议握手
很多初次接触 MCP 的开发者以为工具调用就是"Agent → MCP Server"一条直线。实际上在 MCP 协议中,工具调用流程是这样的:
- 客户端声明能力(client/initialize)
- 客户端请求服务端工具列表(tools/list)
- 模型根据工具描述生成调用意图
- 客户端发送工具调用请求(tools/call)
- 服务端执行并返回结果(isError 标志 + 内容)
- 客户端把结果交还给模型,模型决定下一步行动
这意味着每一次工具调用都是一次完整的协议交互。中台的性能优化要重点看 tools/list 的响应速度和 tools/call 的超时设置。我们当时给 List 结果加了缓存,效果显著——因为 Agent 在一次多步任务里可能反复获取工具列表。
3. 架构演进的三阶段:单体脚本、模块化编排、中台化治理
不少团队第一次做 MCP 项目都是从一个脚本开始:
# 1. 启动一个 MCP Server npx tsx server.ts # 2. 在一个 Agent 框架里配置 client就这样跑起来了。但这个阶段只适合验证,距离"中台"还差得很远。我把它分成三个阶段来拆解。
3.1 第一阶段:单体脚本(玩具期)
特点:
- 一个进程内包含全部 MCP Server 逻辑
- Tool 直接操作数据库/文件系统
- 权限控制为零或仅依赖硬编码账号
- 问题排查靠 print
这个阶段的核心目标是快速验证 MCP 协议能不能解决业务问题。如果验证通过,就要尽快做第二阶段,不要贪图方便继续往上叠功能。叠得越多,后面的重构成本越高。
3.2 第二阶段:模块化编排(可用期)
模块化编排是"中台化"的过渡形态。这个阶段的架构核心包括:
- 客户端管理模块:统一管理多个 MCP Server 的连接状态、重连机制。
- 工具注册中心:汇总所有 MCP Server 暴露的工具,做统一索引和去重。
- 执行编排层:支持一个业务任务拆成多个工具调用,加上顺序执行、条件分支。
- 审计日志模块:为每次工具调用记录入参、出参摘要、耗时、调用方信息。
以工具注册中心的实现为例,本质上就是把所有 MCP Server 的 tools/list 结果合并成一张"工具总表",并缓存下来:工具 ID 由serverName + toolName组成,这样不同 MCP Server 里同名工具不会冲突。中台对外暴露的任务接口,只需要接收自然语言指令,内部通过大模型做任务规划,然后按编排逻辑调用工具。
此时权限沙箱开始介入,但还比较粗糙:只有调用方认证(应用级 Token),没有精细化到工具级参数级。如果团队能接受"内部使用 + 低风险场景",这一步已经够用。但如果涉及财务、删除、写库,必须进入第三阶段。
3.3 第三阶段:中台化治理(生产期)
生产期架构需要在第二阶段基础上增加四块:
- 统一网关:所有 Agent 请求进入网关,做应用认证、频控、审计标记生成 Trace ID。
- 权限决策服务:用独立的策略引擎,评估"谁(应用/用户)在什么条件下、可以对什么工具操作",输出允许/拒绝/二次确认。
- 动态工具发现:新接入一个 MCP Server 时,不需要改中台核心代码,通过配置注册即可自动完成工具列表同步。
- 运维支撑:链路追踪、日志汇聚、告警,全部对齐公司统一监控体系。
这个阶段的权限决策已经不能散落在各个工具里写了,必须集中统一,否则审计和策略调整就是灾难。另一个关键点是把"模型决策"和"平台执行"解耦:模型只负责生成意图,是否真正执行由平台侧的动作策略控制。生产环境里的 Agent 哪怕模型判断失误,平台侧仍能拦住危险操作——这就是安全兜底。
3.4 演进过程的版本节奏参考
| 阶段 | 周期参考 | 核心目标 | 验收标准 |
|---|---|---|---|
| 单体脚本 | 1-2 周 | 验证协议可行 | 一个完整业务链路跑通 |
| 模块化编排 | 2-4 周 | 支撑多条业务 | 5 个以上 MCP Server 接入,无代码侵入 |
| 中台化治理 | 4-8 周 | 权限/审计/运维完善 | 通过内部安全评审,稳定运行 2 周以上 |
4. 权限沙箱:让大模型拿到真实权限的核心防线
权限沙箱是 MCP 中台生产化最容易被低估的部分。很多人觉得"大模型只是调工具,有什么威胁?"——这话大错特错。大模型的工具调用是自主决策的,你无法保证它在复杂任务中不会误触危险操作。更不要说提示注入攻击:一段看似无害的外部数据可能诱导模型去调用敏感工具。所以权限沙箱不是可选项,是必需项。
4.1 双 Token 鉴权:区分"调用者身份"和"工具访问权"
我们的沙箱设计了双层身份:
- 应用 Token:代表调用中台的是哪个业务方,比如"供应链系统""客服助手"。
- 用户 Token:代表当前操作者,通常在任务提交时透传,中台侧换取用户上下文。
权限判断时,用用户 + 应用 + 工具三元组。如果业务方没有用户概念,就用应用 Token 降级。这样设计的好处是:审计日志里能同时看到"谁的应用、哪个用户、调了什么工具",出了事故不用大海捞针。
4.2 工具级权限组:按危险度分层
把所有注册进中台的工具按危险度分四档:
- L1 只读查询:如查询订单、获取天气、检索文档。无需额外确认。
- L2 可写操作:如创建草稿、发送通知、更新自身业务数据。
- L3 敏感操作:如删除数据、修改核心配置、转账、推送消息到外部用户。
- L4 高危操作:如批量删除、读取敏感个人信息、生产环境运维命令。
L1 直接放行;L2 需要应用 Token 具备对应权限;L3 除了权限,还要强制二次确认(用户在交互端点击确认,或者配置策略自动放行+事后通知);L4 默认禁止,需要单独走审批流程开通白名单。
权限策略示例(JSON):
{ "policyId": "erp-bond-policy", "subjects": ["user:zhangsan", "app:supply-chain"], "resource": "tool:erp-connector:delete_order", "action": "allow", "conditions": { "requireConfirmation": true, "paramsConstraint": { "orderStatus": ["cancelled"], "operator": "admin" } }, "expiresAt": "2025-12-31T23:59:59Z" }这里有一个常见误区:只校验工具是否在白名单,不校验参数。比如"删除订单"工具本身是必要的,但如果没有参数约束,模型可能在某个提示注入下把全量订单都删掉。我们后来给所有 L3/L4 工具都加了参数约束,只允许满足条件的调用。
4.3 参数级校验:别完全信任 JSON Schema
MCP 工具注册时会带一个 JSON Schema,但实际调用时它只能做类型校验,做不了业务语义校验。比如一个"查询员工工资"的工具,Schema 要求传入员工 ID,你无法通过 Schema 判断当前请求的用户是否允许查看这个员工的工资。这需要在工具执行前插入一层业务校验钩子,我们称之为"参数守卫"。
参数守卫做的事:
- 字段合法性:枚举值、正则匹配、长度限制。
- 数据权限过滤:比如订单工具自动把当前用户不可见的客户信息从结果中剔除。
- 危险参数检测:比如出现
rm -rf、drop table、URL 指向内网 IP 等情况,拦截并告警。
另外非常建议对 MCP Server 返回的内容做脱敏:返回给模型的输出里,身份证号、手机号、银行卡号统一打码。模型不需要完整明文也能完成多数任务,但一旦返回出去,这些信息就可能出现在日志里,扩大到不必要的暴露面。
4.4 进程隔离:沙箱不能只靠逻辑
权限控制之外,还要考虑运行隔离。我们的实践是:
- 每个 MCP Server 单独跑在容器里,Container 内非 root 用户运行。
- 容器只挂载最小文件系统,禁止写 host 路径。
- CPU/内存限额严格设置,防止某个工具死循环拖垮整个中台。
- 对需要访问外部系统的工具网络出方向做白名单,只允许访问必要的端点和端口。
这个做法的本质是:即使权限决策环节被攻破,也还有运行环境这层兜底。不要依赖单一防线,纵深防御才是生产级的思路。
4.5 一次完整的权限校验链路
一个工具调用从请求到返回,中间要经过的检查点按顺序是:
- 网关认证:校验应用 Token / 用户 Token,做好频控计数。
- 工具级策略:查策略引擎,判断该用户+应用是否有此工具调用权。
- 危险度分流:L1 直接通过;L3/L4 进入二次确认或审批流程。
- 参数守卫:业务语义校验和数据权限过滤。
- 执行审计:发出调用前先落审计日志(状态:执行中)。
- 结果脱敏:返回前统一处理敏感字段。
- 调度监控:记录耗时、结果、Token 消耗,写回监控系统。
这样走下来,每次工具调用平均增加大约 15-40ms 的额外延迟。用真实业务场景压测过,整体影响在可接受范围,换来的是事故发生后能精确回溯到每一步决策依据。
5. 实测踩坑清单:从本地验证到部署上线的十类问题
这部分是我最想分享的。网上教程讲流程、讲概念的多,把踩坑细节讲透的少。下面这些坑,每一个我都真金白银地踩过,按"现象 → 原因 → 解决方案"的结构记录。
5.1 stdio 进程残留
现象:本地联调跑了几十次后,机器上多了一堆 node 子进程。 原因:MCP Client 异常退出时,不会主动 kill 掉子进程;如果用 tsx 启动,每跑一次还会多一层进程。 解决方案:生产环境全量切 HTTP/SSE;本地调试用--inspect加超时退出脚本,或者直接给子进程设置detached: false且监听父进程退出信号。
5.2 工具描述太抽象导致误调用
现象:Agent 经常在应该调用"查询库存"时调用了"查询商品列表"。 原因:注册工具时 description 写得太专业,"查库存水位"和"查询商品基础信息"在语义边界上没写清楚。 解决方案:所有工具 description 按"用途 + 什么时候用 + 什么时候不用"三段式写。并且做一次工具命名审查,避免多个工具名称包含相同的关键词。
5.3 tools/list 返回速度拖慢 Agent 决策
现象:Agent 多步任务执行非常慢,每步都要 3-5 秒。 原因:每次 initialize 后,Agent 框架都会重新拉取工具列表;如果 MCP Server 端 tools/list 里还动态生成了很多 Resource 定义,耗时更高。 解决方案:在中台侧缓存工具列表,定期刷新而不是每次都实时拉取;同时在 MCP Server 端把 Resource 声明精简,避免把大量数据塞进协议元数据。
5.4 JSON Schema 校验的隐性 Bug
现象:合法参数被 MCP SDK 拒掉,错误信息含糊。 原因:某些 SDK 对additionalProperties的处理不一致,或者 Agent 框架在构造参数时多带了一个额外字段导致整包被拒。 解决方案:服务端注册 Tool 的入参不要完全交给 SDK 默认校验,自己在参数守卫层做更高容错的解析,过滤掉多余字段后再进入业务逻辑。
5.5 逃生通道工具
现象:测试中发现用户可以通过一个"通用 HTTP 请求工具"访问到内网元数据服务。 原因:工具库里有开发者调试时留下一个http_request通用工具,本来用于调用任意 HTTP API,结果被模型利用去请求内部地址。 解决方案:彻底排查所有 MCP Server 注册的工具清单,删除一切"通用执行类"工具(shell、http_request、任意 SQL 执行)。如果需要访问特定 API,写专用的 MCP 工具,并在代码里写死目标端点。
5.6 并发调用与状态残留
现象:两个任务同时调用同一个"导出报表"工具,导致文件互相覆盖。 原因:工具实现里用了一个固定的临时文件名。 解决方案:所有工具实现做到无状态化,输出文件名带唯一 ID;跨工具的共享状态不能留在内存里,要么用 Redis,要么通过资源 ID 在调用参数里显式传递。这是 MCP 工具设计里非常容易踩的隐性约束。
5.7 缺少超时与重试导致链路阻塞
现象:某个第三方接口抖动,Agent 任务卡了 8 分钟才失败。 原因:没有设置工具级超时;底层 HTTP Client 默认超时很长。 解决方案:所有 MCP Client 的 tools/call 请求设 30 秒超时,工具内部调用外部 API 设 10 秒超时,并区分"可重试错误"(网络波动)和"不可重试错误"(参数错误、权限错误)。重试采用指数退避,最大 3 次。
5.8 审计日志里记录全量出参,导致敏感数据泄漏
现象:合规审查发现日志平台里有完整身份证信息。 原因:审计模块直接把工具调用出参全量打印了。 解决方案:出参日志只保留isError、耗时、结果摘要,具体数据字段不落日志;需要排查问题时再通过 Trace ID 关联到临时存储,且临时存储 24 小时自动清理。
5.9 动态接入 MCP Server 时的版本漂移
现象:一个 MCP Server 升级后,工具入参从order_id改成了orderNo,线上 Agent 开始报错。 原因:工具注册中心缓存了旧 Schema,但运行时没有做兼容校验。 解决方案:接入 MCP Server 时建立契约测试,每次上线前自动比对工具 Schema 变更,有破坏性变更(比如字段改名、删参数)直接拦截上线并通知负责人。
5.10 模型上下文过长导致"遗忘"工具
现象:任务做到第二三步,模型开始"忘记"工具约束,直接返回一段随意内容。 原因:Agent 的上下文窗口被大量工具结果塞满。 解决方案:工具返回结果要做精简,只返回任务需要的核心字段;超大结果分页返回,不让模型一次读太多。同时,中台的编排层尽量把多工具操作拆成短链路,减少单轮上下文压力。
6. 生产级中台还需要补齐的工程能力:可观测性、契约测试与灰度发布
权限沙箱和踩坑清单解决了"安全"和"正确"的问题,但要让中台系统真正可持续运行,还差最后一块:常规的工程化治理。这里聊聊我看来最必要的三块。
6.1 可观测性:从 Metric、Log 到 Trace 全都要
MCP 中台的调用链路比普通 API 长,一次用户请求会经过:Agent 框架 → 中台编排 → 多次 tool 调用 → 多个外部系统。任何一个环节出问题,都要能快速定位。
我们最终的方案:
- Metrics:记录工具调用 QPS、错误率、P99 耗时、Token 消耗量、二次确认通过率。
- Logs:业务日志按 Trace ID 串联,工具入参出参摘要、权限决策原因全记录。
- Traces:接入 OpenTelemetry,每个 tools/call 作为一个 Span,标注 serverName、toolName、是否命中权限策略。
建议从第一天就接入 Trace,不要等问题出现再补——补 Trace 的成本远高于一开始就埋点。工具链尽量用公司已有的监控体系,避免中台单独造一套,否则日常运维没人愿意看。
6.2 契约测试:把 MCP Server 当成第三方服务来测
MCP Server 接入中台的本质是系统集成,那就应该按集成测试的标准来要求。我们写了一套契约测试用例,核心覆盖:
- 工具列表返回符合预期,且没有未授权新增工具。
- 每个工具的正常返回结构稳定(MCP 协议要求
content数组)。 - 错误返回必须设置
isError: true,否则模型会把错误当成正常结果继续推理。 - 参数边界测试:空值、超长字符串、缺字段、非法枚举。
这套测试跑在 CI 里,每次 MCP Server 镜像更新自动触发。从这里尝到甜头后,我又把性能基线测试加了进去,比如"某工具响应耗时超过 500ms 就报警",这能尽早发现工具实现退化。
6.3 版本发布与灰度
MCP Server 的更新比普通 Web 服务更敏感:工具 Schema 一变,模型的行为就可能变。所以生产环境的发布我们要有灰度策略:
- 先在预发环境接入新版本 MCP Server,跑冒烟用例(核心链路)。
- 线上只把 5% 流量切到新版本,对比错误率和 P99 耗时。
- 如果连续 30 分钟无异常,逐步放大到 50%、100%。
- 发布过程保留上一版本容器,支持分钟级回滚。
灰度发布配合契约测试,基本能避免线上"Schema 突然变了导致 Agent 集体失灵"的事故。这种事故在早期发生过一次,教训很深刻:Agent 批量任务跑一半全报错,问题定位还要靠对比新旧镜像的工具列表,特别痛苦。
最后说几句实在话
六周改造做下来,我最大的体会是:MCP 协议把"接入"变简单了,但它同时把"治理"的压力集中到了中台层。协议只负责传输,不管你的权限怎么管控、工具怎么编排、事故怎么回溯,这些都是平台要去补齐的。
如果你正处在从 Demo 往生产走的阶段,我建议的优先级是:先把权限沙箱和审计做扎实,再补可观测性,最后才是架构拆分。顺序反了,前期跑得再快,后面都会以另一种形式还回去。
还有一个小技巧愿意分享:不要放过 Agent 每轮推理的中间结果。我们后来把模型的决策理由(thought/action)也作为审计数据存了一份,很多工具调用的异常行为,靠它就是几秒钟定位,而不是反复复现。这一点常规 MCP 文档里很少提到,但我认为它值得成为生产级中台的一个标配。