Kuberwastaken/claurst · error · anyhow::Error

Redirect URI ' ' is missing host

Error message

Redirect URI '{}' is missing host

What it means

The OAuth redirect_uri parsed successfully as a URL but contains no host component (e.g. a relative or scheme-only URI). The callback listener needs a host to bind a TCP socket on, so the flow aborts.

Solutions

  1. Ensure the redirect URI includes a host, e.g. http://127.0.0.1:PORT/callback
  2. Use 127.0.0.1 or localhost as the host for loopback OAuth callbacks
  3. Check settings.json / env for a mangled URI missing the host segment
  4. Only use this flow with http(s) redirect URIs, not custom app schemes

Example fix

// before
let redirect_uri = "http:///callback";
// after
let redirect_uri = "http://127.0.0.1:8080/callback";
Defensive patterns

Strategy: validation

Validate before calling

fn has_host(uri: &str) -> bool {
    url::Url::parse(uri).map(|u| u.host_str().is_some()).unwrap_or(false)
}

Prevention

When it happens

Trigger: Passing a scheme-only or non-hierarchical URI, e.g. 'http:///callback', 'mailto:foo@bar.com', or 'about:blank' as the redirect_uri to run_mcp_auth_session.

Common situations: Config where the host part was accidentally deleted ('http:///callback'); using a custom-scheme redirect (com.example.app:/oauth) with a loopback-based flow; corrupted config merges dropping the host.

Understand the failure class

Background: "Invalid URL" errors: why new URL(), URI.parse, and reqwest::Url reject your string — missing scheme, whitespace, and bad path format — this error's family across 39 libraries.

Related errors


AI-assisted analysis of Kuberwastaken/claurst@b0637c97ec (2026-09-10). Data as JSON: /api/errors/10d27c69defcf9ec. Report an issue: GitHub.

Appendix: source

Thrown at src-rust/crates/mcp/src/oauth.rs:221

    );

    Ok(McpAuthSession {
        server_name: server_name.to_string(),
        auth_url,
        redirect_uri,
        verifier,
        metadata,
    })
}

async fn bind_callback_listener(
    redirect_uri: &str,
) -> anyhow::Result<(TcpListener, String, String)> {
    let redirect_url = url::Url::parse(redirect_uri)
        .map_err(|e| anyhow::anyhow!("Failed to parse redirect URI '{}': {}", redirect_uri, e))?;
    let host = redirect_url
        .host_str()
        .ok_or_else(|| anyhow::anyhow!("Redirect URI '{}' is missing host", redirect_uri))?
        .to_string();
    let port = redirect_url
        .port_or_known_default()
        .ok_or_else(|| anyhow::anyhow!("Redirect URI '{}' is missing port", redirect_uri))?;
    let callback_path = if redirect_url.path().is_empty() {
        "/callback".to_string()
    } else {
        redirect_url.path().to_string()
    };
    let listener = TcpListener::bind((host.as_str(), port))
        .await
        .map_err(|e| anyhow::anyhow!("Failed to bind OAuth callback listener on {}:{}: {}", host, port, e))?;

    Ok((listener, host, callback_path))
}

async fn wait_for_authorization_code(
    listener: TcpListener,

View on GitHub (pinned to b0637c97ec)