use rand;
use std::net::ToSocketAddrs;
use tokio::net::TcpListener;
const REDIRECT_PORT_FALLBACK: u16 = 3118;
pub fn build_redirect_uri(port: u16) -> String {
format!("http://localhost:{port}/callback")
}
fn get_mcp_oauth_callback_port() -> Option<u16> {
std::env::var("AI_CODE_MCP_OAUTH_CALLBACK_PORT")
.ok()
.and_then(|v| v.parse().ok())
.filter(|p| *p > 0)
}
pub async fn find_available_port() -> anyhow::Result<u16> {
if let Some(configured) = get_mcp_oauth_callback_port() {
return Ok(configured);
}
const MIN_PORT: u16 = 49152;
const MAX_PORT: u16 = 65535;
let range = (MAX_PORT - MIN_PORT + 1) as u32;
let max_attempts = std::cmp::min(range, 100);
let mut rng = rand::thread_rng();
for _ in 0..max_attempts {
let port = MIN_PORT + (rand::random::<u32>() % range) as u16;
if is_port_available(port).await {
return Ok(port);
}
}
if is_port_available(REDIRECT_PORT_FALLBACK).await {
return Ok(REDIRECT_PORT_FALLBACK);
}
Err(anyhow::anyhow!("No available ports for OAuth redirect"))
}
async fn is_port_available(port: u16) -> bool {
let addr = format!("127.0.0.1:{port}");
match TcpListener::bind(&addr).await {
Ok(listener) => {
drop(listener);
true
}
Err(_) => false,
}
}