Hmbown/CodeWhale · error
unsafe MCP HTTP header '{name}'
Error message
unsafe MCP HTTP header '{name}' What it means
insert_header (oauth.rs:592) validates every header built by build_default_headers with super::headers::is_safe_custom_header (headers.rs:38), which rejects three cases: empty/whitespace-only header names, names that override the protocol-framing headers Accept or Content-Type, and values containing CR or LF (response-splitting defense). Unlike apply_safe_custom_headers, which only warns and skips, the default-headers builder hard-fails the whole call because the map is constructed up front.
Source
Thrown at crates/tui/src/mcp/oauth.rs:592
env_headers: &HashMap<String, String>,
) -> Result<HeaderMap> {
let mut headers = HeaderMap::new();
for (name, value) in http_headers {
insert_header(&mut headers, name, value)?;
}
for (name, env_var) in env_headers {
if let Ok(value) = std::env::var(env_var)
&& !value.trim().is_empty()
{
insert_header(&mut headers, name, &value)?;
}
}
Ok(headers)
}
fn insert_header(headers: &mut HeaderMap, name: &str, value: &str) -> Result<()> {
if !super::headers::is_safe_custom_header(name, value) {
bail!("unsafe MCP HTTP header '{name}'");
}
let name = HeaderName::from_bytes(name.as_bytes())
.with_context(|| format!("invalid MCP HTTP header name '{name}'"))?;
let value = HeaderValue::from_str(value).with_context(|| "invalid MCP HTTP header value")?;
headers.insert(name, value);
Ok(())
}
pub fn apply_default_headers(
builder: reqwest::ClientBuilder,
headers: &HeaderMap,
) -> reqwest::ClientBuilder {
if headers.is_empty() {
builder
} else {
builder.default_headers(headers.clone())
}
}View on GitHub (pinned to 0c42157ee5)
Solutions
- Strip CR/LF from environment values before they reach config: tr -d '\r\n' < token.txt
- Rename or remove empty header keys and any Accept/Content-Type overrides — the transport sets those itself
- Validate the header map with the same three rules before passing config to the OAuth/login path
Example fix
# before export MY_SERVER_TOKEN="$(cat token.txt)" # trailing \n in value [mcp.servers.my-server.env-headers] X-Api-Token = "MY_SERVER_TOKEN" Accept = "text/event-stream" # after export MY_SERVER_TOKEN="$(tr -d '\r\n' < token.txt)" [mcp.servers.my-server.env-headers] X-Api-Token = "MY_SERVER_TOKEN"
Defensive patterns
Strategy: validation
Validate before calling
fn is_safe_custom_header(key: &str, value: &str) -> bool {
let trimmed = key.trim();
!trimmed.is_empty()
&& !trimmed.eq_ignore_ascii_case("accept")
&& !trimmed.eq_ignore_ascii_case("content-type")
&& !value.contains('\r')
&& !value.contains('\n')
}
for (k, v) in &server.headers {
assert!(is_safe_custom_header(k, v), "unsafe header {k:?}");
} Try / catch
match build_default_headers(&server.headers, &server.env_headers) {
Err(err) if err.to_string().contains("unsafe MCP HTTP header") => {
// strip the named header from config and retry once
}
other => other?,
} Prevention
- Trim CR/LF from any secret read from files or env before it becomes a header value
- Never configure Accept or Content-Type on MCP servers — the transport owns protocol framing
- Validate header maps with the same three rules in config-loading code so mistakes fail at load, not at connect
When it happens
Trigger: An McpServerConfig headers or env_headers entry has an empty key, an `Accept`/`Content-Type` key (any case), or a value with embedded \r or \n. For env_headers the value comes from the environment variable at runtime, so a token file read with a trailing newline triggers it.
Common situations: TOKEN=$(cat token.txt) leaves a trailing newline that lands in a header value; copy-pasted header blocks that set Accept: text/event-stream, fighting the transport's own negotiation; YAML/TOML template with an empty header placeholder key.
Related errors
- MCP server '{server_name}' has no command configured
- agent profile {} may not request allow_shell=true
- agent profile {} may not request trust=true
- MCP server '{name}' connection to '{host}' blocked by networ
- MCP server '{name}' connection to '{host}' requires approval
AI-assisted analysis of Hmbown/CodeWhale@0c42157ee5 (2026-08-20).
Data as JSON: /api/errors/2e996438a50ab455.
Report an issue: GitHub.