things3_cli/
mcp.rs

1//! MCP (Model Context Protocol) server implementation for Things 3 integration
2
3use serde::{Deserialize, Serialize};
4use serde_json::Value;
5use std::sync::Arc;
6use things3_core::{
7    BackupManager, DataExporter, DeleteChildHandling, McpServerConfig, PerformanceMonitor,
8    ThingsCache, ThingsConfig, ThingsDatabase, ThingsError,
9};
10use thiserror::Error;
11use tokio::sync::Mutex;
12use uuid::Uuid;
13
14pub mod io_wrapper;
15pub mod middleware;
16// pub mod performance_tests; // Temporarily disabled due to API changes
17pub mod test_harness;
18
19use io_wrapper::{McpIo, StdIo};
20use middleware::{MiddlewareChain, MiddlewareConfig};
21
22/// MCP-specific error types for better error handling and user experience
23#[derive(Error, Debug)]
24pub enum McpError {
25    #[error("Tool not found: {tool_name}")]
26    ToolNotFound { tool_name: String },
27
28    #[error("Resource not found: {uri}")]
29    ResourceNotFound { uri: String },
30
31    #[error("Prompt not found: {prompt_name}")]
32    PromptNotFound { prompt_name: String },
33
34    #[error("Invalid parameter: {parameter_name} - {message}")]
35    InvalidParameter {
36        parameter_name: String,
37        message: String,
38    },
39
40    #[error("Missing required parameter: {parameter_name}")]
41    MissingParameter { parameter_name: String },
42
43    #[error("Invalid format: {format} - supported formats: {supported}")]
44    InvalidFormat { format: String, supported: String },
45
46    #[error("Invalid data type: {data_type} - supported types: {supported}")]
47    InvalidDataType {
48        data_type: String,
49        supported: String,
50    },
51
52    #[error("Database operation failed: {operation}")]
53    DatabaseOperationFailed {
54        operation: String,
55        source: ThingsError,
56    },
57
58    #[error("Backup operation failed: {operation}")]
59    BackupOperationFailed {
60        operation: String,
61        source: ThingsError,
62    },
63
64    #[error("Export operation failed: {operation}")]
65    ExportOperationFailed {
66        operation: String,
67        source: ThingsError,
68    },
69
70    #[error("Performance monitoring failed: {operation}")]
71    PerformanceMonitoringFailed {
72        operation: String,
73        source: ThingsError,
74    },
75
76    #[error("Cache operation failed: {operation}")]
77    CacheOperationFailed {
78        operation: String,
79        source: ThingsError,
80    },
81
82    #[error("Serialization failed: {operation}")]
83    SerializationFailed {
84        operation: String,
85        source: serde_json::Error,
86    },
87
88    #[error("IO operation failed: {operation}")]
89    IoOperationFailed {
90        operation: String,
91        source: std::io::Error,
92    },
93
94    #[error("Configuration error: {message}")]
95    ConfigurationError { message: String },
96
97    #[error("Validation error: {message}")]
98    ValidationError { message: String },
99
100    #[error("Internal error: {message}")]
101    InternalError { message: String },
102}
103
104impl McpError {
105    /// Create a tool not found error
106    pub fn tool_not_found(tool_name: impl Into<String>) -> Self {
107        Self::ToolNotFound {
108            tool_name: tool_name.into(),
109        }
110    }
111
112    /// Create a resource not found error
113    pub fn resource_not_found(uri: impl Into<String>) -> Self {
114        Self::ResourceNotFound { uri: uri.into() }
115    }
116
117    /// Create a prompt not found error
118    pub fn prompt_not_found(prompt_name: impl Into<String>) -> Self {
119        Self::PromptNotFound {
120            prompt_name: prompt_name.into(),
121        }
122    }
123
124    /// Create an invalid parameter error
125    pub fn invalid_parameter(
126        parameter_name: impl Into<String>,
127        message: impl Into<String>,
128    ) -> Self {
129        Self::InvalidParameter {
130            parameter_name: parameter_name.into(),
131            message: message.into(),
132        }
133    }
134
135    /// Create a missing parameter error
136    pub fn missing_parameter(parameter_name: impl Into<String>) -> Self {
137        Self::MissingParameter {
138            parameter_name: parameter_name.into(),
139        }
140    }
141
142    /// Create an invalid format error
143    pub fn invalid_format(format: impl Into<String>, supported: impl Into<String>) -> Self {
144        Self::InvalidFormat {
145            format: format.into(),
146            supported: supported.into(),
147        }
148    }
149
150    /// Create an invalid data type error
151    pub fn invalid_data_type(data_type: impl Into<String>, supported: impl Into<String>) -> Self {
152        Self::InvalidDataType {
153            data_type: data_type.into(),
154            supported: supported.into(),
155        }
156    }
157
158    /// Create a database operation failed error
159    pub fn database_operation_failed(operation: impl Into<String>, source: ThingsError) -> Self {
160        Self::DatabaseOperationFailed {
161            operation: operation.into(),
162            source,
163        }
164    }
165
166    /// Create a backup operation failed error
167    pub fn backup_operation_failed(operation: impl Into<String>, source: ThingsError) -> Self {
168        Self::BackupOperationFailed {
169            operation: operation.into(),
170            source,
171        }
172    }
173
174    /// Create an export operation failed error
175    pub fn export_operation_failed(operation: impl Into<String>, source: ThingsError) -> Self {
176        Self::ExportOperationFailed {
177            operation: operation.into(),
178            source,
179        }
180    }
181
182    /// Create a performance monitoring failed error
183    pub fn performance_monitoring_failed(
184        operation: impl Into<String>,
185        source: ThingsError,
186    ) -> Self {
187        Self::PerformanceMonitoringFailed {
188            operation: operation.into(),
189            source,
190        }
191    }
192
193    /// Create a cache operation failed error
194    pub fn cache_operation_failed(operation: impl Into<String>, source: ThingsError) -> Self {
195        Self::CacheOperationFailed {
196            operation: operation.into(),
197            source,
198        }
199    }
200
201    /// Create a serialization failed error
202    pub fn serialization_failed(operation: impl Into<String>, source: serde_json::Error) -> Self {
203        Self::SerializationFailed {
204            operation: operation.into(),
205            source,
206        }
207    }
208
209    /// Create an IO operation failed error
210    pub fn io_operation_failed(operation: impl Into<String>, source: std::io::Error) -> Self {
211        Self::IoOperationFailed {
212            operation: operation.into(),
213            source,
214        }
215    }
216
217    /// Create a configuration error
218    pub fn configuration_error(message: impl Into<String>) -> Self {
219        Self::ConfigurationError {
220            message: message.into(),
221        }
222    }
223
224    /// Create a validation error
225    pub fn validation_error(message: impl Into<String>) -> Self {
226        Self::ValidationError {
227            message: message.into(),
228        }
229    }
230
231    /// Create an internal error
232    pub fn internal_error(message: impl Into<String>) -> Self {
233        Self::InternalError {
234            message: message.into(),
235        }
236    }
237
238    /// Convert error to MCP call result
239    #[must_use]
240    pub fn to_call_result(self) -> CallToolResult {
241        let error_message = match &self {
242            McpError::ToolNotFound { tool_name } => {
243                format!("Tool '{tool_name}' not found. Available tools can be listed using the list_tools method.")
244            }
245            McpError::ResourceNotFound { uri } => {
246                format!("Resource '{uri}' not found. Available resources can be listed using the list_resources method.")
247            }
248            McpError::PromptNotFound { prompt_name } => {
249                format!("Prompt '{prompt_name}' not found. Available prompts can be listed using the list_prompts method.")
250            }
251            McpError::InvalidParameter {
252                parameter_name,
253                message,
254            } => {
255                format!("Invalid parameter '{parameter_name}': {message}. Please check the parameter format and try again.")
256            }
257            McpError::MissingParameter { parameter_name } => {
258                format!("Missing required parameter '{parameter_name}'. Please provide this parameter and try again.")
259            }
260            McpError::InvalidFormat { format, supported } => {
261                format!("Invalid format '{format}'. Supported formats: {supported}. Please use one of the supported formats.")
262            }
263            McpError::InvalidDataType {
264                data_type,
265                supported,
266            } => {
267                format!("Invalid data type '{data_type}'. Supported types: {supported}. Please use one of the supported types.")
268            }
269            McpError::DatabaseOperationFailed { operation, source } => {
270                format!("Database operation '{operation}' failed: {source}. Please check your database connection and try again.")
271            }
272            McpError::BackupOperationFailed { operation, source } => {
273                format!("Backup operation '{operation}' failed: {source}. Please check backup permissions and try again.")
274            }
275            McpError::ExportOperationFailed { operation, source } => {
276                format!("Export operation '{operation}' failed: {source}. Please check export parameters and try again.")
277            }
278            McpError::PerformanceMonitoringFailed { operation, source } => {
279                format!("Performance monitoring '{operation}' failed: {source}. Please try again later.")
280            }
281            McpError::CacheOperationFailed { operation, source } => {
282                format!("Cache operation '{operation}' failed: {source}. Please try again later.")
283            }
284            McpError::SerializationFailed { operation, source } => {
285                format!("Serialization '{operation}' failed: {source}. Please check data format and try again.")
286            }
287            McpError::IoOperationFailed { operation, source } => {
288                format!("IO operation '{operation}' failed: {source}. Please check file permissions and try again.")
289            }
290            McpError::ConfigurationError { message } => {
291                format!("Configuration error: {message}. Please check your configuration and try again.")
292            }
293            McpError::ValidationError { message } => {
294                format!("Validation error: {message}. Please check your input and try again.")
295            }
296            McpError::InternalError { message } => {
297                format!("Internal error: {message}. Please try again later or contact support if the issue persists.")
298            }
299        };
300
301        CallToolResult {
302            content: vec![Content::Text {
303                text: error_message,
304            }],
305            is_error: true,
306        }
307    }
308
309    /// Convert error to MCP prompt result
310    #[must_use]
311    pub fn to_prompt_result(self) -> GetPromptResult {
312        let error_message = match &self {
313            McpError::PromptNotFound { prompt_name } => {
314                format!("Prompt '{prompt_name}' not found. Available prompts can be listed using the list_prompts method.")
315            }
316            McpError::InvalidParameter {
317                parameter_name,
318                message,
319            } => {
320                format!("Invalid parameter '{parameter_name}': {message}. Please check the parameter format and try again.")
321            }
322            McpError::MissingParameter { parameter_name } => {
323                format!("Missing required parameter '{parameter_name}'. Please provide this parameter and try again.")
324            }
325            McpError::DatabaseOperationFailed { operation, source } => {
326                format!("Database operation '{operation}' failed: {source}. Please check your database connection and try again.")
327            }
328            McpError::SerializationFailed { operation, source } => {
329                format!("Serialization '{operation}' failed: {source}. Please check data format and try again.")
330            }
331            McpError::ValidationError { message } => {
332                format!("Validation error: {message}. Please check your input and try again.")
333            }
334            McpError::InternalError { message } => {
335                format!("Internal error: {message}. Please try again later or contact support if the issue persists.")
336            }
337            _ => {
338                format!("Error: {self}. Please try again later.")
339            }
340        };
341
342        GetPromptResult {
343            content: vec![Content::Text {
344                text: error_message,
345            }],
346            is_error: true,
347        }
348    }
349
350    /// Convert error to MCP resource result
351    #[must_use]
352    pub fn to_resource_result(self) -> ReadResourceResult {
353        let error_message = match &self {
354            McpError::ResourceNotFound { uri } => {
355                format!("Resource '{uri}' not found. Available resources can be listed using the list_resources method.")
356            }
357            McpError::DatabaseOperationFailed { operation, source } => {
358                format!("Database operation '{operation}' failed: {source}. Please check your database connection and try again.")
359            }
360            McpError::SerializationFailed { operation, source } => {
361                format!("Serialization '{operation}' failed: {source}. Please check data format and try again.")
362            }
363            McpError::InternalError { message } => {
364                format!("Internal error: {message}. Please try again later or contact support if the issue persists.")
365            }
366            _ => {
367                format!("Error: {self}. Please try again later.")
368            }
369        };
370
371        ReadResourceResult {
372            contents: vec![Content::Text {
373                text: error_message,
374            }],
375        }
376    }
377}
378
379/// Result type alias for MCP operations
380pub type McpResult<T> = std::result::Result<T, McpError>;
381
382/// From trait implementations for common error types
383impl From<ThingsError> for McpError {
384    fn from(error: ThingsError) -> Self {
385        match error {
386            ThingsError::Database(e) => {
387                McpError::database_operation_failed("database operation", ThingsError::Database(e))
388            }
389            ThingsError::Serialization(e) => McpError::serialization_failed("serialization", e),
390            ThingsError::Io(e) => McpError::io_operation_failed("io operation", e),
391            ThingsError::DatabaseNotFound { path } => {
392                McpError::configuration_error(format!("Database not found at: {path}"))
393            }
394            ThingsError::InvalidUuid { uuid } => {
395                McpError::validation_error(format!("Invalid UUID format: {uuid}"))
396            }
397            ThingsError::InvalidDate { date } => {
398                McpError::validation_error(format!("Invalid date format: {date}"))
399            }
400            ThingsError::TaskNotFound { uuid } => {
401                McpError::validation_error(format!("Task not found: {uuid}"))
402            }
403            ThingsError::ProjectNotFound { uuid } => {
404                McpError::validation_error(format!("Project not found: {uuid}"))
405            }
406            ThingsError::AreaNotFound { uuid } => {
407                McpError::validation_error(format!("Area not found: {uuid}"))
408            }
409            ThingsError::Validation { message } => McpError::validation_error(message),
410            ThingsError::Configuration { message } => McpError::configuration_error(message),
411            ThingsError::DateValidation(e) => {
412                McpError::validation_error(format!("Date validation failed: {e}"))
413            }
414            ThingsError::DateConversion(e) => {
415                McpError::validation_error(format!("Date conversion failed: {e}"))
416            }
417            ThingsError::Unknown { message } => McpError::internal_error(message),
418        }
419    }
420}
421
422impl From<serde_json::Error> for McpError {
423    fn from(error: serde_json::Error) -> Self {
424        McpError::serialization_failed("json serialization", error)
425    }
426}
427
428impl From<std::io::Error> for McpError {
429    fn from(error: std::io::Error) -> Self {
430        McpError::io_operation_failed("file operation", error)
431    }
432}
433
434/// Simplified MCP types for our implementation
435#[derive(Debug, Serialize, Deserialize)]
436pub struct Tool {
437    pub name: String,
438    pub description: String,
439    pub input_schema: Value,
440}
441
442#[derive(Debug, Clone, Serialize, Deserialize)]
443pub struct CallToolRequest {
444    pub name: String,
445    pub arguments: Option<Value>,
446}
447
448#[derive(Debug, Serialize, Deserialize)]
449pub struct CallToolResult {
450    pub content: Vec<Content>,
451    pub is_error: bool,
452}
453
454#[derive(Debug, Serialize, Deserialize)]
455pub enum Content {
456    Text { text: String },
457}
458
459#[derive(Debug, Serialize, Deserialize)]
460pub struct ListToolsResult {
461    pub tools: Vec<Tool>,
462}
463
464/// MCP Resource for data exposure
465#[derive(Debug, Serialize, Deserialize)]
466pub struct Resource {
467    pub uri: String,
468    pub name: String,
469    pub description: String,
470    pub mime_type: Option<String>,
471}
472
473#[derive(Debug, Serialize, Deserialize)]
474pub struct ListResourcesResult {
475    pub resources: Vec<Resource>,
476}
477
478#[derive(Debug, Serialize, Deserialize)]
479pub struct ReadResourceRequest {
480    pub uri: String,
481}
482
483#[derive(Debug, Serialize, Deserialize)]
484pub struct ReadResourceResult {
485    pub contents: Vec<Content>,
486}
487
488/// MCP Prompt for reusable templates
489#[derive(Debug, Serialize, Deserialize)]
490pub struct Prompt {
491    pub name: String,
492    pub description: String,
493    pub arguments: Value,
494}
495
496#[derive(Debug, Serialize, Deserialize)]
497pub struct ListPromptsResult {
498    pub prompts: Vec<Prompt>,
499}
500
501#[derive(Debug, Serialize, Deserialize)]
502pub struct GetPromptRequest {
503    pub name: String,
504    pub arguments: Option<Value>,
505}
506
507#[derive(Debug, Serialize, Deserialize)]
508pub struct GetPromptResult {
509    pub content: Vec<Content>,
510    pub is_error: bool,
511}
512
513/// MCP server for Things 3 integration
514pub struct ThingsMcpServer {
515    #[allow(dead_code)]
516    pub db: Arc<ThingsDatabase>,
517    #[allow(dead_code)]
518    cache: Arc<Mutex<ThingsCache>>,
519    #[allow(dead_code)]
520    performance_monitor: Arc<Mutex<PerformanceMonitor>>,
521    #[allow(dead_code)]
522    exporter: DataExporter,
523    #[allow(dead_code)]
524    backup_manager: Arc<Mutex<BackupManager>>,
525    /// Middleware chain for cross-cutting concerns
526    middleware_chain: MiddlewareChain,
527}
528
529#[allow(dead_code)]
530/// Start the MCP server
531///
532/// # Errors
533/// Returns an error if the server fails to start
534pub async fn start_mcp_server(
535    db: Arc<ThingsDatabase>,
536    config: ThingsConfig,
537) -> things3_core::Result<()> {
538    let io = StdIo::new();
539    start_mcp_server_generic(db, config, io).await
540}
541
542/// Generic MCP server implementation that works with any I/O implementation
543///
544/// This function is generic over the I/O layer, allowing it to work with both
545/// production stdin/stdout (via `StdIo`) and test mocks (via `MockIo`).
546pub async fn start_mcp_server_generic<I: McpIo>(
547    db: Arc<ThingsDatabase>,
548    config: ThingsConfig,
549    mut io: I,
550) -> things3_core::Result<()> {
551    let server = Arc::new(tokio::sync::Mutex::new(ThingsMcpServer::new(db, config)));
552
553    // Read JSON-RPC requests line by line
554    loop {
555        // Read a line from input
556        let line = io.read_line().await.map_err(|e| {
557            things3_core::ThingsError::unknown(format!("Failed to read from input: {}", e))
558        })?;
559
560        // EOF reached
561        let Some(line) = line else {
562            break;
563        };
564
565        // Skip empty lines
566        if line.is_empty() {
567            continue;
568        }
569
570        // Parse JSON-RPC request
571        let request: serde_json::Value = serde_json::from_str(&line).map_err(|e| {
572            things3_core::ThingsError::unknown(format!("Failed to parse JSON-RPC request: {}", e))
573        })?;
574
575        // Handle the request
576        let server_clone = Arc::clone(&server);
577        let response_opt = {
578            let server = server_clone.lock().await;
579            server.handle_jsonrpc_request(request).await
580        }?;
581
582        // Only write response if this is a request (not a notification)
583        if let Some(response) = response_opt {
584            let response_str = serde_json::to_string(&response).map_err(|e| {
585                things3_core::ThingsError::unknown(format!("Failed to serialize response: {}", e))
586            })?;
587
588            io.write_line(&response_str).await.map_err(|e| {
589                things3_core::ThingsError::unknown(format!("Failed to write response: {}", e))
590            })?;
591
592            io.flush().await.map_err(|e| {
593                things3_core::ThingsError::unknown(format!("Failed to flush output: {}", e))
594            })?;
595        }
596        // Notifications don't require a response, so we silently continue
597    }
598
599    Ok(())
600}
601
602/// Start the MCP server with comprehensive configuration
603///
604/// # Arguments
605/// * `db` - Database connection
606/// * `mcp_config` - MCP server configuration
607///
608/// # Errors
609/// Returns an error if the server fails to start
610pub async fn start_mcp_server_with_config(
611    db: Arc<ThingsDatabase>,
612    mcp_config: McpServerConfig,
613) -> things3_core::Result<()> {
614    let io = StdIo::new();
615    start_mcp_server_with_config_generic(db, mcp_config, io).await
616}
617
618/// Generic MCP server with config implementation that works with any I/O implementation
619pub async fn start_mcp_server_with_config_generic<I: McpIo>(
620    db: Arc<ThingsDatabase>,
621    mcp_config: McpServerConfig,
622    mut io: I,
623) -> things3_core::Result<()> {
624    // Convert McpServerConfig to ThingsConfig for backward compatibility
625    let things_config = ThingsConfig::new(
626        mcp_config.database.path.clone(),
627        mcp_config.database.fallback_to_default,
628    );
629
630    let server = Arc::new(tokio::sync::Mutex::new(
631        ThingsMcpServer::new_with_mcp_config(db, things_config, mcp_config),
632    ));
633
634    // Read JSON-RPC requests line by line
635    loop {
636        // Read a line from input
637        let line = io.read_line().await.map_err(|e| {
638            things3_core::ThingsError::unknown(format!("Failed to read from input: {}", e))
639        })?;
640
641        // EOF reached
642        let Some(line) = line else {
643            break;
644        };
645
646        // Skip empty lines
647        if line.is_empty() {
648            continue;
649        }
650
651        // Parse JSON-RPC request
652        let request: serde_json::Value = serde_json::from_str(&line).map_err(|e| {
653            things3_core::ThingsError::unknown(format!("Failed to parse JSON-RPC request: {}", e))
654        })?;
655
656        // Handle the request
657        let server_clone = Arc::clone(&server);
658        let response_opt = {
659            let server = server_clone.lock().await;
660            server.handle_jsonrpc_request(request).await
661        }?;
662
663        // Only write response if this is a request (not a notification)
664        if let Some(response) = response_opt {
665            let response_str = serde_json::to_string(&response).map_err(|e| {
666                things3_core::ThingsError::unknown(format!("Failed to serialize response: {}", e))
667            })?;
668
669            io.write_line(&response_str).await.map_err(|e| {
670                things3_core::ThingsError::unknown(format!("Failed to write response: {}", e))
671            })?;
672
673            io.flush().await.map_err(|e| {
674                things3_core::ThingsError::unknown(format!("Failed to flush output: {}", e))
675            })?;
676        }
677        // Notifications don't require a response, so we silently continue
678    }
679
680    Ok(())
681}
682
683impl ThingsMcpServer {
684    #[must_use]
685    pub fn new(db: Arc<ThingsDatabase>, config: ThingsConfig) -> Self {
686        let cache = ThingsCache::new_default();
687        let performance_monitor = PerformanceMonitor::new_default();
688        let exporter = DataExporter::new_default();
689        let backup_manager = BackupManager::new(config);
690        // Use silent middleware config for MCP mode (no logging to stdout)
691        let mut middleware_config = MiddlewareConfig::default();
692        middleware_config.logging.enabled = false; // Disable logging to prevent stdout interference
693        let middleware_chain = middleware_config.build_chain();
694
695        Self {
696            db,
697            cache: Arc::new(Mutex::new(cache)),
698            performance_monitor: Arc::new(Mutex::new(performance_monitor)),
699            exporter,
700            backup_manager: Arc::new(Mutex::new(backup_manager)),
701            middleware_chain,
702        }
703    }
704
705    /// Create a new MCP server with custom middleware configuration
706    #[must_use]
707    pub fn with_middleware_config(
708        db: ThingsDatabase,
709        config: ThingsConfig,
710        middleware_config: MiddlewareConfig,
711    ) -> Self {
712        let cache = ThingsCache::new_default();
713        let performance_monitor = PerformanceMonitor::new_default();
714        let exporter = DataExporter::new_default();
715        let backup_manager = BackupManager::new(config);
716        let middleware_chain = middleware_config.build_chain();
717
718        Self {
719            db: Arc::new(db),
720            cache: Arc::new(Mutex::new(cache)),
721            performance_monitor: Arc::new(Mutex::new(performance_monitor)),
722            exporter,
723            backup_manager: Arc::new(Mutex::new(backup_manager)),
724            middleware_chain,
725        }
726    }
727
728    /// Create a new MCP server with comprehensive configuration
729    #[must_use]
730    pub fn new_with_mcp_config(
731        db: Arc<ThingsDatabase>,
732        config: ThingsConfig,
733        mcp_config: McpServerConfig,
734    ) -> Self {
735        let cache = ThingsCache::new_default();
736        let performance_monitor = PerformanceMonitor::new_default();
737        let exporter = DataExporter::new_default();
738        let backup_manager = BackupManager::new(config);
739
740        // Convert McpServerConfig to MiddlewareConfig
741        // Always disable logging in MCP mode to prevent stdout interference with JSON-RPC
742        let middleware_config = MiddlewareConfig {
743            logging: middleware::LoggingConfig {
744                enabled: false, // Always disabled in MCP mode for JSON-RPC compatibility
745                level: mcp_config.logging.level.clone(),
746            },
747            validation: middleware::ValidationConfig {
748                enabled: mcp_config.security.validation.enabled,
749                strict_mode: mcp_config.security.validation.strict_mode,
750            },
751            performance: middleware::PerformanceConfig {
752                enabled: mcp_config.performance.enabled,
753                slow_request_threshold_ms: mcp_config.performance.slow_request_threshold_ms,
754            },
755            security: middleware::SecurityConfig {
756                authentication: middleware::AuthenticationConfig {
757                    enabled: mcp_config.security.authentication.enabled,
758                    require_auth: mcp_config.security.authentication.require_auth,
759                    jwt_secret: mcp_config.security.authentication.jwt_secret,
760                    api_keys: mcp_config
761                        .security
762                        .authentication
763                        .api_keys
764                        .iter()
765                        .map(|key| middleware::ApiKeyConfig {
766                            key: key.key.clone(),
767                            key_id: key.key_id.clone(),
768                            permissions: key.permissions.clone(),
769                            expires_at: key.expires_at.clone(),
770                        })
771                        .collect(),
772                    oauth: mcp_config
773                        .security
774                        .authentication
775                        .oauth
776                        .as_ref()
777                        .map(|oauth| middleware::OAuth2Config {
778                            client_id: oauth.client_id.clone(),
779                            client_secret: oauth.client_secret.clone(),
780                            token_endpoint: oauth.token_endpoint.clone(),
781                            scopes: oauth.scopes.clone(),
782                        }),
783                },
784                rate_limiting: middleware::RateLimitingConfig {
785                    enabled: mcp_config.security.rate_limiting.enabled,
786                    requests_per_minute: mcp_config.security.rate_limiting.requests_per_minute,
787                    burst_limit: mcp_config.security.rate_limiting.burst_limit,
788                    custom_limits: mcp_config.security.rate_limiting.custom_limits.clone(),
789                },
790            },
791        };
792
793        let middleware_chain = middleware_config.build_chain();
794
795        Self {
796            db,
797            cache: Arc::new(Mutex::new(cache)),
798            performance_monitor: Arc::new(Mutex::new(performance_monitor)),
799            exporter,
800            backup_manager: Arc::new(Mutex::new(backup_manager)),
801            middleware_chain,
802        }
803    }
804
805    /// Get the middleware chain for inspection or modification
806    #[must_use]
807    pub fn middleware_chain(&self) -> &MiddlewareChain {
808        &self.middleware_chain
809    }
810
811    /// List available MCP tools
812    ///
813    /// # Errors
814    /// Returns an error if tool generation fails
815    pub fn list_tools(&self) -> McpResult<ListToolsResult> {
816        Ok(ListToolsResult {
817            tools: Self::get_available_tools(),
818        })
819    }
820
821    /// Call a specific MCP tool
822    ///
823    /// # Errors
824    /// Returns an error if tool execution fails or tool is not found
825    pub async fn call_tool(&self, request: CallToolRequest) -> McpResult<CallToolResult> {
826        self.middleware_chain
827            .execute(
828                request,
829                |req| async move { self.handle_tool_call(req).await },
830            )
831            .await
832    }
833
834    /// Call a specific MCP tool with fallback error handling
835    ///
836    /// This method provides backward compatibility by converting `McpError` to `CallToolResult`
837    /// for cases where the caller expects a `CallToolResult` even on error
838    pub async fn call_tool_with_fallback(&self, request: CallToolRequest) -> CallToolResult {
839        match self.handle_tool_call(request).await {
840            Ok(result) => result,
841            Err(error) => error.to_call_result(),
842        }
843    }
844
845    /// List available MCP resources
846    ///
847    /// # Errors
848    /// Returns an error if resource generation fails
849    pub fn list_resources(&self) -> McpResult<ListResourcesResult> {
850        Ok(ListResourcesResult {
851            resources: Self::get_available_resources(),
852        })
853    }
854
855    /// Read a specific MCP resource
856    ///
857    /// # Errors
858    /// Returns an error if resource reading fails or resource is not found
859    pub async fn read_resource(
860        &self,
861        request: ReadResourceRequest,
862    ) -> McpResult<ReadResourceResult> {
863        self.handle_resource_read(request).await
864    }
865
866    /// Read a specific MCP resource with fallback error handling
867    ///
868    /// This method provides backward compatibility by converting `McpError` to `ReadResourceResult`
869    /// for cases where the caller expects a `ReadResourceResult` even on error
870    pub async fn read_resource_with_fallback(
871        &self,
872        request: ReadResourceRequest,
873    ) -> ReadResourceResult {
874        match self.handle_resource_read(request).await {
875            Ok(result) => result,
876            Err(error) => error.to_resource_result(),
877        }
878    }
879
880    /// List available MCP prompts
881    ///
882    /// # Errors
883    /// Returns an error if prompt generation fails
884    pub fn list_prompts(&self) -> McpResult<ListPromptsResult> {
885        Ok(ListPromptsResult {
886            prompts: Self::get_available_prompts(),
887        })
888    }
889
890    /// Get a specific MCP prompt with arguments
891    ///
892    /// # Errors
893    /// Returns an error if prompt retrieval fails or prompt is not found
894    pub async fn get_prompt(&self, request: GetPromptRequest) -> McpResult<GetPromptResult> {
895        self.handle_prompt_request(request).await
896    }
897
898    /// Get a specific MCP prompt with fallback error handling
899    ///
900    /// This method provides backward compatibility by converting `McpError` to `GetPromptResult`
901    /// for cases where the caller expects a `GetPromptResult` even on error
902    pub async fn get_prompt_with_fallback(&self, request: GetPromptRequest) -> GetPromptResult {
903        match self.handle_prompt_request(request).await {
904            Ok(result) => result,
905            Err(error) => error.to_prompt_result(),
906        }
907    }
908
909    /// Get available MCP tools
910    fn get_available_tools() -> Vec<Tool> {
911        let mut tools = Vec::new();
912        tools.extend(Self::get_data_retrieval_tools());
913        tools.extend(Self::get_task_management_tools());
914        tools.extend(Self::get_bulk_operation_tools());
915        tools.extend(Self::get_tag_management_tools());
916        tools.extend(Self::get_analytics_tools());
917        tools.extend(Self::get_backup_tools());
918        tools.extend(Self::get_system_tools());
919        tools
920    }
921
922    fn get_data_retrieval_tools() -> Vec<Tool> {
923        vec![
924            Tool {
925                name: "get_inbox".to_string(),
926                description: "Get tasks from the inbox".to_string(),
927                input_schema: serde_json::json!({
928                    "type": "object",
929                    "properties": {
930                        "limit": {
931                            "type": "integer",
932                            "description": "Maximum number of tasks to return"
933                        }
934                    }
935                }),
936            },
937            Tool {
938                name: "get_today".to_string(),
939                description: "Get tasks scheduled for today".to_string(),
940                input_schema: serde_json::json!({
941                    "type": "object",
942                    "properties": {
943                        "limit": {
944                            "type": "integer",
945                            "description": "Maximum number of tasks to return"
946                        }
947                    }
948                }),
949            },
950            Tool {
951                name: "get_projects".to_string(),
952                description: "Get all projects, optionally filtered by area".to_string(),
953                input_schema: serde_json::json!({
954                    "type": "object",
955                    "properties": {
956                        "area_uuid": {
957                            "type": "string",
958                            "description": "Optional area UUID to filter projects"
959                        }
960                    }
961                }),
962            },
963            Tool {
964                name: "get_areas".to_string(),
965                description: "Get all areas".to_string(),
966                input_schema: serde_json::json!({
967                    "type": "object",
968                    "properties": {}
969                }),
970            },
971            Tool {
972                name: "search_tasks".to_string(),
973                description: "Search for tasks by query".to_string(),
974                input_schema: serde_json::json!({
975                    "type": "object",
976                    "properties": {
977                        "query": {
978                            "type": "string",
979                            "description": "Search query"
980                        },
981                        "limit": {
982                            "type": "integer",
983                            "description": "Maximum number of tasks to return"
984                        }
985                    },
986                    "required": ["query"]
987                }),
988            },
989            Tool {
990                name: "get_recent_tasks".to_string(),
991                description: "Get recently created or modified tasks".to_string(),
992                input_schema: serde_json::json!({
993                    "type": "object",
994                    "properties": {
995                        "limit": {
996                            "type": "integer",
997                            "description": "Maximum number of tasks to return"
998                        },
999                        "hours": {
1000                            "type": "integer",
1001                            "description": "Number of hours to look back"
1002                        }
1003                    }
1004                }),
1005            },
1006            Tool {
1007                name: "logbook_search".to_string(),
1008                description: "Search completed tasks in the Things 3 logbook. Supports text search, date ranges, and filtering by project/area/tags.".to_string(),
1009                input_schema: serde_json::json!({
1010                    "type": "object",
1011                    "properties": {
1012                        "search_text": {
1013                            "type": "string",
1014                            "description": "Search in task titles and notes (case-insensitive)"
1015                        },
1016                        "from_date": {
1017                            "type": "string",
1018                            "format": "date",
1019                            "description": "Start date for completion date range (YYYY-MM-DD)"
1020                        },
1021                        "to_date": {
1022                            "type": "string",
1023                            "format": "date",
1024                            "description": "End date for completion date range (YYYY-MM-DD)"
1025                        },
1026                        "project_uuid": {
1027                            "type": "string",
1028                            "format": "uuid",
1029                            "description": "Filter by project UUID"
1030                        },
1031                        "area_uuid": {
1032                            "type": "string",
1033                            "format": "uuid",
1034                            "description": "Filter by area UUID"
1035                        },
1036                        "tags": {
1037                            "type": "array",
1038                            "items": { "type": "string" },
1039                            "description": "Filter by one or more tags (all must match)"
1040                        },
1041                        "limit": {
1042                            "type": "integer",
1043                            "default": 50,
1044                            "minimum": 1,
1045                            "maximum": 500,
1046                            "description": "Maximum number of results to return (default: 50, max: 500)"
1047                        }
1048                    }
1049                }),
1050            },
1051        ]
1052    }
1053
1054    fn get_task_management_tools() -> Vec<Tool> {
1055        vec![
1056            Tool {
1057                name: "create_task".to_string(),
1058                description: "Create a new task in Things 3".to_string(),
1059                input_schema: serde_json::json!({
1060                    "type": "object",
1061                    "properties": {
1062                        "title": {
1063                            "type": "string",
1064                            "description": "Task title (required)"
1065                        },
1066                        "task_type": {
1067                            "type": "string",
1068                            "enum": ["to-do", "project", "heading"],
1069                            "description": "Task type (default: to-do)"
1070                        },
1071                        "notes": {
1072                            "type": "string",
1073                            "description": "Task notes"
1074                        },
1075                        "start_date": {
1076                            "type": "string",
1077                            "format": "date",
1078                            "description": "Start date (YYYY-MM-DD)"
1079                        },
1080                        "deadline": {
1081                            "type": "string",
1082                            "format": "date",
1083                            "description": "Deadline (YYYY-MM-DD)"
1084                        },
1085                        "project_uuid": {
1086                            "type": "string",
1087                            "format": "uuid",
1088                            "description": "Project UUID"
1089                        },
1090                        "area_uuid": {
1091                            "type": "string",
1092                            "format": "uuid",
1093                            "description": "Area UUID"
1094                        },
1095                        "parent_uuid": {
1096                            "type": "string",
1097                            "format": "uuid",
1098                            "description": "Parent task UUID (for subtasks)"
1099                        },
1100                        "tags": {
1101                            "type": "array",
1102                            "items": {"type": "string"},
1103                            "description": "Tag names"
1104                        },
1105                        "status": {
1106                            "type": "string",
1107                            "enum": ["incomplete", "completed", "canceled", "trashed"],
1108                            "description": "Initial status (default: incomplete)"
1109                        }
1110                    },
1111                    "required": ["title"]
1112                }),
1113            },
1114            Tool {
1115                name: "update_task".to_string(),
1116                description: "Update an existing task (only provided fields will be updated)"
1117                    .to_string(),
1118                input_schema: serde_json::json!({
1119                    "type": "object",
1120                    "properties": {
1121                        "uuid": {
1122                            "type": "string",
1123                            "format": "uuid",
1124                            "description": "Task UUID (required)"
1125                        },
1126                        "title": {
1127                            "type": "string",
1128                            "description": "New task title"
1129                        },
1130                        "notes": {
1131                            "type": "string",
1132                            "description": "New task notes"
1133                        },
1134                        "start_date": {
1135                            "type": "string",
1136                            "format": "date",
1137                            "description": "New start date (YYYY-MM-DD)"
1138                        },
1139                        "deadline": {
1140                            "type": "string",
1141                            "format": "date",
1142                            "description": "New deadline (YYYY-MM-DD)"
1143                        },
1144                        "status": {
1145                            "type": "string",
1146                            "enum": ["incomplete", "completed", "canceled", "trashed"],
1147                            "description": "New task status"
1148                        },
1149                        "project_uuid": {
1150                            "type": "string",
1151                            "format": "uuid",
1152                            "description": "New project UUID"
1153                        },
1154                        "area_uuid": {
1155                            "type": "string",
1156                            "format": "uuid",
1157                            "description": "New area UUID"
1158                        },
1159                        "tags": {
1160                            "type": "array",
1161                            "items": {"type": "string"},
1162                            "description": "New tag names"
1163                        }
1164                    },
1165                    "required": ["uuid"]
1166                }),
1167            },
1168            Tool {
1169                name: "complete_task".to_string(),
1170                description: "Mark a task as completed".to_string(),
1171                input_schema: serde_json::json!({
1172                    "type": "object",
1173                    "properties": {
1174                        "uuid": {
1175                            "type": "string",
1176                            "format": "uuid",
1177                            "description": "UUID of the task to complete"
1178                        }
1179                    },
1180                    "required": ["uuid"]
1181                }),
1182            },
1183            Tool {
1184                name: "uncomplete_task".to_string(),
1185                description: "Mark a completed task as incomplete".to_string(),
1186                input_schema: serde_json::json!({
1187                    "type": "object",
1188                    "properties": {
1189                        "uuid": {
1190                            "type": "string",
1191                            "format": "uuid",
1192                            "description": "UUID of the task to mark incomplete"
1193                        }
1194                    },
1195                    "required": ["uuid"]
1196                }),
1197            },
1198            Tool {
1199                name: "delete_task".to_string(),
1200                description: "Soft delete a task (set trashed=1)".to_string(),
1201                input_schema: serde_json::json!({
1202                    "type": "object",
1203                    "properties": {
1204                        "uuid": {
1205                            "type": "string",
1206                            "format": "uuid",
1207                            "description": "UUID of the task to delete"
1208                        },
1209                        "child_handling": {
1210                            "type": "string",
1211                            "enum": ["error", "cascade", "orphan"],
1212                            "default": "error",
1213                            "description": "How to handle child tasks: error (fail if children exist), cascade (delete children too), orphan (delete parent only)"
1214                        }
1215                    },
1216                    "required": ["uuid"]
1217                }),
1218            },
1219            Tool {
1220                name: "bulk_create_tasks".to_string(),
1221                description: "Create multiple tasks at once".to_string(),
1222                input_schema: serde_json::json!({
1223                    "type": "object",
1224                    "properties": {
1225                        "tasks": {
1226                            "type": "array",
1227                            "description": "Array of task objects to create",
1228                            "items": {
1229                                "type": "object",
1230                                "properties": {
1231                                    "title": {"type": "string"},
1232                                    "notes": {"type": "string"},
1233                                    "project_uuid": {"type": "string"},
1234                                    "area_uuid": {"type": "string"}
1235                                },
1236                                "required": ["title"]
1237                            }
1238                        }
1239                    },
1240                    "required": ["tasks"]
1241                }),
1242            },
1243            Tool {
1244                name: "create_project".to_string(),
1245                description: "Create a new project (a task with type=project)".to_string(),
1246                input_schema: serde_json::json!({
1247                    "type": "object",
1248                    "properties": {
1249                        "title": {
1250                            "type": "string",
1251                            "description": "Project title (required)"
1252                        },
1253                        "notes": {
1254                            "type": "string",
1255                            "description": "Project notes"
1256                        },
1257                        "area_uuid": {
1258                            "type": "string",
1259                            "format": "uuid",
1260                            "description": "Area UUID"
1261                        },
1262                        "start_date": {
1263                            "type": "string",
1264                            "format": "date",
1265                            "description": "Start date (YYYY-MM-DD)"
1266                        },
1267                        "deadline": {
1268                            "type": "string",
1269                            "format": "date",
1270                            "description": "Deadline (YYYY-MM-DD)"
1271                        },
1272                        "tags": {
1273                            "type": "array",
1274                            "items": {"type": "string"},
1275                            "description": "Tag names"
1276                        }
1277                    },
1278                    "required": ["title"]
1279                }),
1280            },
1281            Tool {
1282                name: "update_project".to_string(),
1283                description: "Update an existing project (only provided fields will be updated)".to_string(),
1284                input_schema: serde_json::json!({
1285                    "type": "object",
1286                    "properties": {
1287                        "uuid": {
1288                            "type": "string",
1289                            "format": "uuid",
1290                            "description": "Project UUID (required)"
1291                        },
1292                        "title": {
1293                            "type": "string",
1294                            "description": "New project title"
1295                        },
1296                        "notes": {
1297                            "type": "string",
1298                            "description": "New project notes"
1299                        },
1300                        "area_uuid": {
1301                            "type": "string",
1302                            "format": "uuid",
1303                            "description": "New area UUID"
1304                        },
1305                        "start_date": {
1306                            "type": "string",
1307                            "format": "date",
1308                            "description": "New start date (YYYY-MM-DD)"
1309                        },
1310                        "deadline": {
1311                            "type": "string",
1312                            "format": "date",
1313                            "description": "New deadline (YYYY-MM-DD)"
1314                        },
1315                        "tags": {
1316                            "type": "array",
1317                            "items": {"type": "string"},
1318                            "description": "New tag names"
1319                        }
1320                    },
1321                    "required": ["uuid"]
1322                }),
1323            },
1324            Tool {
1325                name: "complete_project".to_string(),
1326                description: "Mark a project as completed, with options for handling child tasks".to_string(),
1327                input_schema: serde_json::json!({
1328                    "type": "object",
1329                    "properties": {
1330                        "uuid": {
1331                            "type": "string",
1332                            "format": "uuid",
1333                            "description": "UUID of the project to complete"
1334                        },
1335                        "child_handling": {
1336                            "type": "string",
1337                            "enum": ["error", "cascade", "orphan"],
1338                            "default": "error",
1339                            "description": "How to handle child tasks: error (fail if children exist), cascade (complete children too), orphan (move children to inbox)"
1340                        }
1341                    },
1342                    "required": ["uuid"]
1343                }),
1344            },
1345            Tool {
1346                name: "delete_project".to_string(),
1347                description: "Soft delete a project (set trashed=1), with options for handling child tasks".to_string(),
1348                input_schema: serde_json::json!({
1349                    "type": "object",
1350                    "properties": {
1351                        "uuid": {
1352                            "type": "string",
1353                            "format": "uuid",
1354                            "description": "UUID of the project to delete"
1355                        },
1356                        "child_handling": {
1357                            "type": "string",
1358                            "enum": ["error", "cascade", "orphan"],
1359                            "default": "error",
1360                            "description": "How to handle child tasks: error (fail if children exist), cascade (delete children too), orphan (move children to inbox)"
1361                        }
1362                    },
1363                    "required": ["uuid"]
1364                }),
1365            },
1366            Tool {
1367                name: "create_area".to_string(),
1368                description: "Create a new area".to_string(),
1369                input_schema: serde_json::json!({
1370                    "type": "object",
1371                    "properties": {
1372                        "title": {
1373                            "type": "string",
1374                            "description": "Area title (required)"
1375                        }
1376                    },
1377                    "required": ["title"]
1378                }),
1379            },
1380            Tool {
1381                name: "update_area".to_string(),
1382                description: "Update an existing area".to_string(),
1383                input_schema: serde_json::json!({
1384                    "type": "object",
1385                    "properties": {
1386                        "uuid": {
1387                            "type": "string",
1388                            "format": "uuid",
1389                            "description": "Area UUID (required)"
1390                        },
1391                        "title": {
1392                            "type": "string",
1393                            "description": "New area title (required)"
1394                        }
1395                    },
1396                    "required": ["uuid", "title"]
1397                }),
1398            },
1399            Tool {
1400                name: "delete_area".to_string(),
1401                description: "Delete an area (hard delete). All projects in this area will be moved to no area.".to_string(),
1402                input_schema: serde_json::json!({
1403                    "type": "object",
1404                    "properties": {
1405                        "uuid": {
1406                            "type": "string",
1407                            "format": "uuid",
1408                            "description": "UUID of the area to delete"
1409                        }
1410                    },
1411                    "required": ["uuid"]
1412                }),
1413            },
1414        ]
1415    }
1416
1417    fn get_analytics_tools() -> Vec<Tool> {
1418        vec![
1419            Tool {
1420                name: "get_productivity_metrics".to_string(),
1421                description: "Get productivity metrics and statistics".to_string(),
1422                input_schema: serde_json::json!({
1423                    "type": "object",
1424                    "properties": {
1425                        "days": {
1426                            "type": "integer",
1427                            "description": "Number of days to look back for metrics"
1428                        }
1429                    }
1430                }),
1431            },
1432            Tool {
1433                name: "export_data".to_string(),
1434                description: "Export data in various formats".to_string(),
1435                input_schema: serde_json::json!({
1436                    "type": "object",
1437                    "properties": {
1438                        "format": {
1439                            "type": "string",
1440                            "description": "Export format",
1441                            "enum": ["json", "csv", "markdown"]
1442                        },
1443                        "data_type": {
1444                            "type": "string",
1445                            "description": "Type of data to export",
1446                            "enum": ["tasks", "projects", "areas", "all"]
1447                        }
1448                    },
1449                    "required": ["format", "data_type"]
1450                }),
1451            },
1452        ]
1453    }
1454
1455    fn get_backup_tools() -> Vec<Tool> {
1456        vec![
1457            Tool {
1458                name: "backup_database".to_string(),
1459                description: "Create a backup of the Things 3 database".to_string(),
1460                input_schema: serde_json::json!({
1461                    "type": "object",
1462                    "properties": {
1463                        "backup_dir": {
1464                            "type": "string",
1465                            "description": "Directory to store the backup"
1466                        },
1467                        "description": {
1468                            "type": "string",
1469                            "description": "Optional description for the backup"
1470                        }
1471                    },
1472                    "required": ["backup_dir"]
1473                }),
1474            },
1475            Tool {
1476                name: "restore_database".to_string(),
1477                description: "Restore from a backup".to_string(),
1478                input_schema: serde_json::json!({
1479                    "type": "object",
1480                    "properties": {
1481                        "backup_path": {
1482                            "type": "string",
1483                            "description": "Path to the backup file"
1484                        }
1485                    },
1486                    "required": ["backup_path"]
1487                }),
1488            },
1489            Tool {
1490                name: "list_backups".to_string(),
1491                description: "List available backups".to_string(),
1492                input_schema: serde_json::json!({
1493                    "type": "object",
1494                    "properties": {
1495                        "backup_dir": {
1496                            "type": "string",
1497                            "description": "Directory containing backups"
1498                        }
1499                    },
1500                    "required": ["backup_dir"]
1501                }),
1502            },
1503        ]
1504    }
1505
1506    fn get_system_tools() -> Vec<Tool> {
1507        vec![
1508            Tool {
1509                name: "get_performance_stats".to_string(),
1510                description: "Get performance statistics and metrics".to_string(),
1511                input_schema: serde_json::json!({
1512                    "type": "object",
1513                    "properties": {}
1514                }),
1515            },
1516            Tool {
1517                name: "get_system_metrics".to_string(),
1518                description: "Get current system resource metrics".to_string(),
1519                input_schema: serde_json::json!({
1520                    "type": "object",
1521                    "properties": {}
1522                }),
1523            },
1524            Tool {
1525                name: "get_cache_stats".to_string(),
1526                description: "Get cache statistics and hit rates".to_string(),
1527                input_schema: serde_json::json!({
1528                    "type": "object",
1529                    "properties": {}
1530                }),
1531            },
1532        ]
1533    }
1534
1535    fn get_bulk_operation_tools() -> Vec<Tool> {
1536        vec![
1537            Tool {
1538                name: "bulk_move".to_string(),
1539                description: "Move multiple tasks to a project or area (transactional)".to_string(),
1540                input_schema: serde_json::json!({
1541                    "type": "object",
1542                    "properties": {
1543                        "task_uuids": {
1544                            "type": "array",
1545                            "items": {"type": "string"},
1546                            "description": "Array of task UUIDs to move"
1547                        },
1548                        "project_uuid": {
1549                            "type": "string",
1550                            "format": "uuid",
1551                            "description": "Target project UUID (optional)"
1552                        },
1553                        "area_uuid": {
1554                            "type": "string",
1555                            "format": "uuid",
1556                            "description": "Target area UUID (optional)"
1557                        }
1558                    },
1559                    "required": ["task_uuids"]
1560                }),
1561            },
1562            Tool {
1563                name: "bulk_update_dates".to_string(),
1564                description: "Update dates for multiple tasks with validation (transactional)"
1565                    .to_string(),
1566                input_schema: serde_json::json!({
1567                    "type": "object",
1568                    "properties": {
1569                        "task_uuids": {
1570                            "type": "array",
1571                            "items": {"type": "string"},
1572                            "description": "Array of task UUIDs to update"
1573                        },
1574                        "start_date": {
1575                            "type": "string",
1576                            "format": "date",
1577                            "description": "New start date (YYYY-MM-DD, optional)"
1578                        },
1579                        "deadline": {
1580                            "type": "string",
1581                            "format": "date",
1582                            "description": "New deadline (YYYY-MM-DD, optional)"
1583                        },
1584                        "clear_start_date": {
1585                            "type": "boolean",
1586                            "description": "Clear start date (set to NULL, default: false)"
1587                        },
1588                        "clear_deadline": {
1589                            "type": "boolean",
1590                            "description": "Clear deadline (set to NULL, default: false)"
1591                        }
1592                    },
1593                    "required": ["task_uuids"]
1594                }),
1595            },
1596            Tool {
1597                name: "bulk_complete".to_string(),
1598                description: "Mark multiple tasks as completed (transactional)".to_string(),
1599                input_schema: serde_json::json!({
1600                    "type": "object",
1601                    "properties": {
1602                        "task_uuids": {
1603                            "type": "array",
1604                            "items": {"type": "string"},
1605                            "description": "Array of task UUIDs to complete"
1606                        }
1607                    },
1608                    "required": ["task_uuids"]
1609                }),
1610            },
1611            Tool {
1612                name: "bulk_delete".to_string(),
1613                description: "Delete multiple tasks (soft delete, transactional)".to_string(),
1614                input_schema: serde_json::json!({
1615                    "type": "object",
1616                    "properties": {
1617                        "task_uuids": {
1618                            "type": "array",
1619                            "items": {"type": "string"},
1620                            "description": "Array of task UUIDs to delete"
1621                        }
1622                    },
1623                    "required": ["task_uuids"]
1624                }),
1625            },
1626        ]
1627    }
1628
1629    fn get_tag_management_tools() -> Vec<Tool> {
1630        vec![
1631            // Tag Discovery Tools
1632            Tool {
1633                name: "search_tags".to_string(),
1634                description: "Search for existing tags (finds exact and similar matches)"
1635                    .to_string(),
1636                input_schema: serde_json::json!({
1637                    "type": "object",
1638                    "properties": {
1639                        "query": {
1640                            "type": "string",
1641                            "description": "Search query for tag titles"
1642                        },
1643                        "include_similar": {
1644                            "type": "boolean",
1645                            "description": "Include fuzzy matches (default: true)"
1646                        },
1647                        "min_similarity": {
1648                            "type": "number",
1649                            "description": "Minimum similarity score 0.0-1.0 (default: 0.7)"
1650                        }
1651                    },
1652                    "required": ["query"]
1653                }),
1654            },
1655            Tool {
1656                name: "get_tag_suggestions".to_string(),
1657                description: "Get tag suggestions for a title (prevents duplicates)".to_string(),
1658                input_schema: serde_json::json!({
1659                    "type": "object",
1660                    "properties": {
1661                        "title": {
1662                            "type": "string",
1663                            "description": "Proposed tag title"
1664                        }
1665                    },
1666                    "required": ["title"]
1667                }),
1668            },
1669            Tool {
1670                name: "get_popular_tags".to_string(),
1671                description: "Get most frequently used tags".to_string(),
1672                input_schema: serde_json::json!({
1673                    "type": "object",
1674                    "properties": {
1675                        "limit": {
1676                            "type": "integer",
1677                            "description": "Maximum number of tags to return (default: 20)"
1678                        }
1679                    }
1680                }),
1681            },
1682            Tool {
1683                name: "get_recent_tags".to_string(),
1684                description: "Get recently used tags".to_string(),
1685                input_schema: serde_json::json!({
1686                    "type": "object",
1687                    "properties": {
1688                        "limit": {
1689                            "type": "integer",
1690                            "description": "Maximum number of tags to return (default: 20)"
1691                        }
1692                    }
1693                }),
1694            },
1695            // Tag CRUD Operations
1696            Tool {
1697                name: "create_tag".to_string(),
1698                description: "Create a new tag (checks for duplicates first)".to_string(),
1699                input_schema: serde_json::json!({
1700                    "type": "object",
1701                    "properties": {
1702                        "title": {
1703                            "type": "string",
1704                            "description": "Tag title (required)"
1705                        },
1706                        "shortcut": {
1707                            "type": "string",
1708                            "description": "Keyboard shortcut"
1709                        },
1710                        "parent_uuid": {
1711                            "type": "string",
1712                            "format": "uuid",
1713                            "description": "Parent tag UUID for nesting"
1714                        },
1715                        "force": {
1716                            "type": "boolean",
1717                            "description": "Skip duplicate check (default: false)"
1718                        }
1719                    },
1720                    "required": ["title"]
1721                }),
1722            },
1723            Tool {
1724                name: "update_tag".to_string(),
1725                description: "Update an existing tag".to_string(),
1726                input_schema: serde_json::json!({
1727                    "type": "object",
1728                    "properties": {
1729                        "uuid": {
1730                            "type": "string",
1731                            "format": "uuid",
1732                            "description": "Tag UUID (required)"
1733                        },
1734                        "title": {
1735                            "type": "string",
1736                            "description": "New title"
1737                        },
1738                        "shortcut": {
1739                            "type": "string",
1740                            "description": "New shortcut"
1741                        },
1742                        "parent_uuid": {
1743                            "type": "string",
1744                            "format": "uuid",
1745                            "description": "New parent UUID"
1746                        }
1747                    },
1748                    "required": ["uuid"]
1749                }),
1750            },
1751            Tool {
1752                name: "delete_tag".to_string(),
1753                description: "Delete a tag".to_string(),
1754                input_schema: serde_json::json!({
1755                    "type": "object",
1756                    "properties": {
1757                        "uuid": {
1758                            "type": "string",
1759                            "format": "uuid",
1760                            "description": "Tag UUID (required)"
1761                        },
1762                        "remove_from_tasks": {
1763                            "type": "boolean",
1764                            "description": "Remove tag from all tasks (default: false)"
1765                        }
1766                    },
1767                    "required": ["uuid"]
1768                }),
1769            },
1770            Tool {
1771                name: "merge_tags".to_string(),
1772                description: "Merge two tags (combine source into target)".to_string(),
1773                input_schema: serde_json::json!({
1774                    "type": "object",
1775                    "properties": {
1776                        "source_uuid": {
1777                            "type": "string",
1778                            "format": "uuid",
1779                            "description": "UUID of tag to merge from (will be deleted)"
1780                        },
1781                        "target_uuid": {
1782                            "type": "string",
1783                            "format": "uuid",
1784                            "description": "UUID of tag to merge into (will remain)"
1785                        }
1786                    },
1787                    "required": ["source_uuid", "target_uuid"]
1788                }),
1789            },
1790            // Tag Assignment Tools
1791            Tool {
1792                name: "add_tag_to_task".to_string(),
1793                description: "Add a tag to a task (suggests existing tags)".to_string(),
1794                input_schema: serde_json::json!({
1795                    "type": "object",
1796                    "properties": {
1797                        "task_uuid": {
1798                            "type": "string",
1799                            "format": "uuid",
1800                            "description": "Task UUID (required)"
1801                        },
1802                        "tag_title": {
1803                            "type": "string",
1804                            "description": "Tag title (required)"
1805                        }
1806                    },
1807                    "required": ["task_uuid", "tag_title"]
1808                }),
1809            },
1810            Tool {
1811                name: "remove_tag_from_task".to_string(),
1812                description: "Remove a tag from a task".to_string(),
1813                input_schema: serde_json::json!({
1814                    "type": "object",
1815                    "properties": {
1816                        "task_uuid": {
1817                            "type": "string",
1818                            "format": "uuid",
1819                            "description": "Task UUID (required)"
1820                        },
1821                        "tag_title": {
1822                            "type": "string",
1823                            "description": "Tag title (required)"
1824                        }
1825                    },
1826                    "required": ["task_uuid", "tag_title"]
1827                }),
1828            },
1829            Tool {
1830                name: "set_task_tags".to_string(),
1831                description: "Replace all tags on a task".to_string(),
1832                input_schema: serde_json::json!({
1833                    "type": "object",
1834                    "properties": {
1835                        "task_uuid": {
1836                            "type": "string",
1837                            "format": "uuid",
1838                            "description": "Task UUID (required)"
1839                        },
1840                        "tag_titles": {
1841                            "type": "array",
1842                            "items": {"type": "string"},
1843                            "description": "Array of tag titles"
1844                        }
1845                    },
1846                    "required": ["task_uuid", "tag_titles"]
1847                }),
1848            },
1849            // Tag Analytics
1850            Tool {
1851                name: "get_tag_statistics".to_string(),
1852                description: "Get detailed statistics for a tag".to_string(),
1853                input_schema: serde_json::json!({
1854                    "type": "object",
1855                    "properties": {
1856                        "uuid": {
1857                            "type": "string",
1858                            "format": "uuid",
1859                            "description": "Tag UUID (required)"
1860                        }
1861                    },
1862                    "required": ["uuid"]
1863                }),
1864            },
1865            Tool {
1866                name: "find_duplicate_tags".to_string(),
1867                description: "Find duplicate or highly similar tags".to_string(),
1868                input_schema: serde_json::json!({
1869                    "type": "object",
1870                    "properties": {
1871                        "min_similarity": {
1872                            "type": "number",
1873                            "description": "Minimum similarity score 0.0-1.0 (default: 0.85)"
1874                        }
1875                    }
1876                }),
1877            },
1878            Tool {
1879                name: "get_tag_completions".to_string(),
1880                description: "Get tag auto-completions for partial input".to_string(),
1881                input_schema: serde_json::json!({
1882                    "type": "object",
1883                    "properties": {
1884                        "partial_input": {
1885                            "type": "string",
1886                            "description": "Partial tag input (required)"
1887                        },
1888                        "limit": {
1889                            "type": "integer",
1890                            "description": "Maximum completions to return (default: 10)"
1891                        }
1892                    },
1893                    "required": ["partial_input"]
1894                }),
1895            },
1896        ]
1897    }
1898
1899    /// Handle tool call
1900    async fn handle_tool_call(&self, request: CallToolRequest) -> McpResult<CallToolResult> {
1901        let tool_name = &request.name;
1902        let arguments = request.arguments.unwrap_or_default();
1903
1904        let result = match tool_name.as_str() {
1905            "get_inbox" => self.handle_get_inbox(arguments).await,
1906            "get_today" => self.handle_get_today(arguments).await,
1907            "get_projects" => self.handle_get_projects(arguments).await,
1908            "get_areas" => self.handle_get_areas(arguments).await,
1909            "search_tasks" => self.handle_search_tasks(arguments).await,
1910            "logbook_search" => self.handle_logbook_search(arguments).await,
1911            "create_task" => self.handle_create_task(arguments).await,
1912            "update_task" => self.handle_update_task(arguments).await,
1913            "complete_task" => self.handle_complete_task(arguments).await,
1914            "uncomplete_task" => self.handle_uncomplete_task(arguments).await,
1915            "delete_task" => self.handle_delete_task(arguments).await,
1916            "bulk_move" => self.handle_bulk_move(arguments).await,
1917            "bulk_update_dates" => self.handle_bulk_update_dates(arguments).await,
1918            "bulk_complete" => self.handle_bulk_complete(arguments).await,
1919            "bulk_delete" => self.handle_bulk_delete(arguments).await,
1920            "create_project" => self.handle_create_project(arguments).await,
1921            "update_project" => self.handle_update_project(arguments).await,
1922            "complete_project" => self.handle_complete_project(arguments).await,
1923            "delete_project" => self.handle_delete_project(arguments).await,
1924            "create_area" => self.handle_create_area(arguments).await,
1925            "update_area" => self.handle_update_area(arguments).await,
1926            "delete_area" => self.handle_delete_area(arguments).await,
1927            "get_productivity_metrics" => self.handle_get_productivity_metrics(arguments).await,
1928            "export_data" => self.handle_export_data(arguments).await,
1929            "bulk_create_tasks" => Self::handle_bulk_create_tasks(&arguments),
1930            "get_recent_tasks" => self.handle_get_recent_tasks(arguments).await,
1931            "backup_database" => self.handle_backup_database(arguments).await,
1932            "restore_database" => self.handle_restore_database(arguments).await,
1933            "list_backups" => self.handle_list_backups(arguments).await,
1934            "get_performance_stats" => self.handle_get_performance_stats(arguments).await,
1935            "get_system_metrics" => self.handle_get_system_metrics(arguments).await,
1936            "get_cache_stats" => self.handle_get_cache_stats(arguments).await,
1937            // Tag discovery tools
1938            "search_tags" => self.handle_search_tags_tool(arguments).await,
1939            "get_tag_suggestions" => self.handle_get_tag_suggestions(arguments).await,
1940            "get_popular_tags" => self.handle_get_popular_tags(arguments).await,
1941            "get_recent_tags" => self.handle_get_recent_tags(arguments).await,
1942            // Tag CRUD
1943            "create_tag" => self.handle_create_tag(arguments).await,
1944            "update_tag" => self.handle_update_tag(arguments).await,
1945            "delete_tag" => self.handle_delete_tag(arguments).await,
1946            "merge_tags" => self.handle_merge_tags(arguments).await,
1947            // Tag assignment
1948            "add_tag_to_task" => self.handle_add_tag_to_task(arguments).await,
1949            "remove_tag_from_task" => self.handle_remove_tag_from_task(arguments).await,
1950            "set_task_tags" => self.handle_set_task_tags(arguments).await,
1951            // Tag analytics
1952            "get_tag_statistics" => self.handle_get_tag_statistics(arguments).await,
1953            "find_duplicate_tags" => self.handle_find_duplicate_tags(arguments).await,
1954            "get_tag_completions" => self.handle_get_tag_completions(arguments).await,
1955            _ => {
1956                return Err(McpError::tool_not_found(tool_name));
1957            }
1958        };
1959
1960        result
1961    }
1962
1963    async fn handle_get_inbox(&self, args: Value) -> McpResult<CallToolResult> {
1964        let limit = args
1965            .get("limit")
1966            .and_then(serde_json::Value::as_u64)
1967            .map(|v| usize::try_from(v).unwrap_or(usize::MAX));
1968
1969        let tasks = self
1970            .db
1971            .get_inbox(limit)
1972            .await
1973            .map_err(|e| McpError::database_operation_failed("get_inbox", e))?;
1974
1975        let json = serde_json::to_string_pretty(&tasks)
1976            .map_err(|e| McpError::serialization_failed("get_inbox serialization", e))?;
1977
1978        Ok(CallToolResult {
1979            content: vec![Content::Text { text: json }],
1980            is_error: false,
1981        })
1982    }
1983
1984    async fn handle_get_today(&self, args: Value) -> McpResult<CallToolResult> {
1985        let limit = args
1986            .get("limit")
1987            .and_then(serde_json::Value::as_u64)
1988            .map(|v| usize::try_from(v).unwrap_or(usize::MAX));
1989
1990        let tasks = self.db.get_today(limit).await.map_err(|e| {
1991            // Include the actual error message for debugging
1992            McpError::database_operation_failed(
1993                "get_today",
1994                things3_core::ThingsError::unknown(format!("Failed to get today's tasks: {}", e)),
1995            )
1996        })?;
1997
1998        let json = serde_json::to_string_pretty(&tasks)
1999            .map_err(|e| McpError::serialization_failed("get_today serialization", e))?;
2000
2001        Ok(CallToolResult {
2002            content: vec![Content::Text { text: json }],
2003            is_error: false,
2004        })
2005    }
2006
2007    async fn handle_get_projects(&self, args: Value) -> McpResult<CallToolResult> {
2008        let _area_uuid = args
2009            .get("area_uuid")
2010            .and_then(|v| v.as_str())
2011            .and_then(|s| uuid::Uuid::parse_str(s).ok());
2012
2013        let projects = self
2014            .db
2015            .get_projects(None)
2016            .await
2017            .map_err(|e| McpError::database_operation_failed("get_projects", e))?;
2018
2019        let json = serde_json::to_string_pretty(&projects)
2020            .map_err(|e| McpError::serialization_failed("get_projects serialization", e))?;
2021
2022        Ok(CallToolResult {
2023            content: vec![Content::Text { text: json }],
2024            is_error: false,
2025        })
2026    }
2027
2028    async fn handle_get_areas(&self, _args: Value) -> McpResult<CallToolResult> {
2029        let areas = self
2030            .db
2031            .get_areas()
2032            .await
2033            .map_err(|e| McpError::database_operation_failed("get_areas", e))?;
2034
2035        let json = serde_json::to_string_pretty(&areas)
2036            .map_err(|e| McpError::serialization_failed("get_areas serialization", e))?;
2037
2038        Ok(CallToolResult {
2039            content: vec![Content::Text { text: json }],
2040            is_error: false,
2041        })
2042    }
2043
2044    async fn handle_search_tasks(&self, args: Value) -> McpResult<CallToolResult> {
2045        let query = args
2046            .get("query")
2047            .and_then(|v| v.as_str())
2048            .ok_or_else(|| McpError::missing_parameter("query"))?;
2049
2050        let _limit = args
2051            .get("limit")
2052            .and_then(serde_json::Value::as_u64)
2053            .map(|v| usize::try_from(v).unwrap_or(usize::MAX));
2054
2055        let tasks = self
2056            .db
2057            .search_tasks(query)
2058            .await
2059            .map_err(|e| McpError::database_operation_failed("search_tasks", e))?;
2060
2061        let json = serde_json::to_string_pretty(&tasks)
2062            .map_err(|e| McpError::serialization_failed("search_tasks serialization", e))?;
2063
2064        Ok(CallToolResult {
2065            content: vec![Content::Text { text: json }],
2066            is_error: false,
2067        })
2068    }
2069
2070    async fn handle_logbook_search(&self, args: Value) -> McpResult<CallToolResult> {
2071        // Parse all optional parameters
2072        let search_text = args
2073            .get("search_text")
2074            .and_then(|v| v.as_str())
2075            .map(|s| s.to_string());
2076
2077        let from_date = args
2078            .get("from_date")
2079            .and_then(|v| v.as_str())
2080            .and_then(|s| chrono::NaiveDate::parse_from_str(s, "%Y-%m-%d").ok());
2081
2082        let to_date = args
2083            .get("to_date")
2084            .and_then(|v| v.as_str())
2085            .and_then(|s| chrono::NaiveDate::parse_from_str(s, "%Y-%m-%d").ok());
2086
2087        let project_uuid = args
2088            .get("project_uuid")
2089            .and_then(|v| v.as_str())
2090            .and_then(|s| Uuid::parse_str(s).ok());
2091
2092        let area_uuid = args
2093            .get("area_uuid")
2094            .and_then(|v| v.as_str())
2095            .and_then(|s| Uuid::parse_str(s).ok());
2096
2097        let tags = args.get("tags").and_then(|v| v.as_array()).map(|arr| {
2098            arr.iter()
2099                .filter_map(|v| v.as_str().map(|s| s.to_string()))
2100                .collect::<Vec<String>>()
2101        });
2102
2103        let limit = args.get("limit").and_then(|v| v.as_u64()).map(|v| v as u32);
2104
2105        // Call database method
2106        let tasks = self
2107            .db
2108            .search_logbook(
2109                search_text,
2110                from_date,
2111                to_date,
2112                project_uuid,
2113                area_uuid,
2114                tags,
2115                limit,
2116            )
2117            .await
2118            .map_err(|e| McpError::database_operation_failed("logbook_search", e))?;
2119
2120        // Serialize results
2121        let json = serde_json::to_string_pretty(&tasks)
2122            .map_err(|e| McpError::serialization_failed("logbook_search serialization", e))?;
2123
2124        Ok(CallToolResult {
2125            content: vec![Content::Text { text: json }],
2126            is_error: false,
2127        })
2128    }
2129
2130    async fn handle_create_task(&self, args: Value) -> McpResult<CallToolResult> {
2131        // Parse request from JSON
2132        let request: things3_core::CreateTaskRequest =
2133            serde_json::from_value(args).map_err(|e| {
2134                McpError::invalid_parameter(
2135                    "request",
2136                    format!("Failed to parse create task request: {e}"),
2137                )
2138            })?;
2139
2140        // Create task
2141        let uuid = self
2142            .db
2143            .create_task(request)
2144            .await
2145            .map_err(|e| McpError::database_operation_failed("create_task", e))?;
2146
2147        // Return created task UUID
2148        let response = serde_json::json!({
2149            "uuid": uuid,
2150            "message": "Task created successfully"
2151        });
2152
2153        Ok(CallToolResult {
2154            content: vec![Content::Text {
2155                text: serde_json::to_string_pretty(&response)
2156                    .map_err(|e| McpError::serialization_failed("create_task response", e))?,
2157            }],
2158            is_error: false,
2159        })
2160    }
2161
2162    async fn handle_update_task(&self, args: Value) -> McpResult<CallToolResult> {
2163        // Parse request from JSON
2164        let request: things3_core::UpdateTaskRequest =
2165            serde_json::from_value(args).map_err(|e| {
2166                McpError::invalid_parameter(
2167                    "request",
2168                    format!("Failed to parse update task request: {e}"),
2169                )
2170            })?;
2171
2172        // Update task
2173        self.db
2174            .update_task(request)
2175            .await
2176            .map_err(|e| McpError::database_operation_failed("update_task", e))?;
2177
2178        // Return success
2179        let response = serde_json::json!({
2180            "message": "Task updated successfully"
2181        });
2182
2183        Ok(CallToolResult {
2184            content: vec![Content::Text {
2185                text: serde_json::to_string_pretty(&response)
2186                    .map_err(|e| McpError::serialization_failed("update_task response", e))?,
2187            }],
2188            is_error: false,
2189        })
2190    }
2191
2192    async fn handle_complete_task(&self, args: Value) -> McpResult<CallToolResult> {
2193        let uuid_str = args
2194            .get("uuid")
2195            .and_then(|v| v.as_str())
2196            .ok_or_else(|| McpError::invalid_parameter("uuid", "UUID is required"))?;
2197
2198        let uuid = uuid::Uuid::parse_str(uuid_str)
2199            .map_err(|e| McpError::invalid_parameter("uuid", format!("Invalid UUID: {e}")))?;
2200
2201        self.db
2202            .complete_task(&uuid)
2203            .await
2204            .map_err(|e| McpError::database_operation_failed("complete_task", e))?;
2205
2206        let response = serde_json::json!({
2207            "message": "Task completed successfully",
2208            "uuid": uuid_str
2209        });
2210
2211        Ok(CallToolResult {
2212            content: vec![Content::Text {
2213                text: serde_json::to_string_pretty(&response)
2214                    .map_err(|e| McpError::serialization_failed("complete_task response", e))?,
2215            }],
2216            is_error: false,
2217        })
2218    }
2219
2220    async fn handle_uncomplete_task(&self, args: Value) -> McpResult<CallToolResult> {
2221        let uuid_str = args
2222            .get("uuid")
2223            .and_then(|v| v.as_str())
2224            .ok_or_else(|| McpError::invalid_parameter("uuid", "UUID is required"))?;
2225
2226        let uuid = uuid::Uuid::parse_str(uuid_str)
2227            .map_err(|e| McpError::invalid_parameter("uuid", format!("Invalid UUID: {e}")))?;
2228
2229        self.db
2230            .uncomplete_task(&uuid)
2231            .await
2232            .map_err(|e| McpError::database_operation_failed("uncomplete_task", e))?;
2233
2234        let response = serde_json::json!({
2235            "message": "Task marked as incomplete successfully",
2236            "uuid": uuid_str
2237        });
2238
2239        Ok(CallToolResult {
2240            content: vec![Content::Text {
2241                text: serde_json::to_string_pretty(&response)
2242                    .map_err(|e| McpError::serialization_failed("uncomplete_task response", e))?,
2243            }],
2244            is_error: false,
2245        })
2246    }
2247
2248    async fn handle_delete_task(&self, args: Value) -> McpResult<CallToolResult> {
2249        let uuid_str = args
2250            .get("uuid")
2251            .and_then(|v| v.as_str())
2252            .ok_or_else(|| McpError::invalid_parameter("uuid", "UUID is required"))?;
2253
2254        let uuid = uuid::Uuid::parse_str(uuid_str)
2255            .map_err(|e| McpError::invalid_parameter("uuid", format!("Invalid UUID: {e}")))?;
2256
2257        let child_handling_str = args
2258            .get("child_handling")
2259            .and_then(|v| v.as_str())
2260            .unwrap_or("error");
2261
2262        let child_handling = match child_handling_str {
2263            "cascade" => DeleteChildHandling::Cascade,
2264            "orphan" => DeleteChildHandling::Orphan,
2265            _ => DeleteChildHandling::Error,
2266        };
2267
2268        self.db
2269            .delete_task(&uuid, child_handling)
2270            .await
2271            .map_err(|e| McpError::database_operation_failed("delete_task", e))?;
2272
2273        let response = serde_json::json!({
2274            "message": "Task deleted successfully",
2275            "uuid": uuid_str
2276        });
2277
2278        Ok(CallToolResult {
2279            content: vec![Content::Text {
2280                text: serde_json::to_string_pretty(&response)
2281                    .map_err(|e| McpError::serialization_failed("delete_task response", e))?,
2282            }],
2283            is_error: false,
2284        })
2285    }
2286
2287    // ============================================================================
2288    // Bulk Operation Handlers
2289    // ============================================================================
2290
2291    async fn handle_bulk_move(&self, args: Value) -> McpResult<CallToolResult> {
2292        // Parse task UUIDs
2293        let task_uuid_strs: Vec<String> = args
2294            .get("task_uuids")
2295            .and_then(|v| v.as_array())
2296            .ok_or_else(|| McpError::invalid_parameter("task_uuids", "Array of UUIDs is required"))?
2297            .iter()
2298            .filter_map(|v| v.as_str().map(String::from))
2299            .collect();
2300
2301        let task_uuids: Vec<uuid::Uuid> = task_uuid_strs
2302            .iter()
2303            .map(|s| {
2304                uuid::Uuid::parse_str(s).map_err(|e| {
2305                    McpError::invalid_parameter("task_uuids", format!("Invalid UUID: {e}"))
2306                })
2307            })
2308            .collect::<McpResult<Vec<_>>>()?;
2309
2310        // Parse optional project_uuid
2311        let project_uuid = args
2312            .get("project_uuid")
2313            .and_then(|v| v.as_str())
2314            .map(|s| {
2315                uuid::Uuid::parse_str(s).map_err(|e| {
2316                    McpError::invalid_parameter("project_uuid", format!("Invalid UUID: {e}"))
2317                })
2318            })
2319            .transpose()?;
2320
2321        // Parse optional area_uuid
2322        let area_uuid = args
2323            .get("area_uuid")
2324            .and_then(|v| v.as_str())
2325            .map(|s| {
2326                uuid::Uuid::parse_str(s).map_err(|e| {
2327                    McpError::invalid_parameter("area_uuid", format!("Invalid UUID: {e}"))
2328                })
2329            })
2330            .transpose()?;
2331
2332        let request = things3_core::models::BulkMoveRequest {
2333            task_uuids,
2334            project_uuid,
2335            area_uuid,
2336        };
2337
2338        let result = self
2339            .db
2340            .bulk_move(request)
2341            .await
2342            .map_err(|e| McpError::database_operation_failed("bulk_move", e))?;
2343
2344        let response = serde_json::json!({
2345            "success": result.success,
2346            "processed_count": result.processed_count,
2347            "message": result.message
2348        });
2349
2350        Ok(CallToolResult {
2351            content: vec![Content::Text {
2352                text: serde_json::to_string_pretty(&response)
2353                    .map_err(|e| McpError::serialization_failed("bulk_move response", e))?,
2354            }],
2355            is_error: false,
2356        })
2357    }
2358
2359    async fn handle_bulk_update_dates(&self, args: Value) -> McpResult<CallToolResult> {
2360        use chrono::NaiveDate;
2361
2362        // Parse task UUIDs
2363        let task_uuid_strs: Vec<String> = args
2364            .get("task_uuids")
2365            .and_then(|v| v.as_array())
2366            .ok_or_else(|| McpError::invalid_parameter("task_uuids", "Array of UUIDs is required"))?
2367            .iter()
2368            .filter_map(|v| v.as_str().map(String::from))
2369            .collect();
2370
2371        let task_uuids: Vec<uuid::Uuid> = task_uuid_strs
2372            .iter()
2373            .map(|s| {
2374                uuid::Uuid::parse_str(s).map_err(|e| {
2375                    McpError::invalid_parameter("task_uuids", format!("Invalid UUID: {e}"))
2376                })
2377            })
2378            .collect::<McpResult<Vec<_>>>()?;
2379
2380        // Parse optional dates
2381        let start_date = args
2382            .get("start_date")
2383            .and_then(|v| v.as_str())
2384            .map(|s| {
2385                NaiveDate::parse_from_str(s, "%Y-%m-%d").map_err(|e| {
2386                    McpError::invalid_parameter("start_date", format!("Invalid date format: {e}"))
2387                })
2388            })
2389            .transpose()?;
2390
2391        let deadline = args
2392            .get("deadline")
2393            .and_then(|v| v.as_str())
2394            .map(|s| {
2395                NaiveDate::parse_from_str(s, "%Y-%m-%d").map_err(|e| {
2396                    McpError::invalid_parameter("deadline", format!("Invalid date format: {e}"))
2397                })
2398            })
2399            .transpose()?;
2400
2401        let clear_start_date = args
2402            .get("clear_start_date")
2403            .and_then(|v| v.as_bool())
2404            .unwrap_or(false);
2405
2406        let clear_deadline = args
2407            .get("clear_deadline")
2408            .and_then(|v| v.as_bool())
2409            .unwrap_or(false);
2410
2411        let request = things3_core::models::BulkUpdateDatesRequest {
2412            task_uuids,
2413            start_date,
2414            deadline,
2415            clear_start_date,
2416            clear_deadline,
2417        };
2418
2419        let result = self
2420            .db
2421            .bulk_update_dates(request)
2422            .await
2423            .map_err(|e| McpError::database_operation_failed("bulk_update_dates", e))?;
2424
2425        let response = serde_json::json!({
2426            "success": result.success,
2427            "processed_count": result.processed_count,
2428            "message": result.message
2429        });
2430
2431        Ok(CallToolResult {
2432            content: vec![Content::Text {
2433                text: serde_json::to_string_pretty(&response)
2434                    .map_err(|e| McpError::serialization_failed("bulk_update_dates response", e))?,
2435            }],
2436            is_error: false,
2437        })
2438    }
2439
2440    async fn handle_bulk_complete(&self, args: Value) -> McpResult<CallToolResult> {
2441        // Parse task UUIDs
2442        let task_uuid_strs: Vec<String> = args
2443            .get("task_uuids")
2444            .and_then(|v| v.as_array())
2445            .ok_or_else(|| McpError::invalid_parameter("task_uuids", "Array of UUIDs is required"))?
2446            .iter()
2447            .filter_map(|v| v.as_str().map(String::from))
2448            .collect();
2449
2450        let task_uuids: Vec<uuid::Uuid> = task_uuid_strs
2451            .iter()
2452            .map(|s| {
2453                uuid::Uuid::parse_str(s).map_err(|e| {
2454                    McpError::invalid_parameter("task_uuids", format!("Invalid UUID: {e}"))
2455                })
2456            })
2457            .collect::<McpResult<Vec<_>>>()?;
2458
2459        let request = things3_core::models::BulkCompleteRequest { task_uuids };
2460
2461        let result = self
2462            .db
2463            .bulk_complete(request)
2464            .await
2465            .map_err(|e| McpError::database_operation_failed("bulk_complete", e))?;
2466
2467        let response = serde_json::json!({
2468            "success": result.success,
2469            "processed_count": result.processed_count,
2470            "message": result.message
2471        });
2472
2473        Ok(CallToolResult {
2474            content: vec![Content::Text {
2475                text: serde_json::to_string_pretty(&response)
2476                    .map_err(|e| McpError::serialization_failed("bulk_complete response", e))?,
2477            }],
2478            is_error: false,
2479        })
2480    }
2481
2482    async fn handle_bulk_delete(&self, args: Value) -> McpResult<CallToolResult> {
2483        // Parse task UUIDs
2484        let task_uuid_strs: Vec<String> = args
2485            .get("task_uuids")
2486            .and_then(|v| v.as_array())
2487            .ok_or_else(|| McpError::invalid_parameter("task_uuids", "Array of UUIDs is required"))?
2488            .iter()
2489            .filter_map(|v| v.as_str().map(String::from))
2490            .collect();
2491
2492        let task_uuids: Vec<uuid::Uuid> = task_uuid_strs
2493            .iter()
2494            .map(|s| {
2495                uuid::Uuid::parse_str(s).map_err(|e| {
2496                    McpError::invalid_parameter("task_uuids", format!("Invalid UUID: {e}"))
2497                })
2498            })
2499            .collect::<McpResult<Vec<_>>>()?;
2500
2501        let request = things3_core::models::BulkDeleteRequest { task_uuids };
2502
2503        let result = self
2504            .db
2505            .bulk_delete(request)
2506            .await
2507            .map_err(|e| McpError::database_operation_failed("bulk_delete", e))?;
2508
2509        let response = serde_json::json!({
2510            "success": result.success,
2511            "processed_count": result.processed_count,
2512            "message": result.message
2513        });
2514
2515        Ok(CallToolResult {
2516            content: vec![Content::Text {
2517                text: serde_json::to_string_pretty(&response)
2518                    .map_err(|e| McpError::serialization_failed("bulk_delete response", e))?,
2519            }],
2520            is_error: false,
2521        })
2522    }
2523
2524    async fn handle_create_project(&self, args: Value) -> McpResult<CallToolResult> {
2525        let title = args
2526            .get("title")
2527            .and_then(|v| v.as_str())
2528            .ok_or_else(|| McpError::invalid_parameter("title", "Project title is required"))?
2529            .to_string();
2530
2531        let notes = args.get("notes").and_then(|v| v.as_str()).map(String::from);
2532
2533        let area_uuid = args
2534            .get("area_uuid")
2535            .and_then(|v| v.as_str())
2536            .map(|s| {
2537                uuid::Uuid::parse_str(s).map_err(|e| {
2538                    McpError::invalid_parameter("area_uuid", format!("Invalid UUID: {e}"))
2539                })
2540            })
2541            .transpose()?;
2542
2543        let start_date = args
2544            .get("start_date")
2545            .and_then(|v| v.as_str())
2546            .map(|s| {
2547                chrono::NaiveDate::parse_from_str(s, "%Y-%m-%d").map_err(|e| {
2548                    McpError::invalid_parameter("start_date", format!("Invalid date: {e}"))
2549                })
2550            })
2551            .transpose()?;
2552
2553        let deadline = args
2554            .get("deadline")
2555            .and_then(|v| v.as_str())
2556            .map(|s| {
2557                chrono::NaiveDate::parse_from_str(s, "%Y-%m-%d").map_err(|e| {
2558                    McpError::invalid_parameter("deadline", format!("Invalid date: {e}"))
2559                })
2560            })
2561            .transpose()?;
2562
2563        let tags = args.get("tags").and_then(|v| v.as_array()).map(|arr| {
2564            arr.iter()
2565                .filter_map(|v| v.as_str().map(String::from))
2566                .collect::<Vec<_>>()
2567        });
2568
2569        let request = things3_core::models::CreateProjectRequest {
2570            title,
2571            notes,
2572            area_uuid,
2573            start_date,
2574            deadline,
2575            tags,
2576        };
2577
2578        let uuid = self
2579            .db
2580            .create_project(request)
2581            .await
2582            .map_err(|e| McpError::database_operation_failed("create_project", e))?;
2583
2584        let response = serde_json::json!({
2585            "message": "Project created successfully",
2586            "uuid": uuid.to_string()
2587        });
2588
2589        Ok(CallToolResult {
2590            content: vec![Content::Text {
2591                text: serde_json::to_string_pretty(&response)
2592                    .map_err(|e| McpError::serialization_failed("create_project response", e))?,
2593            }],
2594            is_error: false,
2595        })
2596    }
2597
2598    async fn handle_update_project(&self, args: Value) -> McpResult<CallToolResult> {
2599        let uuid_str = args
2600            .get("uuid")
2601            .and_then(|v| v.as_str())
2602            .ok_or_else(|| McpError::invalid_parameter("uuid", "UUID is required"))?;
2603
2604        let uuid = uuid::Uuid::parse_str(uuid_str)
2605            .map_err(|e| McpError::invalid_parameter("uuid", format!("Invalid UUID: {e}")))?;
2606
2607        let title = args.get("title").and_then(|v| v.as_str()).map(String::from);
2608        let notes = args.get("notes").and_then(|v| v.as_str()).map(String::from);
2609
2610        let area_uuid = args
2611            .get("area_uuid")
2612            .and_then(|v| v.as_str())
2613            .map(|s| {
2614                uuid::Uuid::parse_str(s).map_err(|e| {
2615                    McpError::invalid_parameter("area_uuid", format!("Invalid UUID: {e}"))
2616                })
2617            })
2618            .transpose()?;
2619
2620        let start_date = args
2621            .get("start_date")
2622            .and_then(|v| v.as_str())
2623            .map(|s| {
2624                chrono::NaiveDate::parse_from_str(s, "%Y-%m-%d").map_err(|e| {
2625                    McpError::invalid_parameter("start_date", format!("Invalid date: {e}"))
2626                })
2627            })
2628            .transpose()?;
2629
2630        let deadline = args
2631            .get("deadline")
2632            .and_then(|v| v.as_str())
2633            .map(|s| {
2634                chrono::NaiveDate::parse_from_str(s, "%Y-%m-%d").map_err(|e| {
2635                    McpError::invalid_parameter("deadline", format!("Invalid date: {e}"))
2636                })
2637            })
2638            .transpose()?;
2639
2640        let tags = args.get("tags").and_then(|v| v.as_array()).map(|arr| {
2641            arr.iter()
2642                .filter_map(|v| v.as_str().map(String::from))
2643                .collect::<Vec<_>>()
2644        });
2645
2646        let request = things3_core::models::UpdateProjectRequest {
2647            uuid,
2648            title,
2649            notes,
2650            area_uuid,
2651            start_date,
2652            deadline,
2653            tags,
2654        };
2655
2656        self.db
2657            .update_project(request)
2658            .await
2659            .map_err(|e| McpError::database_operation_failed("update_project", e))?;
2660
2661        let response = serde_json::json!({
2662            "message": "Project updated successfully",
2663            "uuid": uuid_str
2664        });
2665
2666        Ok(CallToolResult {
2667            content: vec![Content::Text {
2668                text: serde_json::to_string_pretty(&response)
2669                    .map_err(|e| McpError::serialization_failed("update_project response", e))?,
2670            }],
2671            is_error: false,
2672        })
2673    }
2674
2675    async fn handle_complete_project(&self, args: Value) -> McpResult<CallToolResult> {
2676        let uuid_str = args
2677            .get("uuid")
2678            .and_then(|v| v.as_str())
2679            .ok_or_else(|| McpError::invalid_parameter("uuid", "UUID is required"))?;
2680
2681        let uuid = uuid::Uuid::parse_str(uuid_str)
2682            .map_err(|e| McpError::invalid_parameter("uuid", format!("Invalid UUID: {e}")))?;
2683
2684        let child_handling_str = args
2685            .get("child_handling")
2686            .and_then(|v| v.as_str())
2687            .unwrap_or("error");
2688
2689        let child_handling = match child_handling_str {
2690            "cascade" => things3_core::models::ProjectChildHandling::Cascade,
2691            "orphan" => things3_core::models::ProjectChildHandling::Orphan,
2692            _ => things3_core::models::ProjectChildHandling::Error,
2693        };
2694
2695        self.db
2696            .complete_project(&uuid, child_handling)
2697            .await
2698            .map_err(|e| McpError::database_operation_failed("complete_project", e))?;
2699
2700        let response = serde_json::json!({
2701            "message": "Project completed successfully",
2702            "uuid": uuid_str
2703        });
2704
2705        Ok(CallToolResult {
2706            content: vec![Content::Text {
2707                text: serde_json::to_string_pretty(&response)
2708                    .map_err(|e| McpError::serialization_failed("complete_project response", e))?,
2709            }],
2710            is_error: false,
2711        })
2712    }
2713
2714    async fn handle_delete_project(&self, args: Value) -> McpResult<CallToolResult> {
2715        let uuid_str = args
2716            .get("uuid")
2717            .and_then(|v| v.as_str())
2718            .ok_or_else(|| McpError::invalid_parameter("uuid", "UUID is required"))?;
2719
2720        let uuid = uuid::Uuid::parse_str(uuid_str)
2721            .map_err(|e| McpError::invalid_parameter("uuid", format!("Invalid UUID: {e}")))?;
2722
2723        let child_handling_str = args
2724            .get("child_handling")
2725            .and_then(|v| v.as_str())
2726            .unwrap_or("error");
2727
2728        let child_handling = match child_handling_str {
2729            "cascade" => things3_core::models::ProjectChildHandling::Cascade,
2730            "orphan" => things3_core::models::ProjectChildHandling::Orphan,
2731            _ => things3_core::models::ProjectChildHandling::Error,
2732        };
2733
2734        self.db
2735            .delete_project(&uuid, child_handling)
2736            .await
2737            .map_err(|e| McpError::database_operation_failed("delete_project", e))?;
2738
2739        let response = serde_json::json!({
2740            "message": "Project deleted successfully",
2741            "uuid": uuid_str
2742        });
2743
2744        Ok(CallToolResult {
2745            content: vec![Content::Text {
2746                text: serde_json::to_string_pretty(&response)
2747                    .map_err(|e| McpError::serialization_failed("delete_project response", e))?,
2748            }],
2749            is_error: false,
2750        })
2751    }
2752
2753    async fn handle_create_area(&self, args: Value) -> McpResult<CallToolResult> {
2754        let title = args
2755            .get("title")
2756            .and_then(|v| v.as_str())
2757            .ok_or_else(|| McpError::invalid_parameter("title", "Area title is required"))?
2758            .to_string();
2759
2760        let request = things3_core::models::CreateAreaRequest { title };
2761
2762        let uuid = self
2763            .db
2764            .create_area(request)
2765            .await
2766            .map_err(|e| McpError::database_operation_failed("create_area", e))?;
2767
2768        let response = serde_json::json!({
2769            "message": "Area created successfully",
2770            "uuid": uuid.to_string()
2771        });
2772
2773        Ok(CallToolResult {
2774            content: vec![Content::Text {
2775                text: serde_json::to_string_pretty(&response)
2776                    .map_err(|e| McpError::serialization_failed("create_area response", e))?,
2777            }],
2778            is_error: false,
2779        })
2780    }
2781
2782    async fn handle_update_area(&self, args: Value) -> McpResult<CallToolResult> {
2783        let uuid_str = args
2784            .get("uuid")
2785            .and_then(|v| v.as_str())
2786            .ok_or_else(|| McpError::invalid_parameter("uuid", "UUID is required"))?;
2787
2788        let uuid = uuid::Uuid::parse_str(uuid_str)
2789            .map_err(|e| McpError::invalid_parameter("uuid", format!("Invalid UUID: {e}")))?;
2790
2791        let title = args
2792            .get("title")
2793            .and_then(|v| v.as_str())
2794            .ok_or_else(|| McpError::invalid_parameter("title", "Title is required"))?
2795            .to_string();
2796
2797        let request = things3_core::models::UpdateAreaRequest { uuid, title };
2798
2799        self.db
2800            .update_area(request)
2801            .await
2802            .map_err(|e| McpError::database_operation_failed("update_area", e))?;
2803
2804        let response = serde_json::json!({
2805            "message": "Area updated successfully",
2806            "uuid": uuid_str
2807        });
2808
2809        Ok(CallToolResult {
2810            content: vec![Content::Text {
2811                text: serde_json::to_string_pretty(&response)
2812                    .map_err(|e| McpError::serialization_failed("update_area response", e))?,
2813            }],
2814            is_error: false,
2815        })
2816    }
2817
2818    async fn handle_delete_area(&self, args: Value) -> McpResult<CallToolResult> {
2819        let uuid_str = args
2820            .get("uuid")
2821            .and_then(|v| v.as_str())
2822            .ok_or_else(|| McpError::invalid_parameter("uuid", "UUID is required"))?;
2823
2824        let uuid = uuid::Uuid::parse_str(uuid_str)
2825            .map_err(|e| McpError::invalid_parameter("uuid", format!("Invalid UUID: {e}")))?;
2826
2827        self.db
2828            .delete_area(&uuid)
2829            .await
2830            .map_err(|e| McpError::database_operation_failed("delete_area", e))?;
2831
2832        let response = serde_json::json!({
2833            "message": "Area deleted successfully",
2834            "uuid": uuid_str
2835        });
2836
2837        Ok(CallToolResult {
2838            content: vec![Content::Text {
2839                text: serde_json::to_string_pretty(&response)
2840                    .map_err(|e| McpError::serialization_failed("delete_area response", e))?,
2841            }],
2842            is_error: false,
2843        })
2844    }
2845
2846    async fn handle_get_productivity_metrics(&self, args: Value) -> McpResult<CallToolResult> {
2847        let days = usize::try_from(
2848            args.get("days")
2849                .and_then(serde_json::Value::as_u64)
2850                .unwrap_or(7),
2851        )
2852        .unwrap_or(7);
2853
2854        // Get various metrics
2855        let db = &self.db;
2856        let inbox_tasks = db
2857            .get_inbox(None)
2858            .await
2859            .map_err(|e| McpError::database_operation_failed("get_inbox for metrics", e))?;
2860        let today_tasks = db
2861            .get_today(None)
2862            .await
2863            .map_err(|e| McpError::database_operation_failed("get_today for metrics", e))?;
2864        let projects = db
2865            .get_projects(None)
2866            .await
2867            .map_err(|e| McpError::database_operation_failed("get_projects for metrics", e))?;
2868        let areas = db
2869            .get_areas()
2870            .await
2871            .map_err(|e| McpError::database_operation_failed("get_areas for metrics", e))?;
2872        let _ = db;
2873
2874        let metrics = serde_json::json!({
2875            "period_days": days,
2876            "inbox_tasks_count": inbox_tasks.len(),
2877            "today_tasks_count": today_tasks.len(),
2878            "projects_count": projects.len(),
2879            "areas_count": areas.len(),
2880            "completed_tasks": projects.iter().filter(|p| p.status == things3_core::TaskStatus::Completed).count(),
2881            "incomplete_tasks": projects.iter().filter(|p| p.status == things3_core::TaskStatus::Incomplete).count(),
2882            "timestamp": chrono::Utc::now()
2883        });
2884
2885        Ok(CallToolResult {
2886            content: vec![Content::Text {
2887                text: serde_json::to_string_pretty(&metrics).map_err(|e| {
2888                    McpError::serialization_failed("productivity_metrics serialization", e)
2889                })?,
2890            }],
2891            is_error: false,
2892        })
2893    }
2894
2895    async fn handle_export_data(&self, args: Value) -> McpResult<CallToolResult> {
2896        let format = args
2897            .get("format")
2898            .and_then(|v| v.as_str())
2899            .ok_or_else(|| McpError::missing_parameter("format"))?;
2900        let data_type = args
2901            .get("data_type")
2902            .and_then(|v| v.as_str())
2903            .ok_or_else(|| McpError::missing_parameter("data_type"))?;
2904
2905        let db = &self.db;
2906        let export_data =
2907            match data_type {
2908                "tasks" => {
2909                    let inbox = db.get_inbox(None).await.map_err(|e| {
2910                        McpError::database_operation_failed("get_inbox for export", e)
2911                    })?;
2912                    let today = db.get_today(None).await.map_err(|e| {
2913                        McpError::database_operation_failed("get_today for export", e)
2914                    })?;
2915                    serde_json::json!({
2916                        "inbox": inbox,
2917                        "today": today
2918                    })
2919                }
2920                "projects" => {
2921                    let projects = db.get_projects(None).await.map_err(|e| {
2922                        McpError::database_operation_failed("get_projects for export", e)
2923                    })?;
2924                    serde_json::json!({ "projects": projects })
2925                }
2926                "areas" => {
2927                    let areas = db.get_areas().await.map_err(|e| {
2928                        McpError::database_operation_failed("get_areas for export", e)
2929                    })?;
2930                    serde_json::json!({ "areas": areas })
2931                }
2932                "all" => {
2933                    let inbox = db.get_inbox(None).await.map_err(|e| {
2934                        McpError::database_operation_failed("get_inbox for export", e)
2935                    })?;
2936                    let today = db.get_today(None).await.map_err(|e| {
2937                        McpError::database_operation_failed("get_today for export", e)
2938                    })?;
2939                    let projects = db.get_projects(None).await.map_err(|e| {
2940                        McpError::database_operation_failed("get_projects for export", e)
2941                    })?;
2942                    let areas = db.get_areas().await.map_err(|e| {
2943                        McpError::database_operation_failed("get_areas for export", e)
2944                    })?;
2945                    let _ = db;
2946                    serde_json::json!({
2947                        "inbox": inbox,
2948                        "today": today,
2949                        "projects": projects,
2950                        "areas": areas
2951                    })
2952                }
2953                _ => {
2954                    return Err(McpError::invalid_data_type(
2955                        data_type,
2956                        "tasks, projects, areas, all",
2957                    ))
2958                }
2959            };
2960
2961        let result = match format {
2962            "json" => serde_json::to_string_pretty(&export_data)
2963                .map_err(|e| McpError::serialization_failed("export_data serialization", e))?,
2964            "csv" => "CSV export not yet implemented".to_string(),
2965            "markdown" => "Markdown export not yet implemented".to_string(),
2966            _ => return Err(McpError::invalid_format(format, "json, csv, markdown")),
2967        };
2968
2969        Ok(CallToolResult {
2970            content: vec![Content::Text { text: result }],
2971            is_error: false,
2972        })
2973    }
2974
2975    fn handle_bulk_create_tasks(args: &Value) -> McpResult<CallToolResult> {
2976        let tasks = args
2977            .get("tasks")
2978            .and_then(|v| v.as_array())
2979            .ok_or_else(|| McpError::missing_parameter("tasks"))?;
2980
2981        let response = serde_json::json!({
2982            "message": "Bulk task creation not yet implemented",
2983            "tasks_count": tasks.len(),
2984            "status": "placeholder"
2985        });
2986
2987        Ok(CallToolResult {
2988            content: vec![Content::Text {
2989                text: serde_json::to_string_pretty(&response)
2990                    .map_err(|e| McpError::serialization_failed("bulk_create_tasks response", e))?,
2991            }],
2992            is_error: false,
2993        })
2994    }
2995
2996    async fn handle_get_recent_tasks(&self, args: Value) -> McpResult<CallToolResult> {
2997        let limit = args
2998            .get("limit")
2999            .and_then(serde_json::Value::as_u64)
3000            .map(|v| usize::try_from(v).unwrap_or(usize::MAX));
3001        let hours = i64::try_from(
3002            args.get("hours")
3003                .and_then(serde_json::Value::as_u64)
3004                .unwrap_or(24),
3005        )
3006        .unwrap_or(24);
3007
3008        // For now, return inbox tasks as a proxy for recent tasks
3009        // In a real implementation, this would query by creation/modification date
3010        let tasks = self
3011            .db
3012            .get_inbox(limit)
3013            .await
3014            .map_err(|e| McpError::database_operation_failed("get_recent_tasks", e))?;
3015
3016        let response = serde_json::json!({
3017            "message": "Recent tasks (using inbox as proxy)",
3018            "hours_lookback": hours,
3019            "tasks": tasks
3020        });
3021
3022        Ok(CallToolResult {
3023            content: vec![Content::Text {
3024                text: serde_json::to_string_pretty(&response)
3025                    .map_err(|e| McpError::serialization_failed("get_recent_tasks response", e))?,
3026            }],
3027            is_error: false,
3028        })
3029    }
3030
3031    async fn handle_backup_database(&self, args: Value) -> McpResult<CallToolResult> {
3032        let backup_dir = args
3033            .get("backup_dir")
3034            .and_then(|v| v.as_str())
3035            .ok_or_else(|| McpError::missing_parameter("backup_dir"))?;
3036        let description = args.get("description").and_then(|v| v.as_str());
3037
3038        let backup_path = std::path::Path::new(backup_dir);
3039        let metadata = self
3040            .backup_manager
3041            .lock()
3042            .await
3043            .create_backup(backup_path, description)
3044            .map_err(|e| {
3045                McpError::backup_operation_failed(
3046                    "create_backup",
3047                    things3_core::ThingsError::unknown(e.to_string()),
3048                )
3049            })?;
3050
3051        let response = serde_json::json!({
3052            "message": "Backup created successfully",
3053            "backup_path": metadata.backup_path,
3054            "file_size": metadata.file_size,
3055            "created_at": metadata.created_at
3056        });
3057
3058        Ok(CallToolResult {
3059            content: vec![Content::Text {
3060                text: serde_json::to_string_pretty(&response)
3061                    .map_err(|e| McpError::serialization_failed("backup_database response", e))?,
3062            }],
3063            is_error: false,
3064        })
3065    }
3066
3067    async fn handle_restore_database(&self, args: Value) -> McpResult<CallToolResult> {
3068        let backup_path = args
3069            .get("backup_path")
3070            .and_then(|v| v.as_str())
3071            .ok_or_else(|| McpError::missing_parameter("backup_path"))?;
3072
3073        let backup_file = std::path::Path::new(backup_path);
3074        self.backup_manager
3075            .lock()
3076            .await
3077            .restore_backup(backup_file)
3078            .map_err(|e| {
3079                McpError::backup_operation_failed(
3080                    "restore_backup",
3081                    things3_core::ThingsError::unknown(e.to_string()),
3082                )
3083            })?;
3084
3085        let response = serde_json::json!({
3086            "message": "Database restored successfully",
3087            "backup_path": backup_path
3088        });
3089
3090        Ok(CallToolResult {
3091            content: vec![Content::Text {
3092                text: serde_json::to_string_pretty(&response)
3093                    .map_err(|e| McpError::serialization_failed("restore_database response", e))?,
3094            }],
3095            is_error: false,
3096        })
3097    }
3098
3099    async fn handle_list_backups(&self, args: Value) -> McpResult<CallToolResult> {
3100        let backup_dir = args
3101            .get("backup_dir")
3102            .and_then(|v| v.as_str())
3103            .ok_or_else(|| McpError::missing_parameter("backup_dir"))?;
3104
3105        let backup_path = std::path::Path::new(backup_dir);
3106        let backups = self
3107            .backup_manager
3108            .lock()
3109            .await
3110            .list_backups(backup_path)
3111            .map_err(|e| {
3112                McpError::backup_operation_failed(
3113                    "list_backups",
3114                    things3_core::ThingsError::unknown(e.to_string()),
3115                )
3116            })?;
3117
3118        let response = serde_json::json!({
3119            "backups": backups,
3120            "count": backups.len()
3121        });
3122
3123        Ok(CallToolResult {
3124            content: vec![Content::Text {
3125                text: serde_json::to_string_pretty(&response)
3126                    .map_err(|e| McpError::serialization_failed("list_backups response", e))?,
3127            }],
3128            is_error: false,
3129        })
3130    }
3131
3132    async fn handle_get_performance_stats(&self, _args: Value) -> McpResult<CallToolResult> {
3133        let monitor = self.performance_monitor.lock().await;
3134        let stats = monitor.get_all_stats();
3135        let summary = monitor.get_summary();
3136        drop(monitor);
3137
3138        let response = serde_json::json!({
3139            "summary": summary,
3140            "operation_stats": stats
3141        });
3142
3143        Ok(CallToolResult {
3144            content: vec![Content::Text {
3145                text: serde_json::to_string_pretty(&response)
3146                    .map_err(|e| McpError::serialization_failed("performance_stats response", e))?,
3147            }],
3148            is_error: false,
3149        })
3150    }
3151
3152    async fn handle_get_system_metrics(&self, _args: Value) -> McpResult<CallToolResult> {
3153        let metrics = self
3154            .performance_monitor
3155            .lock()
3156            .await
3157            .get_system_metrics()
3158            .map_err(|e| {
3159                McpError::performance_monitoring_failed(
3160                    "get_system_metrics",
3161                    things3_core::ThingsError::unknown(e.to_string()),
3162                )
3163            })?;
3164
3165        Ok(CallToolResult {
3166            content: vec![Content::Text {
3167                text: serde_json::to_string_pretty(&metrics)
3168                    .map_err(|e| McpError::serialization_failed("system_metrics response", e))?,
3169            }],
3170            is_error: false,
3171        })
3172    }
3173
3174    async fn handle_get_cache_stats(&self, _args: Value) -> McpResult<CallToolResult> {
3175        let stats = self.cache.lock().await.get_stats();
3176
3177        Ok(CallToolResult {
3178            content: vec![Content::Text {
3179                text: serde_json::to_string_pretty(&stats)
3180                    .map_err(|e| McpError::serialization_failed("cache_stats response", e))?,
3181            }],
3182            is_error: false,
3183        })
3184    }
3185
3186    // ========================================================================
3187    // TAG TOOL HANDLERS
3188    // ========================================================================
3189
3190    async fn handle_search_tags_tool(&self, args: Value) -> McpResult<CallToolResult> {
3191        let query: String = args
3192            .get("query")
3193            .and_then(|v| v.as_str())
3194            .ok_or_else(|| McpError::invalid_parameter("query", "Missing 'query' parameter"))?
3195            .to_string();
3196
3197        let include_similar = args
3198            .get("include_similar")
3199            .and_then(|v| v.as_bool())
3200            .unwrap_or(true);
3201
3202        let min_similarity = args
3203            .get("min_similarity")
3204            .and_then(|v| v.as_f64())
3205            .unwrap_or(0.7) as f32;
3206
3207        let tags = if include_similar {
3208            self.db
3209                .find_similar_tags(&query, min_similarity)
3210                .await
3211                .map_err(|e| McpError::database_operation_failed("search_tags", e))?
3212                .into_iter()
3213                .map(|tm| tm.tag)
3214                .collect()
3215        } else {
3216            self.db
3217                .search_tags(&query)
3218                .await
3219                .map_err(|e| McpError::database_operation_failed("search_tags", e))?
3220        };
3221
3222        Ok(CallToolResult {
3223            content: vec![Content::Text {
3224                text: serde_json::to_string_pretty(&tags)
3225                    .map_err(|e| McpError::serialization_failed("tags", e))?,
3226            }],
3227            is_error: false,
3228        })
3229    }
3230
3231    async fn handle_get_tag_suggestions(&self, args: Value) -> McpResult<CallToolResult> {
3232        let title: String = args
3233            .get("title")
3234            .and_then(|v| v.as_str())
3235            .ok_or_else(|| McpError::invalid_parameter("title", "Missing 'title' parameter"))?
3236            .to_string();
3237
3238        use things3_core::database::tag_utils::normalize_tag_title;
3239        let normalized = normalize_tag_title(&title);
3240
3241        // Check for exact match
3242        let exact_match = self
3243            .db
3244            .find_tag_by_normalized_title(&normalized)
3245            .await
3246            .map_err(|e| McpError::database_operation_failed("get_tag_suggestions", e))?;
3247
3248        // Find similar tags
3249        let similar_tags = self
3250            .db
3251            .find_similar_tags(&normalized, 0.7)
3252            .await
3253            .map_err(|e| McpError::database_operation_failed("get_tag_suggestions", e))?;
3254
3255        let recommendation = if exact_match.is_some() {
3256            "use_existing"
3257        } else if !similar_tags.is_empty() {
3258            "consider_similar"
3259        } else {
3260            "create_new"
3261        };
3262
3263        let response = serde_json::json!({
3264            "exact_match": exact_match,
3265            "similar_tags": similar_tags,
3266            "recommendation": recommendation
3267        });
3268
3269        Ok(CallToolResult {
3270            content: vec![Content::Text {
3271                text: serde_json::to_string_pretty(&response)
3272                    .map_err(|e| McpError::serialization_failed("tag_suggestions", e))?,
3273            }],
3274            is_error: false,
3275        })
3276    }
3277
3278    async fn handle_get_popular_tags(&self, args: Value) -> McpResult<CallToolResult> {
3279        let limit = args.get("limit").and_then(|v| v.as_u64()).unwrap_or(20) as usize;
3280
3281        let tags = self
3282            .db
3283            .get_popular_tags(limit)
3284            .await
3285            .map_err(|e| McpError::database_operation_failed("get_popular_tags", e))?;
3286
3287        Ok(CallToolResult {
3288            content: vec![Content::Text {
3289                text: serde_json::to_string_pretty(&tags)
3290                    .map_err(|e| McpError::serialization_failed("popular_tags", e))?,
3291            }],
3292            is_error: false,
3293        })
3294    }
3295
3296    async fn handle_get_recent_tags(&self, args: Value) -> McpResult<CallToolResult> {
3297        let limit = args.get("limit").and_then(|v| v.as_u64()).unwrap_or(20) as usize;
3298
3299        let tags = self
3300            .db
3301            .get_recent_tags(limit)
3302            .await
3303            .map_err(|e| McpError::database_operation_failed("get_recent_tags", e))?;
3304
3305        Ok(CallToolResult {
3306            content: vec![Content::Text {
3307                text: serde_json::to_string_pretty(&tags)
3308                    .map_err(|e| McpError::serialization_failed("recent_tags", e))?,
3309            }],
3310            is_error: false,
3311        })
3312    }
3313
3314    async fn handle_create_tag(&self, args: Value) -> McpResult<CallToolResult> {
3315        let title: String = args
3316            .get("title")
3317            .and_then(|v| v.as_str())
3318            .ok_or_else(|| McpError::invalid_parameter("title", "Missing 'title' parameter"))?
3319            .to_string();
3320
3321        let shortcut: Option<String> = args
3322            .get("shortcut")
3323            .and_then(|v| v.as_str())
3324            .map(|s| s.to_string());
3325
3326        let parent_uuid: Option<Uuid> = args
3327            .get("parent_uuid")
3328            .and_then(|v| v.as_str())
3329            .and_then(|s| Uuid::parse_str(s).ok());
3330
3331        let force = args.get("force").and_then(|v| v.as_bool()).unwrap_or(false);
3332
3333        let request = things3_core::models::CreateTagRequest {
3334            title,
3335            shortcut,
3336            parent_uuid,
3337        };
3338
3339        let result = if force {
3340            let uuid = self
3341                .db
3342                .create_tag_force(request)
3343                .await
3344                .map_err(|e| McpError::database_operation_failed("create_tag", e))?;
3345            serde_json::json!({
3346                "status": "created",
3347                "uuid": uuid,
3348                "message": "Tag created successfully (duplicate check skipped)"
3349            })
3350        } else {
3351            match self
3352                .db
3353                .create_tag_smart(request)
3354                .await
3355                .map_err(|e| McpError::database_operation_failed("create_tag", e))?
3356            {
3357                things3_core::models::TagCreationResult::Created { uuid, .. } => {
3358                    serde_json::json!({
3359                        "status": "created",
3360                        "uuid": uuid,
3361                        "message": "Tag created successfully"
3362                    })
3363                }
3364                things3_core::models::TagCreationResult::Existing { tag, .. } => {
3365                    serde_json::json!({
3366                        "status": "existing",
3367                        "uuid": tag.uuid,
3368                        "tag": tag,
3369                        "message": "Tag already exists"
3370                    })
3371                }
3372                things3_core::models::TagCreationResult::SimilarFound {
3373                    similar_tags,
3374                    requested_title,
3375                } => {
3376                    serde_json::json!({
3377                        "status": "similar_found",
3378                        "similar_tags": similar_tags,
3379                        "requested_title": requested_title,
3380                        "message": "Similar tags found. Use force=true to create anyway."
3381                    })
3382                }
3383            }
3384        };
3385
3386        Ok(CallToolResult {
3387            content: vec![Content::Text {
3388                text: serde_json::to_string_pretty(&result)
3389                    .map_err(|e| McpError::serialization_failed("create_tag_response", e))?,
3390            }],
3391            is_error: false,
3392        })
3393    }
3394
3395    async fn handle_update_tag(&self, args: Value) -> McpResult<CallToolResult> {
3396        let uuid: Uuid = args
3397            .get("uuid")
3398            .and_then(|v| v.as_str())
3399            .and_then(|s| Uuid::parse_str(s).ok())
3400            .ok_or_else(|| {
3401                McpError::invalid_parameter("uuid", "Missing or invalid 'uuid' parameter")
3402            })?;
3403
3404        let title: Option<String> = args
3405            .get("title")
3406            .and_then(|v| v.as_str())
3407            .map(|s| s.to_string());
3408
3409        let shortcut: Option<String> = args
3410            .get("shortcut")
3411            .and_then(|v| v.as_str())
3412            .map(|s| s.to_string());
3413
3414        let parent_uuid: Option<Uuid> = args
3415            .get("parent_uuid")
3416            .and_then(|v| v.as_str())
3417            .and_then(|s| Uuid::parse_str(s).ok());
3418
3419        let request = things3_core::models::UpdateTagRequest {
3420            uuid,
3421            title,
3422            shortcut,
3423            parent_uuid,
3424        };
3425
3426        self.db
3427            .update_tag(request)
3428            .await
3429            .map_err(|e| McpError::database_operation_failed("update_tag", e))?;
3430
3431        let response = serde_json::json!({
3432            "message": "Tag updated successfully",
3433            "uuid": uuid
3434        });
3435
3436        Ok(CallToolResult {
3437            content: vec![Content::Text {
3438                text: serde_json::to_string_pretty(&response)
3439                    .map_err(|e| McpError::serialization_failed("update_tag_response", e))?,
3440            }],
3441            is_error: false,
3442        })
3443    }
3444
3445    async fn handle_delete_tag(&self, args: Value) -> McpResult<CallToolResult> {
3446        let uuid: Uuid = args
3447            .get("uuid")
3448            .and_then(|v| v.as_str())
3449            .and_then(|s| Uuid::parse_str(s).ok())
3450            .ok_or_else(|| {
3451                McpError::invalid_parameter("uuid", "Missing or invalid 'uuid' parameter")
3452            })?;
3453
3454        let remove_from_tasks = args
3455            .get("remove_from_tasks")
3456            .and_then(|v| v.as_bool())
3457            .unwrap_or(false);
3458
3459        self.db
3460            .delete_tag(&uuid, remove_from_tasks)
3461            .await
3462            .map_err(|e| McpError::database_operation_failed("delete_tag", e))?;
3463
3464        let response = serde_json::json!({
3465            "message": "Tag deleted successfully",
3466            "uuid": uuid
3467        });
3468
3469        Ok(CallToolResult {
3470            content: vec![Content::Text {
3471                text: serde_json::to_string_pretty(&response)
3472                    .map_err(|e| McpError::serialization_failed("delete_tag_response", e))?,
3473            }],
3474            is_error: false,
3475        })
3476    }
3477
3478    async fn handle_merge_tags(&self, args: Value) -> McpResult<CallToolResult> {
3479        let source_uuid: Uuid = args
3480            .get("source_uuid")
3481            .and_then(|v| v.as_str())
3482            .and_then(|s| Uuid::parse_str(s).ok())
3483            .ok_or_else(|| {
3484                McpError::invalid_parameter(
3485                    "source_uuid",
3486                    "Missing or invalid 'source_uuid' parameter",
3487                )
3488            })?;
3489
3490        let target_uuid: Uuid = args
3491            .get("target_uuid")
3492            .and_then(|v| v.as_str())
3493            .and_then(|s| Uuid::parse_str(s).ok())
3494            .ok_or_else(|| {
3495                McpError::invalid_parameter(
3496                    "target_uuid",
3497                    "Missing or invalid 'target_uuid' parameter",
3498                )
3499            })?;
3500
3501        self.db
3502            .merge_tags(&source_uuid, &target_uuid)
3503            .await
3504            .map_err(|e| McpError::database_operation_failed("merge_tags", e))?;
3505
3506        let response = serde_json::json!({
3507            "message": "Tags merged successfully",
3508            "source_uuid": source_uuid,
3509            "target_uuid": target_uuid
3510        });
3511
3512        Ok(CallToolResult {
3513            content: vec![Content::Text {
3514                text: serde_json::to_string_pretty(&response)
3515                    .map_err(|e| McpError::serialization_failed("merge_tags_response", e))?,
3516            }],
3517            is_error: false,
3518        })
3519    }
3520
3521    async fn handle_add_tag_to_task(&self, args: Value) -> McpResult<CallToolResult> {
3522        let task_uuid: Uuid = args
3523            .get("task_uuid")
3524            .and_then(|v| v.as_str())
3525            .and_then(|s| Uuid::parse_str(s).ok())
3526            .ok_or_else(|| {
3527                McpError::invalid_parameter("task_uuid", "Missing or invalid 'task_uuid' parameter")
3528            })?;
3529
3530        let tag_title: String = args
3531            .get("tag_title")
3532            .and_then(|v| v.as_str())
3533            .ok_or_else(|| {
3534                McpError::invalid_parameter("tag_title", "Missing 'tag_title' parameter")
3535            })?
3536            .to_string();
3537
3538        let result = self
3539            .db
3540            .add_tag_to_task(&task_uuid, &tag_title)
3541            .await
3542            .map_err(|e| McpError::database_operation_failed("add_tag_to_task", e))?;
3543
3544        let response = match result {
3545            things3_core::models::TagAssignmentResult::Assigned { tag_uuid } => {
3546                serde_json::json!({
3547                    "status": "assigned",
3548                    "tag_uuid": tag_uuid,
3549                    "message": "Tag added to task successfully"
3550                })
3551            }
3552            things3_core::models::TagAssignmentResult::Suggestions { similar_tags } => {
3553                serde_json::json!({
3554                    "status": "suggestions",
3555                    "similar_tags": similar_tags,
3556                    "message": "Similar tags found. Please confirm or use a different tag."
3557                })
3558            }
3559        };
3560
3561        Ok(CallToolResult {
3562            content: vec![Content::Text {
3563                text: serde_json::to_string_pretty(&response)
3564                    .map_err(|e| McpError::serialization_failed("add_tag_to_task_response", e))?,
3565            }],
3566            is_error: false,
3567        })
3568    }
3569
3570    async fn handle_remove_tag_from_task(&self, args: Value) -> McpResult<CallToolResult> {
3571        let task_uuid: Uuid = args
3572            .get("task_uuid")
3573            .and_then(|v| v.as_str())
3574            .and_then(|s| Uuid::parse_str(s).ok())
3575            .ok_or_else(|| {
3576                McpError::invalid_parameter("task_uuid", "Missing or invalid 'task_uuid' parameter")
3577            })?;
3578
3579        let tag_title: String = args
3580            .get("tag_title")
3581            .and_then(|v| v.as_str())
3582            .ok_or_else(|| {
3583                McpError::invalid_parameter("tag_title", "Missing 'tag_title' parameter")
3584            })?
3585            .to_string();
3586
3587        self.db
3588            .remove_tag_from_task(&task_uuid, &tag_title)
3589            .await
3590            .map_err(|e| McpError::database_operation_failed("remove_tag_from_task", e))?;
3591
3592        let response = serde_json::json!({
3593            "message": "Tag removed from task successfully",
3594            "task_uuid": task_uuid,
3595            "tag_title": tag_title
3596        });
3597
3598        Ok(CallToolResult {
3599            content: vec![Content::Text {
3600                text: serde_json::to_string_pretty(&response).map_err(|e| {
3601                    McpError::serialization_failed("remove_tag_from_task_response", e)
3602                })?,
3603            }],
3604            is_error: false,
3605        })
3606    }
3607
3608    async fn handle_set_task_tags(&self, args: Value) -> McpResult<CallToolResult> {
3609        let task_uuid: Uuid = args
3610            .get("task_uuid")
3611            .and_then(|v| v.as_str())
3612            .and_then(|s| Uuid::parse_str(s).ok())
3613            .ok_or_else(|| {
3614                McpError::invalid_parameter("task_uuid", "Missing or invalid 'task_uuid' parameter")
3615            })?;
3616
3617        let tag_titles: Vec<String> = args
3618            .get("tag_titles")
3619            .and_then(|v| v.as_array())
3620            .ok_or_else(|| {
3621                McpError::invalid_parameter("tag_titles", "Missing 'tag_titles' parameter")
3622            })?
3623            .iter()
3624            .filter_map(|v| v.as_str().map(|s| s.to_string()))
3625            .collect();
3626
3627        let suggestions = self
3628            .db
3629            .set_task_tags(&task_uuid, tag_titles.clone())
3630            .await
3631            .map_err(|e| McpError::database_operation_failed("set_task_tags", e))?;
3632
3633        let response = serde_json::json!({
3634            "message": "Task tags updated successfully",
3635            "task_uuid": task_uuid,
3636            "tags": tag_titles,
3637            "suggestions": suggestions
3638        });
3639
3640        Ok(CallToolResult {
3641            content: vec![Content::Text {
3642                text: serde_json::to_string_pretty(&response)
3643                    .map_err(|e| McpError::serialization_failed("set_task_tags_response", e))?,
3644            }],
3645            is_error: false,
3646        })
3647    }
3648
3649    async fn handle_get_tag_statistics(&self, args: Value) -> McpResult<CallToolResult> {
3650        let uuid: Uuid = args
3651            .get("uuid")
3652            .and_then(|v| v.as_str())
3653            .and_then(|s| Uuid::parse_str(s).ok())
3654            .ok_or_else(|| {
3655                McpError::invalid_parameter("uuid", "Missing or invalid 'uuid' parameter")
3656            })?;
3657
3658        let stats = self
3659            .db
3660            .get_tag_statistics(&uuid)
3661            .await
3662            .map_err(|e| McpError::database_operation_failed("get_tag_statistics", e))?;
3663
3664        Ok(CallToolResult {
3665            content: vec![Content::Text {
3666                text: serde_json::to_string_pretty(&stats)
3667                    .map_err(|e| McpError::serialization_failed("tag_statistics", e))?,
3668            }],
3669            is_error: false,
3670        })
3671    }
3672
3673    async fn handle_find_duplicate_tags(&self, args: Value) -> McpResult<CallToolResult> {
3674        let min_similarity = args
3675            .get("min_similarity")
3676            .and_then(|v| v.as_f64())
3677            .unwrap_or(0.85) as f32;
3678
3679        let duplicates = self
3680            .db
3681            .find_duplicate_tags(min_similarity)
3682            .await
3683            .map_err(|e| McpError::database_operation_failed("find_duplicate_tags", e))?;
3684
3685        Ok(CallToolResult {
3686            content: vec![Content::Text {
3687                text: serde_json::to_string_pretty(&duplicates)
3688                    .map_err(|e| McpError::serialization_failed("duplicate_tags", e))?,
3689            }],
3690            is_error: false,
3691        })
3692    }
3693
3694    async fn handle_get_tag_completions(&self, args: Value) -> McpResult<CallToolResult> {
3695        let partial_input: String = args
3696            .get("partial_input")
3697            .and_then(|v| v.as_str())
3698            .ok_or_else(|| {
3699                McpError::invalid_parameter("partial_input", "Missing 'partial_input' parameter")
3700            })?
3701            .to_string();
3702
3703        let limit = args.get("limit").and_then(|v| v.as_u64()).unwrap_or(10) as usize;
3704
3705        let completions = self
3706            .db
3707            .get_tag_completions(&partial_input, limit)
3708            .await
3709            .map_err(|e| McpError::database_operation_failed("get_tag_completions", e))?;
3710
3711        Ok(CallToolResult {
3712            content: vec![Content::Text {
3713                text: serde_json::to_string_pretty(&completions)
3714                    .map_err(|e| McpError::serialization_failed("tag_completions", e))?,
3715            }],
3716            is_error: false,
3717        })
3718    }
3719
3720    /// Get available MCP prompts
3721    fn get_available_prompts() -> Vec<Prompt> {
3722        vec![
3723            Self::create_task_review_prompt(),
3724            Self::create_project_planning_prompt(),
3725            Self::create_productivity_analysis_prompt(),
3726            Self::create_backup_strategy_prompt(),
3727        ]
3728    }
3729
3730    /// Create task review prompt
3731    fn create_task_review_prompt() -> Prompt {
3732        Prompt {
3733            name: "task_review".to_string(),
3734            description: "Review task for completeness and clarity".to_string(),
3735            arguments: serde_json::json!({
3736                "type": "object",
3737                "properties": {
3738                    "task_title": {
3739                        "type": "string",
3740                        "description": "The title of the task to review"
3741                    },
3742                    "task_notes": {
3743                        "type": "string",
3744                        "description": "Optional notes or description of the task"
3745                    },
3746                    "context": {
3747                        "type": "string",
3748                        "description": "Optional context about the task or project"
3749                    }
3750                },
3751                "required": ["task_title"]
3752            }),
3753        }
3754    }
3755
3756    /// Create project planning prompt
3757    fn create_project_planning_prompt() -> Prompt {
3758        Prompt {
3759            name: "project_planning".to_string(),
3760            description: "Help plan projects with tasks and deadlines".to_string(),
3761            arguments: serde_json::json!({
3762                "type": "object",
3763                "properties": {
3764                    "project_title": {
3765                        "type": "string",
3766                        "description": "The title of the project to plan"
3767                    },
3768                    "project_description": {
3769                        "type": "string",
3770                        "description": "Description of what the project aims to achieve"
3771                    },
3772                    "deadline": {
3773                        "type": "string",
3774                        "description": "Optional deadline for the project"
3775                    },
3776                    "complexity": {
3777                        "type": "string",
3778                        "description": "Project complexity level",
3779                        "enum": ["simple", "medium", "complex"]
3780                    }
3781                },
3782                "required": ["project_title"]
3783            }),
3784        }
3785    }
3786
3787    /// Create productivity analysis prompt
3788    fn create_productivity_analysis_prompt() -> Prompt {
3789        Prompt {
3790            name: "productivity_analysis".to_string(),
3791            description: "Analyze productivity patterns".to_string(),
3792            arguments: serde_json::json!({
3793                "type": "object",
3794                "properties": {
3795                    "time_period": {
3796                        "type": "string",
3797                        "description": "Time period to analyze",
3798                        "enum": ["week", "month", "quarter", "year"]
3799                    },
3800                    "focus_area": {
3801                        "type": "string",
3802                        "description": "Specific area to focus analysis on",
3803                        "enum": ["completion_rate", "time_management", "task_distribution", "all"]
3804                    },
3805                    "include_recommendations": {
3806                        "type": "boolean",
3807                        "description": "Whether to include improvement recommendations"
3808                    }
3809                },
3810                "required": ["time_period"]
3811            }),
3812        }
3813    }
3814
3815    /// Create backup strategy prompt
3816    fn create_backup_strategy_prompt() -> Prompt {
3817        Prompt {
3818            name: "backup_strategy".to_string(),
3819            description: "Suggest backup strategies".to_string(),
3820            arguments: serde_json::json!({
3821                "type": "object",
3822                "properties": {
3823                    "data_volume": {
3824                        "type": "string",
3825                        "description": "Estimated data volume",
3826                        "enum": ["small", "medium", "large"]
3827                    },
3828                    "frequency": {
3829                        "type": "string",
3830                        "description": "Desired backup frequency",
3831                        "enum": ["daily", "weekly", "monthly"]
3832                    },
3833                    "retention_period": {
3834                        "type": "string",
3835                        "description": "How long to keep backups",
3836                        "enum": ["1_month", "3_months", "6_months", "1_year", "indefinite"]
3837                    },
3838                    "storage_preference": {
3839                        "type": "string",
3840                        "description": "Preferred storage type",
3841                        "enum": ["local", "cloud", "hybrid"]
3842                    }
3843                },
3844                "required": ["data_volume", "frequency"]
3845            }),
3846        }
3847    }
3848
3849    /// Handle prompt request
3850    async fn handle_prompt_request(&self, request: GetPromptRequest) -> McpResult<GetPromptResult> {
3851        let prompt_name = &request.name;
3852        let arguments = request.arguments.unwrap_or_default();
3853
3854        match prompt_name.as_str() {
3855            "task_review" => self.handle_task_review_prompt(arguments).await,
3856            "project_planning" => self.handle_project_planning_prompt(arguments).await,
3857            "productivity_analysis" => self.handle_productivity_analysis_prompt(arguments).await,
3858            "backup_strategy" => self.handle_backup_strategy_prompt(arguments).await,
3859            _ => Err(McpError::prompt_not_found(prompt_name)),
3860        }
3861    }
3862
3863    /// Handle task review prompt
3864    async fn handle_task_review_prompt(&self, args: Value) -> McpResult<GetPromptResult> {
3865        let task_title = args
3866            .get("task_title")
3867            .and_then(|v| v.as_str())
3868            .ok_or_else(|| McpError::missing_parameter("task_title"))?;
3869        let task_notes = args.get("task_notes").and_then(|v| v.as_str());
3870        let context = args.get("context").and_then(|v| v.as_str());
3871
3872        // Get current data for context
3873        let db = &self.db;
3874        let inbox_tasks = db
3875            .get_inbox(Some(5))
3876            .await
3877            .map_err(|e| McpError::database_operation_failed("get_inbox for task_review", e))?;
3878        let today_tasks = db
3879            .get_today(Some(5))
3880            .await
3881            .map_err(|e| McpError::database_operation_failed("get_today for task_review", e))?;
3882        let _ = db;
3883
3884        let prompt_text = format!(
3885            "# Task Review: {}\n\n\
3886            ## Current Task Details\n\
3887            - **Title**: {}\n\
3888            - **Notes**: {}\n\
3889            - **Context**: {}\n\n\
3890            ## Review Checklist\n\
3891            Please review this task for:\n\
3892            1. **Clarity**: Is the task title clear and actionable?\n\
3893            2. **Completeness**: Does it have all necessary details?\n\
3894            3. **Priority**: How urgent/important is this task?\n\
3895            4. **Dependencies**: Are there any prerequisites?\n\
3896            5. **Time Estimate**: How long should this take?\n\n\
3897            ## Current Context\n\
3898            - **Inbox Tasks**: {} tasks\n\
3899            - **Today's Tasks**: {} tasks\n\n\
3900            ## Recommendations\n\
3901            Based on the current workload and task details, provide specific recommendations for:\n\
3902            - Improving task clarity\n\
3903            - Breaking down complex tasks\n\
3904            - Setting appropriate deadlines\n\
3905            - Managing dependencies\n\n\
3906            ## Next Steps\n\
3907            Suggest concrete next steps to move this task forward effectively.",
3908            task_title,
3909            task_title,
3910            task_notes.unwrap_or("No notes provided"),
3911            context.unwrap_or("No additional context"),
3912            inbox_tasks.len(),
3913            today_tasks.len()
3914        );
3915
3916        Ok(GetPromptResult {
3917            content: vec![Content::Text { text: prompt_text }],
3918            is_error: false,
3919        })
3920    }
3921
3922    /// Handle project planning prompt
3923    async fn handle_project_planning_prompt(&self, args: Value) -> McpResult<GetPromptResult> {
3924        let project_title = args
3925            .get("project_title")
3926            .and_then(|v| v.as_str())
3927            .ok_or_else(|| McpError::missing_parameter("project_title"))?;
3928        let project_description = args.get("project_description").and_then(|v| v.as_str());
3929        let deadline = args.get("deadline").and_then(|v| v.as_str());
3930        let complexity = args
3931            .get("complexity")
3932            .and_then(|v| v.as_str())
3933            .unwrap_or("medium");
3934
3935        // Get current data for context
3936        let db = &self.db;
3937        let projects = db.get_projects(None).await.map_err(|e| {
3938            McpError::database_operation_failed("get_projects for project_planning", e)
3939        })?;
3940        let areas = db.get_areas().await.map_err(|e| {
3941            McpError::database_operation_failed("get_areas for project_planning", e)
3942        })?;
3943        let _ = db;
3944
3945        let prompt_text = format!(
3946            "# Project Planning: {}\n\n\
3947            ## Project Overview\n\
3948            - **Title**: {}\n\
3949            - **Description**: {}\n\
3950            - **Deadline**: {}\n\
3951            - **Complexity**: {}\n\n\
3952            ## Planning Framework\n\
3953            Please help plan this project by:\n\
3954            1. **Breaking down** the project into manageable tasks\n\
3955            2. **Estimating** time requirements for each task\n\
3956            3. **Identifying** dependencies between tasks\n\
3957            4. **Suggesting** milestones and checkpoints\n\
3958            5. **Recommending** project organization (areas, tags, etc.)\n\n\
3959            ## Current Context\n\
3960            - **Existing Projects**: {} projects\n\
3961            - **Available Areas**: {} areas\n\n\
3962            ## Task Breakdown\n\
3963            Create a detailed task list with:\n\
3964            - Clear, actionable task titles\n\
3965            - Estimated time for each task\n\
3966            - Priority levels\n\
3967            - Dependencies\n\
3968            - Suggested deadlines\n\n\
3969            ## Project Organization\n\
3970            Suggest:\n\
3971            - Appropriate area for this project\n\
3972            - Useful tags for organization\n\
3973            - Project structure and hierarchy\n\n\
3974            ## Risk Assessment\n\
3975            Identify potential challenges and mitigation strategies.\n\n\
3976            ## Success Metrics\n\
3977            Define how to measure project success and completion.",
3978            project_title,
3979            project_title,
3980            project_description.unwrap_or("No description provided"),
3981            deadline.unwrap_or("No deadline specified"),
3982            complexity,
3983            projects.len(),
3984            areas.len()
3985        );
3986
3987        Ok(GetPromptResult {
3988            content: vec![Content::Text { text: prompt_text }],
3989            is_error: false,
3990        })
3991    }
3992
3993    /// Handle productivity analysis prompt
3994    async fn handle_productivity_analysis_prompt(&self, args: Value) -> McpResult<GetPromptResult> {
3995        let time_period = args
3996            .get("time_period")
3997            .and_then(|v| v.as_str())
3998            .ok_or_else(|| McpError::missing_parameter("time_period"))?;
3999        let focus_area = args
4000            .get("focus_area")
4001            .and_then(|v| v.as_str())
4002            .unwrap_or("all");
4003        let include_recommendations = args
4004            .get("include_recommendations")
4005            .and_then(serde_json::Value::as_bool)
4006            .unwrap_or(true);
4007
4008        // Get current data for analysis
4009        let db = &self.db;
4010        let inbox_tasks = db.get_inbox(None).await.map_err(|e| {
4011            McpError::database_operation_failed("get_inbox for productivity_analysis", e)
4012        })?;
4013        let today_tasks = db.get_today(None).await.map_err(|e| {
4014            McpError::database_operation_failed("get_today for productivity_analysis", e)
4015        })?;
4016        let projects = db.get_projects(None).await.map_err(|e| {
4017            McpError::database_operation_failed("get_projects for productivity_analysis", e)
4018        })?;
4019        let areas = db.get_areas().await.map_err(|e| {
4020            McpError::database_operation_failed("get_areas for productivity_analysis", e)
4021        })?;
4022        let _ = db;
4023
4024        let completed_tasks = projects
4025            .iter()
4026            .filter(|p| p.status == things3_core::TaskStatus::Completed)
4027            .count();
4028        let incomplete_tasks = projects
4029            .iter()
4030            .filter(|p| p.status == things3_core::TaskStatus::Incomplete)
4031            .count();
4032
4033        let prompt_text = format!(
4034            "# Productivity Analysis - {}\n\n\
4035            ## Analysis Period: {}\n\
4036            ## Focus Area: {}\n\n\
4037            ## Current Data Overview\n\
4038            - **Inbox Tasks**: {} tasks\n\
4039            - **Today's Tasks**: {} tasks\n\
4040            - **Total Projects**: {} projects\n\
4041            - **Areas**: {} areas\n\
4042            - **Completed Tasks**: {} tasks\n\
4043            - **Incomplete Tasks**: {} tasks\n\n\
4044            ## Analysis Framework\n\
4045            Please analyze productivity patterns focusing on:\n\n\
4046            ### 1. Task Completion Patterns\n\
4047            - Completion rates over the period\n\
4048            - Task types that are completed vs. delayed\n\
4049            - Time patterns in task completion\n\n\
4050            ### 2. Workload Distribution\n\
4051            - Balance between different areas/projects\n\
4052            - Task complexity distribution\n\
4053            - Deadline adherence patterns\n\n\
4054            ### 3. Time Management\n\
4055            - Task scheduling effectiveness\n\
4056            - Inbox vs. scheduled task completion\n\
4057            - Overdue task patterns\n\n\
4058            ### 4. Project Progress\n\
4059            - Project completion rates\n\
4060            - Project complexity vs. completion time\n\
4061            - Area-based productivity differences\n\n\
4062            ## Key Insights\n\
4063            Identify:\n\
4064            - Peak productivity times\n\
4065            - Most/least productive areas\n\
4066            - Common bottlenecks\n\
4067            - Success patterns\n\n\
4068            ## Recommendations\n\
4069            {}",
4070            time_period,
4071            time_period,
4072            focus_area,
4073            inbox_tasks.len(),
4074            today_tasks.len(),
4075            projects.len(),
4076            areas.len(),
4077            completed_tasks,
4078            incomplete_tasks,
4079            if include_recommendations {
4080                "Provide specific, actionable recommendations for:\n\
4081                - Improving task completion rates\n\
4082                - Better time management\n\
4083                - Workload balancing\n\
4084                - Process optimization\n\
4085                - Goal setting and tracking"
4086            } else {
4087                "Focus on analysis without recommendations"
4088            }
4089        );
4090
4091        Ok(GetPromptResult {
4092            content: vec![Content::Text { text: prompt_text }],
4093            is_error: false,
4094        })
4095    }
4096
4097    /// Handle backup strategy prompt
4098    async fn handle_backup_strategy_prompt(&self, args: Value) -> McpResult<GetPromptResult> {
4099        let data_volume = args
4100            .get("data_volume")
4101            .and_then(|v| v.as_str())
4102            .ok_or_else(|| McpError::missing_parameter("data_volume"))?;
4103        let frequency = args
4104            .get("frequency")
4105            .and_then(|v| v.as_str())
4106            .ok_or_else(|| McpError::missing_parameter("frequency"))?;
4107        let retention_period = args
4108            .get("retention_period")
4109            .and_then(|v| v.as_str())
4110            .unwrap_or("3_months");
4111        let storage_preference = args
4112            .get("storage_preference")
4113            .and_then(|v| v.as_str())
4114            .unwrap_or("hybrid");
4115
4116        // Get current data for context
4117        let db = &self.db;
4118        let projects = db.get_projects(None).await.map_err(|e| {
4119            McpError::database_operation_failed("get_projects for backup_strategy", e)
4120        })?;
4121        let areas = db
4122            .get_areas()
4123            .await
4124            .map_err(|e| McpError::database_operation_failed("get_areas for backup_strategy", e))?;
4125        let _ = db;
4126
4127        let prompt_text = format!(
4128            "# Backup Strategy Recommendation\n\n\
4129            ## Requirements\n\
4130            - **Data Volume**: {}\n\
4131            - **Backup Frequency**: {}\n\
4132            - **Retention Period**: {}\n\
4133            - **Storage Preference**: {}\n\n\
4134            ## Current Data Context\n\
4135            - **Projects**: {} projects\n\
4136            - **Areas**: {} areas\n\
4137            - **Database Type**: SQLite (Things 3)\n\n\
4138            ## Backup Strategy Analysis\n\n\
4139            ### 1. Data Assessment\n\
4140            Analyze the current data volume and growth patterns:\n\
4141            - Database size estimation\n\
4142            - Growth rate projections\n\
4143            - Critical data identification\n\n\
4144            ### 2. Backup Frequency Optimization\n\
4145            For {} frequency backups:\n\
4146            - Optimal timing considerations\n\
4147            - Incremental vs. full backup strategy\n\
4148            - Performance impact analysis\n\n\
4149            ### 3. Storage Strategy\n\
4150            For {} storage preference:\n\
4151            - Local storage recommendations\n\
4152            - Cloud storage options\n\
4153            - Hybrid approach benefits\n\
4154            - Cost considerations\n\n\
4155            ### 4. Retention Policy\n\
4156            For {} retention period:\n\
4157            - Data lifecycle management\n\
4158            - Compliance considerations\n\
4159            - Storage optimization\n\n\
4160            ## Recommended Implementation\n\
4161            Provide specific recommendations for:\n\
4162            - Backup tools and software\n\
4163            - Storage locations and providers\n\
4164            - Automation setup\n\
4165            - Monitoring and alerting\n\
4166            - Recovery procedures\n\n\
4167            ## Risk Mitigation\n\
4168            Address:\n\
4169            - Data loss prevention\n\
4170            - Backup verification\n\
4171            - Disaster recovery planning\n\
4172            - Security considerations\n\n\
4173            ## Cost Analysis\n\
4174            Estimate costs for:\n\
4175            - Storage requirements\n\
4176            - Backup software/tools\n\
4177            - Cloud services\n\
4178            - Maintenance overhead",
4179            data_volume,
4180            frequency,
4181            retention_period,
4182            storage_preference,
4183            projects.len(),
4184            areas.len(),
4185            frequency,
4186            storage_preference,
4187            retention_period
4188        );
4189
4190        Ok(GetPromptResult {
4191            content: vec![Content::Text { text: prompt_text }],
4192            is_error: false,
4193        })
4194    }
4195
4196    /// Get available MCP resources
4197    fn get_available_resources() -> Vec<Resource> {
4198        vec![
4199            Resource {
4200                uri: "things://inbox".to_string(),
4201                name: "Inbox Tasks".to_string(),
4202                description: "Current inbox tasks from Things 3".to_string(),
4203                mime_type: Some("application/json".to_string()),
4204            },
4205            Resource {
4206                uri: "things://projects".to_string(),
4207                name: "All Projects".to_string(),
4208                description: "All projects in Things 3".to_string(),
4209                mime_type: Some("application/json".to_string()),
4210            },
4211            Resource {
4212                uri: "things://areas".to_string(),
4213                name: "All Areas".to_string(),
4214                description: "All areas in Things 3".to_string(),
4215                mime_type: Some("application/json".to_string()),
4216            },
4217            Resource {
4218                uri: "things://today".to_string(),
4219                name: "Today's Tasks".to_string(),
4220                description: "Tasks scheduled for today".to_string(),
4221                mime_type: Some("application/json".to_string()),
4222            },
4223        ]
4224    }
4225
4226    /// Handle resource read request
4227    async fn handle_resource_read(
4228        &self,
4229        request: ReadResourceRequest,
4230    ) -> McpResult<ReadResourceResult> {
4231        let uri = &request.uri;
4232
4233        let db = &self.db;
4234        let data = match uri.as_str() {
4235            "things://inbox" => {
4236                let tasks = db.get_inbox(None).await.map_err(|e| {
4237                    McpError::database_operation_failed("get_inbox for resource", e)
4238                })?;
4239                serde_json::to_string_pretty(&tasks).map_err(|e| {
4240                    McpError::serialization_failed("inbox resource serialization", e)
4241                })?
4242            }
4243            "things://projects" => {
4244                let projects = db.get_projects(None).await.map_err(|e| {
4245                    McpError::database_operation_failed("get_projects for resource", e)
4246                })?;
4247                serde_json::to_string_pretty(&projects).map_err(|e| {
4248                    McpError::serialization_failed("projects resource serialization", e)
4249                })?
4250            }
4251            "things://areas" => {
4252                let areas = db.get_areas().await.map_err(|e| {
4253                    McpError::database_operation_failed("get_areas for resource", e)
4254                })?;
4255                serde_json::to_string_pretty(&areas).map_err(|e| {
4256                    McpError::serialization_failed("areas resource serialization", e)
4257                })?
4258            }
4259            "things://today" => {
4260                let tasks = db.get_today(None).await.map_err(|e| {
4261                    McpError::database_operation_failed("get_today for resource", e)
4262                })?;
4263                let _ = db;
4264                serde_json::to_string_pretty(&tasks).map_err(|e| {
4265                    McpError::serialization_failed("today resource serialization", e)
4266                })?
4267            }
4268            _ => {
4269                return Err(McpError::resource_not_found(uri));
4270            }
4271        };
4272
4273        Ok(ReadResourceResult {
4274            contents: vec![Content::Text { text: data }],
4275        })
4276    }
4277
4278    /// Handle a JSON-RPC request and return a JSON-RPC response
4279    ///
4280    /// Returns `None` for notifications (messages without `id` field) - these don't require a response
4281    ///
4282    /// # Errors
4283    /// Returns an error if request parsing or handling fails
4284    pub async fn handle_jsonrpc_request(
4285        &self,
4286        request: serde_json::Value,
4287    ) -> things3_core::Result<Option<serde_json::Value>> {
4288        use serde_json::json;
4289
4290        let method = request["method"].as_str().ok_or_else(|| {
4291            things3_core::ThingsError::unknown("Missing method in JSON-RPC request".to_string())
4292        })?;
4293        let params = request["params"].clone();
4294
4295        // Check if this is a notification (no `id` field present)
4296        // In JSON-RPC, notifications don't have an `id` field, so get("id") returns None
4297        let is_notification = request.get("id").is_none();
4298
4299        // Handle notifications silently (they don't require a response)
4300        if is_notification {
4301            match method {
4302                "notifications/initialized" => {
4303                    // Silently acknowledge the initialized notification
4304                    return Ok(None);
4305                }
4306                _ => {
4307                    // Unknown notification - silently ignore
4308                    return Ok(None);
4309                }
4310            }
4311        }
4312
4313        // For requests (with `id` field), we need the id for the response
4314        let id = request["id"].clone();
4315
4316        let result = match method {
4317            "initialize" => {
4318                // Handle initialize request
4319                json!({
4320                    "protocolVersion": "2024-11-05",
4321                    "capabilities": {
4322                        "tools": { "listChanged": false },
4323                        "resources": { "subscribe": false, "listChanged": false },
4324                        "prompts": { "listChanged": false }
4325                    },
4326                    "serverInfo": {
4327                        "name": "things3-mcp",
4328                        "version": env!("CARGO_PKG_VERSION")
4329                    }
4330                })
4331            }
4332            "tools/list" => {
4333                let tools_result = self.list_tools().map_err(|e| {
4334                    things3_core::ThingsError::unknown(format!("Failed to list tools: {}", e))
4335                })?;
4336                json!(tools_result.tools)
4337            }
4338            "tools/call" => {
4339                let tool_name = params["name"]
4340                    .as_str()
4341                    .ok_or_else(|| {
4342                        things3_core::ThingsError::unknown(
4343                            "Missing tool name in params".to_string(),
4344                        )
4345                    })?
4346                    .to_string();
4347                let arguments = params["arguments"].clone();
4348
4349                let call_request = CallToolRequest {
4350                    name: tool_name,
4351                    arguments: Some(arguments),
4352                };
4353
4354                let call_result = self.call_tool(call_request).await.map_err(|e| {
4355                    things3_core::ThingsError::unknown(format!("Failed to call tool: {}", e))
4356                })?;
4357
4358                json!(call_result)
4359            }
4360            "resources/list" => {
4361                let resources_result = self.list_resources().map_err(|e| {
4362                    things3_core::ThingsError::unknown(format!("Failed to list resources: {}", e))
4363                })?;
4364                json!(resources_result.resources)
4365            }
4366            "resources/read" => {
4367                let uri = params["uri"]
4368                    .as_str()
4369                    .ok_or_else(|| {
4370                        things3_core::ThingsError::unknown("Missing URI in params".to_string())
4371                    })?
4372                    .to_string();
4373
4374                let read_request = ReadResourceRequest { uri };
4375                let read_result = self.read_resource(read_request).await.map_err(|e| {
4376                    things3_core::ThingsError::unknown(format!("Failed to read resource: {}", e))
4377                })?;
4378
4379                json!(read_result)
4380            }
4381            "prompts/list" => {
4382                let prompts_result = self.list_prompts().map_err(|e| {
4383                    things3_core::ThingsError::unknown(format!("Failed to list prompts: {}", e))
4384                })?;
4385                json!(prompts_result.prompts)
4386            }
4387            "prompts/get" => {
4388                let prompt_name = params["name"]
4389                    .as_str()
4390                    .ok_or_else(|| {
4391                        things3_core::ThingsError::unknown(
4392                            "Missing prompt name in params".to_string(),
4393                        )
4394                    })?
4395                    .to_string();
4396                let arguments = params.get("arguments").cloned();
4397
4398                let get_request = GetPromptRequest {
4399                    name: prompt_name,
4400                    arguments,
4401                };
4402
4403                let get_result = self.get_prompt(get_request).await.map_err(|e| {
4404                    things3_core::ThingsError::unknown(format!("Failed to get prompt: {}", e))
4405                })?;
4406
4407                json!(get_result)
4408            }
4409            _ => {
4410                return Ok(Some(json!({
4411                    "jsonrpc": "2.0",
4412                    "id": id,
4413                    "error": {
4414                        "code": -32601,
4415                        "message": format!("Method not found: {}", method)
4416                    }
4417                })));
4418            }
4419        };
4420
4421        Ok(Some(json!({
4422            "jsonrpc": "2.0",
4423            "id": id,
4424            "result": result
4425        })))
4426    }
4427}