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