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

  1. Emit exactly one flat JSON object: {"Header-Name": "value", ...}
  2. Quote every value (numbers included) and keep values as strings
  3. Strip banners/debug lines so stdout contains only the JSON object
  4. 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

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


AI-assisted analysis of openai/codex@339751715c (2026-08-25). Data as JSON: /api/errors/0e8448240da16149. Report an issue: GitHub.