1use serde::{Deserialize, Serialize};
4use std::time::Duration;
5
6#[derive(Debug, Clone, Serialize, Deserialize, Default)]
8pub struct ClientConfig {
9 pub client_info: ClientInfo,
11
12 pub timeouts: TimeoutConfig,
14
15 pub retry: RetryConfig,
17
18 pub connection: ConnectionConfig,
20
21 pub logging: LoggingConfig,
23}
24
25#[derive(Debug, Clone, Serialize, Deserialize)]
27pub struct ClientInfo {
28 pub name: String,
30
31 pub version: String,
33
34 pub description: Option<String>,
36
37 pub vendor: Option<String>,
39
40 pub metadata: Option<serde_json::Value>,
42}
43
44#[derive(Debug, Clone, Serialize, Deserialize)]
46pub struct TimeoutConfig {
47 #[serde(with = "duration_serde")]
49 pub connect: Duration,
50
51 #[serde(with = "duration_serde")]
53 pub request: Duration,
54
55 #[serde(with = "duration_serde")]
57 pub long_operation: Duration,
58
59 #[serde(with = "duration_serde")]
61 pub initialization: Duration,
62
63 #[serde(with = "duration_serde")]
65 pub heartbeat: Duration,
66}
67
68#[derive(Debug, Clone, Serialize, Deserialize)]
70pub struct RetryConfig {
71 pub max_attempts: u32,
73
74 #[serde(with = "duration_serde")]
76 pub initial_delay: Duration,
77
78 #[serde(with = "duration_serde")]
80 pub max_delay: Duration,
81
82 pub backoff_multiplier: f64,
84
85 pub jitter: f64,
87
88 pub exponential_backoff: bool,
90}
91
92#[derive(Debug, Clone, Serialize, Deserialize)]
94pub struct ConnectionConfig {
95 pub user_agent: Option<String>,
97
98 pub headers: Option<std::collections::HashMap<String, String>>,
100
101 pub follow_redirects: bool,
103
104 pub max_redirects: u32,
106
107 pub keep_alive: bool,
109
110 pub pool_settings: PoolConfig,
112}
113
114#[derive(Debug, Clone, Serialize, Deserialize)]
116pub struct PoolConfig {
117 pub max_idle_per_host: u32,
119
120 #[serde(with = "duration_serde")]
122 pub idle_timeout: Duration,
123
124 #[serde(with = "duration_serde")]
126 pub max_lifetime: Duration,
127}
128
129#[derive(Debug, Clone, Serialize, Deserialize)]
131pub struct LoggingConfig {
132 pub level: String,
134
135 pub log_requests: bool,
137
138 pub log_responses: bool,
140
141 pub log_transport: bool,
143
144 pub redact_sensitive: bool,
146}
147
148impl Default for ClientInfo {
149 fn default() -> Self {
150 Self {
151 name: "mcp-client".to_string(),
152 version: env!("CARGO_PKG_VERSION").to_string(),
153 description: Some("Rust MCP Client Library".to_string()),
154 vendor: Some("MCP Framework".to_string()),
155 metadata: None,
156 }
157 }
158}
159
160impl Default for TimeoutConfig {
161 fn default() -> Self {
162 Self {
163 connect: Duration::from_secs(10),
164 request: Duration::from_secs(30),
165 long_operation: Duration::from_secs(300), initialization: Duration::from_secs(15),
167 heartbeat: Duration::from_secs(30),
168 }
169 }
170}
171
172impl Default for RetryConfig {
173 fn default() -> Self {
174 Self {
175 max_attempts: 3,
176 initial_delay: Duration::from_millis(100),
177 max_delay: Duration::from_secs(10),
178 backoff_multiplier: 2.0,
179 jitter: 0.1,
180 exponential_backoff: true,
181 }
182 }
183}
184
185impl Default for ConnectionConfig {
186 fn default() -> Self {
187 Self {
188 user_agent: Some(format!("mcp-client/{}", env!("CARGO_PKG_VERSION"))),
189 headers: None,
190 follow_redirects: true,
191 max_redirects: 5,
192 keep_alive: true,
193 pool_settings: PoolConfig::default(),
194 }
195 }
196}
197
198impl Default for PoolConfig {
199 fn default() -> Self {
200 Self {
201 max_idle_per_host: 5,
202 idle_timeout: Duration::from_secs(90),
203 max_lifetime: Duration::from_secs(300),
204 }
205 }
206}
207
208impl Default for LoggingConfig {
209 fn default() -> Self {
210 Self {
211 level: "info".to_string(),
212 log_requests: true,
213 log_responses: true,
214 log_transport: false,
215 redact_sensitive: true,
216 }
217 }
218}
219
220impl RetryConfig {
221 pub fn delay_for_attempt(&self, attempt: u32) -> Duration {
223 if attempt == 0 {
224 return Duration::from_millis(0);
225 }
226
227 let mut delay = self.initial_delay;
228
229 if self.exponential_backoff && attempt > 1 {
230 let multiplier = self.backoff_multiplier.powi((attempt - 1) as i32);
232 delay = Duration::from_millis((delay.as_millis() as f64 * multiplier) as u64);
233 }
234
235 if delay > self.max_delay {
237 delay = self.max_delay;
238 }
239
240 if self.jitter > 0.0 {
242 let jitter_ms = (delay.as_millis() as f64 * self.jitter) as u64;
243 let random_offset = rand::random::<f64>() * jitter_ms as f64;
244 delay = Duration::from_millis(delay.as_millis() as u64 + random_offset as u64);
245 }
246
247 if delay > self.max_delay {
249 delay = self.max_delay;
250 }
251
252 delay
253 }
254
255 pub fn should_retry(&self, attempt: u32) -> bool {
257 attempt < self.max_attempts
258 }
259}
260
261mod duration_serde {
263 use serde::{Deserialize, Deserializer, Serializer};
264 use std::time::Duration;
265
266 pub fn serialize<S>(duration: &Duration, serializer: S) -> Result<S::Ok, S::Error>
267 where
268 S: Serializer,
269 {
270 serializer.serialize_u64(duration.as_millis() as u64)
271 }
272
273 pub fn deserialize<'de, D>(deserializer: D) -> Result<Duration, D::Error>
274 where
275 D: Deserializer<'de>,
276 {
277 let millis = u64::deserialize(deserializer)?;
278 Ok(Duration::from_millis(millis))
279 }
280}
281
282#[cfg(test)]
283mod tests {
284 use super::*;
285
286 #[test]
287 fn test_retry_delay_calculation() {
288 let config = RetryConfig::default();
289
290 assert_eq!(config.delay_for_attempt(0), Duration::from_millis(0));
292
293 let delay1 = config.delay_for_attempt(1);
295 assert!(delay1 >= config.initial_delay);
296
297 let delay2 = config.delay_for_attempt(2);
299 assert!(delay2 > delay1);
300
301 let large_delay = config.delay_for_attempt(20);
303 assert!(large_delay <= config.max_delay);
304 }
305
306 #[test]
307 fn test_retry_attempts() {
308 let config = RetryConfig::default();
309
310 assert!(config.should_retry(0));
311 assert!(config.should_retry(1));
312 assert!(config.should_retry(2));
313 assert!(!config.should_retry(3)); }
315
316 #[test]
317 fn test_config_serialization() {
318 let config = ClientConfig::default();
319 let json = serde_json::to_string(&config).unwrap();
320 let _deserialized: ClientConfig = serde_json::from_str(&json).unwrap();
321 }
322}