Kuberwastaken/claurst · error · anyhow::Error

Redirect URI ' ' is missing port

Error message

Redirect URI '{}' is missing port

What it means

Thrown in bind_callback_listener when port_or_known_default() returns None — only possible for URLs whose scheme has no default port and which carry no explicit port. The library must bind a concrete local port for the OAuth callback.

Solutions

  1. Use an explicit port in the redirect URI: http://127.0.0.1:PORT/callback
  2. Use the http scheme so the default port logic applies
  3. Ensure the port matches what the OAuth provider registered for the redirect
  4. Fix the URI scheme typo if the scheme was mangled

Example fix

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

Strategy: validation

Validate before calling

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

Prevention

When it happens

Trigger: Passing a redirect URI with a scheme lacking a default port and no :port, e.g. 'ftp://host/callback' or a malformed/custom scheme; with http/https URIs this cannot trigger since defaults (80/443) exist.

Common situations: Copy-pasting a redirect URI with a typo'd scheme; constructing the URI from parts and omitting the port with a non-http scheme.

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/e21456bcae15ed73. Report an issue: GitHub.

Appendix: source

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

        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,
    host: &str,
    callback_path: &str,
    expected_state: Option<&str>,
) -> anyhow::Result<String> {

View on GitHub (pinned to b0637c97ec)