跳到主要内容

MCP 可组合性陷阱:当「再加一个服务器」变成依赖地狱

· 阅读需 11 分钟
Tian Pan
Software Engineer

MCP 生态已拥有 10,000+ 服务器和 9700 万次 SDK 下载量。但同时也在六十天内出现了 30 个 CVE、502 个未锁定版本的服务器配置,以及一个在十五个版本中悄悄将每封外发邮件密送给攻击者的供应链攻击。可组合性的承诺——「只需再接入一个 MCP 服务器」——是真实的。但它带来的依赖蔓延也是真实的,大多数团队在深陷集成债务之后才发现其代价。

如果你在 npm 上构建过生产系统,你一定看过这部电影。MCP 生态正在加速重演同一剧情,只不过这次的「包」拥有对你机器的 shell 访问权限和生产系统的凭证。

npm 的类比并非隐喻

当人们将 MCP 生态的增长与早期 npm 相比时,通常是在夸奖——快速采用、充满活力的社区、可组合的构建块。但这个类比也延伸到了失败模式,而且失败模式更加严重。

对七个注册中心的 42,000+ MCP 工具进行扫描后,发现了与 npm 最严重供应链事件相似的模式。数据描绘出一幅具体的画面:

  • 502 个配置使用不带版本锁定的 npx,每次智能体启动时都拉取最新版本
  • 1,050 个配置指向没有证书固定或身份验证的远程 URL
  • 448 个配置启用了自动安装标志,跳过用户确认直接执行代码
  • 467 个工具引用了可变的 GitHub raw URL,分支更新时内容随之改变
  • 1,679 个工具包含嵌入式 pip 命令;742 个包含系统包管理器

与 npm 的关键区别在于:npm 包在沙箱中运行。MCP 服务器以完整的机器访问权限执行——你的文件系统、你的 shell、你的凭证。2018 年 event-stream 攻击中,一个被入侵的维护者影响了数百万次下载。但爆炸半径被浏览器沙箱所限制。MCP 没有等效的隔离机制。

2026 年初的 postmark-mcp 攻击完美地证明了这一点。攻击者在十五个看似正常的版本中建立信任,然后加入代码将每封外发邮件密送到外部地址。由于 MCP 服务器拥有合法的邮件发送权限,恶意行为在工具调用层面与正常操作无法区分。

信任模型默认就是有缺陷的

大多数 MCP 客户端实现的是首次使用即信任(TOFU)。你在初始设置时批准一个服务器,后续更新不再验证。这创造了一个窗口期,已批准的服务器可以在此期间被悄悄入侵——而这正是攻击者瞄准的窗口。

当你考虑大多数团队实际管理 MCP 配置的方式时,问题会进一步恶化。服务器引用存在于 JSON 配置文件中,通常被提交到仓库或在团队间共享。一个典型的智能体配置可能引用八到十二个 MCP 服务器,每个都有自己的版本(或没有版本)、凭证和信任假设。

以下是会出问题的地方:

  • 静默更新:没有版本锁定,你的智能体能力在每次重启时都会改变。昨天正常的服务器今天可能暴露不同的工具,或者同一工具的行为可能不同。
  • 凭证蔓延:88% 的 MCP 服务器需要凭证,但 53% 依赖以环境变量传递的长期静态密钥。当你运行十个服务器时,就有十组凭证,没有集中吊销机制。
  • 继承权限:MCP 规范不包含授权机制。每个服务器继承它被授予的所有权限,每个请求未经验证就直接通过,除非你添加外部控制。

2026 年 1 月至 2 月期间,研究人员针对 MCP 基础设施提交了超过 30 个 CVE。漏洞范围从简单的路径遍历到 CVSS 9.6 的远程代码执行缺陷(该包被下载近 50 万次)。根本原因是一致的:缺少输入验证、没有身份验证,以及对工具描述的盲目信任。

规模化工具冲突:发现问题

可组合性的承诺假设添加服务器是累加的——更多服务器意味着更多能力。实际上,组合 MCP 服务器会产生在导致故障之前不可见的冲突。

最直接的问题是工具命名。当两个服务器暴露同名工具时,智能体必须选择一个,而解析策略因客户端而异。有些选择最后注册的服务器。有些报错。有些悄悄地用一个工具遮蔽另一个。当你没有预料到冲突时,这些结果都不正确。

命名空间——像 calendar.list_eventsslack.search_messages 这样的前缀——解决了语法冲突。但语义冲突更难处理。两个不同的 MCP 服务器可能都提供 send_email 工具,但参数模式、认证上下文和副作用各不相同。智能体看到两个做大致相同事情的工具,必须仅基于描述来选择。

随着工具数量增长,情况会更糟。工具选择准确性的研究表明,随着工具库规模扩大,智能体性能会下降——四个工具时运行良好的东西,到五十个时就变得不可靠。你添加的每个 MCP 服务器不仅仅是增加能力;它在扩大智能体每次操作时必须导航的决策空间。

MCP 提供的能力协商——客户端和服务器在初始化握手中声明支持的特性——处理的是协议级兼容性。它不处理来自不同服务器的、操作同一领域的工具之间的语义兼容性。这个问题完全留给了智能体的推理能力,也就是说留给了运气。

没人预算的运维税

在生产中成功部署 MCP 驱动智能体的团队有一个共同特征:他们将 MCP 服务器视为运维依赖,而不是即插即用的组件。这两种心态之间的差距正是集成债务积累的地方。

版本管理是基础。每个 MCP 服务器引用都应该锁定到特定版本或提交哈希。不是分支。不是 latest。一个特定的、不可变的制品。这在传统依赖管理中是基本要求,但 MCP 生态的默认模式积极鼓励相反的做法——文档和示例中 npx some-mcp-server 不带版本是常态。

健康监控对 MCP 服务器需要不同于传统服务的监控方式。MCP 服务器在 HTTP 意义上可以是「正常」的,但返回降级的工具描述、缺失的能力或过时的数据。你需要监控的不仅是可用性,还有行为一致性——这个服务器是否仍然暴露你的智能体期望的工具,以及你的智能体构建时所依据的模式?

凭证轮换在十个 MCP 服务器上是不同于单个服务器的问题。大多数团队通过每个服务器的环境变量来解决,这意味着轮换需要重新部署。在代理层处理密钥的集中式凭证网关允许你在不触及智能体配置的情况下轮换,但构建这个网关是听到「只需再加一个 MCP 服务器」时没人计划的工作。

审计日志应该记录哪个智能体调用了哪个工具、在哪个服务器上、用什么参数、结果如何。没有集中式日志,调试一个十服务器 MCP 设置中的故障意味着要关联十个不同服务的日志,每个都有自己的格式和保留策略。

真正有效的架构

在生产中可靠运行 MCP 的团队已经收敛到类似的架构,即使他们从不同的起点出发。

网关模式:智能体和 MCP 服务器之间有一个轻量级编排层。这个网关处理认证、凭证注入、工具发现缓存、请求路由和审计日志。单个 MCP 服务器不需要实现这些关注点,因为网关统一处理它们。这与微服务(API 网关)和云 API(服务网格)中出现的模式相同——只是需要为 MCP 重新学习这个教训。

分层工具分配:不是每个智能体都需要每个工具。组织级策略定义哪些服务器可用,工作区级范围控制哪些智能体可以发现哪些工具。如果一个工具在组织级被禁用,它在所有地方都不可见。这防止了环境权限问题——即范围限定于客户查询的智能体发现并调用内部管理工具。

显式依赖清单:不要内联配置 MCP 服务器,而是在清单文件中定义它们,包含锁定的版本、所需能力和健康检查端点。把这个清单当作 package-lock.json 来对待——它是一份描述你的智能体确切依赖的合同,对它的更改应该经过审查。

优雅降级:当一个 MCP 服务器失败时,智能体应该以减少的能力继续运行,而不是完全失败。这要求智能体知道哪些工具是必要的、哪些是可选的,这意味着你需要像分类任何服务依赖一样分类你的 MCP 依赖——它是否在关键路径上?

依赖成熟度清单

在向你的智能体配置中添加另一个 MCP 服务器之前,运行以下清单:

  • 版本已锁定? 特定版本或提交哈希,而不是 latest 或分支引用
  • 凭证已限制范围? 最小必要权限,有轮换计划
  • 工具冲突已检查? 与现有服务器没有命名冲突;语义重叠已记录
  • 健康已监控? 行为检查,不仅仅是可用性探测
  • 审计已记录? 所有工具调用都已捕获参数和结果
  • 降级已规划? 此服务器不可用时的智能体行为已定义
  • 供应链已验证? 维护者声誉、更新频率、已知漏洞已扫描

如果你不能勾选所有七项,你不是在给智能体添加能力。你是在添加负债。

前进之路

MCP 协议本身是合理的。初始化握手、能力协商和传输抽象是精心设计的原语。问题不在于协议——而在于在生产纪律跟上之前围绕它成长起来的生态实践。

MCP 注册中心正在向命名空间认证和元数据托管方向发展,但它有意将安全扫描留给更广泛的生态系统。这是正确的架构决策——注册中心不应该成为安全边界——但这意味着团队必须构建自己的审核流程,而不是相信「在注册中心里」就意味着「是安全的」。

与 npm 成熟化的类比实际上令人鼓舞。npm 经历了同样的成长阵痛——left-padevent-stream、依赖混淆攻击——最终拥有了锁文件、审计命令、来源证明和早期不存在的安全文化。MCP 可能会遵循同样的弧线,但你不必等待生态成熟就可以在今天构建有纪律的 MCP 部署。

回顾这个时代而不感到后悔的团队,是那些将 MCP 服务器视为其本质的团队:在生产系统中以提升权限运行的第三方代码。不是插件。不是扩展。是依赖——带有这个词所暗示的一切运维纪律。

References:Let's stay in touch and Follow me for more thoughts and updates