//! HTTP proxy configuration for MCP connections. //! //! Resolves proxy settings and creates HTTP clients for MCP server connections. use std::time::Duration; use crate::mcp::{McpError, McpProxyConfig, McpResult, McpServerConfig}; /// Resolve proxy configuration for a server /// Priority: server.proxy > global.proxy > None /// /// # Arguments /// * `server_config` - Server-specific configuration /// * `global_proxy` - Global proxy configuration from McpConfig /// /// # Returns /// The resolved proxy configuration, or None for direct connection pub fn resolve_proxy_config<'a>( server_config: &'a McpServerConfig, global_proxy: Option<&'a McpProxyConfig>, ) -> Option<&'a McpProxyConfig> { // Priority 1: Check if server has explicit proxy config // Note: server.proxy = Some(config) uses that config // server.proxy = None (set explicitly in YAML as null) forces direct connection // server.proxy not set (field missing) falls back to global if server_config.proxy.is_some() { server_config.proxy.as_ref() } else { // Priority 2: Fall back to global proxy global_proxy } } /// Apply proxy configuration to a ClientBuilder /// /// This is a reusable helper that applies proxy settings without building the client, /// allowing additional configuration (like auth headers) to be added afterward. /// /// # Arguments /// * `builder` - The reqwest::ClientBuilder to configure /// * `proxy_config` - The proxy configuration to apply /// /// # Returns /// The configured builder or error pub fn apply_proxy_to_builder( mut builder: reqwest::ClientBuilder, proxy_cfg: &McpProxyConfig, ) -> McpResult { // Configure HTTP proxy if let Some(ref http_proxy) = proxy_cfg.http { let mut proxy = reqwest::Proxy::http(http_proxy) .map_err(|e| McpError::Config(format!("Invalid HTTP proxy: {}", e)))?; // Apply no_proxy exclusions if let Some(ref no_proxy) = proxy_cfg.no_proxy { proxy = proxy.no_proxy(reqwest::NoProxy::from_string(no_proxy)); } // Apply authentication if configured if let (Some(ref username), Some(ref password)) = (&proxy_cfg.username, &proxy_cfg.password) { proxy = proxy.basic_auth(username, password); } builder = builder.proxy(proxy); } // Configure HTTPS proxy if let Some(ref https_proxy) = proxy_cfg.https { let mut proxy = reqwest::Proxy::https(https_proxy) .map_err(|e| McpError::Config(format!("Invalid HTTPS proxy: {}", e)))?; // Apply no_proxy exclusions if let Some(ref no_proxy) = proxy_cfg.no_proxy { proxy = proxy.no_proxy(reqwest::NoProxy::from_string(no_proxy)); } // Apply authentication if configured if let (Some(ref username), Some(ref password)) = (&proxy_cfg.username, &proxy_cfg.password) { proxy = proxy.basic_auth(username, password); } builder = builder.proxy(proxy); } Ok(builder) } /// Create HTTP client with MCP-specific proxy configuration /// /// # Arguments /// * `proxy_config` - Optional proxy configuration to apply /// /// # Returns /// A configured reqwest::Client or error pub fn create_http_client(proxy_config: Option<&McpProxyConfig>) -> McpResult { let mut builder = reqwest::Client::builder() .timeout(Duration::from_secs(30)) .connect_timeout(Duration::from_secs(10)); // Apply MCP-specific proxy if configured if let Some(proxy_cfg) = proxy_config { builder = apply_proxy_to_builder(builder, proxy_cfg)?; } builder .build() .map_err(|e| McpError::Transport(format!("Failed to build HTTP client: {}", e))) } #[cfg(test)] mod tests { use super::*; use crate::mcp::McpTransport; #[test] fn test_resolve_proxy_no_config() { let server = McpServerConfig { name: "test".to_string(), transport: McpTransport::Sse { url: "http://localhost:3000/sse".to_string(), token: None, }, proxy: None, required: false, }; let result = resolve_proxy_config(&server, None); assert!( result.is_none(), "Should return None when no proxy configured" ); } #[test] fn test_resolve_proxy_global_only() { let server = McpServerConfig { name: "test".to_string(), transport: McpTransport::Sse { url: "http://localhost:3000/sse".to_string(), token: None, }, proxy: None, required: false, }; let global = McpProxyConfig { http: Some("http://global-proxy:8080".to_string()), https: None, no_proxy: None, username: None, password: None, }; let result = resolve_proxy_config(&server, Some(&global)); assert!(result.is_some(), "Should use global proxy"); assert_eq!( result.unwrap().http.as_ref().unwrap(), "http://global-proxy:8080" ); } #[test] fn test_resolve_proxy_server_override() { let server_proxy = McpProxyConfig { http: Some("http://server-proxy:9090".to_string()), https: None, no_proxy: None, username: None, password: None, }; let server = McpServerConfig { name: "test".to_string(), transport: McpTransport::Sse { url: "http://localhost:3000/sse".to_string(), token: None, }, proxy: Some(server_proxy), required: false, }; let global = McpProxyConfig { http: Some("http://global-proxy:8080".to_string()), https: None, no_proxy: None, username: None, password: None, }; let result = resolve_proxy_config(&server, Some(&global)); assert!(result.is_some(), "Should use server-specific proxy"); assert_eq!( result.unwrap().http.as_ref().unwrap(), "http://server-proxy:9090", "Server proxy should override global" ); } #[test] fn test_create_http_client_no_proxy() { let client = create_http_client(None); assert!(client.is_ok(), "Should create client without proxy"); } #[test] fn test_create_http_client_with_proxy() { let proxy = McpProxyConfig { http: Some("http://proxy.example.com:8080".to_string()), https: None, no_proxy: Some("localhost,127.0.0.1".to_string()), username: None, password: None, }; let client = create_http_client(Some(&proxy)); assert!(client.is_ok(), "Should create client with proxy"); } #[test] fn test_create_http_client_with_auth() { let proxy = McpProxyConfig { http: Some("http://proxy.example.com:8080".to_string()), https: None, no_proxy: None, username: Some("user".to_string()), password: Some("pass".to_string()), }; let client = create_http_client(Some(&proxy)); assert!( client.is_ok(), "Should create client with proxy authentication" ); } #[test] fn test_create_http_client_invalid_proxy() { let proxy = McpProxyConfig { http: Some("://invalid".to_string()), // Invalid URL format https: None, no_proxy: None, username: None, password: None, }; let client = create_http_client(Some(&proxy)); assert!(client.is_err(), "Should fail with invalid proxy URL"); } }