Skip to main content

mcp_common/
mirror.rs

1//! 镜像源配置:通过进程级环境变量为 npx/uvx 子进程设置国内镜像源
2
3/// 镜像源配置
4#[derive(Debug, Clone, Default)]
5pub struct MirrorConfig {
6    pub npm_registry: Option<String>,
7    pub pypi_index_url: Option<String>,
8}
9
10impl MirrorConfig {
11    /// 从环境变量 `MCP_PROXY_NPM_REGISTRY` / `MCP_PROXY_PYPI_INDEX_URL` 加载
12    pub fn from_env() -> Self {
13        Self {
14            npm_registry: std::env::var("MCP_PROXY_NPM_REGISTRY").ok(),
15            pypi_index_url: std::env::var("MCP_PROXY_PYPI_INDEX_URL").ok(),
16        }
17    }
18
19    pub fn is_empty(&self) -> bool {
20        self.npm_registry.is_none() && self.pypi_index_url.is_none()
21    }
22
23    /// 设为进程级环境变量,所有子进程自动继承。
24    ///
25    /// # Safety
26    /// 应在 main() 启动早期、单线程阶段调用。
27    pub fn apply_to_process_env(&self) {
28        unsafe {
29            if let Some(ref registry) = self.npm_registry
30                && std::env::var("npm_config_registry").is_err()
31            {
32                std::env::set_var("npm_config_registry", registry);
33            }
34            if let Some(ref index_url) = self.pypi_index_url {
35                if std::env::var("UV_INDEX_URL").is_err() {
36                    std::env::set_var("UV_INDEX_URL", index_url);
37                }
38                if std::env::var("PIP_INDEX_URL").is_err() {
39                    std::env::set_var("PIP_INDEX_URL", index_url);
40                }
41                if std::env::var("UV_INSECURE_HOST").is_err()
42                    && index_url.starts_with("http://")
43                    && let Some(host) = extract_host(index_url)
44                {
45                    std::env::set_var("UV_INSECURE_HOST", &host);
46                }
47            }
48        }
49    }
50}
51
52/// 从 URL 中提取 host
53fn extract_host(url: &str) -> Option<String> {
54    let without_scheme = url
55        .strip_prefix("https://")
56        .or_else(|| url.strip_prefix("http://"))?;
57    let host = without_scheme.split('/').next()?.split(':').next()?;
58    if host.is_empty() {
59        None
60    } else {
61        Some(host.to_string())
62    }
63}
64
65#[cfg(test)]
66mod tests {
67    use super::*;
68
69    #[test]
70    fn test_mirror_config_is_empty() {
71        assert!(MirrorConfig::default().is_empty());
72        assert!(
73            !MirrorConfig {
74                npm_registry: Some("test".to_string()),
75                pypi_index_url: None,
76            }
77            .is_empty()
78        );
79    }
80
81    #[test]
82    fn test_extract_host() {
83        assert_eq!(
84            extract_host("https://mirrors.aliyun.com/pypi/simple/"),
85            Some("mirrors.aliyun.com".to_string())
86        );
87        assert_eq!(
88            extract_host("https://example.com:8080/path"),
89            Some("example.com".to_string())
90        );
91        assert_eq!(extract_host("not-a-url"), None);
92    }
93}