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
- Use an explicit port in the redirect URI: http://127.0.0.1:PORT/callback
- Use the http scheme so the default port logic applies
- Ensure the port matches what the OAuth provider registered for the redirect
- 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
- Always specify an explicit port: http://127.0.0.1:PORT/callback
- Stick to the http scheme so a default port always exists
- Match the registered port at the OAuth provider
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
- models endpoint returned
- Bridge register: server returned
- start_bridge: bridge is not active
- Token exchange failed
- Token exchange failed
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)