Skip to main content

mcp_gmailcal/
utils.rs

1use crate::errors::GmailApiError;
2use base64;
3use log::{debug, error};
4use mcp_attr::{jsoncall::ErrorCode, Error as McpError};
5use serde_json;
6
7// Error code constants for MCP errors
8pub mod error_codes {
9    /// General internal errors
10    pub const GENERAL_ERROR: u32 = 1000;
11    
12    /// Configuration related errors (environment variables, etc.)
13    pub const CONFIG_ERROR: u32 = 1001;
14
15    /// Authentication errors (tokens, OAuth, etc.)
16    pub const AUTH_ERROR: u32 = 1002;
17
18    /// API errors from Gmail
19    pub const API_ERROR: u32 = 1003;
20
21    /// Message format/missing field errors
22    pub const MESSAGE_FORMAT_ERROR: u32 = 1005;
23
24    // Map error codes to human-readable descriptions
25    pub fn get_error_description(code: u32) -> &'static str {
26        match code {
27            CONFIG_ERROR => "Configuration Error: Missing or invalid environment variables required for Gmail authentication",
28            AUTH_ERROR => "Authentication Error: Failed to authenticate with Gmail API using the provided credentials",
29            API_ERROR => "Gmail API Error: The request to the Gmail API failed",
30            MESSAGE_FORMAT_ERROR => "Message Format Error: The response from Gmail API has missing or invalid fields",
31            GENERAL_ERROR => "General Error: An unspecified error occurred in the Gmail MCP server",
32            _ => "Unknown Error: An unclassified error occurred",
33        }
34    }
35
36    // Get detailed troubleshooting steps for each error code
37    pub fn get_troubleshooting_steps(code: u32) -> &'static str {
38        match code {
39            CONFIG_ERROR => "Check that you have correctly set the following environment variables: GMAIL_CLIENT_ID, GMAIL_CLIENT_SECRET, and GMAIL_REFRESH_TOKEN. These should be in your .env file or exported in your shell.",
40            AUTH_ERROR => "Verify your OAuth credentials. Your refresh token may have expired or been revoked. Try generating new OAuth credentials and updating your environment variables.",
41            API_ERROR => "The Gmail API request failed. This could be due to API rate limits, network issues, or an invalid request. Check your internet connection and review the specific error details.",
42            MESSAGE_FORMAT_ERROR => "The Gmail API returned data in an unexpected format. This may be due to changes in the API or issues with specific messages. Try with a different message ID or update the server code.",
43            GENERAL_ERROR => "Review server logs for more details about what went wrong. Check for any recent changes to your code or environment.",
44            _ => "Check the server logs for more specific error information. Ensure all dependencies are up to date.",
45        }
46    }
47}
48
49/// Parse maximum results parameter from JSON value
50pub fn parse_max_results(value: Option<serde_json::Value>, default: u32) -> u32 {
51    match value {
52        Some(val) => {
53            match val {
54                serde_json::Value::Number(num) => {
55                    // Handle number input
56                    if let Some(n) = num.as_u64() {
57                        // Ensure it fits in u32
58                        if n <= u32::MAX as u64 {
59                            n as u32
60                        } else {
61                            debug!("Number too large for u32, using default {}", default);
62                            default
63                        }
64                    } else {
65                        debug!("Number not convertible to u32, using default {}", default);
66                        default
67                    }
68                }
69                serde_json::Value::String(s) => {
70                    // Handle string input
71                    match s.parse::<u32>() {
72                        Ok(n) => n,
73                        Err(_) => {
74                            debug!(
75                                "String \"{}\" not convertible to u32, using default {}",
76                                s, default
77                            );
78                            default
79                        }
80                    }
81                }
82                _ => {
83                    debug!("Invalid type for max_results, using default {}", default);
84                    default
85                }
86            }
87        }
88        None => default,
89    }
90}
91
92/// Decode a base64 encoded string
93pub fn decode_base64(data: &str) -> Result<String, String> {
94    let bytes = base64::decode(data).map_err(|e| format!("Error decoding base64: {}", e))?;
95
96    String::from_utf8(bytes).map_err(|e| format!("Error converting base64 to string: {}", e))
97}
98
99/// Encode data to base64 URL safe string
100pub fn encode_base64_url_safe(data: &[u8]) -> String {
101    base64::encode_config(data, base64::URL_SAFE)
102}
103
104/// Convert an error message and code to an MCP error
105pub fn to_mcp_error(message: &str, code: u32) -> McpError {
106    use error_codes::{get_error_description, get_troubleshooting_steps};
107
108    // Get the generic description for this error code
109    let description = get_error_description(code);
110
111    // Get troubleshooting steps
112    let steps = get_troubleshooting_steps(code);
113
114    // Create a detailed error message with multiple parts
115    let detailed_error =
116        format!(
117        "ERROR CODE {}: {}\n\nDETAILS: {}\n\nTROUBLESHOOTING: {}\n\nSERVER MESSAGE: {}", 
118        code, description, message, steps,
119        "If the problem persists, contact the server administrator and reference this error code."
120    );
121
122    // Log the full error details
123    error!(
124        "Creating MCP error: {} (code: {})\n{}",
125        message, code, detailed_error
126    );
127
128    // Create the MCP error with the detailed message
129    McpError::new(ErrorCode(code as i64)).with_message(detailed_error, true)
130}
131
132/// Map Gmail API errors to MCP errors
133pub fn map_gmail_error(err: GmailApiError) -> McpError {
134    match err {
135        GmailApiError::ApiError(e) => {
136            // Analyze the error message to provide more context
137            let (code, detailed_msg) = if e.contains("quota")
138                || e.contains("rate")
139                || e.contains("limit")
140            {
141                (
142                    error_codes::API_ERROR,
143                    format!(
144                        "Gmail API rate limit exceeded: {}. The server has made too many requests to the Gmail API. \
145                        This typically happens when many requests are made in quick succession. \
146                        Please try again in a few minutes.", 
147                        e
148                    )
149                )
150            } else if e.contains("network") || e.contains("connection") || e.contains("timeout") {
151                (
152                    error_codes::API_ERROR,
153                    format!(
154                        "Network error while connecting to Gmail API: {}. The server couldn't establish a \
155                        connection to the Gmail API. This may be due to network issues or the Gmail API \
156                        might be experiencing downtime.", 
157                        e
158                    )
159                )
160            } else if e.contains("authentication") || e.contains("auth") || e.contains("token") {
161                (
162                    error_codes::AUTH_ERROR,
163                    format!(
164                        "Gmail API authentication failed: {}. The OAuth token used to authenticate with \
165                        Gmail may have expired or been revoked. Please check your credentials and try \
166                        regenerating your refresh token.", 
167                        e
168                    )
169                )
170            } else if e.contains("format") || e.contains("missing field") || e.contains("parse") {
171                (
172                    error_codes::MESSAGE_FORMAT_ERROR,
173                    format!(
174                        "Gmail API response format error: {}. The API returned data in an unexpected format. \
175                        This might be due to changes in the Gmail API or issues with specific messages.", 
176                        e
177                    )
178                )
179            } else if e.contains("not found") || e.contains("404") {
180                (
181                    error_codes::API_ERROR,
182                    format!(
183                        "Gmail API resource not found: {}. The requested message or resource doesn't exist \
184                        or you don't have permission to access it. Please check the message ID and ensure \
185                        it exists in your Gmail account.", 
186                        e
187                    )
188                )
189            } else {
190                (
191                    error_codes::API_ERROR,
192                    format!(
193                        "Unspecified Gmail API error: {}. An unexpected error occurred when communicating \
194                        with the Gmail API. Please check the server logs for more details.", 
195                        e
196                    )
197                )
198            };
199
200            to_mcp_error(&detailed_msg, code)
201        }
202        GmailApiError::AuthError(e) => {
203            let detailed_msg = format!(
204                "Gmail authentication error: {}. Failed to authenticate with the Gmail API using the provided \
205                credentials. Please verify your client ID, client secret, and refresh token.", 
206                e
207            );
208            to_mcp_error(&detailed_msg, error_codes::AUTH_ERROR)
209        }
210        GmailApiError::MessageRetrievalError(e) => {
211            let detailed_msg = format!(
212                "Message retrieval error: {}. Failed to retrieve the requested message from Gmail. \
213                This may be due to the message being deleted, access permissions, or temporary Gmail API issues.", 
214                e
215            );
216            to_mcp_error(&detailed_msg, error_codes::API_ERROR)
217        }
218        GmailApiError::MessageFormatError(e) => {
219            let detailed_msg = format!(
220                "Message format error: {}. The Gmail API returned a malformed message or one with missing required fields.", 
221                e
222            );
223            to_mcp_error(&detailed_msg, error_codes::MESSAGE_FORMAT_ERROR)
224        }
225        GmailApiError::NetworkError(e) => {
226            let detailed_msg = format!(
227                "Network error: {}. The server couldn't establish a connection to the Gmail API. \
228                This might be due to network configuration issues, outages, or firewall restrictions. \
229                Please check your internet connection and server network configuration.", 
230                e
231            );
232            to_mcp_error(&detailed_msg, error_codes::API_ERROR)
233        }
234        GmailApiError::RateLimitError(e) => {
235            let detailed_msg = format!(
236                "Rate limit error: {}. The Gmail API has rate-limited the server's requests. \
237                This happens when too many requests are made in a short period of time. \
238                The server will automatically retry after a cooldown period, but you may need to wait \
239                or reduce the frequency of requests.", 
240                e
241            );
242            to_mcp_error(&detailed_msg, error_codes::API_ERROR)
243        }
244        GmailApiError::CacheError(e) => {
245            let detailed_msg = format!(
246                "Token cache error: {}. The server encountered an error with the token cache. \
247                This is an internal error and should not affect functionality. \
248                The application will continue with in-memory token handling.", 
249                e
250            );
251            to_mcp_error(&detailed_msg, error_codes::GENERAL_ERROR)
252        }
253    }
254}