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
12mod helpers {
14 pub use crate::utils::parse_max_results;
16}
17
18#[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 async fn init_calendar_service(&self) -> Result<crate::calendar_api::CalendarClient, McpError> {
37 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 Ok(crate::calendar_api::CalendarClient::new(&config))
48 }
49
50 async fn init_people_service(&self) -> Result<crate::people_api::PeopleClient, McpError> {
52 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 Ok(crate::people_api::PeopleClient::new(&config))
63 }
64
65 fn to_mcp_error(&self, message: &str, code: u32) -> McpError {
67 crate::utils::to_mcp_error(message, code)
69 }
70
71 fn map_gmail_error(&self, err: GmailApiError) -> McpError {
73 crate::utils::map_gmail_error(err)
75 }
76
77 async fn init_gmail_service(&self) -> McpResult<GmailService> {
79 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 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]
113impl McpServer for GmailServer {
114 #[prompt]
124 async fn gmail_prompt(&self) -> McpResult<&str> {
125 Ok(crate::prompts::GMAIL_MASTER_PROMPT)
126 }
127
128 #[prompt]
132 async fn email_analysis_prompt(&self) -> McpResult<&str> {
133 Ok(crate::prompts::EMAIL_ANALYSIS_PROMPT)
134 }
135
136 #[prompt]
140 async fn email_summarization_prompt(&self) -> McpResult<&str> {
141 Ok(crate::prompts::EMAIL_SUMMARIZATION_PROMPT)
142 }
143
144 #[prompt]
148 async fn email_search_prompt(&self) -> McpResult<&str> {
149 Ok(crate::prompts::EMAIL_SEARCH_PROMPT)
150 }
151
152 #[prompt]
156 async fn task_extraction_prompt(&self) -> McpResult<&str> {
157 Ok(crate::prompts::TASK_EXTRACTION_PROMPT)
158 }
159
160 #[prompt]
164 async fn meeting_extraction_prompt(&self) -> McpResult<&str> {
165 Ok(crate::prompts::MEETING_EXTRACTION_PROMPT)
166 }
167
168 #[prompt]
172 async fn contact_extraction_prompt(&self) -> McpResult<&str> {
173 Ok(crate::prompts::CONTACT_EXTRACTION_PROMPT)
174 }
175
176 #[prompt]
180 async fn email_categorization_prompt(&self) -> McpResult<&str> {
181 Ok(crate::prompts::EMAIL_CATEGORIZATION_PROMPT)
182 }
183
184 #[prompt]
188 async fn email_prioritization_prompt(&self) -> McpResult<&str> {
189 Ok(crate::prompts::EMAIL_PRIORITIZATION_PROMPT)
190 }
191
192 #[prompt]
196 async fn email_drafting_prompt(&self) -> McpResult<&str> {
197 Ok(crate::prompts::EMAIL_DRAFTING_PROMPT)
198 }
199
200 #[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 let max = helpers::parse_max_results(max_results, 10);
221
222 let mut service = self.init_gmail_service().await?;
224
225 let result = match service.list_messages(max, query.as_deref()).await {
227 Ok(messages) => {
228 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 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 #[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 let mut service = self.init_gmail_service().await?;
268
269 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 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 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 #[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 let max = helpers::parse_max_results(max_results, 10);
319
320 let mut service = self.init_gmail_service().await?;
322
323 let result = match service.list_messages(max, Some(&query)).await {
325 Ok(messages) => {
326 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 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 #[tool]
354 async fn list_labels(&self) -> McpResult<String> {
355 debug!("list_labels called");
356
357 let mut service = self.init_gmail_service().await?;
359
360 match service.list_labels().await {
362 Ok(labels) => Ok(labels),
363 Err(err) => {
364 error!("Failed to list labels: {}", err);
365
366 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 #[tool]
380 async fn check_connection(&self) -> McpResult<String> {
381 info!("=== START check_connection MCP command ===");
382 debug!("check_connection called");
383
384 let mut service = self.init_gmail_service().await?;
386
387 let profile_json = match service.check_connection_raw().await {
389 Ok(json) => json,
390 Err(err) => {
391 error!("Connection check failed: {}", err);
392
393 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 #[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 let mut service = self.init_gmail_service().await?;
429
430 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 let analysis = analysis_type.unwrap_or_else(|| "general".to_string());
441
442 let result = match analysis.to_lowercase().as_str() {
444 "tasks" | "task" => {
445 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 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 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 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 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 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 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 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 #[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 let mut service = self.init_gmail_service().await?;
573
574 let analysis = analysis_type
576 .unwrap_or_else(|| "summary".to_string())
577 .to_lowercase();
578
579 let mut results = Vec::new();
581 for id in message_ids {
582 debug!("Analyzing email {}", id);
583
584 match service.get_message_details(&id).await {
586 Ok(email) => {
587 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, };
594
595 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 error!("Failed to analyze email {}: {}", id, err);
611
612 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 let batch_result = json!({
625 "analysis_type": analysis,
626 "email_count": results.len(),
627 "results": results
628 });
629
630 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 #[tool]
656 #[allow(clippy::too_many_arguments)]
657 async fn create_draft_email(
658 &self,
659 to: String,
661 subject: String,
662 body: String,
663 cc: Option<String>,
665 bcc: Option<String>,
666 thread_id: Option<String>,
668 in_reply_to: Option<String>,
669 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 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 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 let mut service = self.init_gmail_service().await?;
699
700 match service.create_draft(&draft).await {
702 Ok(draft_id) => {
703 let mut result = json!({
705 "status": "success",
706 "draft_id": draft_id,
707 "message": "Draft email created successfully."
708 });
709
710 if let Some(ref thread_id_val) = draft.thread_id {
712 result["thread_id"] = json!(thread_id_val);
713 }
714
715 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 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 #[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 let people_client = self.init_people_service().await?;
757
758 match people_client.list_contacts(max_results).await {
759 Ok(contacts) => {
760 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 #[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 let people_client = self.init_people_service().await?;
799
800 match people_client.search_contacts(&query, max_results).await {
801 Ok(contacts) => {
802 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 #[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 let people_client = self.init_people_service().await?;
837
838 match people_client.get_contact(&resource_name).await {
839 Ok(contact) => {
840 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 #[tool]
865 async fn list_calendars(&self) -> McpResult<String> {
866 info!("=== START list_calendars MCP command ===");
867 debug!("list_calendars called");
868
869 let service = self.init_calendar_service().await?;
871
872 match service.list_calendars().await {
874 Ok(calendars) => {
875 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 #[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 let calendar_id = calendar_id.unwrap_or_else(|| "primary".to_string());
922
923 let max = helpers::parse_max_results(max_results, 10);
925
926 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 let service = self.init_calendar_service().await?;
955
956 match service
958 .list_events(&calendar_id, Some(max), time_min_parsed, time_max_parsed)
959 .await
960 {
961 Ok(events) => {
962 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 #[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 let calendar_id = calendar_id.unwrap_or_else(|| "primary".to_string());
1007
1008 let service = self.init_calendar_service().await?;
1010
1011 match service.get_event(&calendar_id, &event_id).await {
1013 Ok(event) => {
1014 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 #[tool]
1055 #[allow(clippy::too_many_arguments)]
1056 async fn create_event(
1057 &self,
1058 calendar_id: Option<String>,
1060 summary: String,
1062 start_time: String,
1063 end_time: String,
1064 description: Option<String>,
1066 location: Option<String>,
1067 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 let calendar_id = calendar_id.unwrap_or_else(|| "primary".to_string());
1078
1079 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 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 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 let service = self.init_calendar_service().await?;
1127
1128 match service.create_event(&calendar_id, event).await {
1130 Ok(created_event) => {
1131 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}