universal-mcp 0.1.1__py3-none-any.whl → 0.1.2rc1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- universal_mcp/applications/__init__.py +23 -28
 - universal_mcp/applications/application.py +13 -8
 - universal_mcp/applications/e2b/app.py +74 -0
 - universal_mcp/applications/firecrawl/app.py +381 -0
 - universal_mcp/applications/github/README.md +35 -0
 - universal_mcp/applications/github/app.py +133 -100
 - universal_mcp/applications/google_calendar/app.py +170 -139
 - universal_mcp/applications/google_mail/app.py +185 -160
 - universal_mcp/applications/markitdown/app.py +32 -0
 - universal_mcp/applications/reddit/app.py +112 -71
 - universal_mcp/applications/resend/app.py +3 -8
 - universal_mcp/applications/serp/app.py +84 -0
 - universal_mcp/applications/tavily/app.py +11 -10
 - universal_mcp/applications/zenquotes/app.py +3 -3
 - universal_mcp/cli.py +98 -16
 - universal_mcp/config.py +20 -3
 - universal_mcp/exceptions.py +1 -3
 - universal_mcp/integrations/__init__.py +6 -2
 - universal_mcp/integrations/agentr.py +26 -24
 - universal_mcp/integrations/integration.py +72 -35
 - universal_mcp/servers/__init__.py +21 -1
 - universal_mcp/servers/server.py +77 -44
 - universal_mcp/stores/__init__.py +15 -2
 - universal_mcp/stores/store.py +123 -13
 - universal_mcp/utils/__init__.py +1 -0
 - universal_mcp/utils/api_generator.py +269 -0
 - universal_mcp/utils/docgen.py +360 -0
 - universal_mcp/utils/installation.py +17 -2
 - universal_mcp/utils/openapi.py +202 -104
 - {universal_mcp-0.1.1.dist-info → universal_mcp-0.1.2rc1.dist-info}/METADATA +22 -5
 - universal_mcp-0.1.2rc1.dist-info/RECORD +37 -0
 - universal_mcp-0.1.1.dist-info/RECORD +0 -29
 - {universal_mcp-0.1.1.dist-info → universal_mcp-0.1.2rc1.dist-info}/WHEEL +0 -0
 - {universal_mcp-0.1.1.dist-info → universal_mcp-0.1.2rc1.dist-info}/entry_points.txt +0 -0
 
| 
         @@ -1,13 +1,16 @@ 
     | 
|
| 
       1 
     | 
    
         
            -
            from universal_mcp.applications.application import APIApplication
         
     | 
| 
       2 
     | 
    
         
            -
            from universal_mcp.integrations import Integration
         
     | 
| 
       3 
     | 
    
         
            -
            from universal_mcp.exceptions import NotAuthorizedError
         
     | 
| 
       4 
     | 
    
         
            -
            from loguru import logger
         
     | 
| 
       5 
1 
     | 
    
         
             
            import base64
         
     | 
| 
       6 
2 
     | 
    
         
             
            from email.message import EmailMessage
         
     | 
| 
       7 
3 
     | 
    
         | 
| 
       8 
     | 
    
         
            -
             
     | 
| 
      
 4 
     | 
    
         
            +
            from loguru import logger
         
     | 
| 
      
 5 
     | 
    
         
            +
             
     | 
| 
      
 6 
     | 
    
         
            +
            from universal_mcp.applications.application import APIApplication
         
     | 
| 
      
 7 
     | 
    
         
            +
            from universal_mcp.exceptions import NotAuthorizedError
         
     | 
| 
      
 8 
     | 
    
         
            +
            from universal_mcp.integrations import Integration
         
     | 
| 
      
 9 
     | 
    
         
            +
             
     | 
| 
      
 10 
     | 
    
         
            +
             
     | 
| 
      
 11 
     | 
    
         
            +
            class GoogleMailApp(APIApplication):
         
     | 
| 
       9 
12 
     | 
    
         
             
                def __init__(self, integration: Integration) -> None:
         
     | 
| 
       10 
     | 
    
         
            -
                    super().__init__(name=" 
     | 
| 
      
 13 
     | 
    
         
            +
                    super().__init__(name="google-mail", integration=integration)
         
     | 
| 
       11 
14 
     | 
    
         
             
                    self.base_api_url = "https://gmail.googleapis.com/gmail/v1/users/me"
         
     | 
| 
       12 
15 
     | 
    
         | 
| 
       13 
16 
     | 
    
         
             
                def _get_headers(self):
         
     | 
| 
         @@ -18,44 +21,44 @@ class GmailApp(APIApplication): 
     | 
|
| 
       18 
21 
     | 
    
         
             
                        logger.warning("No Gmail credentials found via integration.")
         
     | 
| 
       19 
22 
     | 
    
         
             
                        action = self.integration.authorize()
         
     | 
| 
       20 
23 
     | 
    
         
             
                        raise NotAuthorizedError(action)
         
     | 
| 
       21 
     | 
    
         
            -
             
     | 
| 
      
 24 
     | 
    
         
            +
             
     | 
| 
       22 
25 
     | 
    
         
             
                    if "headers" in credentials:
         
     | 
| 
       23 
26 
     | 
    
         
             
                        return credentials["headers"]
         
     | 
| 
       24 
27 
     | 
    
         
             
                    return {
         
     | 
| 
       25 
28 
     | 
    
         
             
                        "Authorization": f"Bearer {credentials['access_token']}",
         
     | 
| 
       26 
     | 
    
         
            -
                         
     | 
| 
      
 29 
     | 
    
         
            +
                        "Content-Type": "application/json",
         
     | 
| 
       27 
30 
     | 
    
         
             
                    }
         
     | 
| 
       28 
31 
     | 
    
         | 
| 
       29 
32 
     | 
    
         
             
                def send_email(self, to: str, subject: str, body: str) -> str:
         
     | 
| 
       30 
33 
     | 
    
         
             
                    """Send an email
         
     | 
| 
       31 
     | 
    
         
            -
             
     | 
| 
      
 34 
     | 
    
         
            +
             
     | 
| 
       32 
35 
     | 
    
         
             
                    Args:
         
     | 
| 
       33 
36 
     | 
    
         
             
                        to: The email address of the recipient
         
     | 
| 
       34 
37 
     | 
    
         
             
                        subject: The subject of the email
         
     | 
| 
       35 
38 
     | 
    
         
             
                        body: The body of the email
         
     | 
| 
       36 
     | 
    
         
            -
             
     | 
| 
      
 39 
     | 
    
         
            +
             
     | 
| 
       37 
40 
     | 
    
         
             
                    Returns:
         
     | 
| 
       38 
41 
     | 
    
         
             
                        A confirmation message
         
     | 
| 
       39 
42 
     | 
    
         
             
                    """
         
     | 
| 
       40 
43 
     | 
    
         
             
                    try:
         
     | 
| 
       41 
44 
     | 
    
         
             
                        url = f"{self.base_api_url}/messages/send"
         
     | 
| 
       42 
     | 
    
         
            -
             
     | 
| 
      
 45 
     | 
    
         
            +
             
     | 
| 
       43 
46 
     | 
    
         
             
                        # Create email in base64 encoded format
         
     | 
| 
       44 
47 
     | 
    
         
             
                        raw_message = self._create_message(to, subject, body)
         
     | 
| 
       45 
     | 
    
         
            -
             
     | 
| 
      
 48 
     | 
    
         
            +
             
     | 
| 
       46 
49 
     | 
    
         
             
                        # Format the data as expected by Gmail API
         
     | 
| 
       47 
     | 
    
         
            -
                        email_data = {
         
     | 
| 
       48 
     | 
    
         
            -
             
     | 
| 
       49 
     | 
    
         
            -
                        }
         
     | 
| 
       50 
     | 
    
         
            -
                        
         
     | 
| 
      
 50 
     | 
    
         
            +
                        email_data = {"raw": raw_message}
         
     | 
| 
      
 51 
     | 
    
         
            +
             
     | 
| 
       51 
52 
     | 
    
         
             
                        logger.info(f"Sending email to {to}")
         
     | 
| 
       52 
     | 
    
         
            -
             
     | 
| 
      
 53 
     | 
    
         
            +
             
     | 
| 
       53 
54 
     | 
    
         
             
                        response = self._post(url, email_data)
         
     | 
| 
       54 
     | 
    
         
            -
             
     | 
| 
      
 55 
     | 
    
         
            +
             
     | 
| 
       55 
56 
     | 
    
         
             
                        if response.status_code == 200:
         
     | 
| 
       56 
57 
     | 
    
         
             
                            return f"Successfully sent email to {to}"
         
     | 
| 
       57 
58 
     | 
    
         
             
                        else:
         
     | 
| 
       58 
     | 
    
         
            -
                            logger.error( 
     | 
| 
      
 59 
     | 
    
         
            +
                            logger.error(
         
     | 
| 
      
 60 
     | 
    
         
            +
                                f"Gmail API Error: {response.status_code} - {response.text}"
         
     | 
| 
      
 61 
     | 
    
         
            +
                            )
         
     | 
| 
       59 
62 
     | 
    
         
             
                            return f"Error sending email: {response.status_code} - {response.text}"
         
     | 
| 
       60 
63 
     | 
    
         
             
                    except NotAuthorizedError as e:
         
     | 
| 
       61 
64 
     | 
    
         
             
                        # Return the authorization message directly
         
     | 
| 
         @@ -67,17 +70,17 @@ class GmailApp(APIApplication): 
     | 
|
| 
       67 
70 
     | 
    
         
             
                    except Exception as e:
         
     | 
| 
       68 
71 
     | 
    
         
             
                        logger.exception(f"Error sending email: {type(e).__name__} - {str(e)}")
         
     | 
| 
       69 
72 
     | 
    
         
             
                        return f"Error sending email: {type(e).__name__} - {str(e)}"
         
     | 
| 
       70 
     | 
    
         
            -
             
     | 
| 
      
 73 
     | 
    
         
            +
             
     | 
| 
       71 
74 
     | 
    
         
             
                def _create_message(self, to, subject, body):
         
     | 
| 
       72 
75 
     | 
    
         
             
                    try:
         
     | 
| 
       73 
76 
     | 
    
         
             
                        message = EmailMessage()
         
     | 
| 
       74 
     | 
    
         
            -
                        message[ 
     | 
| 
       75 
     | 
    
         
            -
                        message[ 
     | 
| 
      
 77 
     | 
    
         
            +
                        message["to"] = to
         
     | 
| 
      
 78 
     | 
    
         
            +
                        message["subject"] = subject
         
     | 
| 
       76 
79 
     | 
    
         
             
                        message.set_content(body)
         
     | 
| 
       77 
     | 
    
         
            -
             
     | 
| 
      
 80 
     | 
    
         
            +
             
     | 
| 
       78 
81 
     | 
    
         
             
                        # Use "me" as the default sender
         
     | 
| 
       79 
     | 
    
         
            -
                        message[ 
     | 
| 
       80 
     | 
    
         
            -
             
     | 
| 
      
 82 
     | 
    
         
            +
                        message["from"] = "me"
         
     | 
| 
      
 83 
     | 
    
         
            +
             
     | 
| 
       81 
84 
     | 
    
         
             
                        # Encode as base64 string
         
     | 
| 
       82 
85 
     | 
    
         
             
                        raw = base64.urlsafe_b64encode(message.as_bytes()).decode()
         
     | 
| 
       83 
86 
     | 
    
         
             
                        return raw
         
     | 
| 
         @@ -87,35 +90,33 @@ class GmailApp(APIApplication): 
     | 
|
| 
       87 
90 
     | 
    
         | 
| 
       88 
91 
     | 
    
         
             
                def create_draft(self, to: str, subject: str, body: str) -> str:
         
     | 
| 
       89 
92 
     | 
    
         
             
                    """Create a draft email
         
     | 
| 
       90 
     | 
    
         
            -
             
     | 
| 
      
 93 
     | 
    
         
            +
             
     | 
| 
       91 
94 
     | 
    
         
             
                    Args:
         
     | 
| 
       92 
95 
     | 
    
         
             
                        to: The email address of the recipient
         
     | 
| 
       93 
96 
     | 
    
         
             
                        subject: The subject of the email
         
     | 
| 
       94 
97 
     | 
    
         
             
                        body: The body of the email
         
     | 
| 
       95 
     | 
    
         
            -
             
     | 
| 
      
 98 
     | 
    
         
            +
             
     | 
| 
       96 
99 
     | 
    
         
             
                    Returns:
         
     | 
| 
       97 
100 
     | 
    
         
             
                        A confirmation message with the draft ID
         
     | 
| 
       98 
101 
     | 
    
         
             
                    """
         
     | 
| 
       99 
102 
     | 
    
         
             
                    try:
         
     | 
| 
       100 
103 
     | 
    
         
             
                        url = f"{self.base_api_url}/drafts"
         
     | 
| 
       101 
     | 
    
         
            -
             
     | 
| 
      
 104 
     | 
    
         
            +
             
     | 
| 
       102 
105 
     | 
    
         
             
                        raw_message = self._create_message(to, subject, body)
         
     | 
| 
       103 
     | 
    
         
            -
             
     | 
| 
       104 
     | 
    
         
            -
                        draft_data = {
         
     | 
| 
       105 
     | 
    
         
            -
             
     | 
| 
       106 
     | 
    
         
            -
                                "raw": raw_message
         
     | 
| 
       107 
     | 
    
         
            -
                            }
         
     | 
| 
       108 
     | 
    
         
            -
                        }
         
     | 
| 
       109 
     | 
    
         
            -
                        
         
     | 
| 
      
 106 
     | 
    
         
            +
             
     | 
| 
      
 107 
     | 
    
         
            +
                        draft_data = {"message": {"raw": raw_message}}
         
     | 
| 
      
 108 
     | 
    
         
            +
             
     | 
| 
       110 
109 
     | 
    
         
             
                        logger.info(f"Creating draft email to {to}")
         
     | 
| 
       111 
     | 
    
         
            -
             
     | 
| 
      
 110 
     | 
    
         
            +
             
     | 
| 
       112 
111 
     | 
    
         
             
                        response = self._post(url, draft_data)
         
     | 
| 
       113 
     | 
    
         
            -
             
     | 
| 
      
 112 
     | 
    
         
            +
             
     | 
| 
       114 
113 
     | 
    
         
             
                        if response.status_code == 200:
         
     | 
| 
       115 
114 
     | 
    
         
             
                            draft_id = response.json().get("id", "unknown")
         
     | 
| 
       116 
115 
     | 
    
         
             
                            return f"Successfully created draft email with ID: {draft_id}"
         
     | 
| 
       117 
116 
     | 
    
         
             
                        else:
         
     | 
| 
       118 
     | 
    
         
            -
                            logger.error( 
     | 
| 
      
 117 
     | 
    
         
            +
                            logger.error(
         
     | 
| 
      
 118 
     | 
    
         
            +
                                f"Gmail API Error: {response.status_code} - {response.text}"
         
     | 
| 
      
 119 
     | 
    
         
            +
                            )
         
     | 
| 
       119 
120 
     | 
    
         
             
                            return f"Error creating draft: {response.status_code} - {response.text}"
         
     | 
| 
       120 
121 
     | 
    
         
             
                    except NotAuthorizedError as e:
         
     | 
| 
       121 
122 
     | 
    
         
             
                        logger.warning(f"Gmail authorization required: {e.message}")
         
     | 
| 
         @@ -129,29 +130,29 @@ class GmailApp(APIApplication): 
     | 
|
| 
       129 
130 
     | 
    
         | 
| 
       130 
131 
     | 
    
         
             
                def send_draft(self, draft_id: str) -> str:
         
     | 
| 
       131 
132 
     | 
    
         
             
                    """Send an existing draft email
         
     | 
| 
       132 
     | 
    
         
            -
             
     | 
| 
      
 133 
     | 
    
         
            +
             
     | 
| 
       133 
134 
     | 
    
         
             
                    Args:
         
     | 
| 
       134 
135 
     | 
    
         
             
                        draft_id: The ID of the draft to send
         
     | 
| 
       135 
     | 
    
         
            -
             
     | 
| 
      
 136 
     | 
    
         
            +
             
     | 
| 
       136 
137 
     | 
    
         
             
                    Returns:
         
     | 
| 
       137 
138 
     | 
    
         
             
                        A confirmation message
         
     | 
| 
       138 
139 
     | 
    
         
             
                    """
         
     | 
| 
       139 
140 
     | 
    
         
             
                    try:
         
     | 
| 
       140 
141 
     | 
    
         
             
                        url = f"{self.base_api_url}/drafts/send"
         
     | 
| 
       141 
     | 
    
         
            -
             
     | 
| 
       142 
     | 
    
         
            -
                        draft_data = {
         
     | 
| 
       143 
     | 
    
         
            -
             
     | 
| 
       144 
     | 
    
         
            -
                        }
         
     | 
| 
       145 
     | 
    
         
            -
                        
         
     | 
| 
      
 142 
     | 
    
         
            +
             
     | 
| 
      
 143 
     | 
    
         
            +
                        draft_data = {"id": draft_id}
         
     | 
| 
      
 144 
     | 
    
         
            +
             
     | 
| 
       146 
145 
     | 
    
         
             
                        logger.info(f"Sending draft email with ID: {draft_id}")
         
     | 
| 
       147 
     | 
    
         
            -
             
     | 
| 
      
 146 
     | 
    
         
            +
             
     | 
| 
       148 
147 
     | 
    
         
             
                        response = self._post(url, draft_data)
         
     | 
| 
       149 
     | 
    
         
            -
             
     | 
| 
      
 148 
     | 
    
         
            +
             
     | 
| 
       150 
149 
     | 
    
         
             
                        if response.status_code == 200:
         
     | 
| 
       151 
150 
     | 
    
         
             
                            message_id = response.json().get("id", "unknown")
         
     | 
| 
       152 
151 
     | 
    
         
             
                            return f"Successfully sent draft email. Message ID: {message_id}"
         
     | 
| 
       153 
152 
     | 
    
         
             
                        else:
         
     | 
| 
       154 
     | 
    
         
            -
                            logger.error( 
     | 
| 
      
 153 
     | 
    
         
            +
                            logger.error(
         
     | 
| 
      
 154 
     | 
    
         
            +
                                f"Gmail API Error: {response.status_code} - {response.text}"
         
     | 
| 
      
 155 
     | 
    
         
            +
                            )
         
     | 
| 
       155 
156 
     | 
    
         
             
                            return f"Error sending draft: {response.status_code} - {response.text}"
         
     | 
| 
       156 
157 
     | 
    
         
             
                    except NotAuthorizedError as e:
         
     | 
| 
       157 
158 
     | 
    
         
             
                        logger.warning(f"Gmail authorization required: {e.message}")
         
     | 
| 
         @@ -165,50 +166,50 @@ class GmailApp(APIApplication): 
     | 
|
| 
       165 
166 
     | 
    
         | 
| 
       166 
167 
     | 
    
         
             
                def get_draft(self, draft_id: str, format: str = "full") -> str:
         
     | 
| 
       167 
168 
     | 
    
         
             
                    """Get a specific draft email by ID
         
     | 
| 
       168 
     | 
    
         
            -
             
     | 
| 
      
 169 
     | 
    
         
            +
             
     | 
| 
       169 
170 
     | 
    
         
             
                    Args:
         
     | 
| 
       170 
171 
     | 
    
         
             
                        draft_id: The ID of the draft to retrieve
         
     | 
| 
       171 
172 
     | 
    
         
             
                        format: The format to return the draft in (minimal, full, raw, metadata)
         
     | 
| 
       172 
     | 
    
         
            -
             
     | 
| 
      
 173 
     | 
    
         
            +
             
     | 
| 
       173 
174 
     | 
    
         
             
                    Returns:
         
     | 
| 
       174 
175 
     | 
    
         
             
                        The draft information or an error message
         
     | 
| 
       175 
176 
     | 
    
         
             
                    """
         
     | 
| 
       176 
177 
     | 
    
         
             
                    try:
         
     | 
| 
       177 
178 
     | 
    
         
             
                        url = f"{self.base_api_url}/drafts/{draft_id}"
         
     | 
| 
       178 
     | 
    
         
            -
             
     | 
| 
      
 179 
     | 
    
         
            +
             
     | 
| 
       179 
180 
     | 
    
         
             
                        # Add format parameter as query param
         
     | 
| 
       180 
181 
     | 
    
         
             
                        params = {"format": format}
         
     | 
| 
       181 
     | 
    
         
            -
             
     | 
| 
      
 182 
     | 
    
         
            +
             
     | 
| 
       182 
183 
     | 
    
         
             
                        logger.info(f"Retrieving draft with ID: {draft_id}")
         
     | 
| 
       183 
     | 
    
         
            -
             
     | 
| 
      
 184 
     | 
    
         
            +
             
     | 
| 
       184 
185 
     | 
    
         
             
                        response = self._get(url, params=params)
         
     | 
| 
       185 
     | 
    
         
            -
             
     | 
| 
      
 186 
     | 
    
         
            +
             
     | 
| 
       186 
187 
     | 
    
         
             
                        if response.status_code == 200:
         
     | 
| 
       187 
188 
     | 
    
         
             
                            draft_data = response.json()
         
     | 
| 
       188 
     | 
    
         
            -
             
     | 
| 
      
 189 
     | 
    
         
            +
             
     | 
| 
       189 
190 
     | 
    
         
             
                            # Format the response in a readable way
         
     | 
| 
       190 
191 
     | 
    
         
             
                            message = draft_data.get("message", {})
         
     | 
| 
       191 
192 
     | 
    
         
             
                            headers = {}
         
     | 
| 
       192 
     | 
    
         
            -
             
     | 
| 
      
 193 
     | 
    
         
            +
             
     | 
| 
       193 
194 
     | 
    
         
             
                            # Extract headers if they exist
         
     | 
| 
       194 
195 
     | 
    
         
             
                            for header in message.get("payload", {}).get("headers", []):
         
     | 
| 
       195 
196 
     | 
    
         
             
                                name = header.get("name", "")
         
     | 
| 
       196 
197 
     | 
    
         
             
                                value = header.get("value", "")
         
     | 
| 
       197 
198 
     | 
    
         
             
                                headers[name] = value
         
     | 
| 
       198 
     | 
    
         
            -
             
     | 
| 
      
 199 
     | 
    
         
            +
             
     | 
| 
       199 
200 
     | 
    
         
             
                            to = headers.get("To", "Unknown recipient")
         
     | 
| 
       200 
201 
     | 
    
         
             
                            subject = headers.get("Subject", "No subject")
         
     | 
| 
       201 
     | 
    
         
            -
             
     | 
| 
       202 
     | 
    
         
            -
                            result =  
     | 
| 
       203 
     | 
    
         
            -
             
     | 
| 
       204 
     | 
    
         
            -
                                f"To: {to}\n"
         
     | 
| 
       205 
     | 
    
         
            -
                                f"Subject: {subject}\n"
         
     | 
| 
       206 
     | 
    
         
            -
                            )
         
     | 
| 
       207 
     | 
    
         
            -
                            
         
     | 
| 
      
 202 
     | 
    
         
            +
             
     | 
| 
      
 203 
     | 
    
         
            +
                            result = f"Draft ID: {draft_id}\nTo: {to}\nSubject: {subject}\n"
         
     | 
| 
      
 204 
     | 
    
         
            +
             
     | 
| 
       208 
205 
     | 
    
         
             
                            return result
         
     | 
| 
       209 
206 
     | 
    
         
             
                        else:
         
     | 
| 
       210 
     | 
    
         
            -
                            logger.error( 
     | 
| 
       211 
     | 
    
         
            -
             
     | 
| 
      
 207 
     | 
    
         
            +
                            logger.error(
         
     | 
| 
      
 208 
     | 
    
         
            +
                                f"Gmail API Error: {response.status_code} - {response.text}"
         
     | 
| 
      
 209 
     | 
    
         
            +
                            )
         
     | 
| 
      
 210 
     | 
    
         
            +
                            return (
         
     | 
| 
      
 211 
     | 
    
         
            +
                                f"Error retrieving draft: {response.status_code} - {response.text}"
         
     | 
| 
      
 212 
     | 
    
         
            +
                            )
         
     | 
| 
       212 
213 
     | 
    
         
             
                    except NotAuthorizedError as e:
         
     | 
| 
       213 
214 
     | 
    
         
             
                        logger.warning(f"Gmail authorization required: {e.message}")
         
     | 
| 
       214 
215 
     | 
    
         
             
                        return e.message
         
     | 
| 
         @@ -218,57 +219,61 @@ class GmailApp(APIApplication): 
     | 
|
| 
       218 
219 
     | 
    
         
             
                    except Exception as e:
         
     | 
| 
       219 
220 
     | 
    
         
             
                        logger.exception(f"Error retrieving draft: {type(e).__name__} - {str(e)}")
         
     | 
| 
       220 
221 
     | 
    
         
             
                        return f"Error retrieving draft: {type(e).__name__} - {str(e)}"
         
     | 
| 
       221 
     | 
    
         
            -
             
     | 
| 
       222 
     | 
    
         
            -
                def list_drafts( 
     | 
| 
      
 222 
     | 
    
         
            +
             
     | 
| 
      
 223 
     | 
    
         
            +
                def list_drafts(
         
     | 
| 
      
 224 
     | 
    
         
            +
                    self, max_results: int = 20, q: str = None, include_spam_trash: bool = False
         
     | 
| 
      
 225 
     | 
    
         
            +
                ) -> str:
         
     | 
| 
       223 
226 
     | 
    
         
             
                    """List drafts in the user's mailbox
         
     | 
| 
       224 
     | 
    
         
            -
             
     | 
| 
      
 227 
     | 
    
         
            +
             
     | 
| 
       225 
228 
     | 
    
         
             
                    Args:
         
     | 
| 
       226 
229 
     | 
    
         
             
                        max_results: Maximum number of drafts to return (max 500, default 20)
         
     | 
| 
       227 
230 
     | 
    
         
             
                        q: Search query to filter drafts (same format as Gmail search)
         
     | 
| 
       228 
231 
     | 
    
         
             
                        include_spam_trash: Include drafts from spam and trash folders
         
     | 
| 
       229 
     | 
    
         
            -
             
     | 
| 
      
 232 
     | 
    
         
            +
             
     | 
| 
       230 
233 
     | 
    
         
             
                    Returns:
         
     | 
| 
       231 
234 
     | 
    
         
             
                        A formatted list of drafts or an error message
         
     | 
| 
       232 
235 
     | 
    
         
             
                    """
         
     | 
| 
       233 
236 
     | 
    
         
             
                    try:
         
     | 
| 
       234 
237 
     | 
    
         
             
                        url = f"{self.base_api_url}/drafts"
         
     | 
| 
       235 
     | 
    
         
            -
             
     | 
| 
      
 238 
     | 
    
         
            +
             
     | 
| 
       236 
239 
     | 
    
         
             
                        # Build query parameters
         
     | 
| 
       237 
     | 
    
         
            -
                        params = {
         
     | 
| 
       238 
     | 
    
         
            -
             
     | 
| 
       239 
     | 
    
         
            -
                        }
         
     | 
| 
       240 
     | 
    
         
            -
                        
         
     | 
| 
      
 240 
     | 
    
         
            +
                        params = {"maxResults": max_results}
         
     | 
| 
      
 241 
     | 
    
         
            +
             
     | 
| 
       241 
242 
     | 
    
         
             
                        if q:
         
     | 
| 
       242 
243 
     | 
    
         
             
                            params["q"] = q
         
     | 
| 
       243 
     | 
    
         
            -
             
     | 
| 
      
 244 
     | 
    
         
            +
             
     | 
| 
       244 
245 
     | 
    
         
             
                        if include_spam_trash:
         
     | 
| 
       245 
246 
     | 
    
         
             
                            params["includeSpamTrash"] = "true"
         
     | 
| 
       246 
     | 
    
         
            -
             
     | 
| 
      
 247 
     | 
    
         
            +
             
     | 
| 
       247 
248 
     | 
    
         
             
                        logger.info(f"Retrieving drafts list with params: {params}")
         
     | 
| 
       248 
     | 
    
         
            -
             
     | 
| 
      
 249 
     | 
    
         
            +
             
     | 
| 
       249 
250 
     | 
    
         
             
                        response = self._get(url, params=params)
         
     | 
| 
       250 
     | 
    
         
            -
             
     | 
| 
      
 251 
     | 
    
         
            +
             
     | 
| 
       251 
252 
     | 
    
         
             
                        if response.status_code == 200:
         
     | 
| 
       252 
253 
     | 
    
         
             
                            data = response.json()
         
     | 
| 
       253 
254 
     | 
    
         
             
                            drafts = data.get("drafts", [])
         
     | 
| 
       254 
255 
     | 
    
         
             
                            result_size = data.get("resultSizeEstimate", 0)
         
     | 
| 
       255 
     | 
    
         
            -
             
     | 
| 
      
 256 
     | 
    
         
            +
             
     | 
| 
       256 
257 
     | 
    
         
             
                            if not drafts:
         
     | 
| 
       257 
258 
     | 
    
         
             
                                return "No drafts found."
         
     | 
| 
       258 
     | 
    
         
            -
             
     | 
| 
       259 
     | 
    
         
            -
                            result =  
     | 
| 
       260 
     | 
    
         
            -
             
     | 
| 
      
 259 
     | 
    
         
            +
             
     | 
| 
      
 260 
     | 
    
         
            +
                            result = (
         
     | 
| 
      
 261 
     | 
    
         
            +
                                f"Found {len(drafts)} drafts (estimated total: {result_size}):\n\n"
         
     | 
| 
      
 262 
     | 
    
         
            +
                            )
         
     | 
| 
      
 263 
     | 
    
         
            +
             
     | 
| 
       261 
264 
     | 
    
         
             
                            for i, draft in enumerate(drafts, 1):
         
     | 
| 
       262 
265 
     | 
    
         
             
                                draft_id = draft.get("id", "Unknown ID")
         
     | 
| 
       263 
266 
     | 
    
         
             
                                # The message field only contains id and threadId at this level
         
     | 
| 
       264 
267 
     | 
    
         
             
                                result += f"{i}. Draft ID: {draft_id}\n"
         
     | 
| 
       265 
     | 
    
         
            -
             
     | 
| 
      
 268 
     | 
    
         
            +
             
     | 
| 
       266 
269 
     | 
    
         
             
                            if "nextPageToken" in data:
         
     | 
| 
       267 
270 
     | 
    
         
             
                                result += "\nMore drafts available. Use page token to see more."
         
     | 
| 
       268 
     | 
    
         
            -
             
     | 
| 
      
 271 
     | 
    
         
            +
             
     | 
| 
       269 
272 
     | 
    
         
             
                            return result
         
     | 
| 
       270 
273 
     | 
    
         
             
                        else:
         
     | 
| 
       271 
     | 
    
         
            -
                            logger.error( 
     | 
| 
      
 274 
     | 
    
         
            +
                            logger.error(
         
     | 
| 
      
 275 
     | 
    
         
            +
                                f"Gmail API Error: {response.status_code} - {response.text}"
         
     | 
| 
      
 276 
     | 
    
         
            +
                            )
         
     | 
| 
       272 
277 
     | 
    
         
             
                            return f"Error listing drafts: {response.status_code} - {response.text}"
         
     | 
| 
       273 
278 
     | 
    
         
             
                    except NotAuthorizedError as e:
         
     | 
| 
       274 
279 
     | 
    
         
             
                        logger.warning(f"Gmail authorization required: {e.message}")
         
     | 
| 
         @@ -279,40 +284,40 @@ class GmailApp(APIApplication): 
     | 
|
| 
       279 
284 
     | 
    
         
             
                    except Exception as e:
         
     | 
| 
       280 
285 
     | 
    
         
             
                        logger.exception(f"Error listing drafts: {type(e).__name__} - {str(e)}")
         
     | 
| 
       281 
286 
     | 
    
         
             
                        return f"Error listing drafts: {type(e).__name__} - {str(e)}"
         
     | 
| 
       282 
     | 
    
         
            -
             
     | 
| 
      
 287 
     | 
    
         
            +
             
     | 
| 
       283 
288 
     | 
    
         
             
                def get_message(self, message_id: str) -> str:
         
     | 
| 
       284 
289 
     | 
    
         
             
                    """Get a specific email message by ID
         
     | 
| 
       285 
     | 
    
         
            -
             
     | 
| 
      
 290 
     | 
    
         
            +
             
     | 
| 
       286 
291 
     | 
    
         
             
                    Args:
         
     | 
| 
       287 
292 
     | 
    
         
             
                        message_id: The ID of the message to retrieve
         
     | 
| 
       288 
     | 
    
         
            -
             
     | 
| 
      
 293 
     | 
    
         
            +
             
     | 
| 
       289 
294 
     | 
    
         
             
                    Returns:
         
     | 
| 
       290 
295 
     | 
    
         
             
                        The message information or an error message
         
     | 
| 
       291 
296 
     | 
    
         
             
                    """
         
     | 
| 
       292 
297 
     | 
    
         
             
                    try:
         
     | 
| 
       293 
298 
     | 
    
         
             
                        url = f"{self.base_api_url}/messages/{message_id}"
         
     | 
| 
       294 
     | 
    
         
            -
             
     | 
| 
      
 299 
     | 
    
         
            +
             
     | 
| 
       295 
300 
     | 
    
         
             
                        logger.info(f"Retrieving message with ID: {message_id}")
         
     | 
| 
       296 
     | 
    
         
            -
             
     | 
| 
      
 301 
     | 
    
         
            +
             
     | 
| 
       297 
302 
     | 
    
         
             
                        response = self._get(url)
         
     | 
| 
       298 
     | 
    
         
            -
             
     | 
| 
      
 303 
     | 
    
         
            +
             
     | 
| 
       299 
304 
     | 
    
         
             
                        if response.status_code == 200:
         
     | 
| 
       300 
305 
     | 
    
         
             
                            message_data = response.json()
         
     | 
| 
       301 
     | 
    
         
            -
             
     | 
| 
      
 306 
     | 
    
         
            +
             
     | 
| 
       302 
307 
     | 
    
         
             
                            # Extract basic message metadata
         
     | 
| 
       303 
308 
     | 
    
         
             
                            headers = {}
         
     | 
| 
       304 
     | 
    
         
            -
             
     | 
| 
      
 309 
     | 
    
         
            +
             
     | 
| 
       305 
310 
     | 
    
         
             
                            # Extract headers if they exist
         
     | 
| 
       306 
311 
     | 
    
         
             
                            for header in message_data.get("payload", {}).get("headers", []):
         
     | 
| 
       307 
312 
     | 
    
         
             
                                name = header.get("name", "")
         
     | 
| 
       308 
313 
     | 
    
         
             
                                value = header.get("value", "")
         
     | 
| 
       309 
314 
     | 
    
         
             
                                headers[name] = value
         
     | 
| 
       310 
     | 
    
         
            -
             
     | 
| 
      
 315 
     | 
    
         
            +
             
     | 
| 
       311 
316 
     | 
    
         
             
                            from_addr = headers.get("From", "Unknown sender")
         
     | 
| 
       312 
317 
     | 
    
         
             
                            to = headers.get("To", "Unknown recipient")
         
     | 
| 
       313 
318 
     | 
    
         
             
                            subject = headers.get("Subject", "No subject")
         
     | 
| 
       314 
319 
     | 
    
         
             
                            date = headers.get("Date", "Unknown date")
         
     | 
| 
       315 
     | 
    
         
            -
             
     | 
| 
      
 320 
     | 
    
         
            +
             
     | 
| 
       316 
321 
     | 
    
         
             
                            # Format the result
         
     | 
| 
       317 
322 
     | 
    
         
             
                            result = (
         
     | 
| 
       318 
323 
     | 
    
         
             
                                f"Message ID: {message_id}\n"
         
     | 
| 
         @@ -321,14 +326,16 @@ class GmailApp(APIApplication): 
     | 
|
| 
       321 
326 
     | 
    
         
             
                                f"Date: {date}\n"
         
     | 
| 
       322 
327 
     | 
    
         
             
                                f"Subject: {subject}\n\n"
         
     | 
| 
       323 
328 
     | 
    
         
             
                            )
         
     | 
| 
       324 
     | 
    
         
            -
             
     | 
| 
      
 329 
     | 
    
         
            +
             
     | 
| 
       325 
330 
     | 
    
         
             
                            # Include snippet as preview of message content
         
     | 
| 
       326 
331 
     | 
    
         
             
                            if "snippet" in message_data:
         
     | 
| 
       327 
332 
     | 
    
         
             
                                result += f"Preview: {message_data['snippet']}\n"
         
     | 
| 
       328 
     | 
    
         
            -
             
     | 
| 
      
 333 
     | 
    
         
            +
             
     | 
| 
       329 
334 
     | 
    
         
             
                            return result
         
     | 
| 
       330 
335 
     | 
    
         
             
                        else:
         
     | 
| 
       331 
     | 
    
         
            -
                            logger.error( 
     | 
| 
      
 336 
     | 
    
         
            +
                            logger.error(
         
     | 
| 
      
 337 
     | 
    
         
            +
                                f"Gmail API Error: {response.status_code} - {response.text}"
         
     | 
| 
      
 338 
     | 
    
         
            +
                            )
         
     | 
| 
       332 
339 
     | 
    
         
             
                            return f"Error retrieving message: {response.status_code} - {response.text}"
         
     | 
| 
       333 
340 
     | 
    
         
             
                    except NotAuthorizedError as e:
         
     | 
| 
       334 
341 
     | 
    
         
             
                        logger.warning(f"Gmail authorization required: {e.message}")
         
     | 
| 
         @@ -339,62 +346,66 @@ class GmailApp(APIApplication): 
     | 
|
| 
       339 
346 
     | 
    
         
             
                    except Exception as e:
         
     | 
| 
       340 
347 
     | 
    
         
             
                        logger.exception(f"Error retrieving message: {type(e).__name__} - {str(e)}")
         
     | 
| 
       341 
348 
     | 
    
         
             
                        return f"Error retrieving message: {type(e).__name__} - {str(e)}"
         
     | 
| 
       342 
     | 
    
         
            -
             
     | 
| 
       343 
     | 
    
         
            -
                def list_messages( 
     | 
| 
      
 349 
     | 
    
         
            +
             
     | 
| 
      
 350 
     | 
    
         
            +
                def list_messages(
         
     | 
| 
      
 351 
     | 
    
         
            +
                    self, max_results: int = 20, q: str = None, include_spam_trash: bool = False
         
     | 
| 
      
 352 
     | 
    
         
            +
                ) -> str:
         
     | 
| 
       344 
353 
     | 
    
         
             
                    """List messages in the user's mailbox
         
     | 
| 
       345 
     | 
    
         
            -
             
     | 
| 
      
 354 
     | 
    
         
            +
             
     | 
| 
       346 
355 
     | 
    
         
             
                    Args:
         
     | 
| 
       347 
356 
     | 
    
         
             
                        max_results: Maximum number of messages to return (max 500, default 20)
         
     | 
| 
       348 
357 
     | 
    
         
             
                        q: Search query to filter messages (same format as Gmail search)
         
     | 
| 
       349 
358 
     | 
    
         
             
                        include_spam_trash: Include messages from spam and trash folders
         
     | 
| 
       350 
     | 
    
         
            -
             
     | 
| 
      
 359 
     | 
    
         
            +
             
     | 
| 
       351 
360 
     | 
    
         
             
                    Returns:
         
     | 
| 
       352 
361 
     | 
    
         
             
                        A formatted list of messages or an error message
         
     | 
| 
       353 
362 
     | 
    
         
             
                    """
         
     | 
| 
       354 
363 
     | 
    
         
             
                    try:
         
     | 
| 
       355 
364 
     | 
    
         
             
                        url = f"{self.base_api_url}/messages"
         
     | 
| 
       356 
     | 
    
         
            -
             
     | 
| 
      
 365 
     | 
    
         
            +
             
     | 
| 
       357 
366 
     | 
    
         
             
                        # Build query parameters
         
     | 
| 
       358 
     | 
    
         
            -
                        params = {
         
     | 
| 
       359 
     | 
    
         
            -
             
     | 
| 
       360 
     | 
    
         
            -
                        }
         
     | 
| 
       361 
     | 
    
         
            -
                        
         
     | 
| 
      
 367 
     | 
    
         
            +
                        params = {"maxResults": max_results}
         
     | 
| 
      
 368 
     | 
    
         
            +
             
     | 
| 
       362 
369 
     | 
    
         
             
                        if q:
         
     | 
| 
       363 
370 
     | 
    
         
             
                            params["q"] = q
         
     | 
| 
       364 
     | 
    
         
            -
             
     | 
| 
      
 371 
     | 
    
         
            +
             
     | 
| 
       365 
372 
     | 
    
         
             
                        if include_spam_trash:
         
     | 
| 
       366 
373 
     | 
    
         
             
                            params["includeSpamTrash"] = "true"
         
     | 
| 
       367 
     | 
    
         
            -
             
     | 
| 
      
 374 
     | 
    
         
            +
             
     | 
| 
       368 
375 
     | 
    
         
             
                        logger.info(f"Retrieving messages list with params: {params}")
         
     | 
| 
       369 
     | 
    
         
            -
             
     | 
| 
      
 376 
     | 
    
         
            +
             
     | 
| 
       370 
377 
     | 
    
         
             
                        response = self._get(url, params=params)
         
     | 
| 
       371 
     | 
    
         
            -
             
     | 
| 
      
 378 
     | 
    
         
            +
             
     | 
| 
       372 
379 
     | 
    
         
             
                        if response.status_code == 200:
         
     | 
| 
       373 
380 
     | 
    
         
             
                            data = response.json()
         
     | 
| 
       374 
381 
     | 
    
         
             
                            messages = data.get("messages", [])
         
     | 
| 
       375 
382 
     | 
    
         
             
                            result_size = data.get("resultSizeEstimate", 0)
         
     | 
| 
       376 
     | 
    
         
            -
             
     | 
| 
      
 383 
     | 
    
         
            +
             
     | 
| 
       377 
384 
     | 
    
         
             
                            if not messages:
         
     | 
| 
       378 
385 
     | 
    
         
             
                                return "No messages found matching the criteria."
         
     | 
| 
       379 
     | 
    
         
            -
             
     | 
| 
      
 386 
     | 
    
         
            +
             
     | 
| 
       380 
387 
     | 
    
         
             
                            result = f"Found {len(messages)} messages (estimated total: {result_size}):\n\n"
         
     | 
| 
       381 
     | 
    
         
            -
             
     | 
| 
      
 388 
     | 
    
         
            +
             
     | 
| 
       382 
389 
     | 
    
         
             
                            # Just list message IDs without fetching additional details
         
     | 
| 
       383 
390 
     | 
    
         
             
                            for i, msg in enumerate(messages, 1):
         
     | 
| 
       384 
391 
     | 
    
         
             
                                message_id = msg.get("id", "Unknown ID")
         
     | 
| 
       385 
392 
     | 
    
         
             
                                thread_id = msg.get("threadId", "Unknown Thread")
         
     | 
| 
       386 
393 
     | 
    
         
             
                                result += f"{i}. Message ID: {message_id} (Thread: {thread_id})\n"
         
     | 
| 
       387 
     | 
    
         
            -
             
     | 
| 
      
 394 
     | 
    
         
            +
             
     | 
| 
       388 
395 
     | 
    
         
             
                            # Add a note about how to get message details
         
     | 
| 
       389 
396 
     | 
    
         
             
                            result += "\nUse get_message(message_id) to view the contents of a specific message."
         
     | 
| 
       390 
     | 
    
         
            -
             
     | 
| 
      
 397 
     | 
    
         
            +
             
     | 
| 
       391 
398 
     | 
    
         
             
                            if "nextPageToken" in data:
         
     | 
| 
       392 
399 
     | 
    
         
             
                                result += "\nMore messages available. Use page token to see more."
         
     | 
| 
       393 
     | 
    
         
            -
             
     | 
| 
      
 400 
     | 
    
         
            +
             
     | 
| 
       394 
401 
     | 
    
         
             
                            return result
         
     | 
| 
       395 
402 
     | 
    
         
             
                        else:
         
     | 
| 
       396 
     | 
    
         
            -
                            logger.error( 
     | 
| 
       397 
     | 
    
         
            -
             
     | 
| 
      
 403 
     | 
    
         
            +
                            logger.error(
         
     | 
| 
      
 404 
     | 
    
         
            +
                                f"Gmail API Error: {response.status_code} - {response.text}"
         
     | 
| 
      
 405 
     | 
    
         
            +
                            )
         
     | 
| 
      
 406 
     | 
    
         
            +
                            return (
         
     | 
| 
      
 407 
     | 
    
         
            +
                                f"Error listing messages: {response.status_code} - {response.text}"
         
     | 
| 
      
 408 
     | 
    
         
            +
                            )
         
     | 
| 
       398 
409 
     | 
    
         
             
                    except NotAuthorizedError as e:
         
     | 
| 
       399 
410 
     | 
    
         
             
                        logger.warning(f"Gmail authorization required: {e.message}")
         
     | 
| 
       400 
411 
     | 
    
         
             
                        return e.message
         
     | 
| 
         @@ -404,68 +415,68 @@ class GmailApp(APIApplication): 
     | 
|
| 
       404 
415 
     | 
    
         
             
                    except Exception as e:
         
     | 
| 
       405 
416 
     | 
    
         
             
                        logger.exception(f"Error listing messages: {type(e).__name__} - {str(e)}")
         
     | 
| 
       406 
417 
     | 
    
         
             
                        return f"Error listing messages: {type(e).__name__} - {str(e)}"
         
     | 
| 
       407 
     | 
    
         
            -
                        
         
     | 
| 
       408 
     | 
    
         
            -
               
         
     | 
| 
       409 
418 
     | 
    
         | 
| 
       410 
419 
     | 
    
         
             
                def list_labels(self) -> str:
         
     | 
| 
       411 
420 
     | 
    
         
             
                    """List all labels in the user's Gmail account
         
     | 
| 
       412 
     | 
    
         
            -
             
     | 
| 
      
 421 
     | 
    
         
            +
             
     | 
| 
       413 
422 
     | 
    
         
             
                    Returns:
         
     | 
| 
       414 
423 
     | 
    
         
             
                        A formatted list of labels or an error message
         
     | 
| 
       415 
424 
     | 
    
         
             
                    """
         
     | 
| 
       416 
425 
     | 
    
         
             
                    try:
         
     | 
| 
       417 
426 
     | 
    
         
             
                        url = f"{self.base_api_url}/labels"
         
     | 
| 
       418 
     | 
    
         
            -
             
     | 
| 
      
 427 
     | 
    
         
            +
             
     | 
| 
       419 
428 
     | 
    
         
             
                        logger.info("Retrieving Gmail labels")
         
     | 
| 
       420 
     | 
    
         
            -
             
     | 
| 
      
 429 
     | 
    
         
            +
             
     | 
| 
       421 
430 
     | 
    
         
             
                        response = self._get(url)
         
     | 
| 
       422 
     | 
    
         
            -
             
     | 
| 
      
 431 
     | 
    
         
            +
             
     | 
| 
       423 
432 
     | 
    
         
             
                        if response.status_code == 200:
         
     | 
| 
       424 
433 
     | 
    
         
             
                            data = response.json()
         
     | 
| 
       425 
434 
     | 
    
         
             
                            labels = data.get("labels", [])
         
     | 
| 
       426 
     | 
    
         
            -
             
     | 
| 
      
 435 
     | 
    
         
            +
             
     | 
| 
       427 
436 
     | 
    
         
             
                            if not labels:
         
     | 
| 
       428 
437 
     | 
    
         
             
                                return "No labels found in your Gmail account."
         
     | 
| 
       429 
     | 
    
         
            -
             
     | 
| 
      
 438 
     | 
    
         
            +
             
     | 
| 
       430 
439 
     | 
    
         
             
                            # Sort labels by type (system first, then user) and then by name
         
     | 
| 
       431 
440 
     | 
    
         
             
                            system_labels = []
         
     | 
| 
       432 
441 
     | 
    
         
             
                            user_labels = []
         
     | 
| 
       433 
     | 
    
         
            -
             
     | 
| 
      
 442 
     | 
    
         
            +
             
     | 
| 
       434 
443 
     | 
    
         
             
                            for label in labels:
         
     | 
| 
       435 
444 
     | 
    
         
             
                                label_id = label.get("id", "Unknown ID")
         
     | 
| 
       436 
445 
     | 
    
         
             
                                label_name = label.get("name", "Unknown Name")
         
     | 
| 
       437 
446 
     | 
    
         
             
                                label_type = label.get("type", "Unknown Type")
         
     | 
| 
       438 
     | 
    
         
            -
             
     | 
| 
      
 447 
     | 
    
         
            +
             
     | 
| 
       439 
448 
     | 
    
         
             
                                if label_type == "system":
         
     | 
| 
       440 
449 
     | 
    
         
             
                                    system_labels.append((label_id, label_name))
         
     | 
| 
       441 
450 
     | 
    
         
             
                                else:
         
     | 
| 
       442 
451 
     | 
    
         
             
                                    user_labels.append((label_id, label_name))
         
     | 
| 
       443 
     | 
    
         
            -
             
     | 
| 
      
 452 
     | 
    
         
            +
             
     | 
| 
       444 
453 
     | 
    
         
             
                            # Sort by name within each category
         
     | 
| 
       445 
454 
     | 
    
         
             
                            system_labels.sort(key=lambda x: x[1])
         
     | 
| 
       446 
455 
     | 
    
         
             
                            user_labels.sort(key=lambda x: x[1])
         
     | 
| 
       447 
     | 
    
         
            -
             
     | 
| 
      
 456 
     | 
    
         
            +
             
     | 
| 
       448 
457 
     | 
    
         
             
                            result = f"Found {len(labels)} Gmail labels:\n\n"
         
     | 
| 
       449 
     | 
    
         
            -
             
     | 
| 
      
 458 
     | 
    
         
            +
             
     | 
| 
       450 
459 
     | 
    
         
             
                            # System labels
         
     | 
| 
       451 
460 
     | 
    
         
             
                            if system_labels:
         
     | 
| 
       452 
461 
     | 
    
         
             
                                result += "System Labels:\n"
         
     | 
| 
       453 
462 
     | 
    
         
             
                                for label_id, label_name in system_labels:
         
     | 
| 
       454 
463 
     | 
    
         
             
                                    result += f"- {label_name} (ID: {label_id})\n"
         
     | 
| 
       455 
464 
     | 
    
         
             
                                result += "\n"
         
     | 
| 
       456 
     | 
    
         
            -
             
     | 
| 
      
 465 
     | 
    
         
            +
             
     | 
| 
       457 
466 
     | 
    
         
             
                            # User labels
         
     | 
| 
       458 
467 
     | 
    
         
             
                            if user_labels:
         
     | 
| 
       459 
468 
     | 
    
         
             
                                result += "User Labels:\n"
         
     | 
| 
       460 
469 
     | 
    
         
             
                                for label_id, label_name in user_labels:
         
     | 
| 
       461 
470 
     | 
    
         
             
                                    result += f"- {label_name} (ID: {label_id})\n"
         
     | 
| 
       462 
     | 
    
         
            -
             
     | 
| 
      
 471 
     | 
    
         
            +
             
     | 
| 
       463 
472 
     | 
    
         
             
                            # Add note about using labels
         
     | 
| 
       464 
473 
     | 
    
         
             
                            result += "\nThese label IDs can be used with list_messages to filter emails by label."
         
     | 
| 
       465 
     | 
    
         
            -
             
     | 
| 
      
 474 
     | 
    
         
            +
             
     | 
| 
       466 
475 
     | 
    
         
             
                            return result
         
     | 
| 
       467 
476 
     | 
    
         
             
                        else:
         
     | 
| 
       468 
     | 
    
         
            -
                            logger.error( 
     | 
| 
      
 477 
     | 
    
         
            +
                            logger.error(
         
     | 
| 
      
 478 
     | 
    
         
            +
                                f"Gmail API Error: {response.status_code} - {response.text}"
         
     | 
| 
      
 479 
     | 
    
         
            +
                            )
         
     | 
| 
       469 
480 
     | 
    
         
             
                            return f"Error listing labels: {response.status_code} - {response.text}"
         
     | 
| 
       470 
481 
     | 
    
         
             
                    except NotAuthorizedError as e:
         
     | 
| 
       471 
482 
     | 
    
         
             
                        logger.warning(f"Gmail authorization required: {e.message}")
         
     | 
| 
         @@ -476,39 +487,41 @@ class GmailApp(APIApplication): 
     | 
|
| 
       476 
487 
     | 
    
         | 
| 
       477 
488 
     | 
    
         
             
                def create_label(self, name: str) -> str:
         
     | 
| 
       478 
489 
     | 
    
         
             
                    """Create a new Gmail label
         
     | 
| 
       479 
     | 
    
         
            -
             
     | 
| 
      
 490 
     | 
    
         
            +
             
     | 
| 
       480 
491 
     | 
    
         
             
                    Args:
         
     | 
| 
       481 
492 
     | 
    
         
             
                        name: The display name of the label to create
         
     | 
| 
       482 
     | 
    
         
            -
             
     | 
| 
      
 493 
     | 
    
         
            +
             
     | 
| 
       483 
494 
     | 
    
         
             
                    Returns:
         
     | 
| 
       484 
495 
     | 
    
         
             
                        A confirmation message with the new label details
         
     | 
| 
       485 
496 
     | 
    
         
             
                    """
         
     | 
| 
       486 
497 
     | 
    
         
             
                    try:
         
     | 
| 
       487 
498 
     | 
    
         
             
                        url = f"{self.base_api_url}/labels"
         
     | 
| 
       488 
     | 
    
         
            -
             
     | 
| 
      
 499 
     | 
    
         
            +
             
     | 
| 
       489 
500 
     | 
    
         
             
                        # Create the label data with just the essential fields
         
     | 
| 
       490 
501 
     | 
    
         
             
                        label_data = {
         
     | 
| 
       491 
502 
     | 
    
         
             
                            "name": name,
         
     | 
| 
       492 
503 
     | 
    
         
             
                            "labelListVisibility": "labelShow",  # Show in label list
         
     | 
| 
       493 
     | 
    
         
            -
                            "messageListVisibility": "show" 
     | 
| 
      
 504 
     | 
    
         
            +
                            "messageListVisibility": "show",  # Show in message list
         
     | 
| 
       494 
505 
     | 
    
         
             
                        }
         
     | 
| 
       495 
     | 
    
         
            -
             
     | 
| 
      
 506 
     | 
    
         
            +
             
     | 
| 
       496 
507 
     | 
    
         
             
                        logger.info(f"Creating new Gmail label: {name}")
         
     | 
| 
       497 
     | 
    
         
            -
             
     | 
| 
      
 508 
     | 
    
         
            +
             
     | 
| 
       498 
509 
     | 
    
         
             
                        response = self._post(url, label_data)
         
     | 
| 
       499 
     | 
    
         
            -
             
     | 
| 
      
 510 
     | 
    
         
            +
             
     | 
| 
       500 
511 
     | 
    
         
             
                        if response.status_code in [200, 201]:
         
     | 
| 
       501 
512 
     | 
    
         
             
                            new_label = response.json()
         
     | 
| 
       502 
513 
     | 
    
         
             
                            label_id = new_label.get("id", "Unknown")
         
     | 
| 
       503 
514 
     | 
    
         
             
                            label_name = new_label.get("name", name)
         
     | 
| 
       504 
     | 
    
         
            -
             
     | 
| 
       505 
     | 
    
         
            -
                            result =  
     | 
| 
      
 515 
     | 
    
         
            +
             
     | 
| 
      
 516 
     | 
    
         
            +
                            result = "Successfully created new label:\n"
         
     | 
| 
       506 
517 
     | 
    
         
             
                            result += f"- Name: {label_name}\n"
         
     | 
| 
       507 
518 
     | 
    
         
             
                            result += f"- ID: {label_id}\n"
         
     | 
| 
       508 
     | 
    
         
            -
             
     | 
| 
      
 519 
     | 
    
         
            +
             
     | 
| 
       509 
520 
     | 
    
         
             
                            return result
         
     | 
| 
       510 
521 
     | 
    
         
             
                        else:
         
     | 
| 
       511 
     | 
    
         
            -
                            logger.error( 
     | 
| 
      
 522 
     | 
    
         
            +
                            logger.error(
         
     | 
| 
      
 523 
     | 
    
         
            +
                                f"Gmail API Error: {response.status_code} - {response.text}"
         
     | 
| 
      
 524 
     | 
    
         
            +
                            )
         
     | 
| 
       512 
525 
     | 
    
         
             
                            return f"Error creating label: {response.status_code} - {response.text}"
         
     | 
| 
       513 
526 
     | 
    
         
             
                    except NotAuthorizedError as e:
         
     | 
| 
       514 
527 
     | 
    
         
             
                        logger.warning(f"Gmail authorization required: {e.message}")
         
     | 
| 
         @@ -516,41 +529,44 @@ class GmailApp(APIApplication): 
     | 
|
| 
       516 
529 
     | 
    
         
             
                    except Exception as e:
         
     | 
| 
       517 
530 
     | 
    
         
             
                        logger.exception(f"Error creating label: {type(e).__name__} - {str(e)}")
         
     | 
| 
       518 
531 
     | 
    
         
             
                        return f"Error creating label: {type(e).__name__} - {str(e)}"
         
     | 
| 
      
 532 
     | 
    
         
            +
             
     | 
| 
       519 
533 
     | 
    
         
             
                def get_profile(self) -> str:
         
     | 
| 
       520 
534 
     | 
    
         
             
                    """Retrieve the user's Gmail profile information.
         
     | 
| 
       521 
     | 
    
         
            -
             
     | 
| 
      
 535 
     | 
    
         
            +
             
     | 
| 
       522 
536 
     | 
    
         
             
                    This method fetches the user's email address, message count, thread count,
         
     | 
| 
       523 
537 
     | 
    
         
             
                    and current history ID from the Gmail API.
         
     | 
| 
       524 
     | 
    
         
            -
             
     | 
| 
      
 538 
     | 
    
         
            +
             
     | 
| 
       525 
539 
     | 
    
         
             
                    Returns:
         
     | 
| 
       526 
540 
     | 
    
         
             
                        A formatted string containing the user's profile information or an error message
         
     | 
| 
       527 
541 
     | 
    
         
             
                    """
         
     | 
| 
       528 
542 
     | 
    
         
             
                    try:
         
     | 
| 
       529 
543 
     | 
    
         
             
                        url = f"{self.base_api_url}/profile"
         
     | 
| 
       530 
     | 
    
         
            -
             
     | 
| 
      
 544 
     | 
    
         
            +
             
     | 
| 
       531 
545 
     | 
    
         
             
                        logger.info("Retrieving Gmail user profile")
         
     | 
| 
       532 
     | 
    
         
            -
             
     | 
| 
      
 546 
     | 
    
         
            +
             
     | 
| 
       533 
547 
     | 
    
         
             
                        response = self._get(url)
         
     | 
| 
       534 
     | 
    
         
            -
             
     | 
| 
      
 548 
     | 
    
         
            +
             
     | 
| 
       535 
549 
     | 
    
         
             
                        if response.status_code == 200:
         
     | 
| 
       536 
550 
     | 
    
         
             
                            profile_data = response.json()
         
     | 
| 
       537 
     | 
    
         
            -
             
     | 
| 
      
 551 
     | 
    
         
            +
             
     | 
| 
       538 
552 
     | 
    
         
             
                            # Extract profile information
         
     | 
| 
       539 
553 
     | 
    
         
             
                            email_address = profile_data.get("emailAddress", "Unknown")
         
     | 
| 
       540 
554 
     | 
    
         
             
                            messages_total = profile_data.get("messagesTotal", 0)
         
     | 
| 
       541 
555 
     | 
    
         
             
                            threads_total = profile_data.get("threadsTotal", 0)
         
     | 
| 
       542 
556 
     | 
    
         
             
                            history_id = profile_data.get("historyId", "Unknown")
         
     | 
| 
       543 
     | 
    
         
            -
             
     | 
| 
      
 557 
     | 
    
         
            +
             
     | 
| 
       544 
558 
     | 
    
         
             
                            # Format the response
         
     | 
| 
       545 
559 
     | 
    
         
             
                            result = "Gmail Profile Information:\n"
         
     | 
| 
       546 
560 
     | 
    
         
             
                            result += f"- Email Address: {email_address}\n"
         
     | 
| 
       547 
561 
     | 
    
         
             
                            result += f"- Total Messages: {messages_total:,}\n"
         
     | 
| 
       548 
562 
     | 
    
         
             
                            result += f"- Total Threads: {threads_total:,}\n"
         
     | 
| 
       549 
563 
     | 
    
         
             
                            result += f"- History ID: {history_id}\n"
         
     | 
| 
       550 
     | 
    
         
            -
             
     | 
| 
      
 564 
     | 
    
         
            +
             
     | 
| 
       551 
565 
     | 
    
         
             
                            return result
         
     | 
| 
       552 
566 
     | 
    
         
             
                        else:
         
     | 
| 
       553 
     | 
    
         
            -
                            logger.error( 
     | 
| 
      
 567 
     | 
    
         
            +
                            logger.error(
         
     | 
| 
      
 568 
     | 
    
         
            +
                                f"Gmail API Error: {response.status_code} - {response.text}"
         
     | 
| 
      
 569 
     | 
    
         
            +
                            )
         
     | 
| 
       554 
570 
     | 
    
         
             
                            return f"Error retrieving profile: {response.status_code} - {response.text}"
         
     | 
| 
       555 
571 
     | 
    
         
             
                    except NotAuthorizedError as e:
         
     | 
| 
       556 
572 
     | 
    
         
             
                        logger.warning(f"Gmail authorization required: {e.message}")
         
     | 
| 
         @@ -560,6 +576,15 @@ class GmailApp(APIApplication): 
     | 
|
| 
       560 
576 
     | 
    
         
             
                        return f"Error retrieving profile: {type(e).__name__} - {str(e)}"
         
     | 
| 
       561 
577 
     | 
    
         | 
| 
       562 
578 
     | 
    
         
             
                def list_tools(self):
         
     | 
| 
       563 
     | 
    
         
            -
                    return [ 
     | 
| 
       564 
     | 
    
         
            -
             
     | 
| 
       565 
     | 
    
         
            -
             
     | 
| 
      
 579 
     | 
    
         
            +
                    return [
         
     | 
| 
      
 580 
     | 
    
         
            +
                        self.send_email,
         
     | 
| 
      
 581 
     | 
    
         
            +
                        self.create_draft,
         
     | 
| 
      
 582 
     | 
    
         
            +
                        self.send_draft,
         
     | 
| 
      
 583 
     | 
    
         
            +
                        self.get_draft,
         
     | 
| 
      
 584 
     | 
    
         
            +
                        self.list_drafts,
         
     | 
| 
      
 585 
     | 
    
         
            +
                        self.get_message,
         
     | 
| 
      
 586 
     | 
    
         
            +
                        self.list_messages,
         
     | 
| 
      
 587 
     | 
    
         
            +
                        self.list_labels,
         
     | 
| 
      
 588 
     | 
    
         
            +
                        self.create_label,
         
     | 
| 
      
 589 
     | 
    
         
            +
                        self.get_profile,
         
     | 
| 
      
 590 
     | 
    
         
            +
                    ]
         
     |