things3_core/
mcp_config.rs

1//! MCP Server Configuration Management
2//!
3//! This module provides comprehensive configuration management for the MCP server,
4//! including support for environment variables, configuration files, and validation.
5
6use crate::error::{Result, ThingsError};
7use serde::{Deserialize, Serialize};
8use std::collections::HashMap;
9use std::path::{Path, PathBuf};
10
11/// Comprehensive configuration for the MCP server
12#[derive(Debug, Clone, Serialize, Deserialize)]
13pub struct McpServerConfig {
14    /// Server configuration
15    pub server: ServerConfig,
16    /// Database configuration
17    pub database: DatabaseConfig,
18    /// Logging configuration
19    pub logging: LoggingConfig,
20    /// Performance configuration
21    pub performance: PerformanceConfig,
22    /// Security configuration
23    pub security: SecurityConfig,
24    /// Cache configuration
25    pub cache: CacheConfig,
26    /// Monitoring configuration
27    pub monitoring: MonitoringConfig,
28    /// Feature flags
29    pub features: FeatureFlags,
30}
31
32/// Server-specific configuration
33#[derive(Debug, Clone, Serialize, Deserialize)]
34pub struct ServerConfig {
35    /// Server name
36    pub name: String,
37    /// Server version
38    pub version: String,
39    /// Server description
40    pub description: String,
41    /// Maximum concurrent connections
42    pub max_connections: u32,
43    /// Connection timeout in seconds
44    pub connection_timeout: u64,
45    /// Request timeout in seconds
46    pub request_timeout: u64,
47    /// Enable graceful shutdown
48    pub graceful_shutdown: bool,
49    /// Shutdown timeout in seconds
50    pub shutdown_timeout: u64,
51}
52
53/// Database configuration
54#[derive(Debug, Clone, Serialize, Deserialize)]
55pub struct DatabaseConfig {
56    /// Database path
57    pub path: PathBuf,
58    /// Fallback to default path if specified path doesn't exist
59    pub fallback_to_default: bool,
60    /// Connection pool size
61    pub pool_size: u32,
62    /// Connection timeout in seconds
63    pub connection_timeout: u64,
64    /// Query timeout in seconds
65    pub query_timeout: u64,
66    /// Enable query logging
67    pub enable_query_logging: bool,
68    /// Enable query metrics
69    pub enable_query_metrics: bool,
70}
71
72/// Logging configuration
73#[derive(Debug, Clone, Serialize, Deserialize)]
74pub struct LoggingConfig {
75    /// Log level (trace, debug, info, warn, error)
76    pub level: String,
77    /// Enable JSON logging
78    pub json_logs: bool,
79    /// Log file path (optional)
80    pub log_file: Option<PathBuf>,
81    /// Enable console logging
82    pub console_logs: bool,
83    /// Enable structured logging
84    pub structured_logs: bool,
85    /// Log rotation configuration
86    pub rotation: LogRotationConfig,
87}
88
89/// Log rotation configuration
90#[derive(Debug, Clone, Serialize, Deserialize)]
91pub struct LogRotationConfig {
92    /// Enable log rotation
93    pub enabled: bool,
94    /// Maximum file size in MB
95    pub max_file_size_mb: u64,
96    /// Maximum number of files to keep
97    pub max_files: u32,
98    /// Compression enabled
99    pub compress: bool,
100}
101
102/// Performance configuration
103#[derive(Debug, Clone, Serialize, Deserialize)]
104pub struct PerformanceConfig {
105    /// Enable performance monitoring
106    pub enabled: bool,
107    /// Slow request threshold in milliseconds
108    pub slow_request_threshold_ms: u64,
109    /// Enable request profiling
110    pub enable_profiling: bool,
111    /// Memory usage monitoring
112    pub memory_monitoring: MemoryMonitoringConfig,
113    /// CPU usage monitoring
114    pub cpu_monitoring: CpuMonitoringConfig,
115}
116
117/// Memory monitoring configuration
118#[derive(Debug, Clone, Serialize, Deserialize)]
119pub struct MemoryMonitoringConfig {
120    /// Enable memory monitoring
121    pub enabled: bool,
122    /// Memory usage threshold percentage
123    pub threshold_percentage: f64,
124    /// Check interval in seconds
125    pub check_interval: u64,
126}
127
128/// CPU monitoring configuration
129#[derive(Debug, Clone, Serialize, Deserialize)]
130pub struct CpuMonitoringConfig {
131    /// Enable CPU monitoring
132    pub enabled: bool,
133    /// CPU usage threshold percentage
134    pub threshold_percentage: f64,
135    /// Check interval in seconds
136    pub check_interval: u64,
137}
138
139/// Security configuration
140#[derive(Debug, Clone, Serialize, Deserialize)]
141pub struct SecurityConfig {
142    /// Authentication configuration
143    pub authentication: AuthenticationConfig,
144    /// Rate limiting configuration
145    pub rate_limiting: RateLimitingConfig,
146    /// CORS configuration
147    pub cors: CorsConfig,
148    /// Input validation configuration
149    pub validation: ValidationConfig,
150}
151
152/// Authentication configuration
153#[derive(Debug, Clone, Serialize, Deserialize)]
154pub struct AuthenticationConfig {
155    /// Enable authentication
156    pub enabled: bool,
157    /// Require authentication for all requests
158    pub require_auth: bool,
159    /// JWT secret key
160    pub jwt_secret: String,
161    /// JWT expiration time in seconds
162    pub jwt_expiration: u64,
163    /// API keys configuration
164    pub api_keys: Vec<ApiKeyConfig>,
165    /// OAuth 2.0 configuration
166    pub oauth: Option<OAuth2Config>,
167}
168
169/// API key configuration
170#[derive(Debug, Clone, Serialize, Deserialize)]
171pub struct ApiKeyConfig {
172    /// API key value
173    pub key: String,
174    /// Key identifier
175    pub key_id: String,
176    /// Permissions for this key
177    pub permissions: Vec<String>,
178    /// Optional expiration date
179    pub expires_at: Option<String>,
180}
181
182/// OAuth 2.0 configuration
183#[derive(Debug, Clone, Serialize, Deserialize)]
184pub struct OAuth2Config {
185    /// OAuth client ID
186    pub client_id: String,
187    /// OAuth client secret
188    pub client_secret: String,
189    /// Token endpoint URL
190    pub token_endpoint: String,
191    /// Required scopes
192    pub scopes: Vec<String>,
193}
194
195/// Rate limiting configuration
196#[derive(Debug, Clone, Serialize, Deserialize)]
197pub struct RateLimitingConfig {
198    /// Enable rate limiting
199    pub enabled: bool,
200    /// Requests per minute limit
201    pub requests_per_minute: u32,
202    /// Burst limit for short bursts
203    pub burst_limit: u32,
204    /// Custom limits per client type
205    pub custom_limits: Option<HashMap<String, u32>>,
206}
207
208/// CORS configuration
209#[derive(Debug, Clone, Serialize, Deserialize)]
210pub struct CorsConfig {
211    /// Enable CORS
212    pub enabled: bool,
213    /// Allowed origins
214    pub allowed_origins: Vec<String>,
215    /// Allowed methods
216    pub allowed_methods: Vec<String>,
217    /// Allowed headers
218    pub allowed_headers: Vec<String>,
219    /// Exposed headers
220    pub exposed_headers: Vec<String>,
221    /// Allow credentials
222    pub allow_credentials: bool,
223    /// Max age in seconds
224    pub max_age: u64,
225}
226
227/// Input validation configuration
228#[derive(Debug, Clone, Serialize, Deserialize)]
229pub struct ValidationConfig {
230    /// Enable input validation
231    pub enabled: bool,
232    /// Use strict validation mode
233    pub strict_mode: bool,
234    /// Maximum request size in bytes
235    pub max_request_size: u64,
236    /// Maximum field length
237    pub max_field_length: usize,
238}
239
240/// Cache configuration
241#[derive(Debug, Clone, Serialize, Deserialize)]
242pub struct CacheConfig {
243    /// Enable caching
244    pub enabled: bool,
245    /// Cache type (memory, disk, hybrid)
246    pub cache_type: String,
247    /// Maximum cache size in MB
248    pub max_size_mb: u64,
249    /// Cache TTL in seconds
250    pub ttl_seconds: u64,
251    /// Enable cache compression
252    pub compression: bool,
253    /// Cache eviction policy
254    pub eviction_policy: String,
255}
256
257/// Monitoring configuration
258#[derive(Debug, Clone, Serialize, Deserialize)]
259pub struct MonitoringConfig {
260    /// Enable monitoring
261    pub enabled: bool,
262    /// Metrics port
263    pub metrics_port: u16,
264    /// Health check port
265    pub health_port: u16,
266    /// Enable health checks
267    pub health_checks: bool,
268    /// Enable metrics collection
269    pub metrics_collection: bool,
270    /// Metrics endpoint path
271    pub metrics_path: String,
272    /// Health endpoint path
273    pub health_path: String,
274}
275
276/// Feature flags
277#[derive(Debug, Clone, Serialize, Deserialize)]
278#[allow(clippy::struct_excessive_bools)]
279pub struct FeatureFlags {
280    /// Enable real-time updates
281    pub real_time_updates: bool,
282    /// Enable WebSocket server
283    pub websocket_server: bool,
284    /// Enable dashboard
285    pub dashboard: bool,
286    /// Enable bulk operations
287    pub bulk_operations: bool,
288    /// Enable data export
289    pub data_export: bool,
290    /// Enable backup functionality
291    pub backup: bool,
292    /// Enable hot reloading
293    pub hot_reloading: bool,
294}
295
296impl McpServerConfig {
297    /// Create a new MCP server configuration with default values
298    #[must_use]
299    pub fn new() -> Self {
300        Self::default()
301    }
302
303    /// Create configuration from environment variables
304    ///
305    /// # Errors
306    /// Returns an error if environment variables contain invalid values
307    #[allow(clippy::too_many_lines)]
308    pub fn from_env() -> Result<Self> {
309        let mut config = Self::default();
310
311        // Server configuration
312        if let Ok(name) = std::env::var("MCP_SERVER_NAME") {
313            config.server.name = name;
314        }
315        if let Ok(version) = std::env::var("MCP_SERVER_VERSION") {
316            config.server.version = version;
317        }
318        if let Ok(description) = std::env::var("MCP_SERVER_DESCRIPTION") {
319            config.server.description = description;
320        }
321        if let Ok(max_connections) = std::env::var("MCP_MAX_CONNECTIONS") {
322            config.server.max_connections = max_connections
323                .parse()
324                .map_err(|_| ThingsError::configuration("Invalid MCP_MAX_CONNECTIONS value"))?;
325        }
326        if let Ok(connection_timeout) = std::env::var("MCP_CONNECTION_TIMEOUT") {
327            config.server.connection_timeout = connection_timeout
328                .parse()
329                .map_err(|_| ThingsError::configuration("Invalid MCP_CONNECTION_TIMEOUT value"))?;
330        }
331        if let Ok(request_timeout) = std::env::var("MCP_REQUEST_TIMEOUT") {
332            config.server.request_timeout = request_timeout
333                .parse()
334                .map_err(|_| ThingsError::configuration("Invalid MCP_REQUEST_TIMEOUT value"))?;
335        }
336
337        // Database configuration
338        if let Ok(db_path) = std::env::var("MCP_DATABASE_PATH") {
339            config.database.path = PathBuf::from(db_path);
340        }
341        if let Ok(fallback) = std::env::var("MCP_DATABASE_FALLBACK") {
342            config.database.fallback_to_default = parse_bool(&fallback);
343        }
344        if let Ok(pool_size) = std::env::var("MCP_DATABASE_POOL_SIZE") {
345            config.database.pool_size = pool_size
346                .parse()
347                .map_err(|_| ThingsError::configuration("Invalid MCP_DATABASE_POOL_SIZE value"))?;
348        }
349
350        // Logging configuration
351        if let Ok(level) = std::env::var("MCP_LOG_LEVEL") {
352            config.logging.level = level;
353        }
354        if let Ok(json_logs) = std::env::var("MCP_JSON_LOGS") {
355            config.logging.json_logs = parse_bool(&json_logs);
356        }
357        if let Ok(log_file) = std::env::var("MCP_LOG_FILE") {
358            config.logging.log_file = Some(PathBuf::from(log_file));
359        }
360        if let Ok(console_logs) = std::env::var("MCP_CONSOLE_LOGS") {
361            config.logging.console_logs = parse_bool(&console_logs);
362        }
363
364        // Performance configuration
365        if let Ok(enabled) = std::env::var("MCP_PERFORMANCE_ENABLED") {
366            config.performance.enabled = parse_bool(&enabled);
367        }
368        if let Ok(threshold) = std::env::var("MCP_SLOW_REQUEST_THRESHOLD") {
369            config.performance.slow_request_threshold_ms = threshold.parse().map_err(|_| {
370                ThingsError::configuration("Invalid MCP_SLOW_REQUEST_THRESHOLD value")
371            })?;
372        }
373
374        // Security configuration
375        if let Ok(auth_enabled) = std::env::var("MCP_AUTH_ENABLED") {
376            config.security.authentication.enabled = parse_bool(&auth_enabled);
377        }
378        if let Ok(jwt_secret) = std::env::var("MCP_JWT_SECRET") {
379            config.security.authentication.jwt_secret = jwt_secret;
380        }
381        if let Ok(rate_limit_enabled) = std::env::var("MCP_RATE_LIMIT_ENABLED") {
382            config.security.rate_limiting.enabled = parse_bool(&rate_limit_enabled);
383        }
384        if let Ok(requests_per_minute) = std::env::var("MCP_REQUESTS_PER_MINUTE") {
385            config.security.rate_limiting.requests_per_minute = requests_per_minute
386                .parse()
387                .map_err(|_| ThingsError::configuration("Invalid MCP_REQUESTS_PER_MINUTE value"))?;
388        }
389
390        // Cache configuration
391        if let Ok(cache_enabled) = std::env::var("MCP_CACHE_ENABLED") {
392            config.cache.enabled = parse_bool(&cache_enabled);
393        }
394        if let Ok(cache_type) = std::env::var("MCP_CACHE_TYPE") {
395            config.cache.cache_type = cache_type;
396        }
397        if let Ok(max_size) = std::env::var("MCP_CACHE_MAX_SIZE_MB") {
398            config.cache.max_size_mb = max_size
399                .parse()
400                .map_err(|_| ThingsError::configuration("Invalid MCP_CACHE_MAX_SIZE_MB value"))?;
401        }
402
403        // Monitoring configuration
404        if let Ok(monitoring_enabled) = std::env::var("MCP_MONITORING_ENABLED") {
405            config.monitoring.enabled = parse_bool(&monitoring_enabled);
406        }
407        if let Ok(metrics_port) = std::env::var("MCP_METRICS_PORT") {
408            config.monitoring.metrics_port = metrics_port
409                .parse()
410                .map_err(|_| ThingsError::configuration("Invalid MCP_METRICS_PORT value"))?;
411        }
412        if let Ok(health_port) = std::env::var("MCP_HEALTH_PORT") {
413            config.monitoring.health_port = health_port
414                .parse()
415                .map_err(|_| ThingsError::configuration("Invalid MCP_HEALTH_PORT value"))?;
416        }
417
418        // Feature flags
419        if let Ok(real_time) = std::env::var("MCP_REAL_TIME_UPDATES") {
420            config.features.real_time_updates = parse_bool(&real_time);
421        }
422        if let Ok(websocket) = std::env::var("MCP_WEBSOCKET_SERVER") {
423            config.features.websocket_server = parse_bool(&websocket);
424        }
425        if let Ok(dashboard) = std::env::var("MCP_DASHBOARD") {
426            config.features.dashboard = parse_bool(&dashboard);
427        }
428        if let Ok(bulk_ops) = std::env::var("MCP_BULK_OPERATIONS") {
429            config.features.bulk_operations = parse_bool(&bulk_ops);
430        }
431        if let Ok(data_export) = std::env::var("MCP_DATA_EXPORT") {
432            config.features.data_export = parse_bool(&data_export);
433        }
434        if let Ok(backup) = std::env::var("MCP_BACKUP") {
435            config.features.backup = parse_bool(&backup);
436        }
437        if let Ok(hot_reload) = std::env::var("MCP_HOT_RELOADING") {
438            config.features.hot_reloading = parse_bool(&hot_reload);
439        }
440
441        Ok(config)
442    }
443
444    /// Load configuration from a file
445    ///
446    /// # Arguments
447    /// * `path` - Path to the configuration file
448    ///
449    /// # Errors
450    /// Returns an error if the file cannot be read or parsed
451    pub fn from_file<P: AsRef<Path>>(path: P) -> Result<Self> {
452        let path = path.as_ref();
453        let content = std::fs::read_to_string(path).map_err(|e| {
454            ThingsError::Io(std::io::Error::other(format!(
455                "Failed to read config file {}: {}",
456                path.display(),
457                e
458            )))
459        })?;
460
461        let config = if path.extension().and_then(|s| s.to_str()) == Some("yaml")
462            || path.extension().and_then(|s| s.to_str()) == Some("yml")
463        {
464            serde_yaml::from_str(&content).map_err(|e| {
465                ThingsError::configuration(format!("Failed to parse YAML config: {e}"))
466            })?
467        } else {
468            serde_json::from_str(&content).map_err(|e| {
469                ThingsError::configuration(format!("Failed to parse JSON config: {e}"))
470            })?
471        };
472
473        Ok(config)
474    }
475
476    /// Save configuration to a file
477    ///
478    /// # Arguments
479    /// * `path` - Path to save the configuration file
480    /// * `format` - Format to save as ("json" or "yaml")
481    ///
482    /// # Errors
483    /// Returns an error if the file cannot be written
484    pub fn to_file<P: AsRef<Path>>(&self, path: P, format: &str) -> Result<()> {
485        let path = path.as_ref();
486        let content = match format {
487            "yaml" | "yml" => serde_yaml::to_string(self).map_err(|e| {
488                ThingsError::configuration(format!("Failed to serialize YAML: {e}"))
489            })?,
490            "json" => serde_json::to_string_pretty(self).map_err(|e| {
491                ThingsError::configuration(format!("Failed to serialize JSON: {e}"))
492            })?,
493            _ => {
494                return Err(ThingsError::configuration(format!(
495                    "Unsupported format: {format}"
496                )))
497            }
498        };
499
500        std::fs::write(path, content).map_err(|e| {
501            ThingsError::Io(std::io::Error::other(format!(
502                "Failed to write config file {}: {}",
503                path.display(),
504                e
505            )))
506        })?;
507
508        Ok(())
509    }
510
511    /// Validate the configuration
512    ///
513    /// # Errors
514    /// Returns an error if the configuration is invalid
515    pub fn validate(&self) -> Result<()> {
516        // Validate server configuration
517        if self.server.name.is_empty() {
518            return Err(ThingsError::configuration("Server name cannot be empty"));
519        }
520        if self.server.version.is_empty() {
521            return Err(ThingsError::configuration("Server version cannot be empty"));
522        }
523        if self.server.max_connections == 0 {
524            return Err(ThingsError::configuration(
525                "Max connections must be greater than 0",
526            ));
527        }
528
529        // Validate database configuration
530        if self.database.pool_size == 0 {
531            return Err(ThingsError::configuration(
532                "Database pool size must be greater than 0",
533            ));
534        }
535
536        // Validate logging configuration
537        let valid_levels = ["trace", "debug", "info", "warn", "error"];
538        if !valid_levels.contains(&self.logging.level.as_str()) {
539            return Err(ThingsError::configuration(format!(
540                "Invalid log level: {}. Must be one of: {}",
541                self.logging.level,
542                valid_levels.join(", ")
543            )));
544        }
545
546        // Validate performance configuration
547        if self.performance.enabled && self.performance.slow_request_threshold_ms == 0 {
548            return Err(ThingsError::configuration("Slow request threshold must be greater than 0 when performance monitoring is enabled"));
549        }
550
551        // Validate security configuration
552        if self.security.authentication.enabled
553            && self.security.authentication.jwt_secret.is_empty()
554        {
555            return Err(ThingsError::configuration(
556                "JWT secret cannot be empty when authentication is enabled",
557            ));
558        }
559
560        // Validate cache configuration
561        if self.cache.enabled && self.cache.max_size_mb == 0 {
562            return Err(ThingsError::configuration(
563                "Cache max size must be greater than 0 when caching is enabled",
564            ));
565        }
566
567        // Validate monitoring configuration
568        if self.monitoring.enabled && self.monitoring.metrics_port == 0 {
569            return Err(ThingsError::configuration(
570                "Metrics port must be greater than 0 when monitoring is enabled",
571            ));
572        }
573        if self.monitoring.enabled && self.monitoring.health_port == 0 {
574            return Err(ThingsError::configuration(
575                "Health port must be greater than 0 when monitoring is enabled",
576            ));
577        }
578
579        Ok(())
580    }
581
582    /// Merge with another configuration, with the other config taking precedence
583    pub fn merge_with(&mut self, other: &McpServerConfig) {
584        // Merge server config
585        if !other.server.name.is_empty() {
586            self.server.name.clone_from(&other.server.name);
587        }
588        if !other.server.version.is_empty() {
589            self.server.version.clone_from(&other.server.version);
590        }
591        if !other.server.description.is_empty() {
592            self.server
593                .description
594                .clone_from(&other.server.description);
595        }
596        if other.server.max_connections > 0 {
597            self.server.max_connections = other.server.max_connections;
598        }
599        if other.server.connection_timeout > 0 {
600            self.server.connection_timeout = other.server.connection_timeout;
601        }
602        if other.server.request_timeout > 0 {
603            self.server.request_timeout = other.server.request_timeout;
604        }
605
606        // Merge database config
607        if other.database.path != PathBuf::new() {
608            self.database.path.clone_from(&other.database.path);
609        }
610        if other.database.pool_size > 0 {
611            self.database.pool_size = other.database.pool_size;
612        }
613
614        // Merge logging config
615        if !other.logging.level.is_empty() {
616            self.logging.level.clone_from(&other.logging.level);
617        }
618        if other.logging.log_file.is_some() {
619            self.logging.log_file.clone_from(&other.logging.log_file);
620        }
621
622        // Merge performance config
623        self.performance.enabled = other.performance.enabled;
624        if other.performance.slow_request_threshold_ms > 0 {
625            self.performance.slow_request_threshold_ms =
626                other.performance.slow_request_threshold_ms;
627        }
628
629        // Merge security config
630        self.security.authentication.enabled = other.security.authentication.enabled;
631        if !other.security.authentication.jwt_secret.is_empty() {
632            self.security
633                .authentication
634                .jwt_secret
635                .clone_from(&other.security.authentication.jwt_secret);
636        }
637        self.security.rate_limiting.enabled = other.security.rate_limiting.enabled;
638        if other.security.rate_limiting.requests_per_minute > 0 {
639            self.security.rate_limiting.requests_per_minute =
640                other.security.rate_limiting.requests_per_minute;
641        }
642
643        // Merge cache config
644        self.cache.enabled = other.cache.enabled;
645        if other.cache.max_size_mb > 0 {
646            self.cache.max_size_mb = other.cache.max_size_mb;
647        }
648
649        // Merge monitoring config
650        self.monitoring.enabled = other.monitoring.enabled;
651        if other.monitoring.metrics_port > 0 {
652            self.monitoring.metrics_port = other.monitoring.metrics_port;
653        }
654        if other.monitoring.health_port > 0 {
655            self.monitoring.health_port = other.monitoring.health_port;
656        }
657
658        // Merge feature flags
659        self.features.real_time_updates = other.features.real_time_updates;
660        self.features.websocket_server = other.features.websocket_server;
661        self.features.dashboard = other.features.dashboard;
662        self.features.bulk_operations = other.features.bulk_operations;
663        self.features.data_export = other.features.data_export;
664        self.features.backup = other.features.backup;
665        self.features.hot_reloading = other.features.hot_reloading;
666    }
667
668    /// Get the effective database path, falling back to default if needed
669    ///
670    /// # Errors
671    /// Returns an error if neither the specified path nor the default path exists
672    pub fn get_effective_database_path(&self) -> Result<PathBuf> {
673        // Check if the specified path exists
674        if self.database.path.exists() {
675            return Ok(self.database.path.clone());
676        }
677
678        // If fallback is enabled, try the default path
679        if self.database.fallback_to_default {
680            let default_path = Self::get_default_database_path();
681            if default_path.exists() {
682                return Ok(default_path);
683            }
684        }
685
686        Err(ThingsError::configuration(format!(
687            "Database not found at {} and fallback is {}",
688            self.database.path.display(),
689            if self.database.fallback_to_default {
690                "enabled but default path also not found"
691            } else {
692                "disabled"
693            }
694        )))
695    }
696
697    /// Get the default Things 3 database path
698    #[must_use]
699    pub fn get_default_database_path() -> PathBuf {
700        let home = std::env::var("HOME").unwrap_or_else(|_| "~".to_string());
701        PathBuf::from(format!(
702            "{home}/Library/Group Containers/JLMPQHK86H.com.culturedcode.ThingsMac/ThingsData-0Z0Z2/Things Database.thingsdatabase/main.sqlite"
703        ))
704    }
705}
706
707impl Default for McpServerConfig {
708    #[allow(clippy::too_many_lines)]
709    fn default() -> Self {
710        Self {
711            server: ServerConfig {
712                name: "things3-mcp-server".to_string(),
713                version: env!("CARGO_PKG_VERSION").to_string(),
714                description: "Things 3 MCP Server".to_string(),
715                max_connections: 100,
716                connection_timeout: 30,
717                request_timeout: 60,
718                graceful_shutdown: true,
719                shutdown_timeout: 30,
720            },
721            database: DatabaseConfig {
722                path: Self::get_default_database_path(),
723                fallback_to_default: true,
724                pool_size: 10,
725                connection_timeout: 30,
726                query_timeout: 60,
727                enable_query_logging: false,
728                enable_query_metrics: true,
729            },
730            logging: LoggingConfig {
731                level: "info".to_string(),
732                json_logs: false,
733                log_file: None,
734                console_logs: true,
735                structured_logs: true,
736                rotation: LogRotationConfig {
737                    enabled: true,
738                    max_file_size_mb: 100,
739                    max_files: 5,
740                    compress: true,
741                },
742            },
743            performance: PerformanceConfig {
744                enabled: true,
745                slow_request_threshold_ms: 1000,
746                enable_profiling: false,
747                memory_monitoring: MemoryMonitoringConfig {
748                    enabled: true,
749                    threshold_percentage: 80.0,
750                    check_interval: 60,
751                },
752                cpu_monitoring: CpuMonitoringConfig {
753                    enabled: true,
754                    threshold_percentage: 80.0,
755                    check_interval: 60,
756                },
757            },
758            security: SecurityConfig {
759                authentication: AuthenticationConfig {
760                    enabled: false,
761                    require_auth: false,
762                    jwt_secret: "your-secret-key-change-this-in-production".to_string(),
763                    jwt_expiration: 3600,
764                    api_keys: vec![],
765                    oauth: None,
766                },
767                rate_limiting: RateLimitingConfig {
768                    enabled: true,
769                    requests_per_minute: 60,
770                    burst_limit: 10,
771                    custom_limits: None,
772                },
773                cors: CorsConfig {
774                    enabled: true,
775                    allowed_origins: vec!["*".to_string()],
776                    allowed_methods: vec![
777                        "GET".to_string(),
778                        "POST".to_string(),
779                        "PUT".to_string(),
780                        "DELETE".to_string(),
781                    ],
782                    allowed_headers: vec!["*".to_string()],
783                    exposed_headers: vec![],
784                    allow_credentials: false,
785                    max_age: 86400,
786                },
787                validation: ValidationConfig {
788                    enabled: true,
789                    strict_mode: false,
790                    max_request_size: 1024 * 1024, // 1MB
791                    max_field_length: 1000,
792                },
793            },
794            cache: CacheConfig {
795                enabled: true,
796                cache_type: "memory".to_string(),
797                max_size_mb: 100,
798                ttl_seconds: 3600,
799                compression: true,
800                eviction_policy: "lru".to_string(),
801            },
802            monitoring: MonitoringConfig {
803                enabled: true,
804                metrics_port: 9090,
805                health_port: 8080,
806                health_checks: true,
807                metrics_collection: true,
808                metrics_path: "/metrics".to_string(),
809                health_path: "/health".to_string(),
810            },
811            features: FeatureFlags {
812                real_time_updates: true,
813                websocket_server: true,
814                dashboard: true,
815                bulk_operations: true,
816                data_export: true,
817                backup: true,
818                hot_reloading: false,
819            },
820        }
821    }
822}
823
824/// Parse a boolean value from a string
825fn parse_bool(value: &str) -> bool {
826    let lower = value.to_lowercase();
827    matches!(lower.as_str(), "true" | "1" | "yes" | "on")
828}
829
830#[cfg(test)]
831mod tests {
832    use super::*;
833    use std::sync::Mutex;
834    use tempfile::NamedTempFile;
835
836    // Global mutex to synchronize environment variable access across tests
837    static ENV_MUTEX: Mutex<()> = Mutex::new(());
838
839    #[test]
840    fn test_default_config() {
841        let config = McpServerConfig::default();
842        assert_eq!(config.server.name, "things3-mcp-server");
843        assert!(config.database.fallback_to_default);
844        assert_eq!(config.logging.level, "info");
845        assert!(config.performance.enabled);
846        assert!(!config.security.authentication.enabled);
847        assert!(config.cache.enabled);
848        assert!(config.monitoring.enabled);
849    }
850
851    #[test]
852    fn test_config_validation() {
853        let config = McpServerConfig::default();
854        assert!(config.validate().is_ok());
855    }
856
857    #[test]
858    fn test_config_validation_invalid_server_name() {
859        let mut config = McpServerConfig::default();
860        config.server.name = String::new();
861        assert!(config.validate().is_err());
862    }
863
864    #[test]
865    fn test_config_validation_invalid_log_level() {
866        let mut config = McpServerConfig::default();
867        config.logging.level = "invalid".to_string();
868        assert!(config.validate().is_err());
869    }
870
871    #[test]
872    fn test_config_from_env() {
873        let _lock = ENV_MUTEX.lock().unwrap();
874
875        // Clean up any existing environment variables first
876        cleanup_env_vars();
877
878        std::env::set_var("MCP_SERVER_NAME", "test-server");
879        std::env::set_var("MCP_LOG_LEVEL", "debug");
880        std::env::set_var("MCP_CACHE_ENABLED", "false");
881
882        let config = McpServerConfig::from_env().unwrap();
883        assert_eq!(config.server.name, "test-server");
884        assert_eq!(config.logging.level, "debug");
885        assert!(!config.cache.enabled);
886
887        // Clean up
888        cleanup_env_vars();
889    }
890
891    #[test]
892    fn test_config_to_and_from_file_json() {
893        let config = McpServerConfig::default();
894        let temp_file = NamedTempFile::new().unwrap();
895        let path = temp_file.path().with_extension("json");
896
897        config.to_file(&path, "json").unwrap();
898        let loaded_config = McpServerConfig::from_file(&path).unwrap();
899
900        assert_eq!(config.server.name, loaded_config.server.name);
901        assert_eq!(config.logging.level, loaded_config.logging.level);
902    }
903
904    #[test]
905    fn test_config_merge() {
906        let mut config1 = McpServerConfig::default();
907        let mut config2 = McpServerConfig::default();
908        config2.server.name = "merged-server".to_string();
909        config2.logging.level = "debug".to_string();
910        config2.cache.enabled = false;
911
912        config1.merge_with(&config2);
913        assert_eq!(config1.server.name, "merged-server");
914        assert_eq!(config1.logging.level, "debug");
915        assert!(!config1.cache.enabled);
916    }
917
918    #[test]
919    fn test_parse_bool() {
920        assert!(parse_bool("true"));
921        assert!(parse_bool("TRUE"));
922        assert!(parse_bool("1"));
923        assert!(parse_bool("yes"));
924        assert!(parse_bool("on"));
925        assert!(!parse_bool("false"));
926        assert!(!parse_bool("0"));
927        assert!(!parse_bool("no"));
928        assert!(!parse_bool("off"));
929        assert!(!parse_bool("invalid"));
930    }
931
932    fn cleanup_env_vars() {
933        let env_vars = [
934            "MCP_SERVER_NAME",
935            "MCP_SERVER_VERSION",
936            "MCP_SERVER_DESCRIPTION",
937            "MCP_MAX_CONNECTIONS",
938            "MCP_CONNECTION_TIMEOUT",
939            "MCP_REQUEST_TIMEOUT",
940            "MCP_DATABASE_PATH",
941            "MCP_DATABASE_FALLBACK",
942            "MCP_DATABASE_POOL_SIZE",
943            "MCP_LOG_LEVEL",
944            "MCP_LOG_FILE",
945            "MCP_PERFORMANCE_ENABLED",
946            "MCP_SLOW_REQUEST_THRESHOLD",
947            "MCP_AUTH_ENABLED",
948            "MCP_JWT_SECRET",
949            "MCP_RATE_LIMIT_ENABLED",
950            "MCP_REQUESTS_PER_MINUTE",
951            "MCP_CACHE_ENABLED",
952            "MCP_CACHE_MAX_SIZE_MB",
953            "MCP_MONITORING_ENABLED",
954            "MCP_METRICS_PORT",
955            "MCP_HEALTH_PORT",
956            "MCP_REAL_TIME_UPDATES",
957            "MCP_WEBSOCKET_SERVER",
958            "MCP_DASHBOARD",
959            "MCP_BULK_OPERATIONS",
960            "MCP_DATA_EXPORT",
961            "MCP_BACKUP",
962            "MCP_HOT_RELOADING",
963        ];
964
965        for var in env_vars {
966            std::env::remove_var(var);
967        }
968    }
969
970    fn set_test_env_vars() {
971        std::env::set_var("MCP_SERVER_NAME", "test-server");
972        std::env::set_var("MCP_SERVER_VERSION", "1.2.3");
973        std::env::set_var("MCP_SERVER_DESCRIPTION", "Test Description");
974        std::env::set_var("MCP_MAX_CONNECTIONS", "150");
975        std::env::set_var("MCP_CONNECTION_TIMEOUT", "30");
976        std::env::set_var("MCP_REQUEST_TIMEOUT", "60");
977        std::env::set_var("MCP_DATABASE_PATH", "/test/db.sqlite");
978        std::env::set_var("MCP_DATABASE_FALLBACK", "false");
979        std::env::set_var("MCP_DATABASE_POOL_SIZE", "20");
980        std::env::set_var("MCP_LOG_LEVEL", "debug");
981        std::env::set_var("MCP_LOG_FILE", "/test/log.txt");
982        std::env::set_var("MCP_PERFORMANCE_ENABLED", "false");
983        std::env::set_var("MCP_SLOW_REQUEST_THRESHOLD", "5000");
984        std::env::set_var("MCP_AUTH_ENABLED", "true");
985        std::env::set_var("MCP_JWT_SECRET", "test-secret");
986        std::env::set_var("MCP_RATE_LIMIT_ENABLED", "true");
987        std::env::set_var("MCP_REQUESTS_PER_MINUTE", "120");
988        std::env::set_var("MCP_CACHE_ENABLED", "false");
989        std::env::set_var("MCP_CACHE_MAX_SIZE_MB", "500");
990        std::env::set_var("MCP_MONITORING_ENABLED", "false");
991        std::env::set_var("MCP_METRICS_PORT", "9090");
992        std::env::set_var("MCP_HEALTH_PORT", "8080");
993        std::env::set_var("MCP_REAL_TIME_UPDATES", "true");
994        std::env::set_var("MCP_WEBSOCKET_SERVER", "true");
995        std::env::set_var("MCP_DASHBOARD", "true");
996        std::env::set_var("MCP_BULK_OPERATIONS", "true");
997        std::env::set_var("MCP_DATA_EXPORT", "true");
998        std::env::set_var("MCP_BACKUP", "true");
999        std::env::set_var("MCP_HOT_RELOADING", "true");
1000    }
1001
1002    fn assert_config_values(config: &McpServerConfig) {
1003        assert_eq!(config.server.name, "test-server");
1004        assert_eq!(config.server.version, "1.2.3");
1005        assert_eq!(config.server.description, "Test Description");
1006        assert_eq!(config.server.max_connections, 150);
1007        assert_eq!(config.server.connection_timeout, 30);
1008        assert_eq!(config.server.request_timeout, 60);
1009        assert_eq!(config.database.path, PathBuf::from("/test/db.sqlite"));
1010        assert!(!config.database.fallback_to_default);
1011        assert_eq!(config.database.pool_size, 20);
1012        assert_eq!(config.logging.level, "debug");
1013        assert_eq!(
1014            config.logging.log_file,
1015            Some(PathBuf::from("/test/log.txt"))
1016        );
1017        assert!(!config.performance.enabled);
1018        assert_eq!(config.performance.slow_request_threshold_ms, 5000);
1019        assert!(config.security.authentication.enabled);
1020        assert_eq!(config.security.authentication.jwt_secret, "test-secret");
1021        assert!(config.security.rate_limiting.enabled);
1022        assert_eq!(config.security.rate_limiting.requests_per_minute, 120);
1023        assert!(!config.cache.enabled);
1024        assert_eq!(config.cache.max_size_mb, 500);
1025        assert!(!config.monitoring.enabled);
1026        assert_eq!(config.monitoring.metrics_port, 9090);
1027        assert_eq!(config.monitoring.health_port, 8080);
1028        assert!(config.features.real_time_updates);
1029        assert!(config.features.websocket_server);
1030        assert!(config.features.dashboard);
1031        assert!(config.features.bulk_operations);
1032        assert!(config.features.data_export);
1033        assert!(config.features.backup);
1034        assert!(config.features.hot_reloading);
1035    }
1036
1037    #[test]
1038    fn test_config_from_env_comprehensive() {
1039        let _lock = ENV_MUTEX.lock().unwrap();
1040
1041        // Clean up any existing environment variables first
1042        cleanup_env_vars();
1043
1044        // Test all environment variables
1045        set_test_env_vars();
1046
1047        let config = McpServerConfig::from_env().unwrap();
1048        assert_config_values(&config);
1049
1050        // Clean up
1051        cleanup_env_vars();
1052    }
1053
1054    #[test]
1055    fn test_config_from_env_invalid_values() {
1056        let _lock = ENV_MUTEX.lock().unwrap();
1057
1058        // Clean up any existing environment variables first
1059        cleanup_env_vars();
1060
1061        // Test invalid numeric values
1062        std::env::set_var("MCP_MAX_CONNECTIONS", "invalid");
1063        let result = McpServerConfig::from_env();
1064        assert!(result.is_err());
1065        std::env::remove_var("MCP_MAX_CONNECTIONS");
1066
1067        std::env::set_var("MCP_CONNECTION_TIMEOUT", "not-a-number");
1068        let result = McpServerConfig::from_env();
1069        assert!(result.is_err());
1070        std::env::remove_var("MCP_CONNECTION_TIMEOUT");
1071
1072        std::env::set_var("MCP_REQUEST_TIMEOUT", "abc");
1073        let result = McpServerConfig::from_env();
1074        assert!(result.is_err());
1075        std::env::remove_var("MCP_REQUEST_TIMEOUT");
1076
1077        // Clean up
1078        cleanup_env_vars();
1079    }
1080
1081    #[test]
1082    fn test_config_from_file_nonexistent() {
1083        let result = McpServerConfig::from_file("/nonexistent/file.json");
1084        assert!(result.is_err());
1085    }
1086
1087    #[test]
1088    fn test_config_from_file_invalid_yaml() {
1089        let temp_file = NamedTempFile::new().unwrap();
1090        let config_path = temp_file.path().with_extension("yaml");
1091
1092        std::fs::write(&config_path, "invalid: yaml: content: [").unwrap();
1093
1094        let result = McpServerConfig::from_file(&config_path);
1095        assert!(result.is_err());
1096    }
1097
1098    #[test]
1099    fn test_config_to_file_invalid_format() {
1100        let config = McpServerConfig::default();
1101        let temp_file = NamedTempFile::new().unwrap();
1102        let config_path = temp_file.path().with_extension("txt");
1103
1104        let result = config.to_file(&config_path, "invalid");
1105        assert!(result.is_err());
1106    }
1107
1108    #[test]
1109    fn test_config_merge_comprehensive() {
1110        let mut config1 = McpServerConfig::default();
1111        config1.server.name = "server1".to_string();
1112        config1.server.max_connections = 100;
1113        config1.cache.enabled = true;
1114        config1.cache.max_size_mb = 200;
1115        config1.performance.enabled = false;
1116        config1.security.authentication.enabled = true;
1117        config1.security.authentication.jwt_secret = "secret1".to_string();
1118
1119        let mut config2 = McpServerConfig::default();
1120        config2.server.name = "server2".to_string();
1121        config2.server.max_connections = 0; // Should not override
1122        config2.cache.enabled = false;
1123        config2.cache.max_size_mb = 0; // Should not override
1124        config2.performance.enabled = true;
1125        config2.security.authentication.enabled = false;
1126        config2.security.authentication.jwt_secret = "secret2".to_string();
1127
1128        config1.merge_with(&config2);
1129
1130        assert_eq!(config1.server.name, "server2");
1131        assert_eq!(config1.server.max_connections, 100); // Should not be overridden
1132        assert!(!config1.cache.enabled); // Should be overridden
1133        assert_eq!(config1.cache.max_size_mb, 200); // Should not be overridden
1134        assert!(config1.performance.enabled); // Should be overridden
1135        assert!(!config1.security.authentication.enabled); // Should be overridden
1136        assert_eq!(config1.security.authentication.jwt_secret, "secret2");
1137    }
1138
1139    #[test]
1140    fn test_config_validation_comprehensive() {
1141        let mut config = McpServerConfig::default();
1142
1143        // Test empty server name
1144        config.server.name = String::new();
1145        assert!(config.validate().is_err());
1146        config.server.name = "test".to_string();
1147
1148        // Test empty server version
1149        config.server.version = String::new();
1150        assert!(config.validate().is_err());
1151        config.server.version = "1.0.0".to_string();
1152
1153        // Test zero max connections
1154        config.server.max_connections = 0;
1155        assert!(config.validate().is_err());
1156        config.server.max_connections = 100;
1157
1158        // Test zero database pool size
1159        config.database.pool_size = 0;
1160        assert!(config.validate().is_err());
1161        config.database.pool_size = 10;
1162
1163        // Test invalid log level
1164        config.logging.level = "invalid".to_string();
1165        assert!(config.validate().is_err());
1166        config.logging.level = "info".to_string();
1167
1168        // Test performance enabled with zero threshold
1169        config.performance.enabled = true;
1170        config.performance.slow_request_threshold_ms = 0;
1171        assert!(config.validate().is_err());
1172        config.performance.slow_request_threshold_ms = 1000;
1173
1174        // Test auth enabled with empty JWT secret
1175        config.security.authentication.enabled = true;
1176        config.security.authentication.jwt_secret = String::new();
1177        assert!(config.validate().is_err());
1178        config.security.authentication.jwt_secret = "secret".to_string();
1179
1180        // Test cache enabled with zero size
1181        config.cache.enabled = true;
1182        config.cache.max_size_mb = 0;
1183        assert!(config.validate().is_err());
1184        config.cache.max_size_mb = 100;
1185
1186        // Should pass now
1187        assert!(config.validate().is_ok());
1188    }
1189
1190    #[test]
1191    fn test_effective_database_path() {
1192        let temp_file = NamedTempFile::new().unwrap();
1193        let db_path = temp_file.path();
1194        let mut config = McpServerConfig::default();
1195        config.database.path = db_path.to_path_buf();
1196        config.database.fallback_to_default = false;
1197
1198        let effective_path = config.get_effective_database_path().unwrap();
1199        assert_eq!(effective_path, db_path);
1200    }
1201
1202    #[test]
1203    fn test_effective_database_path_fallback() {
1204        let mut config = McpServerConfig::default();
1205        config.database.path = PathBuf::from("/nonexistent/path");
1206        config.database.fallback_to_default = true;
1207
1208        // This will succeed if the default path exists, fail otherwise
1209        let _ = config.get_effective_database_path();
1210    }
1211}