Claude Code 与 Codex 在十个 MCP 特性上各挂一半
Claude Code and Codex break on different MCP features
作者用开源工具 M3 在 2026 年 10 月 9 日对 Claude Code 2.1.287(claude-sonnet-5-5)和 Codex 0.162.0(gpt-5.6-sol)各跑三轮十个 MCP 特性,结果五个特性只在其中一个智能体上失败,且两者失败的特性完全不重叠。
我们让 Claude Code 和 Codex 分别对接同样的十个 MCP 服务器。十个功能中有五个在一个 agent 上正常、在另一个上失败,而且这两个 agent 从未在同一个功能上失败。
如果你发布了一个 MCP 服务器,你大概见过这个问题:“工具在 Claude Code 里不显示”或者“Codex 发送了错误的参数”。我们刚开始时,各个 agent 仓库里大约有 28 个这类未解决的 issue。它们都没有说明是哪个 agent、在哪个版本上、在哪个 MCP 功能上出问题。
所以我们做了一张能说明这些的表。我们称之为 Harness Quirks Matrix,它运行在 M3 上,这是我们开源的、用于测试 MCP 服务器及使用它们的 agent 的工具。
结果:10 个 MCP 功能中有 5 个让两个 agent 表现分化
2026 年 10 月 9 日,我们针对 Claude Code 2.1.287(claude-sonnet-5-5)和 Codex 0.162.0(gpt-5.6-sol)运行了十个 MCP 功能,每个三次试验。Claude Code 在三个功能上失败,Codex 在另外两个上失败。
| MCP 功能 | Claude Code 2.1.287 | Codex 0.162.0 |
|---|---|---|
名为 ids[] 的工具参数 | 被丢弃0/3 | 正常3/3 |
| 12,000 字符工具错误中间的标记 | 结果丢失0/3 | 正常3/3 |
同时包含文本和 structuredContent 的结果 | 结果丢失0/3 | 正常3/3 |
| 工具列表变更后新增的工具 | 正常3/3 | 被丢弃0/3 |
| 14 KB 输入 schema 中的必需嵌套字段 | 正常3/3 | 被丢弃0/3 |
inputSchema 中的根级 anyOf | 正常3/3 | 正常3/3 |
outputSchema 声明 draft-07 | 正常3/3 | 正常3/3 |
| 省略有默认值的可选参数 | 正常3/3 | 正常3/3 |
| 全名长度为 65 个字符的工具 | 正常3/3 | 正常3/3 |
结果中的 resource_link 块 | 正常3/3 | 正常3/3 |
如果你维护一个 MCP 服务器,以下是这五个失败的含义。
Claude Code 从不调用输入属性名为 ids[] 的工具。它不显示任何错误,工具就是不存在。OpenAPI 生成的服务器经常产生这样的名称。
Claude Code 还只保留工具错误的大约前 5,000 个和后 5,000 个字符,所以中间的任何内容都到不了模型。把代码、原因和下一步放在错误消息的开头。而且当结果同时带有 content 和 structuredContent 时,模型报告了结构化值,丢失了文本值。
在服务器宣布新工具后,Codex 仍继续使用旧目录,所以它从未调用新工具。它还会压缩大型输入 schema 以符合预算,在我们 14 KB 的 schema 中,必需的嵌套字段随之被压缩掉了。把 Codex 的每服务器 schema 预算提高到 20,000 后,调用恢复了。
每个结果覆盖固定版本下的一个客户端和模型组合,使用明确的提示词。失败的单元格告诉你数据没有从服务器到达模型的答案;它不告诉你哪一层丢弃了它。每个单元格都随附其运行清单、二进制 SHA-256 和线上追踪记录。
直接探测发现的另外八个失败
我们还直接探测了 Claude Code 和 Codex 的二进制文件(未使用 M3),又发现了八个功能,它们至少在其中一个上会失败。这些功能都尚未经过 M3 验证,因此在验证之前只能视为线索。
| MCP 功能 | Claude Code 2.1.295 | Codex 0.162.0 |
|---|---|---|
根 oneOf 紧邻 properties | 正常 | 属性名丢失 |
| 单个服务器上有 2,049 个工具 | 正常 | 超过 2,048 的工具被丢弃 |
一个工具没有根 type | 整个服务器被丢弃 | 正常 |
int64 参数 1234567890123456789 | 被舍入 | 转为字符串或被舍入 |
anyOf 内的枚举,schema 超过 5 KB | 正常 | 枚举被抹除 |
prefixItems 元组 | 正常 | 被声明为 Array<string> |
同一服务器上的 fetch.page 和 fetch_page | 一个被错误路由 | 正常 |
声明了 outputSchema,但结果仅为文本 | 成功被转为错误 | 正常 |
这些探测也排除了一些旧嫌疑。Claude Code 的根 anyOf 和 65 字符名称问题在当前版本下不再复现,Codex 的 resource_link 处理也不再复现,因此服务器作者可以去掉针对这些问题的变通方案。
M3 如何运行每个单元格
每个单元格都是一个普通的 pytest 测试,M3 针对真实 agent 二进制文件、在精确版本下运行它,同时记录线上传输的内容。以下是 ids[] 用例的完整测试:
@pytest.mark.m3(suite_name="quirks")
def test_q05_args_property_name_brackets(agent) -> None:
state = run_eval(agent, Path(__file__).resolve().parent)
assert state == "works", state
run_eval 调用 agent.run(prompt, servers=(control, quirk)),然后用 M3 的 expect(result).to_have_tool_call(...) 对照线上证据检查结果。agent fixture 来自命令行:
m3 test --runtime=managed \
--harness [email protected]=claude-sonnet-5-5 \
--harness [email protected]=gpt-5.6-sol \
--trials 3 -- quirks/
有几点保证了单元格的可靠性:
- M3 将每个 agent 版本下载到缓存中,并记录其版本和 SHA-256,因此新版本会有自己的列。
- M3 分别记录 agent 是否报告了工具调用,以及服务器是否收到了该调用。上述大多数失败都发生在这两点之间。
- 每次运行都会在待测服务器旁边放一个普通的
echo服务器。如果echo失败,该单元格会被标记为未测量,这样损坏的环境就不会被误报为 agent 的 bug。 - 服务器为每次试验生成一个随机标记,因此模型无法从提示中得出答案。
- 我们会针对同一二进制文件、在不使用 M3 的情况下重新运行每个失败用例,以检查 M3 本身不是原因。
- 表格仅从存储的文件渲染,重新渲染会得到相同的字节。
完整运行共执行 66 次,耗时约 16 分钟。Claude Code 平均每次执行 9.8 秒;Codex 平均 18.2 秒。
全部二十个测试都在 M3 仓库的 benchmarks/harness-quirks 下,并附有自行运行它们的命令。
让我们意外的地方
我们的第一版什么都没发现。我们从公开的 bug 报告中选取了五个功能开始,36 次执行在两个 agent 上都通过了。那些 bug 要么已被修复,要么只出现在我们未测试的环境中,这就是我们回过头来直接探测二进制文件的原因。
提示中的一句话让每次 Codex 运行都失败。我们最初的提示以“如果某个工具不可用,请说明是哪一个并停止”结尾。Codex 将此视为放弃的理由,每个单元格都返回 control failed。如果没有对照服务器,我们就会把它当作 Codex 的 bug 发布出去。
使用 gpt-5.6-sol 时,Codex 以代码模式运行,因此模型永远看不到你的 JSON Schema。它得到的是由此生成的压缩 TypeScript 声明,本文中若干 Codex 失败就发生在这个转换过程中。
在每个 agent 上测试你自己的 MCP 服务器
这个矩阵只是 M3 测试,你可以用大约十行为自己的服务器编写同样的测试。安装 CLI 并搭建一个项目:
uv tool install sf-m3-cli
m3 init # a skipped starter test and an .env.example
m3 setup
把你关心的行为写成一个普通的 pytest 测试:
import pytest
from m3 import expect
@pytest.mark.m3
def test_shipping(agent, shipping_server):
result = agent.run("Get a local shipping quote", server=shipping_server)
expect(result).to_have_tool_call("shipping_quote", server=shipping_server.name,
status="success")
然后针对任意数量的 agent 和版本运行它:
m3 test --runtime=managed \
--harness [email protected]=claude-sonnet-5-5 \
--harness [email protected]=gpt-5.6-sol \
--trials 3 -- tests/test_shipping.py
M3 内置了 Claude Code、Codex、OpenCode 和 Pi,任何支持 ACP 的 agent 都可以通过一个小型 manifest 添加。运行结果会保存在本地,并可与保存的基线进行比较,因此当新的 agent 版本破坏你的服务器时,你会在用户看到 bug 之前先看到一个失败的测试。要将其变成 pull request 检查,请参阅在 CI 中为 MCP 服务器设置门禁。
M3 是基于 Apache 2.0 的开源项目:github.com/sineframe/m3。请从文档开始。如果你的服务器有某个特性需要我们加入矩阵,请在那里提交 issue。
来源:Hacker News · m3.sineframe.com