1use serde::{Deserialize, Serialize};
4use std::collections::HashMap;
5use std::path::PathBuf;
6use std::time::Duration;
7
8#[derive(Debug, Clone, Serialize, Deserialize)]
33pub struct ProtocolVersionConfig {
34 pub preferred: String,
36
37 pub supported: Vec<String>,
40
41 pub allow_fallback: bool,
45}
46
47impl ProtocolVersionConfig {
48 pub fn latest() -> Self {
50 Self {
51 preferred: "2025-11-25".to_string(),
52 supported: turbomcp_protocol::SUPPORTED_VERSIONS
53 .iter()
54 .map(|s| (*s).to_string())
55 .collect(),
56 allow_fallback: true,
57 }
58 }
59
60 pub fn compatible() -> Self {
63 Self {
64 preferred: "2025-06-18".to_string(),
65 supported: vec![
66 "2025-06-18".to_string(),
67 "2025-11-25".to_string(),
68 "2025-03-26".to_string(),
69 "2024-11-05".to_string(),
70 ],
71 allow_fallback: true,
72 }
73 }
74
75 pub fn strict(version: impl Into<String>) -> Self {
77 let version = version.into();
78 Self {
79 preferred: version.clone(),
80 supported: vec![version],
81 allow_fallback: false,
82 }
83 }
84
85 pub fn custom(preferred: impl Into<String>, supported: Vec<impl Into<String>>) -> Self {
87 Self {
88 preferred: preferred.into(),
89 supported: supported.into_iter().map(Into::into).collect(),
90 allow_fallback: true,
91 }
92 }
93}
94
95impl Default for ProtocolVersionConfig {
96 fn default() -> Self {
97 Self::latest()
98 }
99}
100
101#[derive(Debug, Clone, Serialize, Deserialize)]
103pub struct ServerConfig {
104 pub name: String,
106 pub version: String,
108 pub description: Option<String>,
110 pub bind_address: String,
112 pub port: u16,
114 pub enable_tls: bool,
116 pub tls: Option<TlsConfig>,
118 pub protocol_version: ProtocolVersionConfig,
120 pub timeouts: TimeoutConfig,
122 pub rate_limiting: RateLimitingConfig,
124 pub logging: LoggingConfig,
126 pub additional: HashMap<String, serde_json::Value>,
128}
129
130#[derive(Debug, Clone, Serialize, Deserialize)]
132pub struct TlsConfig {
133 pub cert_file: PathBuf,
135 pub key_file: PathBuf,
137}
138
139#[derive(Debug, Clone, Serialize, Deserialize)]
141pub struct TimeoutConfig {
142 pub request_timeout: Duration,
144 pub connection_timeout: Duration,
146 pub keep_alive_timeout: Duration,
148 pub tool_execution_timeout: Duration,
150 pub tool_timeouts: HashMap<String, u64>,
152}
153
154#[derive(Debug, Clone, Serialize, Deserialize)]
156pub struct RateLimitingConfig {
157 pub enabled: bool,
159 pub requests_per_second: u32,
161 pub burst_capacity: u32,
163}
164
165#[derive(Debug, Clone, Serialize, Deserialize)]
167pub struct LoggingConfig {
168 pub level: String,
170 pub structured: bool,
172 pub file: Option<PathBuf>,
174}
175
176impl Default for ServerConfig {
177 fn default() -> Self {
178 Self {
179 name: crate::SERVER_NAME.to_string(),
180 version: crate::SERVER_VERSION.to_string(),
181 description: Some("Next generation MCP server".to_string()),
182 bind_address: "127.0.0.1".to_string(),
183 port: 8080,
184 enable_tls: false,
185 tls: None,
186 protocol_version: ProtocolVersionConfig::default(),
187 timeouts: TimeoutConfig::default(),
188 rate_limiting: RateLimitingConfig::default(),
189 logging: LoggingConfig::default(),
190 additional: HashMap::new(),
191 }
192 }
193}
194
195#[derive(Debug, thiserror::Error)]
197pub enum ConfigError {
198 #[error("Configuration file not found: {0}")]
200 FileNotFound(PathBuf),
201
202 #[error("Unsupported configuration file format. Use .toml, .yaml, .yml, or .json")]
204 UnsupportedFormat,
205
206 #[error("Failed to parse configuration: {0}")]
208 ParseError(#[from] config::ConfigError),
209
210 #[error("IO error: {0}")]
212 IoError(#[from] std::io::Error),
213}
214
215impl ServerConfig {
216 pub fn from_file(path: impl AsRef<std::path::Path>) -> Result<Self, ConfigError> {
241 use config::{Config, File, FileFormat};
242
243 let path = path.as_ref();
244
245 if !path.exists() {
247 return Err(ConfigError::FileNotFound(path.to_path_buf()));
248 }
249
250 let format = match path.extension().and_then(|s| s.to_str()) {
252 Some("toml") => FileFormat::Toml,
253 Some("yaml") | Some("yml") => FileFormat::Yaml,
254 Some("json") => FileFormat::Json,
255 _ => return Err(ConfigError::UnsupportedFormat),
256 };
257
258 let config = Config::builder()
260 .add_source(File::new(
261 path.to_str()
262 .ok_or_else(|| ConfigError::UnsupportedFormat)?,
263 format,
264 ))
265 .add_source(
267 config::Environment::with_prefix("TURBOMCP")
268 .separator("__") .try_parsing(true),
270 )
271 .build()?;
272
273 Ok(config.try_deserialize()?)
274 }
275
276 pub fn from_file_with_prefix(
288 path: impl AsRef<std::path::Path>,
289 env_prefix: &str,
290 ) -> Result<Self, ConfigError> {
291 use config::{Config, File, FileFormat};
292
293 let path = path.as_ref();
294
295 if !path.exists() {
296 return Err(ConfigError::FileNotFound(path.to_path_buf()));
297 }
298
299 let format = match path.extension().and_then(|s| s.to_str()) {
300 Some("toml") => FileFormat::Toml,
301 Some("yaml") | Some("yml") => FileFormat::Yaml,
302 Some("json") => FileFormat::Json,
303 _ => return Err(ConfigError::UnsupportedFormat),
304 };
305
306 let config = Config::builder()
307 .add_source(File::new(
308 path.to_str()
309 .ok_or_else(|| ConfigError::UnsupportedFormat)?,
310 format,
311 ))
312 .add_source(
313 config::Environment::with_prefix(env_prefix)
314 .separator("__")
315 .try_parsing(true),
316 )
317 .build()?;
318
319 Ok(config.try_deserialize()?)
320 }
321
322 pub fn builder() -> ConfigurationBuilder {
337 ConfigurationBuilder::new()
338 }
339}
340
341impl Default for TimeoutConfig {
342 fn default() -> Self {
343 Self {
344 request_timeout: Duration::from_secs(30),
345 connection_timeout: Duration::from_secs(10),
346 keep_alive_timeout: Duration::from_secs(60),
347 tool_execution_timeout: Duration::from_secs(120), tool_timeouts: HashMap::new(), }
350 }
351}
352
353impl Default for RateLimitingConfig {
354 fn default() -> Self {
355 Self {
356 enabled: true,
357 requests_per_second: 100,
358 burst_capacity: 200,
359 }
360 }
361}
362
363impl Default for LoggingConfig {
364 fn default() -> Self {
365 Self {
366 level: "info".to_string(),
367 structured: true,
368 file: None,
369 }
370 }
371}
372
373#[derive(Debug)]
375pub struct ConfigurationBuilder {
376 config: ServerConfig,
378}
379
380impl ConfigurationBuilder {
381 #[must_use]
383 pub fn new() -> Self {
384 Self {
385 config: ServerConfig::default(),
386 }
387 }
388
389 pub fn name(mut self, name: impl Into<String>) -> Self {
391 self.config.name = name.into();
392 self
393 }
394
395 pub fn version(mut self, version: impl Into<String>) -> Self {
397 self.config.version = version.into();
398 self
399 }
400
401 pub fn description(mut self, description: impl Into<String>) -> Self {
403 self.config.description = Some(description.into());
404 self
405 }
406
407 pub fn bind_address(mut self, address: impl Into<String>) -> Self {
409 self.config.bind_address = address.into();
410 self
411 }
412
413 #[must_use]
415 pub const fn port(mut self, port: u16) -> Self {
416 self.config.port = port;
417 self
418 }
419
420 #[must_use]
422 pub fn tls(mut self, cert_file: PathBuf, key_file: PathBuf) -> Self {
423 self.config.enable_tls = true;
424 self.config.tls = Some(TlsConfig {
425 cert_file,
426 key_file,
427 });
428 self
429 }
430
431 #[must_use]
433 pub const fn request_timeout(mut self, timeout: Duration) -> Self {
434 self.config.timeouts.request_timeout = timeout;
435 self
436 }
437
438 #[must_use]
440 pub const fn rate_limiting(mut self, requests_per_second: u32, burst_capacity: u32) -> Self {
441 self.config.rate_limiting.enabled = true;
442 self.config.rate_limiting.requests_per_second = requests_per_second;
443 self.config.rate_limiting.burst_capacity = burst_capacity;
444 self
445 }
446
447 pub fn log_level(mut self, level: impl Into<String>) -> Self {
449 self.config.logging.level = level.into();
450 self
451 }
452
453 pub fn protocol_version(mut self, version: impl Into<String>) -> Self {
469 self.config.protocol_version.preferred = version.into();
470 self
471 }
472
473 pub fn supported_protocol_versions(mut self, versions: Vec<impl Into<String>>) -> Self {
489 self.config.protocol_version.supported = versions.into_iter().map(Into::into).collect();
490 self
491 }
492
493 #[must_use]
510 pub const fn allow_protocol_fallback(mut self, allow: bool) -> Self {
511 self.config.protocol_version.allow_fallback = allow;
512 self
513 }
514
515 #[must_use]
533 pub fn protocol_version_config(mut self, config: ProtocolVersionConfig) -> Self {
534 self.config.protocol_version = config;
535 self
536 }
537
538 #[must_use]
540 pub fn build(self) -> ServerConfig {
541 self.config
542 }
543}
544
545impl Default for ConfigurationBuilder {
546 fn default() -> Self {
547 Self::new()
548 }
549}
550
551pub type Configuration = ServerConfig;
553#[cfg(test)]
554mod inline_tests {
555 use super::*;
556
557 #[test]
558 fn test_default_config() {
559 let config = ServerConfig::default();
560 assert_eq!(config.name, crate::SERVER_NAME);
561 assert_eq!(config.version, crate::SERVER_VERSION);
562 assert_eq!(config.bind_address, "127.0.0.1");
563 assert_eq!(config.port, 8080);
564 assert!(!config.enable_tls);
565 }
566
567 #[test]
568 fn test_config_builder() {
569 let config = ConfigurationBuilder::new()
570 .name("test-server")
571 .port(9000)
572 .build();
573
574 assert_eq!(config.name, "test-server");
575 assert_eq!(config.port, 9000);
576 }
577
578 mod proptest_tests {
580 use super::*;
581 use proptest::prelude::*;
582
583 proptest! {
584 #[test]
586 fn test_config_port_roundtrip(port in 1024u16..65535u16) {
587 let config = ConfigurationBuilder::new()
588 .port(port)
589 .build();
590
591 prop_assert_eq!(config.port, port);
592 }
593
594 #[test]
596 fn test_config_name_preservation(name in "[a-zA-Z0-9_-]{1,50}") {
597 let config = ConfigurationBuilder::new()
598 .name(&name)
599 .build();
600
601 prop_assert_eq!(config.name, name);
602 }
603
604 #[test]
606 fn test_rate_limiting_config(
607 rps in 1u32..10000u32,
608 burst in 1u32..1000u32
609 ) {
610 let config = RateLimitingConfig {
611 enabled: true,
612 requests_per_second: rps,
613 burst_capacity: burst,
614 };
615
616 prop_assert!(config.requests_per_second >= 1);
618 prop_assert!(config.burst_capacity >= 1);
619 }
620 }
621 }
622}
623
624#[derive(Debug, Clone, Serialize, Deserialize)]
628#[cfg(feature = "websocket")]
629pub struct WebSocketServerConfig {
630 pub bind_addr: String,
632 pub endpoint_path: String,
634 pub max_concurrent_requests: usize,
636}
637
638#[cfg(feature = "websocket")]
639impl Default for WebSocketServerConfig {
640 fn default() -> Self {
641 Self {
642 bind_addr: "127.0.0.1:8080".to_string(),
643 endpoint_path: "/ws".to_string(),
644 max_concurrent_requests: 100,
645 }
646 }
647}
648
649#[cfg(feature = "multi-tenancy")]
651pub mod multi_tenant;
652#[cfg(feature = "multi-tenancy")]
653pub use multi_tenant::{
654 NoOpTenantConfigProvider, StaticTenantConfigProvider, TenantConfig, TenantConfigProvider,
655};
656
657#[cfg(test)]
659mod tests;