Hmbown/CodeWhale · error
MCP server URL '{server_url}' must include a host
Error message
MCP server URL '{server_url}' must include a host What it means
callback_id_from_server_url() derives the OAuth callback path by hashing the normalized server URL, which requires a host component. Url::parse() accepted the string but host_str() returned None — true for hostless, non-hierarchical URLs (file:, data:, about:, mailto:, unix:) or URLs like http:///path with an empty authority.
Source
Thrown at crates/tui/src/mcp/oauth.rs:1103
fn callback_bind_host(callback_url: Option<&str>) -> &'static str {
let Some(callback_url) = callback_url else {
return "127.0.0.1";
};
let Ok(parsed) = Url::parse(callback_url) else {
return "127.0.0.1";
};
match parsed.host_str() {
Some("localhost" | "127.0.0.1" | "::1") | None => "127.0.0.1",
Some(_) => "0.0.0.0",
}
}
fn callback_id_from_server_url(server_url: &str) -> Result<String> {
let mut parsed =
Url::parse(server_url).with_context(|| format!("invalid MCP server URL '{server_url}'"))?;
parsed
.host_str()
.ok_or_else(|| anyhow!("MCP server URL '{server_url}' must include a host"))?;
parsed.set_fragment(None);
let digest = Sha256::digest(parsed.as_str().as_bytes());
Ok(URL_SAFE_NO_PAD.encode(&digest[..9]))
}
fn append_callback_id_to_redirect_uri(redirect_uri: &str, callback_id: &str) -> Result<String> {
let mut parsed = Url::parse(redirect_uri)
.with_context(|| format!("invalid redirect URI '{redirect_uri}'"))?;
let path = parsed.path();
let new_path = if path.ends_with('/') {
format!("{path}{callback_id}")
} else {
format!("{path}/{callback_id}")
};
parsed.set_path(&new_path);
Ok(parsed.to_string())
}
View on GitHub (pinned to 8880682c63)
Solutions
- Set the MCP server URL to a hierarchical http(s) URL with an explicit host, e.g. https://mcp.example.com/sse
- For local servers use http://127.0.0.1:PORT/... — this code explicitly recognizes loopback hosts
- Use stdio transport for local command servers instead of HTTP/OAuth
- Validate candidate URLs with url::Url::parse(...).host_str() before saving the config
Example fix
// before let server_url = "file:///opt/mcp/server"; // after let server_url = "https://mcp.internal.example.com/sse";
Defensive patterns
Strategy: validation
Validate before calling
```rust
fn validate_mcp_server_url(server_url: &str) -> anyhow::Result<()> {
let url = url::Url::parse(server_url).context("invalid MCP server URL")?;
anyhow::ensure!(url.host_str().is_some(), "MCP server URL must include a host");
Ok(())
}
``` Type guard
```rust
fn mcp_url_has_host(server_url: &str) -> bool {
url::Url::parse(server_url)
.ok()
.and_then(|u| u.host_str().map(|_| true))
.unwrap_or(false)
}
``` Prevention
- Always configure http(s)://host[:port]/path URLs for HTTP/OAuth MCP server entries
- Lint MCP config URLs with Url::parse + host_str in CI so hostless URLs never reach OAuth setup
When it happens
Trigger: Starting MCP OAuth callback-id derivation with a server URL such as "file:///opt/mcp", "unix:/run/mcp.sock", "about:blank", or "http:///api" — parseable but hostless.
Common situations: Placeholder or stdio-style paths pasted into an HTTP/OAuth MCP server entry; local socket endpoints where an http(s) URL is required; typos dropping the host after the scheme.
Related errors
- invalid JSON payload at key {MCP_SERVER_DEFINITIONS_KEY}; co
- invalid MCP server definition list in key {MCP_SERVER_DEFINI
- Kimi CLI credential import is unsupported. Codewhale does no
- Failed to write MCP config {}: {}
- MCP config path cannot be empty
AI-assisted analysis of Hmbown/CodeWhale@8880682c63 (2026-08-16).
Data as JSON: /api/errors/67e710bd719f4181.
Report an issue: GitHub.