openai / openai/codex

MCP tools/call fails with "Unexpected response type" when content annotations carry a non-integer priority (e.g. 0.6)

Open
#38,979 2 comments 2 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

bug CLI mcp tool-calls
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 with Unexpected 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 types priority as Option<f32>, so 0.6 parses)
  • The full JSON-RPC envelope also parses as JsonRpcMessage::Response with a CallToolResult inside ✔

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

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.