
很长一段时间我都在被同一个问题刷屏MCP 真的要退出历史舞台吗有人拿了 GitHub 上某个仓库的 commit 记录说把薄封装删了还有人在争论 Agent 到底应该用 MCP 还是直接写工具函数。老实说这类讨论看多了会让人心浮气躁但它背后确实藏着一个值得认真聊的问题当我们的 Agent 从玩具走向生产时连接架构到底该怎么选。围绕“MCP、Agent、薄封装、连接架构”这四个词最近半年我前后折腾了两套方案。一开始是标准 MCP 薄封装接入内部工具后来因为并发、审计和工具链治理的问题把中间层拆了重做。这篇文章不打算站队“MCP 必死”或者“MCP 万能”只想把我在这个过程中踩过的坑、想清楚的问题、以及最终落地的架构选择完整记录下来。如果你正在做 Agent 开发或者正在犹豫要不要给现有系统引入 MCP这篇应该能帮你省掉不少试错成本。1. 内容整体设计与思路拆解1.1 标题背后的核心争议到底是什么先还原一下标题里的火药味。“MCP 真的要退出历史舞台吗”这个问法本身就有问题因为 MCPModel Context Protocol本质上是一个协议不是一套业务系统。协议只有被采用、被废弃或者被替代不存在“退出历史舞台”这种戏剧化的生命周期。真正的争议点是Model Context Protocol 这套规范是否还适合作为 Agent 连接工具的默认标准。过去一年里社区里有一批人把 MCP 简化成了“给工具加一层 JSON-RPC 壳子”也就是所谓薄封装。好处是接入快写一个 server 脚本声明几个 tool几分钟就能让大模型调用内部 API。但随着 Agent 数量变多、业务复杂度升高这套薄封装开始暴露问题鉴权散落在各个 server 里、上下文没有统一生命周期、工具参数校验靠各人自觉、并发一上来直接打穿下游。于是又有人开始喊“删掉薄封装”直接让 Agent 业务代码调用内部函数。这种非黑即白的情绪化讨论恰恰掩盖了架构选型真正需要关注的问题。从更宏观的角度看MCP 的处境和早期 WebService 很像。SOAP 协议当年也被骂过太重后来 REST 起来之后很多人也喊过“SOAP 要凉”。但最终留下的是什么不是某一个协议而是大家接受了“接口描述、寻址、鉴权、数据传输”这些连接需求必须被标准化。MCP 现在就是在走这条路只是它出现在 Agent 爆发的前夜很多团队没来得及积累最佳实践就急着上生产受伤之后就开始喊“去掉中间层”。1.2 从“删掉薄封装”到“连接架构重选”的演进逻辑我在自己的项目里完整经历了三个阶段。第一阶段是无脑用 MCP。当时需求很直白要给多个 Agent 提供查询订单、发送通知、读写知识库的能力。MCP 社区生态确实好官方 SDK 齐全客户端和服务端代码都很简洁我用一个周末就把三个工具接完了。那一刻的感觉是“这玩意儿真香”。第二阶段是发现薄封装变厚。业务方开始要求灰度发布、操作审计、失败重试、限流熔断。这些东西加在哪个层只能在 MCP server 内部自己写。结果每个 server 都长出了一套差不多的“中间件代码”维护成本居高不下。更麻烦的是多个 Agent 实例同时调用同一个工具时MCP server 几乎没有天然的并发治理机制导致下游数据库连接池被瞬间打满。第三阶段是架构重选。我没有选择“删掉薄封装”这么激进而是把原本单一的工具调用层拆成了三层协议接入层、业务编排层、工具执行层。MCP 退到了接入层作为 Agent 与外部工具之间的兼容入口真正干活的是内部的服务化工具它们通过内部 RPC 暴露能力。这样一来薄封装变薄了但它没有消失只是回到了它应该在的位置。这段经历让我意识到方案本身没有绝对优劣错的是把协议层当业务层用。2. 核心细节解析与实操要点2.1 MCP 到底解决了什么问题先给不熟悉的读者补一下基础。MCP全称 Model Context Protocol是 Anthropic 在 2024 年底提出的一种开放协议用于统一大语言模型与外部数据源、工具之间的通信。它设计了一套标准化的消息格式和 RPC 语义让模型能够发现工具、调用工具、获取工具执行结果。这套协议核心解决的是“N 个模型对接 M 个工具”时的连接爆炸问题。假设你有 3 个 Agent需要调用 5 个内部系统如果不用 MCP每对接一个系统就要写一套自定义的 API 适配3×5 就是 15 个集成点。用 MCP 之后每个系统只需要实现一次 MCP server每个 Agent 只需要对接 MCP 客户端集成点从乘法变成加法。从这个角度看MCP 的价值不是传输效率多高而是连接治理的标准化。它给工具定义了统一的描述方式名称、参数、返回结构给调用定义了统一的生命周期initialize、listtools、calltool也让连接状态有了可观测的入口。这些能力在没有 MCP 的年代要靠各个团队自己在胶水代码里实现且每个团队实现的思路还不一样交接一次痛苦一次。2.2 “薄封装”病根在哪里我观察到社区里被吐槽的 MCP 薄封装通常有以下几个典型病状。第一逻辑过度膨胀。很多人写 MCP server 时把业务校验、权限判断、数据聚合全塞进去。代码量一上来“薄”就名存实亡。你本意是写一个协议适配层结果它变成了一个隐形的业务服务。一旦 Agent 数量多起来这个隐形的中间层就成了最难维护的瓶颈。第二工具粒度失控。MCP 建议工具设计遵循“单一职责”但实际开发里大家为了避免 Agent 多次调用经常把一堆操作揉成一个工具比如“处理订单”这个工具里既有查询又有改状态还有通知。结果模型调用时参数极其复杂还经常传错出了问题又不好排查。第三身份与上下文割裂。MCP 协议本身没有规定用户身份如何传递、会话状态如何跟踪。每个团队自己定义 header 或 metadata导致审计链路断掉——出了安全问题时无法回答“哪个用户、通过哪个 Agent、在什么上下文里调用了这个工具”。这在生产环境是致命的。第四并发控制缺失。MCP server 通常是无状态的请求响应模型本身并不感知上游 Agent 的整体任务状态。当 10 个 Agent 同时触发同一个耗时的工具调用时服务端没有任务队列、没有速率限制、没有优先级调度下游系统很容易被瞬时流量打崩。2.3 什么时候应该删掉薄封装“删掉薄封装”并不是完全错误的说法。核心逻辑是如果 MCP 中间层没有给你带来任何治理收益仅仅是一个 JSON-RPC 转发器那它确实应该删掉。尤其是工具数量少、调用链路短、只有单个 Agent 的场景下直接让 Agent 业务代码调用函数库是更清晰更高效的选择。我做过一次小实验。一个只有两个工具的 Agent用 MCP server 方式接入时服务进程要单独部署还要处理令牌刷新和请求超时改为直接在 Python 进程里 import 内部 SDK 后时延从 250ms 降到 30ms代码量减少了 40%。这种场景下“薄封装”确实是负担。所以我的结论是不是所有连接问题都值得引入一层协议。只有当工具规模超过 5 个、Agent 实例超过 3 个、或者安全审计成为硬需求时MCP 的标准化价值才开始兑现。否则删掉封装直接调用既简单又稳。3. 实操过程与核心环节实现3.1 架构重选的整体方案设计我在重选架构时定下了一条原则保留 MCP 协议但把它放在它应该在的位置。最终落地的连接架构分成四层。Agent 层负责任务规划、模型调用、上下文管理。协议接入层统一暴露 MCP endpoint接收 Agent 的呼叫完成协议解析、鉴权前置、速率限制。业务编排层将 MCP 工具调用翻译为内部服务调用负责组合多个工具的执行顺序处理部分失败。工具执行层内部服务的真实实现以标准内部 RPC 方式暴露能力有独立的负载均衡、超时重试、监控告警。这个结构的关键改动是把业务逻辑彻底从 MCP server 中剥离出来。MCP server 只剩两个职责协议翻译和前置控制。工具的真实实现下沉到独立服务这样即便某天 MCP 协议真的被替代我们的业务代码也完全不用改动只需要替换接入层的协议解析部分。以下是接入层的伪代码展示了 MCP server 如何变成一个纯转发器# mcp_gateway_server.py from mcp.server import Server from mcp.server.stdio import stdio_server from .auth import verify_token from .rate_limiter import RateLimiter from .rpc_client import InternalRpcClient app Server(gateway) limiter RateLimiter(max_qps50, burst100) rpc InternalRpcClient() app.list_tools() async def list_tools(): upstream_tools await rpc.discover_tools() return [{name: t.name, description: t.description, parameters: t.schema} for t in upstream_tools] app.call_tool() async def call_tool(name: str, arguments: dict, context): user_meta context.user_metadata await verify_token(user_meta[token], required_scopeftool:{name}) await limiter.acquire(user_meta[user_id]) # 转发到内部服务不包含任何业务逻辑 return await rpc.call(name, arguments, trace_idcontext.trace_id)从这个实现能看到MCP server 已经变成一套标准的网关而不是业务执行者。这样做的好处是如果未来协议换成其他东西比如内部自研的 Agent 通信协议网关的逻辑还能复用大部分只是把解析部分换掉。3.2 工具服务化改造实录接下来是我踩坑最深的部分把原本塞在 MCP server 里的业务逻辑下沉成独立工具服务。先梳理现状。我们原本有三个 MCP serverorder_server订单查询、状态变更、notify_server短信、邮件推送、knowledge_server知识库检索。每个 server 都实现了自己的鉴权、超时、日志。重构成独立服务后我把三个服务合并成一个统一的“业务工具服务”但每个工具保留独立的处理函数通过路由分发。改造步骤大致如下抽出公共的鉴权模块统一放到网关层服务端不再感知用户身份只接收user_id和trace_id作为透传字段。定义统一的工具执行接口入参规范JSON Schema、出参规范业务状态码 数据、异常规范错误码、错误信息、可重试标记。每个工具的内部实现独立为一个类类与类之间不共享状态避免并发时互相干扰。服务端增加健康检查和指标暴露接口接入 Prometheus每类工具的执行耗时、成功率、错误分布都有监控。实现上我用的是一个简单的 Python 异步服务内部用routers做工具名到函数的映射。代码类似这样# tool_executor.py class ToolExecutor: def __init__(self): self._registry {} def register(self, name: str, handler: Callable): self._registry[name] handler async def run(self, name: str, args: dict): handler self._registry.get(name) if not handler: raise ToolNotFound(name) # 统一埋点、超时、日志 async with timeout(5): start time.perf_counter() result await handler(**args) metrics.record(name, time.perf_counter() - start) return result这个改造带来的直接收益是测试变好写了。以前测试 MCP server 时要带着整套协议栈跑现在工具执行器就是纯 Python 函数单测直接调用效率高太多。业务方也不需要理解 MCP 协议他们只需要按接口文档写 handler 就行。3.3 会话状态与审计链路设计MCP 的标注设计中没有统一的会话上下文概念。在架构重选里我重点解决了这个问题。我在 Agent 层的请求头里加入了一个标准化的上下文对象包含trace_id一次用户请求的全链路追踪 IDuser_id发起操作的用户标识agent_id发起操作的 Agent 实例标识session_id多轮对话的会话标识scope本次调用的权限范围列表网关把这些字段透传给工具执行层工具执行层的所有日志和审计事件都会带上这五个字段。这样从“用户发了一条消息”到“Agent 调用了哪个工具、传了什么参数、拿到了什么结果”就能完整串起来。审计事件除了记录调用情况还会记录工具的输入输出摘要。涉及敏感信息的工具比如订单查询输出摘要会自动脱敏只保留部分字段。这个能力以前散落在各个 MCP server 里很难统一约束现在放在网关层强制执行。下面是审计日志结构化示例{ event: tool.call, trace_id: tr-20250101-abc123, user_id: u-1024, agent_id: agent-01, session_id: s-88, tool: order.query, params_hash: ..., result_status: success, latency_ms: 320 }这套审计链路上线后安全、合规那边终于不用隔三差五来找我们要“这个操作到底谁发起的”了。3.4 并发与负载治理之前提到的“AI Agent 怎么扛并发”问题在这次重构里也算彻底解决了。关键手段有三个。第一是网关限流。每个用户维度限制了每秒最多 30 次工具调用每个 Agent 实例维度限制了每秒最多 100 次。超限请求直接返回429 Too Many Requests并带上Retry-After头Agent 可以根据这个头做退避重试。这个策略能把瞬时流量平滑到一个可控的斜率。第二是任务队列与信号量。对于耗时较长的工具调用比如知识库批量检索网关层用一个进程内信号量限制最大并发数超过上限的请求排队等待。实际配置如下# gateway/limits.py MAX_CONCURRENT_TOOL_CALLS 20 MAX_CONCURRENT_PER_USER 5 TOOL_TIMEOUT_SECONDS 10 semaphore_global asyncio.Semaphore(MAX_CONCURRENT_PER_USER)第三是下游熔断。工具执行层调用内部某服务失败时连续失败次数超过阈值就触发熔断快速失败并给 Agent 返回一个“服务暂时不可用”的明确错误而不是让对方一直傻等。这些能力叠加之后在一次大促模拟测试中网关同时接住了 2000 个 Agent 实例的打流下游订单服务稳定没有报错。对比改造前 MCP server 直连下游数据库数据库连接池直接被打满两次这是最直观的收益。4. 常见问题与排查技巧实录4.1 MCP 相关典型问题速查表问题现象根因解决办法Agent 调用工具后超时MCP server 中业务逻辑过重串行执行耗时太长业务逻辑下沉网关只做转发给调用加超时和重试Agent 反复报“工具参数错误”参数 Schema 定义与实际 handler 不一致用 JSON Schema 做入参校验拒绝不合格参数多个 Agent 同时调用下游被打崩缺少并发控制、限流、熔断网关层加速率限制、信号量、熔断器工具调用失败后无法定位问题日志缺少 trace_id 和 user_id 关联建立统一的上下文透传机制全链路日志串联知识库检索结果不稳定Schwierigkeiten 在中文语义检索上MCP 工具本身没有问题升级 embedding 模型增加检索重排代码修改后必须重启 MCP server 才生效工具执行逻辑嵌入在 server 内部做不到热加载改为工具服务化支持独立部署、动态路由这张表里前四条是我真实遇到过的后两条是社区里看别人踩坑后归纳的。排查思路基本一致先从调用链上确认哪个环节超时或出错再回到日志系统看上下文。切忌在没有任何日志的情况下瞎猜。4.2 我踩过的三个典型坑第一个坑是把工具状态放进了 MCP server 进程。最开始我为了实现“查询结果缓存”在 MCP server 里放了一个全局字典。上线后多个 Agent 并发调用时状态互相污染A 用户查到的结果被 B 用户读到了。后来彻底把状态全部外置到 RedisMCP server 变成无状态服务才解决。这是个很低级的错误但加班调了半天才意识到问题根源。第二个坑是对 Agent 的失败重试过于信任。大模型 Agent 拿到异常后往往会自行“脑补”重试方案有时候会拿错误信息里的参数去再编一个调用。这种重试不仅浪费 token还会加剧下游压力。后来我选择在工具返回错误时明确附带is_retryablefalse标志并让 Agent 不重试非幂等操作。第三个坑是忽略 MCP 协议版本兼容。有一段时间我的服务端和客户端 SDK 版本不匹配导致工具列表无法正常同步。排查了很久才发现MCP 虽然尽力做了兼容但不同版本的初始化握手消息仍有差异。现在我把 SDK 版本锁死在 requirements.txt 中升级时单独走流程测试。4.3 给新手的落地建议如果你是第一次做 Agent 连接架构我给你三个最朴素也最有用的建议。一开始就规划好 trace_id 的传递。工程上一个很小的工作量但后期排查问题时它是最有用的东西。工具设计从“业务意图”出发而不是从“模型调用习惯”出发。别为了让模型好调用就调粗粒度后续你会付出代价。不要把协议工具当业务服务。MCP server 里只写解析和转发逻辑业务逻辑永远放在后面的独立服务里。如果你已经跌进“MCP 真难用”的坑里了先别急着删协议把 MCP server 的代码打开看一遍。如果里面到处是业务 if 和外部 API 直连那问题出在架构上不是 MCP 的锅。5. 我的判断与经验总结先说结论MCP 不会退出历史舞台但它确实需要让出“万能中间层”的位置。在未来的 Agent 连接架构里MCP 会更像一个通用适配器而不是唯一的连接标准。复杂的生产级 Agent 系统一定会有自己的内部协议、工具注册中心、权限模型和可观测体系。MCP 的价值在于为外部生态交互提供一个稳定的接口让第三方工具能低摩擦地接入你的 Agent 生态。就我现在的项目来说MCP 的使用边界已经被画得很清晰了——外部工具接入走 MCP内部控制走内部 RPC两者之间由网关负责翻译和安全管控。下次再有人问我“MCP 是不是凉了”我会反问一句你把它放在架构的哪一层用最后分享一个小调整的心得。删掉薄封装并不是把代码往回退而是把不属于协议层的东西剥出去。做完那次重构后我的 MCP server 代码只剩几百行几乎所有逻辑都能在 5 分钟内讲清楚。这种轻量感不是靠“删代码”得到的是把“该属于哪层的东西放回哪层”之后的自然结果。你在做 Agent 架构选型时如果也遇到“什么都想加在 MCP 中间层”的冲动可以试试反过来问自己这一行代码如果离开 MCP它属于谁答案清楚了架构就清晰了。