挑战时间
2026年02月14日
前不久, 万众瞩目的 Claude Opus 4.6 和 GPT-5.3-Codex 发布. 这两天, 热度拉满的国产大模型 MiniMax-M2.5, GLM-5, Doubao-Seed-2.0-Code 陆续发布, 基本都在对标 Opus 4.5. 根据宣传语, 略落后 Opus 4.5. 但是我个人的预期是 Sonnet 4.5, 因为 Sonnet 4.5 基本就是目前的"能用"线水平. 只要有 Sonnet 4.5 的水平, 就可以考虑投入生产环境使用了.
所以本次参赛选手为:
- MiniMax-M2.5 (Trae)
- GLM-5 (Trae)
- Doubao-Seed-2.0-Code (Trae)
- Claude Sonnet 4.5 (Claude Code)
- Claude Opus 4.6 (Claude Code)
- GPT-5.3-Codex (Cursor, Extra High)
挑战内容
在使用 github remote mcp server 时, 它默认会返回所有的 tools (目前有 40 个), 大量占据 context. 所以 github remote mcp server 支持了通过注入自定义的 headers 来筛选 tools, 从而减小 context.
遗憾的是, mcp 的官方 rust sdk 目前不支持注入自定义 HTTP headers, 所以本次挑战的内容就是让各大模型来为其支持这个功能, 并且添加完整的单元测试 + 集成测试.
本次挑战基于 bb534a7a68933b587b03167431fa7dc0fcd6d40e commit.
Prompt
这是 mcp rust sdk 仓库, 目前不支持自定义 HTTP headers, 请你为其增添注入自定义 headers 的功能, 并补充单元测试 + 集成测试.
1. 我希望 api 如下:
```rust
let config = StreamableHttpClientTransportConfig::with_uri("https://api.githubcopilot.com/mcp/")
.auth_header(github_token)
.custom_headers(custom_headers);
let transport = StreamableHttpClientTransport::from_config(config);
```
目前只有 `auth_header` 方法, 请你增加一个 `custom_headers` 方法, 参数为 `HashMap<http::HeaderName, http::HeaderValue>`.
2. 当用户传入的 custom headers 里包含 reserved headers 时, 需要在发请求时报错, 告诉用户这个 header 是保留头, 被 mcp 协议所使用, 所以不能自定义. 你需要翻阅 https://modelcontextprotocol.io/specification/2025-11-25/basic/transports 来梳理所有 client HTTP headers 的保留头.
3. 对于集成测试, 你不需要写一个 mcp server 来读取 headers, 用一个简单的 axum server 即可.
4. 在适当的地方需要补充 docs; 不要引入新的依赖; 请着重考虑性能;
5. 需要保证新增和已有的所有测试用例通过, 包括 docs test.
难度分析
和第一期从 0 开始做一个小项目不同, 这个任务的难点主要在于对已有的中型复杂仓库的整体理解. AI 需要追溯调用链, 精准找到最底层使用 reqwest client 发请求的相关代码, 然后实现注入逻辑. 本身的工作量很小, 难点在于上下文理解.
评分维度
- 代码质量 - 性能, 错误处理, 测试用例 (50%)
- Prompt 次数 - 为了完成需求, 一共发了多少次消息 (40%)
- 协议理解 - 根据提供的 mcp transport spec, 是否能判断出哪些 headers 是 client 保留头 (10%)
由于网络环境影响, 速度不在测试范围内. 除非慢到离谱, 否则不会影响最终分数.
1. MiniMax-M2.5 (Trae)
整体表现挺一般的. 虽然考虑了性能, 但是优先用了引用而不是 Arc<HashMap<HeaderName, HeaderValue>>, 导致报了很久的生命周期错误. 并且由于集成测试一直通不过, 索性放弃, 改写了一个很基础的没啥用的测试. 在我要求写正规的集成测试后, 它居然因为启动 axum server 有问题, 直接改用 std::net::TcpListener 和 std::thread::spawn, 然后理所应当地失败了.
代码质量
Pros:
- 使用了
Arc包裹HashMap, 考虑到了性能.
Cons:
- 生成的代码里, 产生了多处重复代码: 一个 for 循环遍历
custom_headers, 一个个插入到请求头里, 没有封装成函数. - 写了一些没有意义的测试用例, 那种结果显而易见的.
- 重构了一些无关的代码, 并且重构毫无意义
- 在多次提醒后, 虽然集成测试写好了, 但是用了全局静态的
static RECEIVED_HEADERS: std::sync::OnceLock<HeaderMap> = std::sync::OnceLock::new();来收集headers, 导致这个文件里只能写一个测试用例. - 将每个保留头都以字面量的形式写到了
RESERVED_HEADERS数组里, 没有复用已定义的常量.
代码质量得分: 6
Prompt 次数
5 次. 主要是集成测试一直写不好.
Prompt 次数得分: 5
协议理解
判断出的保留头为:
- accept
- content-type
- mcp-session-id
- last-event-id
- authorization
- host
- connection
- content-length
很离谱啊, 关键的 mcp-protocol-version 没有列出来, 倒是列出了一堆无关的.
协议理解得分: 4
综合得分
MiniMax-M2.5: 5.4
2. GLM-5 (Trae)
GLM-5 真的慢, 真的慢...首次请求在那 Thinking 了半天, 我还以为卡了准备重启. 不过后续请求会好一些. 在首次 prompt 后, 基本功能都实现了, 但是集成测试没有写好, 里面实际上全都是单元测试.
代码质量
Pros:
- 增加了一个新的错误 enum variant, 用来表明保留头冲突.
- 使用了
eq_ignore_ascii_case来判断是否和保留头冲突, 避免了to_lowercase的 allocation (大力表扬! 我都不知道有这个方法, 而且没有几个 AI 用了这个方法).
Cons:
- 完全没有遵守 prompt 里的
HashMap<HeaderName, HeaderValue>类型要求, 而是自作主张用了http::HeaderMap. HeaderMap没有用Arc包裹, 而是到处 clone. 这个结构体里面还比较复杂, clone 有性能代价.- 在发送请求时, 写了两次循环. 第一次循环判断是否有冲突头, 第二次循环判断再一个个注入. 其实可以在一次循环里就完成的.
- 冲突头检测也产生了三处重复代码, 没有封装成函数.
代码质量得分: 5
Prompt 次数
2 次. 集成测试出了一次问题.
Prompt 次数得分: 8
协议理解
判断出的保留头为:
- accept
- content-type
- mcp-session-id
- last-event-id
- authorization
漏掉了一个 mcp-protocol-version.
协议理解得分: 7
综合得分
GLM-5: 6.4
3. Doubao-Seed-2.0-Code (Trae)
Doubao-Seed-2.0-Code 和 Sonnet 4.5 的产出代码惊人地类似, 但是存在以下两个问题:
- 第一次写完集成测试后, 运行失败, 就直接删掉了, 也没写新的, 在我提醒后写了一个新的.
- 第二次写完后, 只运行了部分测试, 没有使用
just test跑全量测试, 导致甚至存在一些编译错误, 但是它直接结束说成功了.
代码质量
Pros:
- 使用了
Arc包裹HashMap, 考虑到了性能. - 测试用例覆盖完整, 覆盖了所有保留头的报错场景.
Cons:
- Doubao-Seed-2.0-Code 对 prompt 遵循比较差, 有自己的想法. Prompt 里明确说了, 如果请求头冲突, 需要在发请求时报错, 但是它生成的代码里, 将这个冲突检测前移到了
custom_headers方法里. 虽然这样可能更符合最佳实践, 但是还是违背了 prompt 的需求, 同时导致了与 prompt 里给的例子不匹配:custom_headers返回了一个Result, 需要用户做错误处理. - 在保留头检测时, 写出了以下代码:
for name in headers.keys() {
let name_str = name.as_str().to_lowercase();
if name_str == ACCEPT.as_str()
|| name_str == AUTHORIZATION.as_str()
|| name_str == CONTENT_TYPE.as_str()
|| name_str == "mcp-session-id"
|| name_str == "last-event-id"
{
return Err(StreamableHttpError::ReservedHeader(name.to_string()));
}
}
在循环里调用了 to_lowercase, 产生 allocation. 应该使用 eq_ignore_ascii_case 来对比, 这个函数不会产生 allocation. 并且用几个 ||, 而不是维护一个数组.
代码质量得分: 7
Prompt 次数
3 次. 同样是测试的问题.
Prompt 次数得分: 7
协议理解
判断出的保留头为:
- accept
- content-type
- authorization
- mcp-session-id
- last-event-id
漏掉了一个 mcp-protocol-version.
协议理解得分: 7
综合得分
Doubao-Seed-2.0-Code: 7
4. Claude Sonnet 4.5 (Claude Code)
Sonnet 4.5 的表现差强人意, 在两次 prompt 后完成. 整体虽然完成了挑战, 但是有一个致命问题: 写出了两个卡死的集成测试用例, 并且在卡死之后放弃修复, 直接宣告完成.
- 第一次 prompt 后, 完成了功能, 写完了测试, 但是在多次运行测试之后卡死. 等待一两分钟后, 测试依然没有输出结果, Sonnet 4.5 并没有检测到测试卡死, 也没有尝试修复, 直接输出完成需求.
- 卡死的原因是没有遵循 prompt 里的第二条: "对于集成测试, 直接用简单的 axum server", 而是尝试去和一个 mcp server 对接, 中途发生错误, 所以卡死.
- 在我把两个卡死的测试用例发给它之后, 它成功用 axum server 修复好了.
代码质量
Pros:
- 使用了
Arc包裹HashMap, 考虑到了性能. - 测试用例覆盖完整, 覆盖了所有保留头的报错场景.
Cons:
- 和 Doubao 类似, Sonnet 4.5 对 prompt 遵循比较差, 同样有自己的想法. 同样将冲突检测前移到了
custom_headers方法里, 同样导致了与 prompt 里给的例子不匹配:custom_headers返回了一个Result, 需要用户做错误处理. - 判断是否含有保留头时, 在循环里调用了
to_ascii_lowercase, 会产生 allocation. 应该使用eq_ignore_ascii_case来对比, 这个函数不会产生 allocation. - 生成的代码里, 产生了多处重复代码: 一个 for 循环遍历
custom_headers, 一个个插入到请求头里, 没有封装成函数. - 有一个地方写出了
std::sync::Arc<std::collections::HashMap<http::HeaderName, http::HeaderValue>>这种类型, 没有在开头use进来. 小问题, 无伤大雅.
代码质量得分: 7
Prompt 次数
2 次. 测试问题 +1.
Prompt 次数得分: 8
协议理解
判断出的保留头为:
- accept
- content-type
- authorization
- mcp-session-id
- last-event-id
- mcp-protocol-version
- origin
勉强符合预期, 实际上真正使用的只有 accept, mcp-session-id, mcp-protocol-version, last-event-id 这 4 个. origin, authorization 其实是应该允许修改的.
协议理解得分: 9
综合得分
Claude Sonnet 4.5: 7.6
5. Claude Opus 4.6 (Claude Code)
Opus 4.6 这次测试结果大跌眼镜, 连 Sonnet 4.5 都不如. 而且更离谱的是测 Sonnet 4.5 的时候第一次不小心模型设置成了 Opus 4.5, 看了一下输出质量还不错. 本以为 Opus 4.6 会更强, 没想到居然退步了.
代码质量
Pros:
- 只有测试用例完整这一个优点.
Cons:
- 对
HeaderName::from_bytes,HeaderValue::from_bytes, reqwest 的builder.build使用了expect, 会对用户的错误输入直接 panic. - 和 Sonnet 4.5 一样, 判断是否含有保留头时, 在循环里调用了
to_ascii_lowercase, 产生 allocation. - 做保留头检测的时机很奇怪, 既不是在
custom_headers调用时, 也不是在post_message发请求时, 而是在初始化的tokio::spawn里, 在和 mcp server 的初始连接请求发送之前检测, 并且没有新增一个错误类型来返回, 而是用了已有的StreamableHttpError::TransportChannelClosed, 错误表达不够清晰准确. - 重构了与本需求无关的一些代码.
代码质量得分: 4
Prompt 次数
1 次. (Opus 一般会进入 plan mode, 然后让用户 review plan. 我直接点的确定, 所以不算一次 prompt)
Prompt 次数得分: 10
协议理解
同样的 7 个头, 勉强符合预期.
协议理解得分: 9
综合得分
Claude Opus 4.6: 6.9
6. GPT-5.3-Codex (Cursor, Extra High)
GPT-5.3-Codex 的表现非常亮眼, 几乎没有瑕疵, 除了最后疯狂在那 cargo fmt 和执行测试, 有点对自己的格式修改不自信.
代码质量
Pros:
- 使用了
Arc包裹HashMap, 考虑到了性能. - 测试用例覆盖完整, 覆盖了所有保留头的报错场景.
Cons:
- 将每个保留头都定义成了一个常量, 没有复用已有的常量.
代码质量得分: 9
Prompt 次数
1 次.
Prompt 次数得分: 10
协议理解
判断出的保留头为:
- accept
- mcp-session-id
- last-event-id
- mcp-protocol-version
完美! GPT 完全理解了协议内容, 只列出了这 4 个被实际使用的, 而不是把所有提到的都列出来. 满分!
协议理解得分: 10
综合得分
GPT-5.3-Codex: 9.5
排名
| 模型 | 代码质量 (50%) | Prompt 次数 (40%) | 协议理解 (10%) | 综合评分 |
|---|---|---|---|---|
| GPT-5.3-Codex | 9 | 10 | 10 | 9.5 |
| Claude Sonnet 4.5 | 7 | 8 | 9 | 7.6 |
| Doubao-Seed-2.0-Code | 7 | 7 | 7 | 7.0 |
| Claude Opus 4.6 | 4 | 10 | 9 | 6.9 |
| GLM-5 | 5 | 8 | 7 | 6.4 |
| MiniMax-M2.5 | 6 | 5 | 4 | 5.4 |
总结
- 除了 Opus 和 GPT 以外, 所有的 AI 都因为集成测试而多消耗了 prompt.
- Doubao-Seed-2.0-Code 和 Claude Sonnet 4.5 的结果非常相似, 同时也是国产模型里表现最好的.
- Opus 4.6 远远低于预期, 感觉也不是正常的水平.
- GPT-5.3-Codex 真神, 基本挑不出任何毛病.
- GLM-5 对 prompt 的遵守最差: 自作主张改类型; 到处 clone 不考虑性能; 集成测试写成单元测试.
- 国产模型对英文 mcp transport spec 的理解都还不到位.