{"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":"anyhow","httpStatus":null,"severity":"error","filePath":"crates/tui/src/mcp/oauth.rs","lineNumber":2020,"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":2002,"sourceCodeEnd":2038,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/73e0f67d83c59909b571efdfc88c4bc28c309cb1/crates/tui/src/mcp/oauth.rs#L2002-L2038","documentation":"callback_id_from_server_url derives a stable callback identifier by parsing the MCP server URL and hashing it; this requires the URL to have a host component. Url::parse can succeed for URLs without a host (e.g. relative or some non-special schemes), so host_str() returning None triggers this error. The URL is used to route the OAuth callback, so a hostless URL cannot be identified.","triggerScenarios":"Calling callback_id_from_server_url with a server URL that parses but has no host — e.g. \"file:///path/to/server\", a malformed scheme-relative URL, or an empty/whitespace string that still parses.","commonSituations":"Configuring an MCP server entry with a local file or Unix-socket URL instead of an http(s) URL when OAuth is enabled; typos in the configured URL scheme; template variables left unsubstituted in config.","solutions":["Set the MCP server URL to a valid absolute http(s) URL including a host, e.g. https://mcp.example.com","Fix typos in the scheme (file://, missing scheme, etc.) in the server configuration","If the server is local and hostless, do not enable OAuth for it"],"exampleFix":"// before\n\"url\": \"file:///opt/mcp/server\"\n// after\n\"url\": \"https://mcp.example.com\"","handlingStrategy":"validation","validationCode":"fn has_host(url: &str) -> bool {\n    url::Url::parse(url).map(|u| u.host_str().is_some()).unwrap_or(false)\n}\n// call before OAuth setup: if !has_host(server_url) { fix config }","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Always configure MCP servers with absolute http(s) URLs including a host","Only enable OAuth for servers that are actually reachable over http(s)","Validate the server URL at config-load time so hostless URLs fail early"],"tags":["url","validation","oauth","mcp"],"backgroundTag":"invalid-url-format","analyzedSha":"73e0f67d83c59909b571efdfc88c4bc28c309cb1","analyzedAt":"2026-09-22T01:30:00.501Z","contentChangedAt":"2026-09-22T01:30:00.501Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}