{"record":{"id":"67e710bd719f4181","repo":"Hmbown/CodeWhale","slug":"mcp-server-url-server-url-must-include-a-host","errorCode":null,"errorMessage":"MCP server URL '{server_url}' must include a host","messagePattern":"MCP server URL '(.+?)' must include a host","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/tui/src/mcp/oauth.rs","lineNumber":1103,"sourceCode":"fn callback_bind_host(callback_url: Option<&str>) -> &'static str {\n    let Some(callback_url) = callback_url else {\n        return \"127.0.0.1\";\n    };\n    let Ok(parsed) = Url::parse(callback_url) else {\n        return \"127.0.0.1\";\n    };\n    match parsed.host_str() {\n        Some(\"localhost\" | \"127.0.0.1\" | \"::1\") | None => \"127.0.0.1\",\n        Some(_) => \"0.0.0.0\",\n    }\n}\n\nfn callback_id_from_server_url(server_url: &str) -> Result<String> {\n    let mut parsed =\n        Url::parse(server_url).with_context(|| format!(\"invalid MCP server URL '{server_url}'\"))?;\n    parsed\n        .host_str()\n        .ok_or_else(|| anyhow!(\"MCP server URL '{server_url}' must include a host\"))?;\n    parsed.set_fragment(None);\n    let digest = Sha256::digest(parsed.as_str().as_bytes());\n    Ok(URL_SAFE_NO_PAD.encode(&digest[..9]))\n}\n\nfn append_callback_id_to_redirect_uri(redirect_uri: &str, callback_id: &str) -> Result<String> {\n    let mut parsed = Url::parse(redirect_uri)\n        .with_context(|| format!(\"invalid redirect URI '{redirect_uri}'\"))?;\n    let path = parsed.path();\n    let new_path = if path.ends_with('/') {\n        format!(\"{path}{callback_id}\")\n    } else {\n        format!(\"{path}/{callback_id}\")\n    };\n    parsed.set_path(&new_path);\n    Ok(parsed.to_string())\n}\n","sourceCodeStart":1085,"sourceCodeEnd":1121,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/8880682c63083a91624de936797efa3ce9e498fd/crates/tui/src/mcp/oauth.rs#L1085-L1121","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before\nlet server_url = \"file:///opt/mcp/server\";\n\n// after\nlet server_url = \"https://mcp.internal.example.com/sse\";","handlingStrategy":"validation","validationCode":"```rust\nfn validate_mcp_server_url(server_url: &str) -> anyhow::Result<()> {\n    let url = url::Url::parse(server_url).context(\"invalid MCP server URL\")?;\n    anyhow::ensure!(url.host_str().is_some(), \"MCP server URL must include a host\");\n    Ok(())\n}\n```","typeGuard":"```rust\nfn mcp_url_has_host(server_url: &str) -> bool {\n    url::Url::parse(server_url)\n        .ok()\n        .and_then(|u| u.host_str().map(|_| true))\n        .unwrap_or(false)\n}\n```","tryCatchPattern":null,"preventionTips":["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"],"tags":["mcp","oauth","url-validation","config"],"backgroundTag":null,"analyzedSha":"8880682c63083a91624de936797efa3ce9e498fd","analyzedAt":"2026-08-16T11:31:27.956Z","schemaVersion":2},"datasetVersion":"2026-08-16T13:17:31.715Z"}