use std::collections::HashSet;
use std::sync::Mutex;
use crate::utils::http::get_user_agent;
#[derive(Debug, serde::Deserialize)]
pub struct RegistryServer {
#[serde(default)]
pub remotes: Option<Vec<RemoteUrl>>,
}
#[derive(Debug, serde::Deserialize)]
pub struct RemoteUrl {
pub url: String,
}
#[derive(Debug, serde::Deserialize)]
pub struct RegistryResponse {
pub servers: Vec<RegistryServer>,
}
fn normalize_url(url: &str) -> Option<String> {
match url::Url::parse(url) {
Ok(mut u) => {
u.set_query(None);
let mut s = u.to_string();
if s.ends_with('/') {
s.pop();
}
Some(s)
}
Err(_) => None,
}
}
static OFFICIAL_URLS: Mutex<Option<HashSet<String>>> = Mutex::new(None);
pub async fn prefetch_official_mcp_urls() {
if std::env::var("AI_CODE_DISABLE_NONESSENTIAL_TRAFFIC").is_ok() {
return;
}
let client = reqwest::Client::builder()
.timeout(std::time::Duration::from_secs(5))
.build();
let Ok(client) = client else {
log::debug!("Failed to build HTTP client for MCP registry");
return;
};
let result = client
.get("https://api.anthropic.com/mcp-registry/v0/servers?version=latest&visibility=commercial")
.header("Accept", "application/json")
.header("User-Agent", get_user_agent())
.send()
.await;
match result {
Ok(response) => {
if let Ok(data) = response.json::<RegistryResponse>().await {
let mut urls = HashSet::new();
for entry in &data.servers {
if let Some(remotes) = &entry.remotes {
for remote in remotes {
if let Some(normalized) = normalize_url(&remote.url) {
urls.insert(normalized);
}
}
}
}
let mut guard = OFFICIAL_URLS.lock().unwrap();
*guard = Some(urls);
log::debug!("[mcp-registry] Loaded official MCP URLs");
}
}
Err(e) => {
log::debug!("Failed to fetch MCP registry: {}", e);
}
}
}
pub fn is_official_mcp_url(normalized_url: &str) -> bool {
let guard = OFFICIAL_URLS.lock().unwrap();
guard
.as_ref()
.map_or(false, |urls| urls.contains(normalized_url))
}
pub fn reset_official_mcp_urls_for_testing() {
let mut guard = OFFICIAL_URLS.lock().unwrap();
*guard = None;
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_normalize_url() {
assert_eq!(
normalize_url("https://example.com/mcp?foo=bar"),
Some("https://example.com/mcp".to_string())
);
assert_eq!(
normalize_url("https://example.com/mcp/"),
Some("https://example.com/mcp".to_string())
);
}
#[test]
fn test_is_official_mcp_url_empty() {
assert!(!is_official_mcp_url("https://example.com"));
}
}