Skip to main content

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