openai/codex · error

MCP HTTP headers helper returned duplicate header names

Error message

MCP HTTP headers helper returned duplicate header names

What it means

Header names must be unique. Exact byte-identical duplicate JSON keys are caught during deserialization (has_exact_duplicate — ordinary map parsing would silently collapse them), and names colliding case-insensitively are caught when inserting into the HeaderMap, because HTTP header names are case-insensitive. Either form rejects the helper output rather than silently dropping one value.

Source

Thrown at codex-rs/rmcp-client/src/http_headers.rs:359

    })
    .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"
                | "content-length"
                | "content-type"

View on GitHub (pinned to 339751715c)

Solutions

  1. Deduplicate header names case-insensitively in the helper before printing
  2. Pick a single canonical casing per header
  3. Validate with jq: keys | map(ascii_downcase) | length == (unique | length)
  4. If merging configs, last-write-wins in your own tooling so duplicates never reach the helper

Example fix

# before
{"X-Api-Key":"a","x-api-key":"b"}

# after
{"X-Api-Key":"b"}
Defensive patterns

Strategy: validation

Validate before calling

# Reject duplicate header names, case-insensitively
cd "$MCP_CWD" && env -i PATH=/usr/bin:/bin sh -c "$HTTP_HEADERS_HELPER" | jq -e \
  '(keys | map(ascii_downcase) | length) == (keys | map(ascii_downcase) | unique | length)' >/dev/null \
  && echo names-unique || echo duplicate-header-names

Type guard

fn is_helper_duplicate_headers(error: &anyhow::Error) -> bool {
    error.to_string().contains("duplicate header names")
}

Prevention

When it happens

Trigger: Helper JSON containing {"X-Api-Key": "a", "X-Api-Key": "b"} (exact duplicate) or {"X-Api-Key": "a", "x-api-key": "b"} (case-insensitive duplicate via the parsed-map check).

Common situations: Config assembled from multiple sources appending the same header twice; case mismatches between environments (X-API-KEY vs x-api-key); hand-edited JSON with repeated keys that most parsers hide.

Related errors


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