MCP 的 JSON-RPC 传输没有问题。真正的问题是自然语言规则没有代码级强制力——LLM 可以完全无视你的 instructions。我设计了 Intent Validator 模式来补上这个缺口。
一条被忽视的规则
我的系统有一条业务规则:ML 回测必须使用全区间数据,禁止限定日期范围。原因是限定范围会导致样本量不足,回测结果不可靠。
这条规则写在 MCP server 的 instructions 里,用自然语言告诉 Agent:“NEVER restrict to test period only。”
然后某天 Agent 无视了这条规则,填了一个日期范围,请求通过了,任务跑完了,结果看起来正常。没有任何报错。但结果是不可靠的——只是没有人知道。
这不是 MCP 的 bug。协议层工作正常。问题是一个更根本的缺陷:整个 MCP 生态里,自然语言规则和代码执行之间没有任何强制性绑定。
问题的结构
当前 AI 工具调用的校验分三层。中间那层是空的:
自然语言指令(MCP instructions) → LLM "理解"了 → 可能遵守,可能无视
↓
???(空) → 没有任何代码级检查
↓
类型校验(JSON Schema / Pydantic)→ start_date 是 string → 类型合法 → 通过
第一层是劝告,第三层是类型检查。中间缺的是业务语义的代码级强制。
这个空洞不是我的系统独有的。任何用 MCP 或 function calling 的系统都有同样的问题。MCP 的 tool schema 能定义参数类型,但不能表达参数之间的条件约束——“如果 A 为空则 B 必须非空”、“任务类型为 X 时禁止设置 Y"这类规则,JSON Schema 表达不了。
目前业界对这个问题的处理方式是两端用力:
- 上游:优化 prompt,让 LLM 更好地理解规则 → 有上限,永远不会 100% 可靠
- 下游:收紧 JSON Schema,用更严格的类型定义 → 表达力不够,跨参数约束描述不了
两端都在做,中间那层没人做。
我的思考路径
最初的想法是自研一套协议替代 MCP。但分析之后发现这是错误的反应——协议层(JSON-RPC 传输、tool discovery、序列化)没有问题,问题在应用层。换协议解决不了语义问题,只是在搬运复杂度。
然后想到:这个问题的结构类似于 Web 开发中的输入校验。前端表单有 HTML5 的 type=“email” 校验(类似 JSON Schema 的类型检查),但真正的业务校验(“邮箱域名必须是公司域名”、“金额不能超过余额”)是在后端做的。没有人会说"HTML5 校验不够用,我要自研一套 HTTP 协议”。
正确的做法是在 LLM 输出之后、系统执行之前,加一层应用级的业务规则校验。
这就是 Intent Validator 的设计起点。
方案对比
方案 A:强化 Prompt Instructions
把规则写得更明确、更强调、加更多 WARNING 标记。这是当前多数 MCP 应用的做法。
问题:本质上是在跟概率对赌。LLM 不是规则引擎,它是概率模型。“大多数时候遵守"不等于"永远遵守”。在研究实验里偶尔犯错可以接受,在生产环境里不行。
方案 B:收紧 Tool Schema
把 start_date 从 optional 改成不暴露这个字段。但这意味着其他需要日期的任务类型也用不了了——一个 tool 的 schema 是所有调用场景的并集,不是交集。
方案 C:Intent Validator(我的选择)
在 LLM 输出的参数被解析之后、被发送到执行层之前,插入一层代码级校验。每个任务类型注册自己的业务规则,校验器自动执行。
核心设计原则:
规则即代码,不是文档。 “ml_backtest 禁止设日期"不是写在 README 里的注意事项,是一段会 raise error 的 Python 函数。
注册式架构。 新增规则不改框架代码,只加一个装饰器函数。从 0 条规则到 100 条规则,架构代码不变。
可操作的拒绝信息。 拒绝不是一个 “400 Bad Request”。每条 violation 带三个字段:rule(机器可读 ID)、error(问题描述)、fix(具体修复指令)。Agent 收到拒绝后能自动修正重新提交,不需要人介入。
硬拦截和软警告分离。 有些规则是必须遵守的(硬错误直接拒绝),有些是建议(警告附带在成功响应里)。不是非黑即白。
关键判断点
设计过程中最关键的一个决策是:这层校验应该放在哪里?
选项一:放在 MCP server 里。只覆盖 Agent 调用路径。
选项二:放在 REST API 的请求模型里。只覆盖 HTTP 调用路径。
选项三:抽成独立模块,两条路径都调用它。
我选了第三个。原因是一条规则不应该写两遍——MCP 和 HTTP 是同一个系统的两个入口,业务规则不因入口不同而改变。实现方式是在请求模型的基类里加一个 model_validator,自动调用同一套规则引擎。MCP server 的 submit 函数也调用同一套。规则只写一份,覆盖所有入口。
这个决策的启发来源是 DRY 原则的一个推论:校验逻辑的重复比业务逻辑的重复更危险。 业务逻辑重复了,两处表现一致;校验逻辑重复了,两处规则不同步时一个入口放行另一个入口拒绝,安全漏洞就出现了。
实施效果
部署后测试了几个场景:
- Agent 提交 ml_backtest 时带了 start_date → 即时拒绝,返回明确的修复指令,Agent 自动修正后重新提交成功
- 脚本直接 curl 调用 REST API,带了非法参数组合 → Pydantic model_validator 触发同一套规则,返回 422
- 新增一条业务规则 → 写一个装饰器函数,不改任何现有代码
从"规则写在 instructions 里靠 LLM 自觉遵守"变成"规则写在代码里不遵守就报错”。可靠性从概率性变成确定性。
这个决策教会我什么
我从这次实践中更新了一条思维框架:
自然语言和代码之间的每一条关键约束,都需要有一个代码级的强制点。如果一条规则只存在于文档或 prompt 里,它就不是约束,而是建议。
这条原则的适用范围不限于 MCP。任何 AI Agent 操作有业务约束的场景都成立:医疗 Agent 的药物禁忌组合、法律 Agent 的法条时效性、运维 Agent 的变更窗口限制。这些约束如果只靠 prompt 软控制,就是在用概率模型做确定性保证——逻辑上不成立。
Vibe coding 的核心矛盾是自然语言的模糊性和系统操作的精确性之间的鸿沟。Intent Validator 不是终极解法,但它指向了一个正确方向:不要试图让 LLM 变得更严谨,而是在 LLM 后面加一道代码级的关卡。
系列第二篇。上一篇:为什么我没有用多智能体架构。