Streamable HTTP MCP client parses Brotli-compressed JSON without decoding it
Nobody has claimed this yet.
Assessment
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Newbie friendliness
- 74/100
- Issue type
- Bug
- Clarity
- Clearly specified
- Activity status
- Quiet
- Tech stack
- rust
- Domain
- api, networking
Research direction
Start with rust-v0.1.0/codex-rs/rmcp-client/src/http_client_adapter.rs, especially StreamableHttpClientAdapter::post_message and the response body handling, then inspect codex-rs/http-client/Cargo.toml. Run the provided Brotli MCP reproduction and verify that compressed responses are decoded before JSON/SSE parsing, or that unsupported encodings produce a specific error.
Written by the indexing model from the issue text.
Description
What issue are you seeing?
The Streamable HTTP MCP client can receive a JSON response with Content-Encoding: br, pass the compressed bytes directly to Serde, and fail with a misleading JSON error:
MCP client failed to start: MCP startup failed: Transport send error: Transport
[rmcp::transport::worker::WorkerTransport<rmcp::transport::streamable_http_client::StreamableHttpClientWorker<...>>]
error: Deserialize error: expected value at line 1 column 2
This occurs when initialize is small and uncompressed, but a larger tools/list response is Brotli-compressed by an ingress or reverse proxy.
Observed with Codex CLI 0.146.1 on macOS. Decompressing the same response externally produces valid JSON, while Codex passes the encoded body to the JSON parser.
What steps can reproduce the bug?
Run a minimal stateless MCP HTTP server that returns a Brotli-compressed tools/list response:
# pip install brotli
import brotli
import json
from http.server import BaseHTTPRequestHandler, HTTPServer
class Handler(BaseHTTPRequestHandler):
def do_POST(self):
length = int(self.headers.get("Content-Length", "0"))
request = json.loads(self.rfile.read(length))
method = request.get("method")
if method == "notifications/initialized":
self.send_response(202)
self.end_headers()
return
if method == "initialize":
result = {
"protocolVersion": request["params"]["protocolVersion"],
"capabilities": {},
"serverInfo": {"name": "brotli-repro", "version": "1.0.0"},
}
elif method == "tools/list":
result = {
"tools": [
{
"name": f"tool_{index}",
"description": "x" * 1000,
"inputSchema": {"type": "object", "properties": {}},
}
for index in range(100)
]
}
else:
result = {}
body = json.dumps(
{"jsonrpc": "2.0", "id": request.get("id"), "result": result}
).encode()
encoded = brotli.compress(body) if method == "tools/list" else body
self.send_response(200)
self.send_header("Content-Type", "application/json")
if method == "tools/list":
self.send_header("Content-Encoding", "br")
self.send_header("Content-Length", str(len(encoded)))
self.end_headers()
self.wfile.write(encoded)
HTTPServer(("127.0.0.1", 8765), Handler).serve_forever()
Configure Codex:
[mcp_servers.brotli_repro]
url = "http://127.0.0.1:8765/mcp"
Start Codex. MCP initialization reaches tools/list and then fails with the deserialize error.
What is the expected behavior?
Codex should do one of the following:
- Advertise only supported response codings, for example
Accept-Encoding: identity, when response decompression is unavailable; or - Enable Brotli support in the shared Reqwest client and decode the response before passing it to the JSON/SSE parser.
If an unsupported Content-Encoding is received, the error should identify the unsupported encoding instead of reporting malformed JSON.
Additional information
Root-cause analysis
In 0.146.1, StreamableHttpClientAdapter::post_message sets Accept and Content-Type, but does not set Accept-Encoding:
The shared HTTP client enables Reqwest features json, rustls-tls-native-roots, and stream, but not brotli:
https://github.com/openai/codex/blob/rust-v0.146.1/codex-rs/http-client/Cargo.toml#L7-L14
For an application/json response, the adapter collects the body stream and immediately calls serde_json::from_slice without inspecting Content-Encoding:
The same behavior is still present on current main at commit 7a0e974e08c798d1e8d59d407aeb6e24db1313af.
RFC 9110 section 12.5.3 says that when Accept-Encoding is absent, the user agent considers any content coding acceptable. Therefore a server or ingress can validly choose Brotli, even though this Codex client cannot decode it:
https://datatracker.ietf.org/doc/html/rfc9110#section-12.5.3
Reqwest's Brotli response decoding is gated by its optional brotli feature:
https://docs.rs/reqwest/latest/reqwest/struct.ClientBuilder.html#method.brotli
Workarounds
Client-side configuration:
[mcp_servers.example.http_headers]
Accept-Encoding = "identity"
Server-side: disable response compression for the MCP route.
- Dominant language
- Rust
- Stars
- 125k
- Forks
- 19.5k
- Avg merge
- 1m
- Merged PRs (30d)
- 1k
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.
More from openai/codex
-
enhancement remote
Difficulty 2/5 1-3 hours Newbie friendliness 68/100
-
bug CLI windows-os
Difficulty 2/5 1-3 hours Newbie friendliness 76/100
-
macOS sandbox blocks hw.optional.arm64 sysctl, causing Flutter to misdetect Apple Silicon as x64 Openbug CLI sandbox
Difficulty 2/5 1-3 hours Newbie friendliness 78/100
-
bug CLI TUI
Difficulty 2/5 1-3 hours Newbie friendliness 90/100
-
CLI config enhancement skills
Difficulty 2/5 1-3 hours Newbie friendliness 84/100
Similar issues
-
Difficulty 2/5 1-3 hours Newbie friendliness 86/100
kwakseongjae/auto-hwp#319 ·
-
area:cli bug filter-quality good first issue priority:medium
Difficulty 2/5 1-3 hours Newbie friendliness 84/100
-
Difficulty 1/5 Under an hour Newbie friendliness 72/100
bevyengine/bevy#25861 ·
-
comp-datalake
Difficulty 2/5 1-3 hours Newbie friendliness 88/100
ClickHouse/ClickHouse#121222 ·
-
A-linter
Difficulty 2/5 1-3 hours Newbie friendliness 72/100
oxc-project/oxc#26863 ·