开发者在构建MCP服务器时无意中重新发明了DDD的Bounded Context和Anti-Corruption Layer模式。文章揭示这些架构决策的深层原理及最佳实践。
几天前,一位 devops 工程师在 r/devops 上发帖:
"MCP servers just showed up in our infrastructure and I genuinely have no idea how to secure them, anyone been through this?"
文件系统访问、shell 权限、数据库连接器——所有这些都可以由 agent 调用,无需人工批准。写到这篇文章时,该帖已获得 76 个赞和 39 条评论,来自其他工程师的即兴解决方案:"separate by blast radius"(按影响范围分离)、"don't mix list_files and execute_shell in one server"(不要在一个服务器中混合列表文件和执行 shell)、"three security surfaces, not one"(三个安全面而非一个)。
这些评论都在描述同一件事——重新发现了 Eric Evans 在《Domain-Driven Design》(DDD) 中描述的模式。
在他的书中,Eric 引入了 Bounded Contexts(限界上下文)和 Anti-Corruption Layers(反腐层)等概念,这为我们此后描述系统边界的方式奠定了基础。它们帮助我们度过了微服务转型,现在直接适用于 AI 系统面临的架构问题。
在 2010 年代,许多团队在不理解微服务本质的情况下采纳了它。他们把单体应用拆解,把片段称为"服务"。往往结果是分布式单体——具有分布式系统的所有操作复杂性,却没有真正定义明确的边界带来的架构收益。
纠正花了多年。我们(痛苦地)学到,微服务边界不是代码拆分的地方。它是你对应用的心智模型拆分的地方。支付服务和用户服务不仅运行在不同的容器中——它们有不同的术语、不同的不变量、不同的变更理由。
现在同样的错误又在 MCP 服务器上重演。我们把现有 REST API 一对一地包装成 AI 集成。MCP 的创建者之一 David Soria Parra 在 AI Engineer World's Fair 2025 上说过"it's a bit cringe, it just results in horrible things"。Thoughtworks Technology Radar 把"MCP by default"列为"Caution"。深入他们的论证,他们的意思都一样:我们正在重复构建分布式单体。
但这次纠正不必花数年。词汇已经存在——并且已经经过了 20 多年的实战检验。
限界上下文定义了一个特定的(数据或对象)模型的有效范围。在边界内,术语有精确的含义:在财务上下文中,"transaction"(交易)意味着金钱的转移,在预订上下文中,"transaction"意味着预订。在每个边界内存在一种语言和一套规则。跨越边界时,你需要翻译。
从这个角度来看,MCP 特别有趣:该协议已经在拓扑层面强制了限界上下文。
MCP 的架构采用一客户端对一服务器的模型。主机为每个 MCP 服务器生成一个单独的客户端,每个客户端与恰好一个服务器通信。一个数据库的 MCP 服务器无法意外泄露数据给文件系统的 MCP 服务器。与微服务不同(任何服务都可以轻易地通过网络调用任何其他服务),MCP 服务器在协议层面没有办法到达另一个服务器的工具。你必须有意地建造那座桥。跨边界耦合变得可见且有意为之,而不是意外发生。
但这仅在你把服务器设计为限界上下文时才成立。
失败模式是一个 MCP 服务器暴露了所有内容,包括文件系统访问、shell 执行和数据库连接器。这是三个独立的关切堆在一个边界里——相当于一个微服务同时拥有用户、支付和通知功能。
Reddit 上那位写"don't mix list_files and execute_shell in one server"的评论者实际上是在设计上下文边界,只是他不知道这个术语。
反腐层(ACL)防止一个系统的模型污染另一个系统。它在两个不同的世界观之间进行翻译。
在 AI 系统中,每当 agent 调用一个工具时,两个根本不同的模型就会相遇:
对于 LLM,一切都是字符串,参数很简单,上下文是一个 token 窗口。它用自然语言推理来生成结构化调用。
领域包含丰富的类型、配置、状态、复杂的错误处理,以及无论如何调用都必须保持的业务不变量。
工具层位于这两个世界之间。用 Chris Hughes 的话说,它"保护你的领域免受 LLM 的接口要求——在'LLM 能推理的字符串'和'你的代码使用的丰富域对象'之间进行翻译"。
这是一个忽视这个原则的工具:
# Everything in one function - LLM interface mixed with domain logic
@mcp.tool()
async def transfer_funds(from_account: str, to_account: str, amount: str):
amount_decimal = Decimal(amount)
from_acc = await db.get_account(from_account)
if from_acc.balance < amount_decimal:
return "Insufficient funds"
if from_acc.is_frozen:
return "Account frozen"
await db.execute_transfer(from_acc, to_account, amount_decimal)
await audit_log.record(from_account, to_account, amount_decimal)
return f"Transferred {amount} from {from_account} to {to_account}"
这是同样的操作,但有适当的分离:
# Tool layer: thin adapter (the ACL)
@mcp.tool()
async def transfer_funds(from_account: str, to_account: str, amount: str):
result = await transfer_service.execute(
from_account=from_account,
to_account=to_account,
amount=Decimal(amount)
)
return result.to_agent_summary()
# Service layer: domain logic, testable without the LLM
class TransferService:
async def execute(self, from_account, to_account, amount) -> TransferResult:
account = await self.accounts.get(from_account)
account.validate_transfer(amount) # raises on invariant violation
transfer = account.initiate_transfer(to_account, amount)
await self.transfers.save(transfer)
await self.audit.record(transfer)
return TransferResult(transfer)
第二个版本给你带来:
可测试性:服务无需 LLM 就能运行。从测试、CLI、脚本中运行它。
可替换性:改变 LLM 接口(工具参数、响应格式)而不触碰业务逻辑。改变业务规则而不触碰工具层。
可组合性:其他 MCP 服务器、其他 agent 或人类可以通过各自的接口调用同一个服务。
ACL 保护双方。领域不会被 LLM 的基于字符串的世界观污染。LLM 不会被它无法推理的领域复杂性所压倒。
回到那个 Reddit 帖子。
"Separate MCP servers by blast radius."(按影响范围分离 MCP 服务器)。这是限界上下文设计。每个服务器拥有一个领域。影响范围是受限的,因为边界是真实的。
"Three security surfaces, not one - tool capability, tool description, and tool call chains."(三个安全面而非一个——工具能力、工具描述和工具调用链)。ACL 分解成其各自的职责。工具能力是领域允许的。工具描述是 LLM 认为它能做的。工具调用链是需要明确编排的跨边界交互。
"The dangerous part is not one tool in isolation. It is the chain."(危险的部分不是单个工具。是链)。用 DDD 术语来说:聚合不变量违反。一个操作序列跨越限界上下文而没有协调。每个操作在本地成功,而系统在全局失败。
同样的模式,同样的结构问题,独立发现,因为问题是真实的。
一个公平的批评是 MCP 增加了一个层。Thoughtworks Tech Radar 称之为"abstraction tax"(抽象税)——agent 和 API 之间的每一个协议层都会丧失保真度。Simon Willison 注意到"almost everything I might achieve with an MCP can be handled by a CLI tool instead"(几乎我可能用 MCP 实现的一切都可以用 CLI 工具处理)。
这是正确的。而且这正是人们对微服务边界、API 网关和传统系统中的反腐层所提出的同样论证。翻译层伴随成本:你失去了直接性。
但这种失去是有意的。它是 ACL 在做它的工作。LLM 不需要知道你领域的内部类型、重试逻辑或状态管理。领域不需要适应 LLM 的基于字符串的推理模型。这个"税"为你购买了隔离、可替换性,最终还有心灵的安宁。
只有当我们支付这个税而不获得架构收益时,它才是个错误——这正是 REST-to-MCP 1:1 包装器所做的。它们增加了层却没有增加边界:全是成本,没有收益。
我们不必重新发明这些模式——DDD 有 20 多年的实战经验。我们已经艰难地学到了在哪里画边界、如何强制执行它们,以及当我们不这样做时会发生什么。无论有没有 AI,Eric Evans 的《Domain-Driven Design》仍然是复杂软件系统的规范参考。
MCP 已经被设计为建立限界上下文;工具层已经是反腐层。以 MCP 服务器拥有的领域来为它们命名,而不是它们包装的 API,当你团队中的某个人说"separate by blast radius"时——让他们知道有已成熟的模式来描述他们所说的东西。
如果你对词汇歧义如何被 AI 编码 agent 放大感兴趣——以及你可以做什么——我写了一篇后续文章:Your agent keeps using that word ...
Some comments may only be visible to logged-in visitors. Sign in to view all comments.
For further actions, you may consider blocking this person and/or reporting abuse