Skip to main content

mcp_gmailcal/
server.rs

1use log::{debug, error, info};
2use mcp_attr::server::{mcp_server, McpServer};
3use mcp_attr::{Error as McpError, Result as McpResult};
4use serde_json::json;
5
6use crate::config::Config;
7use crate::errors::ConfigError;
8use crate::errors::GmailApiError;
9use crate::gmail_api::GmailService;
10use crate::utils::error_codes;
11
12// Helper functions
13mod helpers {
14    // Re-export the parse_max_results function from utils
15    pub use crate::utils::parse_max_results;
16}
17
18// Error codes have been moved to the utils module
19
20// MCP server for accessing Gmail API
21#[derive(Clone)]
22pub struct GmailServer;
23
24impl Default for GmailServer {
25    fn default() -> Self {
26        Self::new()
27    }
28}
29
30impl GmailServer {
31    pub fn new() -> Self {
32        GmailServer {}
33    }
34
35    // Private method to initialize the Calendar service
36    async fn init_calendar_service(&self) -> Result<crate::calendar_api::CalendarClient, McpError> {
37        // Load the config
38        let config = Config::from_env().map_err(|e| {
39            error!("Failed to load OAuth configuration: {}", e);
40            self.to_mcp_error(
41                &format!("Configuration error: {}", e),
42                error_codes::CONFIG_ERROR,
43            )
44        })?;
45
46        // Create the calendar client
47        Ok(crate::calendar_api::CalendarClient::new(&config))
48    }
49
50    // Private method to initialize the People API service
51    async fn init_people_service(&self) -> Result<crate::people_api::PeopleClient, McpError> {
52        // Load the config
53        let config = Config::from_env().map_err(|e| {
54            error!("Failed to load OAuth configuration: {}", e);
55            self.to_mcp_error(
56                &format!("Configuration error: {}", e),
57                error_codes::CONFIG_ERROR,
58            )
59        })?;
60
61        // Create the people client
62        Ok(crate::people_api::PeopleClient::new(&config))
63    }
64
65    // Helper function to create detailed McpError with appropriate error code and context
66    fn to_mcp_error(&self, message: &str, code: u32) -> McpError {
67        // Delegate to the utility function
68        crate::utils::to_mcp_error(message, code)
69    }
70
71    // Helper function to map GmailApiError to detailed McpError with specific codes
72    fn map_gmail_error(&self, err: GmailApiError) -> McpError {
73        // Delegate to the utility function
74        crate::utils::map_gmail_error(err)
75    }
76
77    // Helper function to initialize Gmail service with detailed error handling
78    async fn init_gmail_service(&self) -> McpResult<GmailService> {
79        // Load configuration
80        let config = Config::from_env().map_err(|err| {
81            let msg = match err {
82                ConfigError::MissingEnvVar(var) => {
83                    format!(
84                        "Missing environment variable: {}. \
85                        This variable is required for Gmail authentication. \
86                        Please ensure you have set up your .env file correctly or exported the variable in your shell. \
87                        Create an OAuth2 client in the Google Cloud Console to obtain these credentials.", 
88                        var
89                    )
90                }
91                ConfigError::EnvError(e) => {
92                    format!(
93                        "Environment variable error: {}. \
94                        There was a problem reading the environment variables needed for Gmail authentication. \
95                        Check permissions on your .env file and ensure it's properly formatted without special characters or quotes.", 
96                        e
97                    )
98                },
99            };
100            self.to_mcp_error(&msg, error_codes::CONFIG_ERROR)
101        })?;
102
103        // Create Gmail service
104        GmailService::new(&config).map_err(|err| {
105            error!("Failed to create Gmail service: {}", err);
106            self.map_gmail_error(err)
107        })
108    }
109}
110
111// MCP server implementation with custom serialization
112#[mcp_server]
113impl McpServer for GmailServer {
114    /// Gmail MCP Server
115    ///
116    /// This MCP server provides direct access to the Gmail API using reqwest.
117    /// It requires the following environment variables to be set:
118    /// - GMAIL_CLIENT_ID
119    /// - GMAIL_CLIENT_SECRET
120    /// - GMAIL_REFRESH_TOKEN
121    ///
122    /// You can provide these in a .env file in the same directory as the executable.
123    #[prompt]
124    async fn gmail_prompt(&self) -> McpResult<&str> {
125        Ok(crate::prompts::GMAIL_MASTER_PROMPT)
126    }
127
128    /// Email Analysis Prompt
129    ///
130    /// Guidelines on how to analyze email content effectively
131    #[prompt]
132    async fn email_analysis_prompt(&self) -> McpResult<&str> {
133        Ok(crate::prompts::EMAIL_ANALYSIS_PROMPT)
134    }
135
136    /// Email Summarization Prompt
137    ///
138    /// Guidelines on how to create concise email summaries
139    #[prompt]
140    async fn email_summarization_prompt(&self) -> McpResult<&str> {
141        Ok(crate::prompts::EMAIL_SUMMARIZATION_PROMPT)
142    }
143
144    /// Email Search Prompt
145    ///
146    /// Guide to effective Gmail search strategies
147    #[prompt]
148    async fn email_search_prompt(&self) -> McpResult<&str> {
149        Ok(crate::prompts::EMAIL_SEARCH_PROMPT)
150    }
151
152    /// Task Extraction Prompt
153    ///
154    /// Instructions for finding action items in emails
155    #[prompt]
156    async fn task_extraction_prompt(&self) -> McpResult<&str> {
157        Ok(crate::prompts::TASK_EXTRACTION_PROMPT)
158    }
159
160    /// Meeting Extraction Prompt
161    ///
162    /// Instructions for finding meeting details in emails
163    #[prompt]
164    async fn meeting_extraction_prompt(&self) -> McpResult<&str> {
165        Ok(crate::prompts::MEETING_EXTRACTION_PROMPT)
166    }
167
168    /// Contact Extraction Prompt
169    ///
170    /// Instructions for extracting contact information from emails
171    #[prompt]
172    async fn contact_extraction_prompt(&self) -> McpResult<&str> {
173        Ok(crate::prompts::CONTACT_EXTRACTION_PROMPT)
174    }
175
176    /// Email Categorization Prompt
177    ///
178    /// Guide to categorizing emails effectively
179    #[prompt]
180    async fn email_categorization_prompt(&self) -> McpResult<&str> {
181        Ok(crate::prompts::EMAIL_CATEGORIZATION_PROMPT)
182    }
183
184    /// Email Prioritization Prompt
185    ///
186    /// Guide to prioritizing emails effectively
187    #[prompt]
188    async fn email_prioritization_prompt(&self) -> McpResult<&str> {
189        Ok(crate::prompts::EMAIL_PRIORITIZATION_PROMPT)
190    }
191
192    /// Email Drafting Prompt
193    ///
194    /// Guide to writing effective emails
195    #[prompt]
196    async fn email_drafting_prompt(&self) -> McpResult<&str> {
197        Ok(crate::prompts::EMAIL_DRAFTING_PROMPT)
198    }
199
200    /// Get a list of emails from the inbox
201    ///
202    /// Returns emails with subject, sender, recipient, date and snippet information.
203    ///
204    /// Args:
205    ///   max_results: Optional maximum number of results to return (default: 10). Can be a number (3) or a string ("3").
206    ///   query: Optional Gmail search query string (e.g. "is:unread from:example.com")
207    #[tool]
208    async fn list_emails(
209        &self,
210        max_results: Option<serde_json::Value>,
211        query: Option<String>,
212    ) -> McpResult<String> {
213        info!("=== START list_emails MCP command ===");
214        debug!(
215            "list_emails called with max_results={:?}, query={:?}",
216            max_results, query
217        );
218
219        // Convert max_results using the helper function (default: 10)
220        let max = helpers::parse_max_results(max_results, 10);
221
222        // Get the Gmail service
223        let mut service = self.init_gmail_service().await?;
224
225        // Get messages with full metadata
226        let result = match service.list_messages(max, query.as_deref()).await {
227            Ok(messages) => {
228                // Convert to JSON
229                serde_json::to_string(&messages).map_err(|e| {
230                    let error_msg = format!("Failed to serialize message list: {}", e);
231                    error!("{}", error_msg);
232                    self.to_mcp_error(&error_msg, error_codes::MESSAGE_FORMAT_ERROR)
233                })?
234            }
235            Err(err) => {
236                let query_info = query.as_deref().unwrap_or("none");
237                error!(
238                    "Failed to list emails with max_results={}, query='{}': {}",
239                    max, query_info, err
240                );
241
242                // Create detailed contextual error
243                error!(
244                    "Context: Failed to list emails with parameters: max_results={}, query='{}'",
245                    max, query_info
246                );
247
248                return Err(self.map_gmail_error(err));
249            }
250        };
251
252        info!("=== END list_emails MCP command (success) ===");
253        Ok(result)
254    }
255    /// Get details for a specific email
256    ///
257    /// Returns the message with all metadata and content parsed into a structured format.
258    ///
259    /// Args:
260    ///   message_id: The ID of the message to retrieve
261    #[tool]
262    async fn get_email(&self, message_id: String) -> McpResult<String> {
263        info!("=== START get_email MCP command ===");
264        debug!("get_email called with message_id={}", message_id);
265
266        // Get the Gmail service
267        let mut service = self.init_gmail_service().await?;
268
269        // Get detailed message directly using the helper method
270        let email = match service.get_message_details(&message_id).await {
271            Ok(email) => email,
272            Err(err) => {
273                error!(
274                    "Failed to get email with message_id='{}': {}",
275                    message_id, err
276                );
277
278                // Create detailed contextual error
279                error!(
280                    "Context: Failed to retrieve email with ID: '{}'",
281                    message_id
282                );
283
284                return Err(self.map_gmail_error(err));
285            }
286        };
287
288        // Convert to JSON
289        let result = serde_json::to_string(&email).map_err(|e| {
290            let error_msg = format!("Failed to serialize email: {}", e);
291            error!("{}", error_msg);
292            self.to_mcp_error(&error_msg, error_codes::MESSAGE_FORMAT_ERROR)
293        })?;
294
295        info!("=== END get_email MCP command (success) ===");
296        Ok(result)
297    }
298    /// Search for emails using a Gmail search query
299    ///
300    /// Returns emails with subject, sender, recipient, date and snippet information.
301    ///
302    /// Args:
303    ///   query: Gmail search query string (e.g. "is:unread from:example.com")
304    ///   max_results: Optional maximum number of results (default: 10). Can be a number (3) or a string ("3").
305    #[tool]
306    async fn search_emails(
307        &self,
308        query: String,
309        max_results: Option<serde_json::Value>,
310    ) -> McpResult<String> {
311        info!("=== START search_emails MCP command ===");
312        debug!(
313            "search_emails called with query={:?}, max_results={:?}",
314            query, max_results
315        );
316
317        // Get the parsed max_results value
318        let max = helpers::parse_max_results(max_results, 10);
319
320        // Get the Gmail service
321        let mut service = self.init_gmail_service().await?;
322
323        // Get messages with full metadata
324        let result = match service.list_messages(max, Some(&query)).await {
325            Ok(messages) => {
326                // Convert to JSON
327                serde_json::to_string(&messages).map_err(|e| {
328                    let error_msg = format!("Failed to serialize message list: {}", e);
329                    error!("{}", error_msg);
330                    self.to_mcp_error(&error_msg, error_codes::MESSAGE_FORMAT_ERROR)
331                })?
332            }
333            Err(err) => {
334                error!(
335                    "Failed to search emails with query='{}', max_results={}: {}",
336                    query, max, err
337                );
338
339                // Create detailed contextual error with specific advice for search queries
340                error!("Context: Failed to search emails with query: '{}'", query);
341
342                return Err(self.map_gmail_error(err));
343            }
344        };
345
346        info!("=== END search_emails MCP command (success) ===");
347        Ok(result)
348    }
349
350    /// Get a list of email labels
351    ///
352    /// Returns the raw JSON response from the Gmail API without any transformation or modification.
353    #[tool]
354    async fn list_labels(&self) -> McpResult<String> {
355        debug!("list_labels called");
356
357        // Get the Gmail service
358        let mut service = self.init_gmail_service().await?;
359
360        // Get labels
361        match service.list_labels().await {
362            Ok(labels) => Ok(labels),
363            Err(err) => {
364                error!("Failed to list labels: {}", err);
365
366                // Provide detailed error with troubleshooting steps
367                // Include detailed context in the error log
368                error!("Context: Failed to retrieve Gmail labels. This operation requires read access permissions.");
369
370                Err(self.map_gmail_error(err))
371            }
372        }
373    }
374
375    /// Check connection status with Gmail API
376    ///
377    /// Tests the connection to Gmail API by retrieving the user's profile.
378    /// Returns the raw JSON response from the Gmail API without any transformation or modification.
379    #[tool]
380    async fn check_connection(&self) -> McpResult<String> {
381        info!("=== START check_connection MCP command ===");
382        debug!("check_connection called");
383
384        // Get the Gmail service
385        let mut service = self.init_gmail_service().await?;
386
387        // Get profile as raw JSON
388        let profile_json = match service.check_connection_raw().await {
389            Ok(json) => json,
390            Err(err) => {
391                error!("Connection check failed: {}", err);
392
393                // Provide helpful information on connectivity issues
394                // Include detailed context in the error log
395                error!("Context: Failed to connect to Gmail API. This is a basic connectivity test failure.");
396
397                return Err(self.map_gmail_error(err));
398            }
399        };
400
401        info!("=== END check_connection MCP command (success) ===");
402        Ok(profile_json)
403    }
404
405    /// Analyze an email to extract key information
406    ///
407    /// Takes an email ID and performs a detailed analysis on its content.
408    /// Extracts information like action items, meeting details, contact information,
409    /// sentiment, priority, and suggested next steps.
410    ///
411    /// Args:
412    ///   message_id: The ID of the message to analyze
413    ///   analysis_type: Optional type of analysis to perform. Can be "general", "tasks",
414    ///                  "meetings", "contacts", or "all". Default is "general".
415    #[tool]
416    async fn analyze_email(
417        &self,
418        message_id: String,
419        analysis_type: Option<String>,
420    ) -> McpResult<String> {
421        info!("=== START analyze_email MCP command ===");
422        debug!(
423            "analyze_email called with message_id={}, analysis_type={:?}",
424            message_id, analysis_type
425        );
426
427        // Get the Gmail service
428        let mut service = self.init_gmail_service().await?;
429
430        // Get the specified email
431        let email = match service.get_message_details(&message_id).await {
432            Ok(msg) => msg,
433            Err(err) => {
434                error!("Failed to get email for analysis: {}", err);
435                return Err(self.map_gmail_error(err));
436            }
437        };
438
439        // Determine what type of analysis to perform
440        let analysis = analysis_type.unwrap_or_else(|| "general".to_string());
441
442        // Prepare the analysis result
443        let result = match analysis.to_lowercase().as_str() {
444            "tasks" | "task" => {
445                // Create a structured JSON for task analysis
446                json!({
447                    "email_id": email.id,
448                    "subject": email.subject,
449                    "from": email.from,
450                    "date": email.date,
451                    "analysis_type": "tasks",
452                    "content": email.body_text.unwrap_or_else(|| email.snippet.unwrap_or_default()),
453                    "analysis_prompt": crate::prompts::TASK_EXTRACTION_PROMPT
454                })
455            }
456            "meetings" | "meeting" => {
457                // Create a structured JSON for meeting analysis
458                json!({
459                    "email_id": email.id,
460                    "subject": email.subject,
461                    "from": email.from,
462                    "date": email.date,
463                    "analysis_type": "meetings",
464                    "content": email.body_text.unwrap_or_else(|| email.snippet.unwrap_or_default()),
465                    "analysis_prompt": crate::prompts::MEETING_EXTRACTION_PROMPT
466                })
467            }
468            "contacts" | "contact" => {
469                // Create a structured JSON for contact analysis
470                json!({
471                    "email_id": email.id,
472                    "subject": email.subject,
473                    "from": email.from,
474                    "date": email.date,
475                    "analysis_type": "contacts",
476                    "content": email.body_text.unwrap_or_else(|| email.snippet.unwrap_or_default()),
477                    "analysis_prompt": crate::prompts::CONTACT_EXTRACTION_PROMPT
478                })
479            }
480            "summary" | "summarize" => {
481                // Create a structured JSON for email summarization
482                json!({
483                    "email_id": email.id,
484                    "subject": email.subject,
485                    "from": email.from,
486                    "date": email.date,
487                    "analysis_type": "summary",
488                    "content": email.body_text.unwrap_or_else(|| email.snippet.unwrap_or_default()),
489                    "analysis_prompt": crate::prompts::EMAIL_SUMMARIZATION_PROMPT
490                })
491            }
492            "priority" | "prioritize" => {
493                // Create a structured JSON for email prioritization
494                json!({
495                    "email_id": email.id,
496                    "subject": email.subject,
497                    "from": email.from,
498                    "date": email.date,
499                    "analysis_type": "priority",
500                    "content": email.body_text.unwrap_or_else(|| email.snippet.unwrap_or_default()),
501                    "analysis_prompt": crate::prompts::EMAIL_PRIORITIZATION_PROMPT
502                })
503            }
504            "all" => {
505                // Create comprehensive JSON with all analysis types
506                json!({
507                    "email_id": email.id,
508                    "subject": email.subject,
509                    "from": email.from,
510                    "to": email.to,
511                    "date": email.date,
512                    "analysis_type": "comprehensive",
513                    "content": email.body_text.unwrap_or_else(|| email.snippet.unwrap_or_default()),
514                    "html_content": email.body_html,
515                    "analysis_prompts": {
516                        "general": crate::prompts::EMAIL_ANALYSIS_PROMPT,
517                        "tasks": crate::prompts::TASK_EXTRACTION_PROMPT,
518                        "meetings": crate::prompts::MEETING_EXTRACTION_PROMPT,
519                        "contacts": crate::prompts::CONTACT_EXTRACTION_PROMPT,
520                        "priority": crate::prompts::EMAIL_PRIORITIZATION_PROMPT
521                    }
522                })
523            }
524            _ => {
525                // Default to general analysis
526                json!({
527                    "email_id": email.id,
528                    "subject": email.subject,
529                    "from": email.from,
530                    "date": email.date,
531                    "analysis_type": "general",
532                    "content": email.body_text.unwrap_or_else(|| email.snippet.unwrap_or_default()),
533                    "analysis_prompt": crate::prompts::EMAIL_ANALYSIS_PROMPT
534                })
535            }
536        };
537
538        // Convert to string
539        let result_json = serde_json::to_string_pretty(&result).map_err(|e| {
540            let error_msg = format!("Failed to serialize analysis result: {}", e);
541            error!("{}", error_msg);
542            self.to_mcp_error(&error_msg, error_codes::MESSAGE_FORMAT_ERROR)
543        })?;
544
545        info!("=== END analyze_email MCP command (success) ===");
546        Ok(result_json)
547    }
548
549    /// Batch analyze multiple emails
550    ///
551    /// Takes a list of email IDs and performs quick analysis on each one.
552    /// Useful for getting an overview of multiple emails at once.
553    ///
554    /// Args:
555    ///   message_ids: List of email IDs to analyze
556    ///   analysis_type: Optional type of analysis to perform. Can be "summary", "tasks",
557    ///                  "priority", or "category". Default is "summary".
558    #[tool]
559    async fn batch_analyze_emails(
560        &self,
561        message_ids: Vec<String>,
562        analysis_type: Option<String>,
563    ) -> McpResult<String> {
564        info!("=== START batch_analyze_emails MCP command ===");
565        debug!(
566            "batch_analyze_emails called with {} messages, analysis_type={:?}",
567            message_ids.len(),
568            analysis_type
569        );
570
571        // Get the Gmail service
572        let mut service = self.init_gmail_service().await?;
573
574        // Determine what type of analysis to perform
575        let analysis = analysis_type
576            .unwrap_or_else(|| "summary".to_string())
577            .to_lowercase();
578
579        // Analyze each email
580        let mut results = Vec::new();
581        for id in message_ids {
582            debug!("Analyzing email {}", id);
583
584            // Get the specified email
585            match service.get_message_details(&id).await {
586                Ok(email) => {
587                    // Prepare analysis based on type
588                    let analysis_prompt = match analysis.as_str() {
589                        "tasks" | "task" => crate::prompts::TASK_EXTRACTION_PROMPT,
590                        "priority" => crate::prompts::EMAIL_PRIORITIZATION_PROMPT,
591                        "category" => crate::prompts::EMAIL_CATEGORIZATION_PROMPT,
592                        _ => crate::prompts::EMAIL_SUMMARIZATION_PROMPT, // Default to summary
593                    };
594
595                    // Create analysis result
596                    let result = json!({
597                        "email_id": email.id,
598                        "subject": email.subject,
599                        "from": email.from,
600                        "date": email.date,
601                        "analysis_type": analysis,
602                        "content": email.body_text.unwrap_or_else(|| email.snippet.unwrap_or_default()),
603                        "analysis_prompt": analysis_prompt
604                    });
605
606                    results.push(result);
607                }
608                Err(err) => {
609                    // Log error but continue with other emails
610                    error!("Failed to analyze email {}: {}", id, err);
611
612                    // Add error entry to results with more detailed information
613                    results.push(json!({
614                        "email_id": id,
615                        "error": format!("Failed to retrieve email: {}", err),
616                        "message": "This email failed processing but other emails in the batch will continue to process",
617                        "status": "error"
618                    }));
619                }
620            }
621        }
622
623        // Create a batch result
624        let batch_result = json!({
625            "analysis_type": analysis,
626            "email_count": results.len(),
627            "results": results
628        });
629
630        // Convert to string
631        let result_json = serde_json::to_string_pretty(&batch_result).map_err(|e| {
632            let error_msg = format!("Failed to serialize batch analysis result: {}", e);
633            error!("{}", error_msg);
634            self.to_mcp_error(&error_msg, error_codes::MESSAGE_FORMAT_ERROR)
635        })?;
636
637        info!("=== END batch_analyze_emails MCP command (success) ===");
638        Ok(result_json)
639    }
640
641    /// Create a draft email
642    ///
643    /// Creates a new draft email in Gmail with the specified content.
644    /// The email will be saved as a draft and can be edited before sending.
645    ///
646    /// Args:
647    ///   to: Email address(es) of the recipient(s). Multiple addresses should be comma-separated.
648    ///   subject: Subject line of the email
649    ///   body: Plain text content of the email
650    ///   cc: Optional CC recipient(s). Multiple addresses should be comma-separated.
651    ///   bcc: Optional BCC recipient(s). Multiple addresses should be comma-separated.
652    ///   thread_id: Optional Gmail thread ID to associate this email with
653    ///   in_reply_to: Optional Message-ID that this email is replying to
654    ///   references: Optional comma-separated list of Message-IDs in the email thread
655    #[tool]
656    #[allow(clippy::too_many_arguments)]
657    async fn create_draft_email(
658        &self,
659        // Required content
660        to: String,
661        subject: String,
662        body: String,
663        // Optional recipients
664        cc: Option<String>,
665        bcc: Option<String>,
666        // Optional threading
667        thread_id: Option<String>,
668        in_reply_to: Option<String>,
669        // Additional options
670        references: Option<String>,
671    ) -> McpResult<String> {
672        info!("=== START create_draft_email MCP command ===");
673        debug!(
674            "create_draft_email called with to={}, subject={}, cc={:?}, bcc={:?}, thread_id={:?}, in_reply_to={:?}",
675            to, subject, cc, bcc, thread_id, in_reply_to
676        );
677
678        // Validate email addresses
679        if to.is_empty() {
680            let error_msg = "Recipient (to) is required for creating a draft email";
681            error!("{}", error_msg);
682            return Err(self.to_mcp_error(error_msg, error_codes::MESSAGE_FORMAT_ERROR));
683        }
684
685        // Create the draft email object
686        let draft = crate::gmail_api::DraftEmail {
687            to,
688            subject,
689            body,
690            cc,
691            bcc,
692            thread_id,
693            in_reply_to,
694            references,
695        };
696
697        // Get the Gmail service
698        let mut service = self.init_gmail_service().await?;
699
700        // Create the draft
701        match service.create_draft(&draft).await {
702            Ok(draft_id) => {
703                // Create success response
704                let mut result = json!({
705                    "status": "success",
706                    "draft_id": draft_id,
707                    "message": "Draft email created successfully."
708                });
709
710                // Add threading info to response if provided
711                if let Some(ref thread_id_val) = draft.thread_id {
712                    result["thread_id"] = json!(thread_id_val);
713                }
714
715                // Convert to string
716                let result_json = serde_json::to_string_pretty(&result).map_err(|e| {
717                    let error_msg = format!("Failed to serialize draft creation result: {}", e);
718                    error!("{}", error_msg);
719                    self.to_mcp_error(&error_msg, error_codes::MESSAGE_FORMAT_ERROR)
720                })?;
721
722                info!("=== END create_draft_email MCP command (success) ===");
723                Ok(result_json)
724            }
725            Err(err) => {
726                error!("Failed to create draft email: {}", err);
727
728                // Create detailed error context for the user
729                error!(
730                    "Context: Failed to create draft email with subject: '{}'",
731                    draft.subject
732                );
733
734                Err(self.map_gmail_error(err))
735            }
736        }
737    }
738
739    /// List contacts
740    ///
741    /// This command retrieves a list of contacts from Google Contacts.
742    ///
743    /// # Parameters
744    ///
745    /// * `max_results` - Optional. The maximum number of contacts to return.
746    ///
747    /// # Returns
748    ///
749    /// A JSON string containing the contact list
750    #[tool]
751    async fn list_contacts(&self, max_results: Option<u32>) -> McpResult<String> {
752        info!("=== START list_contacts MCP command ===");
753        debug!("list_contacts called with max_results={:?}", max_results);
754
755        // Initialize the People API client
756        let people_client = self.init_people_service().await?;
757
758        match people_client.list_contacts(max_results).await {
759            Ok(contacts) => {
760                // Convert to JSON
761                serde_json::to_string(&contacts).map_err(|e| {
762                    let error_msg = format!("Failed to serialize contact list: {}", e);
763                    error!("{}", error_msg);
764                    self.to_mcp_error(&error_msg, error_codes::GENERAL_ERROR)
765                })
766            }
767            Err(err) => {
768                error!("Failed to list contacts: {}", err);
769                Err(self.to_mcp_error(
770                    &format!("Failed to list contacts: {}", err),
771                    error_codes::API_ERROR,
772                ))
773            }
774        }
775    }
776
777    /// Search contacts
778    ///
779    /// This command searches for contacts matching the query.
780    ///
781    /// # Parameters
782    ///
783    /// * `query` - The search query.
784    /// * `max_results` - Optional. The maximum number of contacts to return.
785    ///
786    /// # Returns
787    ///
788    /// A JSON string containing the matching contacts
789    #[tool]
790    async fn search_contacts(&self, query: String, max_results: Option<u32>) -> McpResult<String> {
791        info!("=== START search_contacts MCP command ===");
792        debug!(
793            "search_contacts called with query=\"{}\" and max_results={:?}",
794            query, max_results
795        );
796
797        // Initialize the People API client
798        let people_client = self.init_people_service().await?;
799
800        match people_client.search_contacts(&query, max_results).await {
801            Ok(contacts) => {
802                // Convert to JSON
803                serde_json::to_string(&contacts).map_err(|e| {
804                    let error_msg = format!("Failed to serialize contact search results: {}", e);
805                    error!("{}", error_msg);
806                    self.to_mcp_error(&error_msg, error_codes::GENERAL_ERROR)
807                })
808            }
809            Err(err) => {
810                error!("Failed to search contacts: {}", err);
811                Err(self.to_mcp_error(
812                    &format!("Failed to search contacts: {}", err),
813                    error_codes::API_ERROR,
814                ))
815            }
816        }
817    }
818
819    /// Get contact
820    ///
821    /// This command retrieves a specific contact by resource name.
822    ///
823    /// # Parameters
824    ///
825    /// * `resource_name` - The resource name of the contact to retrieve.
826    ///
827    /// # Returns
828    ///
829    /// A JSON string containing the contact details
830    #[tool]
831    async fn get_contact(&self, resource_name: String) -> McpResult<String> {
832        info!("=== START get_contact MCP command ===");
833        debug!("get_contact called with resource_name={}", resource_name);
834
835        // Initialize the People API client
836        let people_client = self.init_people_service().await?;
837
838        match people_client.get_contact(&resource_name).await {
839            Ok(contact) => {
840                // Convert to JSON
841                serde_json::to_string(&contact).map_err(|e| {
842                    let error_msg = format!("Failed to serialize contact: {}", e);
843                    error!("{}", error_msg);
844                    self.to_mcp_error(&error_msg, error_codes::GENERAL_ERROR)
845                })
846            }
847            Err(err) => {
848                error!("Failed to get contact: {}", err);
849                Err(self.to_mcp_error(
850                    &format!("Failed to get contact: {}", err),
851                    error_codes::API_ERROR,
852                ))
853            }
854        }
855    }
856
857    /// List all available calendars
858    ///
859    /// This command retrieves a list of all calendars the user has access to.
860    ///
861    /// # Returns
862    ///
863    /// A JSON string containing the calendar list
864    #[tool]
865    async fn list_calendars(&self) -> McpResult<String> {
866        info!("=== START list_calendars MCP command ===");
867        debug!("list_calendars called");
868
869        // Initialize the calendar service
870        let service = self.init_calendar_service().await?;
871
872        // Get the calendars
873        match service.list_calendars().await {
874            Ok(calendars) => {
875                // Convert to JSON
876                serde_json::to_string(&calendars).map_err(|e| {
877                    let error_msg = format!("Failed to serialize calendar list: {}", e);
878                    error!("{}", error_msg);
879                    self.to_mcp_error(&error_msg, error_codes::MESSAGE_FORMAT_ERROR)
880                })
881            }
882            Err(err) => {
883                error!("Failed to list calendars: {}", err);
884                Err(self.to_mcp_error(
885                    &format!("Failed to list calendars: {}", err),
886                    error_codes::API_ERROR,
887                ))
888            }
889        }
890    }
891
892    /// List events from a calendar
893    ///
894    /// This command retrieves events from a specified calendar, with options for filtering.
895    ///
896    /// # Arguments
897    ///
898    /// * `calendar_id` - The ID of the calendar to get events from (optional, defaults to primary)
899    /// * `max_results` - Optional maximum number of events to return
900    /// * `time_min` - Optional minimum time bound (RFC3339 timestamp)
901    /// * `time_max` - Optional maximum time bound (RFC3339 timestamp)
902    ///
903    /// # Returns
904    ///
905    /// A JSON string containing the event list
906    #[tool]
907    async fn list_events(
908        &self,
909        calendar_id: Option<String>,
910        max_results: Option<serde_json::Value>,
911        time_min: Option<String>,
912        time_max: Option<String>,
913    ) -> McpResult<String> {
914        info!("=== START list_events MCP command ===");
915        debug!(
916            "list_events called with calendar_id={:?}, max_results={:?}, time_min={:?}, time_max={:?}",
917            calendar_id, max_results, time_min, time_max
918        );
919
920        // Use primary calendar if not specified
921        let calendar_id = calendar_id.unwrap_or_else(|| "primary".to_string());
922
923        // Convert max_results using the helper function (default: 10)
924        let max = helpers::parse_max_results(max_results, 10);
925
926        // Parse time bounds if provided
927        let time_min_parsed = if let Some(t) = time_min {
928            match chrono::DateTime::parse_from_rfc3339(&t) {
929                Ok(dt) => Some(dt.with_timezone(&chrono::Utc)),
930                Err(e) => {
931                    let error_msg = format!("Invalid time_min format (expected RFC3339): {}", e);
932                    error!("{}", error_msg);
933                    return Err(self.to_mcp_error(&error_msg, error_codes::API_ERROR));
934                }
935            }
936        } else {
937            None
938        };
939
940        let time_max_parsed = if let Some(t) = time_max {
941            match chrono::DateTime::parse_from_rfc3339(&t) {
942                Ok(dt) => Some(dt.with_timezone(&chrono::Utc)),
943                Err(e) => {
944                    let error_msg = format!("Invalid time_max format (expected RFC3339): {}", e);
945                    error!("{}", error_msg);
946                    return Err(self.to_mcp_error(&error_msg, error_codes::API_ERROR));
947                }
948            }
949        } else {
950            None
951        };
952
953        // Initialize the calendar service
954        let service = self.init_calendar_service().await?;
955
956        // Get the events
957        match service
958            .list_events(&calendar_id, Some(max), time_min_parsed, time_max_parsed)
959            .await
960        {
961            Ok(events) => {
962                // Convert to JSON
963                serde_json::to_string(&events).map_err(|e| {
964                    let error_msg = format!("Failed to serialize events list: {}", e);
965                    error!("{}", error_msg);
966                    self.to_mcp_error(&error_msg, error_codes::MESSAGE_FORMAT_ERROR)
967                })
968            }
969            Err(err) => {
970                error!(
971                    "Failed to list events from calendar {}: {}",
972                    calendar_id, err
973                );
974                Err(self.to_mcp_error(
975                    &format!(
976                        "Failed to list events from calendar {}: {}",
977                        calendar_id, err
978                    ),
979                    error_codes::API_ERROR,
980                ))
981            }
982        }
983    }
984
985    /// Get a single calendar event
986    ///
987    /// This command retrieves a specific event from a calendar.
988    ///
989    /// # Arguments
990    ///
991    /// * `calendar_id` - The ID of the calendar (optional, defaults to primary)
992    /// * `event_id` - The ID of the event to retrieve
993    ///
994    /// # Returns
995    ///
996    /// A JSON string containing the event details
997    #[tool]
998    async fn get_event(&self, calendar_id: Option<String>, event_id: String) -> McpResult<String> {
999        info!("=== START get_event MCP command ===");
1000        debug!(
1001            "get_event called with calendar_id={:?}, event_id={}",
1002            calendar_id, event_id
1003        );
1004
1005        // Use primary calendar if not specified
1006        let calendar_id = calendar_id.unwrap_or_else(|| "primary".to_string());
1007
1008        // Initialize the calendar service
1009        let service = self.init_calendar_service().await?;
1010
1011        // Get the event
1012        match service.get_event(&calendar_id, &event_id).await {
1013            Ok(event) => {
1014                // Convert to JSON
1015                serde_json::to_string(&event).map_err(|e| {
1016                    let error_msg = format!("Failed to serialize event: {}", e);
1017                    error!("{}", error_msg);
1018                    self.to_mcp_error(&error_msg, error_codes::MESSAGE_FORMAT_ERROR)
1019                })
1020            }
1021            Err(err) => {
1022                error!(
1023                    "Failed to get event {} from calendar {}: {}",
1024                    event_id, calendar_id, err
1025                );
1026                Err(self.to_mcp_error(
1027                    &format!(
1028                        "Failed to get event {} from calendar {}: {}",
1029                        event_id, calendar_id, err
1030                    ),
1031                    error_codes::API_ERROR,
1032                ))
1033            }
1034        }
1035    }
1036
1037    /// Create a new calendar event
1038    ///
1039    /// This command creates a new event in the specified calendar.
1040    ///
1041    /// # Arguments
1042    ///
1043    /// * `calendar_id` - The ID of the calendar (optional, defaults to primary)
1044    /// * `summary` - The title of the event
1045    /// * `description` - Optional description of the event
1046    /// * `location` - Optional location of the event
1047    /// * `start_time` - Start time in RFC3339 format
1048    /// * `end_time` - End time in RFC3339 format
1049    /// * `attendees` - Optional list of attendee emails
1050    ///
1051    /// # Returns
1052    ///
1053    /// A JSON string containing the created event details
1054    #[tool]
1055    #[allow(clippy::too_many_arguments)]
1056    async fn create_event(
1057        &self,
1058        // Calendar identification
1059        calendar_id: Option<String>,
1060        // Event core details
1061        summary: String,
1062        start_time: String,
1063        end_time: String,
1064        // Optional event details
1065        description: Option<String>,
1066        location: Option<String>,
1067        // Participants
1068        attendees: Option<Vec<String>>,
1069    ) -> McpResult<String> {
1070        info!("=== START create_event MCP command ===");
1071        debug!(
1072            "create_event called with calendar_id={:?}, summary={}, description={:?}, location={:?}, start_time={}, end_time={}, attendees={:?}",
1073            calendar_id, summary, description, location, start_time, end_time, attendees
1074        );
1075
1076        // Use primary calendar if not specified
1077        let calendar_id = calendar_id.unwrap_or_else(|| "primary".to_string());
1078
1079        // Parse start and end times
1080        let start_dt = match chrono::DateTime::parse_from_rfc3339(&start_time) {
1081            Ok(dt) => dt.with_timezone(&chrono::Utc),
1082            Err(e) => {
1083                let error_msg = format!("Invalid start_time format (expected RFC3339): {}", e);
1084                error!("{}", error_msg);
1085                return Err(self.to_mcp_error(&error_msg, error_codes::API_ERROR));
1086            }
1087        };
1088
1089        let end_dt = match chrono::DateTime::parse_from_rfc3339(&end_time) {
1090            Ok(dt) => dt.with_timezone(&chrono::Utc),
1091            Err(e) => {
1092                let error_msg = format!("Invalid end_time format (expected RFC3339): {}", e);
1093                error!("{}", error_msg);
1094                return Err(self.to_mcp_error(&error_msg, error_codes::API_ERROR));
1095            }
1096        };
1097
1098        // Create attendee objects from email strings
1099        let attendee_objs = attendees
1100            .unwrap_or_default()
1101            .into_iter()
1102            .map(|email| crate::calendar_api::Attendee {
1103                email,
1104                display_name: None,
1105                response_status: Some("needsAction".to_string()),
1106                optional: None,
1107            })
1108            .collect();
1109
1110        // Create the event
1111        let event = crate::calendar_api::CalendarEvent {
1112            id: None,
1113            summary,
1114            description,
1115            location,
1116            start_time: start_dt,
1117            end_time: end_dt,
1118            attendees: attendee_objs,
1119            conference_data: None,
1120            html_link: None,
1121            creator: None,
1122            organizer: None,
1123        };
1124
1125        // Initialize the calendar service
1126        let service = self.init_calendar_service().await?;
1127
1128        // Create the event
1129        match service.create_event(&calendar_id, event).await {
1130            Ok(created_event) => {
1131                // Convert to JSON
1132                serde_json::to_string(&created_event).map_err(|e| {
1133                    let error_msg = format!("Failed to serialize created event: {}", e);
1134                    error!("{}", error_msg);
1135                    self.to_mcp_error(&error_msg, error_codes::MESSAGE_FORMAT_ERROR)
1136                })
1137            }
1138            Err(err) => {
1139                error!(
1140                    "Failed to create event in calendar {}: {}",
1141                    calendar_id, err
1142                );
1143                Err(self.to_mcp_error(
1144                    &format!(
1145                        "Failed to create event in calendar {}: {}",
1146                        calendar_id, err
1147                    ),
1148                    error_codes::API_ERROR,
1149                ))
1150            }
1151        }
1152    }
1153}