252 lines
7.7 KiB
Rust
252 lines
7.7 KiB
Rust
//! 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<reqwest::ClientBuilder> {
|
|
// 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<reqwest::Client> {
|
|
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");
|
|
}
|
|
}
|