Skip to main content

ultrafast_mcp_sequential_thinking/
config.rs

1//! # Configuration Module
2//!
3//! Configuration management for the UltraFast MCP Sequential Thinking project.
4//!
5//! This module provides configuration structures and loading functionality
6//! for both server and client components.
7
8use serde::{Deserialize, Serialize};
9use std::path::Path;
10
11use crate::thinking::client::ClientThinkingConfig;
12
13/// Server configuration
14#[derive(Debug, Clone, Serialize, Deserialize)]
15pub struct ServerConfig {
16    /// Server name
17    pub name: String,
18    /// Server version
19    pub version: String,
20    /// Transport type (stdio, http)
21    pub transport: String,
22    /// Port for HTTP transport
23    pub port: u16,
24    /// Thinking configuration
25    pub thinking: ThinkingConfig,
26    /// Export configuration
27    pub export: ExportConfig,
28    /// Analytics configuration
29    pub analytics: AnalyticsConfig,
30    /// Logging configuration
31    pub logging: LoggingConfig,
32    /// Security configuration
33    pub security: SecurityConfig,
34}
35
36impl Default for ServerConfig {
37    fn default() -> Self {
38        Self {
39            name: "ultrafast-sequential-thinking".to_string(),
40            version: env!("CARGO_PKG_VERSION").to_string(),
41            transport: "stdio".to_string(),
42            port: 8080,
43            thinking: ThinkingConfig::default(),
44            export: ExportConfig::default(),
45            analytics: AnalyticsConfig::default(),
46            logging: LoggingConfig::default(),
47            security: SecurityConfig::default(),
48        }
49    }
50}
51
52/// Client configuration
53#[derive(Debug, Clone, Serialize, Deserialize)]
54pub struct ClientConfig {
55    /// Server URL
56    pub server_url: String,
57    /// Request timeout in seconds
58    pub timeout_seconds: u64,
59    /// Number of retry attempts
60    pub retry_attempts: u32,
61    /// Thinking configuration
62    pub thinking: ClientThinkingConfig,
63    /// Connection configuration
64    pub connection: ConnectionConfig,
65    /// UI configuration
66    pub ui: UIConfig,
67}
68
69impl Default for ClientConfig {
70    fn default() -> Self {
71        Self {
72            server_url: "stdio://".to_string(),
73            timeout_seconds: 30,
74            retry_attempts: 3,
75            thinking: ClientThinkingConfig::default(),
76            connection: ConnectionConfig::default(),
77            ui: UIConfig::default(),
78        }
79    }
80}
81
82/// Thinking configuration
83#[derive(Debug, Clone, Serialize, Deserialize)]
84pub struct ThinkingConfig {
85    /// Maximum thoughts per session
86    pub max_thoughts_per_session: u32,
87    /// Maximum branches per session
88    pub max_branches_per_session: u32,
89    /// Session timeout in seconds
90    pub session_timeout_seconds: u64,
91    /// Whether to enable analytics
92    pub enable_analytics: bool,
93    /// Whether to enable thought logging
94    pub enable_thought_logging: bool,
95    /// Maximum thought length
96    pub max_thought_length: usize,
97    /// Minimum thought length
98    pub min_thought_length: usize,
99    /// Rate limiting configuration
100    pub rate_limiting: RateLimitingConfig,
101}
102
103impl Default for ThinkingConfig {
104    fn default() -> Self {
105        Self {
106            max_thoughts_per_session: 100,
107            max_branches_per_session: 10,
108            session_timeout_seconds: 3600,
109            enable_analytics: true,
110            enable_thought_logging: true,
111            max_thought_length: 10000,
112            min_thought_length: 10,
113            rate_limiting: RateLimitingConfig::default(),
114        }
115    }
116}
117
118/// Rate limiting configuration
119#[derive(Debug, Clone, Serialize, Deserialize)]
120pub struct RateLimitingConfig {
121    /// Maximum requests per minute
122    pub requests_per_minute: u32,
123    /// Maximum thoughts per minute
124    pub thoughts_per_minute: u32,
125    /// Burst size
126    pub burst_size: u32,
127    /// Whether rate limiting is enabled
128    pub enabled: bool,
129}
130
131impl Default for RateLimitingConfig {
132    fn default() -> Self {
133        Self {
134            requests_per_minute: 1000,
135            thoughts_per_minute: 100,
136            burst_size: 10,
137            enabled: true,
138        }
139    }
140}
141
142/// Export configuration
143#[derive(Debug, Clone, Serialize, Deserialize)]
144pub struct ExportConfig {
145    /// Supported export formats
146    pub formats: Vec<String>,
147    /// Whether to auto-export
148    pub auto_export: bool,
149    /// Export directory
150    pub export_directory: String,
151    /// Export filename template
152    pub filename_template: String,
153    /// Whether to include metadata
154    pub include_metadata: bool,
155    /// Whether to include statistics
156    pub include_statistics: bool,
157}
158
159impl Default for ExportConfig {
160    fn default() -> Self {
161        Self {
162            formats: vec![
163                "json".to_string(),
164                "markdown".to_string(),
165                "pdf".to_string(),
166            ],
167            auto_export: false,
168            export_directory: "./exports".to_string(),
169            filename_template: "session_{session_id}_{timestamp}".to_string(),
170            include_metadata: true,
171            include_statistics: true,
172        }
173    }
174}
175
176/// Analytics configuration
177#[derive(Debug, Clone, Serialize, Deserialize)]
178pub struct AnalyticsConfig {
179    /// Whether analytics is enabled
180    pub enabled: bool,
181    /// Analytics endpoint
182    pub endpoint: String,
183    /// Analytics API key
184    pub api_key: Option<String>,
185    /// Metrics collection interval in seconds
186    pub collection_interval: u64,
187    /// Whether to collect detailed metrics
188    pub detailed_metrics: bool,
189    /// Retention period for metrics in days
190    pub retention_days: u32,
191}
192
193impl Default for AnalyticsConfig {
194    fn default() -> Self {
195        Self {
196            enabled: false,
197            endpoint: "http://localhost:9090".to_string(),
198            api_key: None,
199            collection_interval: 60,
200            detailed_metrics: true,
201            retention_days: 30,
202        }
203    }
204}
205
206/// Logging configuration
207#[derive(Debug, Clone, Serialize, Deserialize)]
208pub struct LoggingConfig {
209    /// Log level
210    pub level: String,
211    /// Log file path
212    pub file_path: Option<String>,
213    /// Whether to log to console
214    pub console: bool,
215    /// Whether to log to file
216    pub file: bool,
217    /// Log format (json, text)
218    pub format: String,
219    /// Whether to include timestamps
220    pub include_timestamps: bool,
221    /// Whether to include thread IDs
222    pub include_thread_ids: bool,
223}
224
225impl Default for LoggingConfig {
226    fn default() -> Self {
227        Self {
228            level: "info".to_string(),
229            file_path: Some("./logs/sequential-thinking.log".to_string()),
230            console: true,
231            file: true,
232            format: "text".to_string(),
233            include_timestamps: true,
234            include_thread_ids: false,
235        }
236    }
237}
238
239/// Security configuration
240#[derive(Debug, Clone, Serialize, Deserialize)]
241pub struct SecurityConfig {
242    /// Whether authentication is required
243    pub require_auth: bool,
244    /// Allowed origins for CORS
245    pub allowed_origins: Vec<String>,
246    /// API key validation
247    pub api_key_validation: bool,
248    /// Rate limiting enabled
249    pub rate_limiting_enabled: bool,
250    /// Session encryption
251    pub session_encryption: bool,
252    /// Audit logging
253    pub audit_logging: bool,
254}
255
256impl Default for SecurityConfig {
257    fn default() -> Self {
258        Self {
259            require_auth: false,
260            allowed_origins: vec!["*".to_string()],
261            api_key_validation: false,
262            rate_limiting_enabled: true,
263            session_encryption: false,
264            audit_logging: true,
265        }
266    }
267}
268
269/// Connection configuration
270#[derive(Debug, Clone, Serialize, Deserialize)]
271pub struct ConnectionConfig {
272    /// Connection timeout in seconds
273    pub timeout_seconds: u64,
274    /// Keep-alive interval in seconds
275    pub keep_alive_interval: u64,
276    /// Maximum connection retries
277    pub max_retries: u32,
278    /// Retry delay in seconds
279    pub retry_delay: u64,
280    /// Whether to use connection pooling
281    pub connection_pooling: bool,
282    /// Pool size
283    pub pool_size: u32,
284}
285
286impl Default for ConnectionConfig {
287    fn default() -> Self {
288        Self {
289            timeout_seconds: 30,
290            keep_alive_interval: 60,
291            max_retries: 3,
292            retry_delay: 1,
293            connection_pooling: true,
294            pool_size: 10,
295        }
296    }
297}
298
299/// UI configuration
300#[derive(Debug, Clone, Serialize, Deserialize)]
301pub struct UIConfig {
302    /// Whether to show progress bars
303    pub show_progress_bars: bool,
304    /// Whether to show thought visualization
305    pub show_thought_visualization: bool,
306    /// Whether to show session statistics
307    pub show_session_stats: bool,
308    /// UI theme
309    pub theme: String,
310    /// Whether to enable color output
311    pub color_output: bool,
312    /// Whether to show timestamps
313    pub show_timestamps: bool,
314}
315
316impl Default for UIConfig {
317    fn default() -> Self {
318        Self {
319            show_progress_bars: true,
320            show_thought_visualization: true,
321            show_session_stats: true,
322            theme: "default".to_string(),
323            color_output: true,
324            show_timestamps: true,
325        }
326    }
327}
328
329/// Configuration manager
330pub struct ConfigManager {
331    /// Server configuration
332    server_config: Option<ServerConfig>,
333    /// Client configuration
334    client_config: Option<ClientConfig>,
335    /// Configuration file path
336    config_path: Option<String>,
337}
338
339impl ConfigManager {
340    /// Create a new configuration manager
341    pub fn new() -> Self {
342        Self {
343            server_config: None,
344            client_config: None,
345            config_path: None,
346        }
347    }
348
349    /// Load configuration from file
350    pub fn load_from_file<P: AsRef<Path>>(
351        &mut self,
352        path: P,
353    ) -> Result<(), Box<dyn std::error::Error>> {
354        let path = path.as_ref();
355        let content = std::fs::read_to_string(path)?;
356
357        if path.extension().and_then(|s| s.to_str()) == Some("toml") {
358            self.load_from_toml(&content)?;
359        } else if path.extension().and_then(|s| s.to_str()) == Some("json") {
360            self.load_from_json(&content)?;
361        } else {
362            return Err("Unsupported configuration file format".into());
363        }
364
365        self.config_path = Some(path.to_string_lossy().to_string());
366        Ok(())
367    }
368
369    /// Load configuration from TOML string
370    pub fn load_from_toml(&mut self, content: &str) -> Result<(), Box<dyn std::error::Error>> {
371        let config: toml::Value = toml::from_str(content)?;
372
373        if let Some(server) = config.get("server") {
374            self.server_config = Some(server.clone().try_into()?);
375        }
376
377        if let Some(client) = config.get("client") {
378            self.client_config = Some(client.clone().try_into()?);
379        }
380
381        Ok(())
382    }
383
384    /// Load configuration from JSON string
385    pub fn load_from_json(&mut self, content: &str) -> Result<(), Box<dyn std::error::Error>> {
386        let config: serde_json::Value = serde_json::from_str(content)?;
387
388        if let Some(server) = config.get("server") {
389            self.server_config = Some(serde_json::from_value(server.clone())?);
390        }
391
392        if let Some(client) = config.get("client") {
393            self.client_config = Some(serde_json::from_value(client.clone())?);
394        }
395
396        Ok(())
397    }
398
399    /// Load configuration from environment variables
400    pub fn load_from_env(&mut self) {
401        // Server configuration from environment
402        if let Ok(name) = std::env::var("SEQUENTIAL_THINKING_SERVER_NAME") {
403            self.server_config
404                .get_or_insert_with(ServerConfig::default)
405                .name = name;
406        }
407
408        if let Ok(transport) = std::env::var("SEQUENTIAL_THINKING_TRANSPORT") {
409            self.server_config
410                .get_or_insert_with(ServerConfig::default)
411                .transport = transport;
412        }
413
414        if let Ok(port) = std::env::var("SEQUENTIAL_THINKING_PORT") {
415            if let Ok(port_num) = port.parse::<u16>() {
416                self.server_config
417                    .get_or_insert_with(ServerConfig::default)
418                    .port = port_num;
419            }
420        }
421
422        // Client configuration from environment
423        if let Ok(server_url) = std::env::var("SEQUENTIAL_THINKING_SERVER_URL") {
424            self.client_config
425                .get_or_insert_with(ClientConfig::default)
426                .server_url = server_url;
427        }
428
429        if let Ok(timeout) = std::env::var("SEQUENTIAL_THINKING_TIMEOUT") {
430            if let Ok(timeout_num) = timeout.parse::<u64>() {
431                self.client_config
432                    .get_or_insert_with(ClientConfig::default)
433                    .timeout_seconds = timeout_num;
434            }
435        }
436    }
437
438    /// Get server configuration
439    pub fn get_server_config(&self) -> ServerConfig {
440        self.server_config.clone().unwrap_or_default()
441    }
442
443    /// Get client configuration
444    pub fn get_client_config(&self) -> ClientConfig {
445        self.client_config.clone().unwrap_or_default()
446    }
447
448    /// Set server configuration
449    pub fn set_server_config(&mut self, config: ServerConfig) {
450        self.server_config = Some(config);
451    }
452
453    /// Set client configuration
454    pub fn set_client_config(&mut self, config: ClientConfig) {
455        self.client_config = Some(config);
456    }
457
458    /// Save configuration to file
459    pub fn save_to_file<P: AsRef<Path>>(&self, path: P) -> Result<(), Box<dyn std::error::Error>> {
460        let path = path.as_ref();
461        let config = serde_json::json!({
462            "server": self.server_config,
463            "client": self.client_config
464        });
465
466        let content = serde_json::to_string_pretty(&config)?;
467        std::fs::write(path, content)?;
468
469        Ok(())
470    }
471
472    /// Validate configuration
473    pub fn validate(&self) -> Result<(), Vec<String>> {
474        let mut errors = Vec::new();
475
476        // Validate server configuration
477        if let Some(ref server_config) = self.server_config {
478            if server_config.name.is_empty() {
479                errors.push("Server name cannot be empty".to_string());
480            }
481
482            if server_config.port == 0 {
483                errors.push("Server port must be greater than 0".to_string());
484            }
485
486            if server_config.thinking.max_thoughts_per_session == 0 {
487                errors.push("Max thoughts per session must be greater than 0".to_string());
488            }
489        }
490
491        // Validate client configuration
492        if let Some(ref client_config) = self.client_config {
493            if client_config.server_url.is_empty() {
494                errors.push("Server URL cannot be empty".to_string());
495            }
496
497            if client_config.timeout_seconds == 0 {
498                errors.push("Timeout must be greater than 0".to_string());
499            }
500        }
501
502        if errors.is_empty() {
503            Ok(())
504        } else {
505            Err(errors)
506        }
507    }
508}
509
510impl Default for ConfigManager {
511    fn default() -> Self {
512        Self::new()
513    }
514}
515
516/// Configuration loading utilities
517pub mod utils {
518    use super::*;
519
520    /// Load configuration from default locations
521    pub fn load_default_config() -> Result<ConfigManager, Box<dyn std::error::Error>> {
522        let mut manager = ConfigManager::new();
523
524        // Try to load from default config file
525        let default_paths = [
526            "./config.toml",
527            "./config.json",
528            "./sequential-thinking.toml",
529            "./sequential-thinking.json",
530        ];
531
532        for path in &default_paths {
533            if std::path::Path::new(path).exists() {
534                if let Ok(()) = manager.load_from_file(path) {
535                    break;
536                }
537            }
538        }
539
540        // Load from environment variables
541        manager.load_from_env();
542
543        // Validate configuration
544        let _ = manager.validate();
545
546        Ok(manager)
547    }
548
549    /// Create a default configuration file
550    pub fn create_default_config<P: AsRef<Path>>(
551        path: P,
552    ) -> Result<(), Box<dyn std::error::Error>> {
553        let mut manager = ConfigManager::new();
554        manager.set_server_config(ServerConfig::default());
555        manager.set_client_config(ClientConfig::default());
556        manager.save_to_file(path)?;
557        Ok(())
558    }
559
560    /// Merge configurations
561    pub fn merge_configs(base: &mut ServerConfig, override_config: &ServerConfig) {
562        if !override_config.name.is_empty() {
563            base.name = override_config.name.clone();
564        }
565        if !override_config.version.is_empty() {
566            base.version = override_config.version.clone();
567        }
568        if !override_config.transport.is_empty() {
569            base.transport = override_config.transport.clone();
570        }
571        if override_config.port != 0 {
572            base.port = override_config.port;
573        }
574        // Merge other fields as needed
575    }
576}
577
578#[cfg(test)]
579mod tests {
580    use super::*;
581
582    #[test]
583    fn test_server_config_default() {
584        let config = ServerConfig::default();
585        assert_eq!(config.name, "ultrafast-sequential-thinking");
586        assert_eq!(config.transport, "stdio");
587        assert_eq!(config.port, 8080);
588    }
589
590    #[test]
591    fn test_client_config_default() {
592        let config = ClientConfig::default();
593        assert_eq!(config.server_url, "stdio://");
594        assert_eq!(config.timeout_seconds, 30);
595        assert_eq!(config.retry_attempts, 3);
596    }
597
598    #[test]
599    fn test_thinking_config_default() {
600        let config = ThinkingConfig::default();
601        assert_eq!(config.max_thoughts_per_session, 100);
602        assert_eq!(config.max_branches_per_session, 10);
603        assert!(config.enable_analytics);
604    }
605
606    #[test]
607    fn test_config_manager() {
608        let mut manager = ConfigManager::new();
609        let server_config = ServerConfig::default();
610        manager.set_server_config(server_config);
611
612        let loaded_config = manager.get_server_config();
613        assert_eq!(loaded_config.name, "ultrafast-sequential-thinking");
614    }
615
616    #[test]
617    fn test_config_validation() {
618        let mut manager = ConfigManager::new();
619        let server_config = ServerConfig {
620            name: String::new(),
621            ..Default::default()
622        };
623        manager.set_server_config(server_config);
624
625        let result = manager.validate();
626        assert!(result.is_err());
627    }
628}