mcp_common/
client_config.rs

1//! Client connection configuration for MCP services
2//!
3//! This module provides a unified configuration structure for connecting
4//! to MCP servers via SSE or Streamable HTTP protocols.
5
6use std::collections::HashMap;
7use std::time::Duration;
8
9/// Configuration for MCP client connections
10///
11/// This struct provides a protocol-agnostic way to configure connections
12/// to MCP servers. It can be used with both SSE and Streamable HTTP transports.
13///
14/// # Example
15///
16/// ```rust
17/// use mcp_common::McpClientConfig;
18/// use std::time::Duration;
19///
20/// let config = McpClientConfig::new("http://localhost:8080/mcp")
21///     .with_header("Authorization", "Bearer token123")
22///     .with_connect_timeout(Duration::from_secs(30));
23/// ```
24#[derive(Clone, Debug, Default)]
25pub struct McpClientConfig {
26    /// Target URL for the MCP server
27    pub url: String,
28    /// HTTP headers to include in requests
29    pub headers: HashMap<String, String>,
30    /// Connection timeout duration
31    pub connect_timeout: Option<Duration>,
32    /// Read timeout duration
33    pub read_timeout: Option<Duration>,
34}
35
36impl McpClientConfig {
37    /// Create a new configuration with the given URL
38    ///
39    /// # Arguments
40    /// * `url` - The MCP server URL (e.g., "http://localhost:8080/mcp")
41    pub fn new(url: impl Into<String>) -> Self {
42        Self {
43            url: url.into(),
44            headers: HashMap::new(),
45            connect_timeout: None,
46            read_timeout: None,
47        }
48    }
49
50    /// Add a header to the configuration
51    ///
52    /// # Arguments
53    /// * `key` - Header name
54    /// * `value` - Header value
55    pub fn with_header(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
56        self.headers.insert(key.into(), value.into());
57        self
58    }
59
60    /// Add multiple headers from a HashMap
61    pub fn with_headers(mut self, headers: HashMap<String, String>) -> Self {
62        self.headers.extend(headers);
63        self
64    }
65
66    /// Set the Authorization header with a Bearer token
67    ///
68    /// # Arguments
69    /// * `token` - The bearer token (without "Bearer " prefix)
70    pub fn with_bearer_auth(self, token: impl Into<String>) -> Self {
71        self.with_header("Authorization", format!("Bearer {}", token.into()))
72    }
73
74    /// Set the connection timeout
75    ///
76    /// # Arguments
77    /// * `timeout` - Maximum time to wait for connection establishment
78    pub fn with_connect_timeout(mut self, timeout: Duration) -> Self {
79        self.connect_timeout = Some(timeout);
80        self
81    }
82
83    /// Set the read timeout
84    ///
85    /// # Arguments
86    /// * `timeout` - Maximum time to wait for response data
87    pub fn with_read_timeout(mut self, timeout: Duration) -> Self {
88        self.read_timeout = Some(timeout);
89        self
90    }
91}
92
93#[cfg(test)]
94mod tests {
95    use super::*;
96
97    #[test]
98    fn test_new_config() {
99        let config = McpClientConfig::new("http://localhost:8080");
100        assert_eq!(config.url, "http://localhost:8080");
101        assert!(config.headers.is_empty());
102    }
103
104    #[test]
105    fn test_with_header() {
106        let config = McpClientConfig::new("http://localhost:8080")
107            .with_header("X-Custom", "value");
108        assert_eq!(config.headers.get("X-Custom"), Some(&"value".to_string()));
109    }
110
111    #[test]
112    fn test_with_bearer_auth() {
113        let config = McpClientConfig::new("http://localhost:8080")
114            .with_bearer_auth("mytoken");
115        assert_eq!(
116            config.headers.get("Authorization"),
117            Some(&"Bearer mytoken".to_string())
118        );
119    }
120
121    #[test]
122    fn test_builder_chain() {
123        let config = McpClientConfig::new("http://localhost:8080")
124            .with_header("X-Api-Key", "key123")
125            .with_connect_timeout(Duration::from_secs(30))
126            .with_read_timeout(Duration::from_secs(60));
127
128        assert_eq!(config.url, "http://localhost:8080");
129        assert_eq!(config.headers.get("X-Api-Key"), Some(&"key123".to_string()));
130        assert_eq!(config.connect_timeout, Some(Duration::from_secs(30)));
131        assert_eq!(config.read_timeout, Some(Duration::from_secs(60)));
132    }
133}