openai/codex · error
MCP HTTP headers helper must output a JSON object of strings
Error message
MCP HTTP headers helper must output a JSON object of strings
What it means
After UTF-8 decoding, stdout must deserialize into a JSON object whose keys and values are all strings, as a single document with nothing after it (deserializer.end() rejects trailing tokens). Arrays, nested objects, numeric/boolean values, or extra text all fail here. The error does not echo the output, so malformed content with embedded secrets is not leaked.
Source
Thrown at codex-rs/rmcp-client/src/http_headers.rs:357
}
Ok(output)
})
.await
.map_err(|_| anyhow!("MCP HTTP headers helper timed out after 10 seconds"))??;
parse_helper_output(output)
}
fn parse_helper_output(stdout: Vec<u8>) -> Result<HeaderMap> {
let stdout = String::from_utf8(stdout)
.map_err(|_| anyhow!("MCP HTTP headers helper wrote non-UTF-8 data"))?;
let mut deserializer = serde_json::Deserializer::from_str(stdout.trim());
let headers = RawHeaderEntries::deserialize(&mut deserializer)
.and_then(|headers| {
deserializer.end()?;
Ok(headers)
})
.map_err(|_| anyhow!("MCP HTTP headers helper must output a JSON object of strings"))?;
if headers.has_exact_duplicate {
return Err(anyhow!(
"MCP HTTP headers helper returned duplicate header names"
));
}
let mut parsed = HeaderMap::with_capacity(headers.entries.len());
for (name, value) in headers.entries {
let name = HeaderName::from_bytes(name.as_bytes())
.map_err(|_| anyhow!("MCP HTTP headers helper returned an invalid header name"))?;
// Helper values replace same-name configured headers; bearer/OAuth owns Authorization.
// Google IAP uses Proxy-Authorization alongside application Authorization. For HTTPS MCP
// URLs it is sent through the forward-proxy tunnel to IAP, not used as CONNECT auth.
if matches!(
name.as_str(),
"accept"
| "authorization"
| "connection"
| "content-encoding"View on GitHub (pinned to 339751715c)
Solutions
- Emit exactly one flat JSON object: {"Header-Name": "value", ...}
- Quote every value (numbers included) and keep values as strings
- Strip banners/debug lines so stdout contains only the JSON object
- Validate shape with jq before wiring the helper in: jq -e 'type == "object"'
Example fix
# before
{"X-Trace-Id": 123, "X-Org": {"id": 7}}
# after
{"X-Trace-Id": "123", "X-Org-Id": "7"} Defensive patterns
Strategy: validation
Validate before calling
# Assert the exact contract: one JSON object, all values strings cd "$MCP_CWD" && env -i PATH=/usr/bin:/bin sh -c "$HTTP_HEADERS_HELPER" | jq -e \ 'type == "object" and ([to_entries[].value | type] | all(. == "string"))' >/dev/null \ && echo shape-ok || echo bad-shape
Type guard
fn is_helper_bad_json_shape(error: &anyhow::Error) -> bool {
error.to_string().contains("must output a JSON object of strings")
} Prevention
- Print exactly one flat JSON object; quote all values
- No banners, no extra lines, no JSONL on stdout
- Validate helper output with jq in CI
When it happens
Trigger: Helper output like ["a"], {"h": {"v": 1}}, {"n": 5}, a JSON object followed by extra text, multiple concatenated JSON documents, or JSONL lines.
Common situations: Helpers returning structured credential envelopes (nested tokens); scripts printing a banner before or after the JSON; tools emitting several JSON objects; YAML or pretty-printed non-JSON output.
Related errors
- failed to parse MCP config for selected plugin `{plugin_id}`
- MCP HTTP headers helper containment failed: {error}
- MCP HTTP headers helper failed to start: {error}
- MCP HTTP headers helper process id was unavailable
- MCP HTTP headers helper stdout was unavailable
AI-assisted analysis of openai/codex@339751715c (2026-08-25).
Data as JSON: /api/errors/0e8448240da16149.
Report an issue: GitHub.