MCP tools/call fails with "Unexpected response type" when content annotations carry a non-integer priority (e.g. 0.6)
Nobody has claimed this yet.
- Dominant language
- Rust
- Stars
- 125k
- Forks
- 19.5k
- PR merge metrics
- PR metrics pending
Description
What version of Codex is running?
codex-cli 0.148.0-alpha.9 (the binary bundled with ChatGPT.app on macOS, /Applications/ChatGPT.app/Contents/Resources/codex, x86_64). The binary embeds rmcp-3.0.0 (visible in its strings).
What platform is your computer?
macOS (Darwin 25.5.0), x86_64.
What issue are you seeing?
Every MCP tools/call whose result content carries a non-integer annotations.priority (for example 0.6) fails with:
tool call error: tool call failed for `<server>/<tool>`
Caused by: Unexpected response type
priority is a spec-valid field: the MCP schema defines Annotations.priority as a number between 0 and 1 describing the importance of the content block, so fractional values like 0.6 are exactly what the spec intends. Servers built on the official Go SDK (github.com/modelcontextprotocol/go-sdk) emit such annotations routinely; in our case (gitlab-mcp-server) every successful tool result was rejected by Codex while the identical server works in Claude Code, MCP Inspector, and other clients.
Bisection results
I replayed a captured 162 KB production CallToolResult through a minimal stdio MCP server and varied one field per run against the real Codex binary (codex exec). Only the annotation priority value matters:
| Variant | Result |
|---|---|
Full result, annotations: {"audience":["assistant"],"priority":0.6} |
FAILED (Unexpected response type) |
Same, annotations: {"priority": 0.6} |
FAILED |
Same, annotations: {"priority": 1} (integer) |
SUCCEEDED |
Same, annotations: {"audience": ["assistant"]} (no priority) |
SUCCEEDED |
| Same, annotations removed entirely | SUCCEEDED |
Same, structuredContent removed (priority 0.6 kept) |
FAILED |
| Text truncated to 1 KB (priority 0.6 kept) | FAILED |
So neither result size, structuredContent, nor audience is involved — only the fractional priority.
What steps can reproduce the bug?
Minimal stdio server (fake_server.py):
#!/usr/bin/env python3
import json, os, sys
PRIORITY = float(os.environ.get("PRIORITY", "0.6"))
def send(o):
sys.stdout.write(json.dumps(o) + "\n"); sys.stdout.flush()
for line in sys.stdin:
req = json.loads(line)
rid, method = req.get("id"), req.get("method")
if rid is None:
continue
if method == "initialize":
send({"jsonrpc": "2.0", "id": rid, "result": {
"protocolVersion": req["params"]["protocolVersion"],
"capabilities": {"tools": {}},
"serverInfo": {"name": "repro", "version": "0.0.1"}}})
elif method == "tools/list":
send({"jsonrpc": "2.0", "id": rid, "result": {"tools": [{
"name": "echo", "description": "echo",
"inputSchema": {"type": "object", "properties": {}},
"annotations": {"readOnlyHint": True}}]}})
elif method == "tools/call":
send({"jsonrpc": "2.0", "id": rid, "result": {"content": [{
"type": "text", "text": "ok",
"annotations": {"audience": ["assistant"], "priority": PRIORITY}}]}})
else:
send({"jsonrpc": "2.0", "id": rid,
"error": {"code": -32601, "message": "Method not found"}})
~/.codex/config.toml:
[mcp_servers.repro]
command = "python3"
args = ["/path/to/fake_server.py"]
[mcp_servers.repro.env]
PRIORITY = "0.6"
Run:
codex exec "Call the echo tool and report whether the call succeeded or failed."
PRIORITY = "0.6"→ the tool call fails withUnexpected response type.PRIORITY = "1"(or removing the field) → the tool call succeeds.
What is the expected behavior?
Fractional priority values (0–1 per the MCP spec) should parse, and the tool result should be delivered as a CallToolResult.
Additional information
The failing payload is valid for the published rmcp crate. I compiled a probe against crates.io rmcp = "=3.0.0" with the same feature set Codex uses (auth,base64,client,macros,schemars,server,transport-*) and fed it the exact captured JSON:
serde_json::from_str::<ServerResult>(payload)→ServerResult::CallToolResult✔ (crates.io rmcp typespriorityasOption<f32>, so0.6parses)- The full JSON-RPC envelope also parses as
JsonRpcMessage::Responsewith aCallToolResultinside ✔
Yet the shipped binary degrades the same payload to ServerResult::CustomResult, which the legacy call path in codex-rs/rmcp-client/src/rmcp_client.rs then rejects:
match result {
ServerResult::CallToolResult(result) => Ok(result),
_ => Err(rmcp::service::ServiceError::UnexpectedResponse),
}
This suggests the rmcp/serde configuration in the released build differs from crates.io rmcp 3.0.0 (patched crate or feature interaction) in a way that rejects non-integer priority values, silently punting the whole result to CustomResult through the untagged ServerResult union.
This is very likely the concrete root cause behind at least some reports of #29002 (valid tool result decodes as CustomResult) — any server that annotates content with fractional priorities hits it deterministically. Related symptom: #33404.
Server-side workaround we ship today: detect clientInfo.name == "codex-mcp-client" and round annotation priorities to 0/1 for those sessions.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start in codex-rs/rmcp-client/src/rmcp_client.rs, where non-CallToolResult responses are rejected, and compare the released build's rmcp/serde decoding with crates.io rmcp 3.0.0. Use the captured JSON or fake_server.py with fractional and integer priorities. Done means a fractional priority parses as CallToolResult and the tool call succeeds.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- rust
- Domain
- api, cli
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 55/100