universal-mcp 0.1.12__py3-none-any.whl → 0.1.13rc2__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 +51 -7
- universal_mcp/cli.py +109 -17
- universal_mcp/integrations/__init__.py +1 -1
- universal_mcp/integrations/integration.py +79 -0
- universal_mcp/servers/README.md +79 -0
- universal_mcp/servers/server.py +17 -29
- universal_mcp/stores/README.md +74 -0
- universal_mcp/stores/store.py +0 -2
- universal_mcp/templates/README.md.j2 +93 -0
- universal_mcp/templates/api_client.py.j2 +27 -0
- universal_mcp/tools/README.md +86 -0
- universal_mcp/tools/tools.py +1 -1
- universal_mcp/utils/agentr.py +90 -0
- universal_mcp/utils/api_generator.py +166 -208
- universal_mcp/utils/openapi.py +221 -321
- universal_mcp/utils/singleton.py +23 -0
- {universal_mcp-0.1.12.dist-info → universal_mcp-0.1.13rc2.dist-info}/METADATA +16 -41
- universal_mcp-0.1.13rc2.dist-info/RECORD +38 -0
- universal_mcp/applications/ahrefs/README.md +0 -76
- universal_mcp/applications/ahrefs/__init__.py +0 -0
- universal_mcp/applications/ahrefs/app.py +0 -2291
- universal_mcp/applications/cal_com_v2/README.md +0 -175
- universal_mcp/applications/cal_com_v2/__init__.py +0 -0
- universal_mcp/applications/cal_com_v2/app.py +0 -5390
- universal_mcp/applications/calendly/README.md +0 -78
- universal_mcp/applications/calendly/__init__.py +0 -0
- universal_mcp/applications/calendly/app.py +0 -1195
- universal_mcp/applications/clickup/README.md +0 -160
- universal_mcp/applications/clickup/__init__.py +0 -0
- universal_mcp/applications/clickup/app.py +0 -5009
- universal_mcp/applications/coda/README.md +0 -133
- universal_mcp/applications/coda/__init__.py +0 -0
- universal_mcp/applications/coda/app.py +0 -3671
- universal_mcp/applications/e2b/README.md +0 -37
- universal_mcp/applications/e2b/app.py +0 -65
- universal_mcp/applications/elevenlabs/README.md +0 -84
- universal_mcp/applications/elevenlabs/__init__.py +0 -0
- universal_mcp/applications/elevenlabs/app.py +0 -1402
- universal_mcp/applications/falai/README.md +0 -42
- universal_mcp/applications/falai/__init__.py +0 -0
- universal_mcp/applications/falai/app.py +0 -332
- universal_mcp/applications/figma/README.md +0 -74
- universal_mcp/applications/figma/__init__.py +0 -0
- universal_mcp/applications/figma/app.py +0 -1261
- universal_mcp/applications/firecrawl/README.md +0 -45
- universal_mcp/applications/firecrawl/app.py +0 -268
- universal_mcp/applications/github/README.md +0 -47
- universal_mcp/applications/github/app.py +0 -429
- universal_mcp/applications/gong/README.md +0 -88
- universal_mcp/applications/gong/__init__.py +0 -0
- universal_mcp/applications/gong/app.py +0 -2297
- universal_mcp/applications/google_calendar/app.py +0 -442
- universal_mcp/applications/google_docs/README.md +0 -40
- universal_mcp/applications/google_docs/app.py +0 -88
- universal_mcp/applications/google_drive/README.md +0 -44
- universal_mcp/applications/google_drive/app.py +0 -286
- universal_mcp/applications/google_mail/README.md +0 -47
- universal_mcp/applications/google_mail/app.py +0 -664
- universal_mcp/applications/google_sheet/README.md +0 -42
- universal_mcp/applications/google_sheet/app.py +0 -150
- universal_mcp/applications/hashnode/app.py +0 -81
- universal_mcp/applications/hashnode/prompt.md +0 -23
- universal_mcp/applications/heygen/README.md +0 -69
- universal_mcp/applications/heygen/__init__.py +0 -0
- universal_mcp/applications/heygen/app.py +0 -956
- universal_mcp/applications/mailchimp/README.md +0 -306
- universal_mcp/applications/mailchimp/__init__.py +0 -0
- universal_mcp/applications/mailchimp/app.py +0 -10937
- universal_mcp/applications/markitdown/app.py +0 -44
- universal_mcp/applications/notion/README.md +0 -55
- universal_mcp/applications/notion/__init__.py +0 -0
- universal_mcp/applications/notion/app.py +0 -527
- universal_mcp/applications/perplexity/README.md +0 -37
- universal_mcp/applications/perplexity/app.py +0 -65
- universal_mcp/applications/reddit/README.md +0 -45
- universal_mcp/applications/reddit/app.py +0 -379
- universal_mcp/applications/replicate/README.md +0 -65
- universal_mcp/applications/replicate/__init__.py +0 -0
- universal_mcp/applications/replicate/app.py +0 -980
- universal_mcp/applications/resend/README.md +0 -38
- universal_mcp/applications/resend/app.py +0 -37
- universal_mcp/applications/retell_ai/README.md +0 -46
- universal_mcp/applications/retell_ai/__init__.py +0 -0
- universal_mcp/applications/retell_ai/app.py +0 -333
- universal_mcp/applications/rocketlane/README.md +0 -42
- universal_mcp/applications/rocketlane/__init__.py +0 -0
- universal_mcp/applications/rocketlane/app.py +0 -194
- universal_mcp/applications/serpapi/README.md +0 -37
- universal_mcp/applications/serpapi/app.py +0 -73
- universal_mcp/applications/spotify/README.md +0 -116
- universal_mcp/applications/spotify/__init__.py +0 -0
- universal_mcp/applications/spotify/app.py +0 -2526
- universal_mcp/applications/supabase/README.md +0 -112
- universal_mcp/applications/supabase/__init__.py +0 -0
- universal_mcp/applications/supabase/app.py +0 -2970
- universal_mcp/applications/tavily/README.md +0 -38
- universal_mcp/applications/tavily/app.py +0 -51
- universal_mcp/applications/wrike/README.md +0 -71
- universal_mcp/applications/wrike/__init__.py +0 -0
- universal_mcp/applications/wrike/app.py +0 -1372
- universal_mcp/applications/youtube/README.md +0 -82
- universal_mcp/applications/youtube/__init__.py +0 -0
- universal_mcp/applications/youtube/app.py +0 -1428
- universal_mcp/applications/zenquotes/README.md +0 -37
- universal_mcp/applications/zenquotes/app.py +0 -31
- universal_mcp/integrations/agentr.py +0 -112
- universal_mcp-0.1.12.dist-info/RECORD +0 -119
- {universal_mcp-0.1.12.dist-info → universal_mcp-0.1.13rc2.dist-info}/WHEEL +0 -0
- {universal_mcp-0.1.12.dist-info → universal_mcp-0.1.13rc2.dist-info}/entry_points.txt +0 -0
@@ -1,664 +0,0 @@
|
|
1
|
-
import base64
|
2
|
-
from email.message import EmailMessage
|
3
|
-
|
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):
|
12
|
-
def __init__(self, integration: Integration) -> None:
|
13
|
-
super().__init__(name="google-mail", integration=integration)
|
14
|
-
self.base_api_url = "https://gmail.googleapis.com/gmail/v1/users/me"
|
15
|
-
|
16
|
-
def send_email(self, to: str, subject: str, body: str) -> str:
|
17
|
-
"""
|
18
|
-
Sends an email using the Gmail API and returns a confirmation or error message.
|
19
|
-
|
20
|
-
Args:
|
21
|
-
to: The email address of the recipient
|
22
|
-
subject: The subject line of the email
|
23
|
-
body: The main content of the email message
|
24
|
-
|
25
|
-
Returns:
|
26
|
-
A string containing either a success confirmation message or an error description
|
27
|
-
|
28
|
-
Raises:
|
29
|
-
NotAuthorizedError: When Gmail API authentication is not valid or has expired
|
30
|
-
KeyError: When required configuration keys are missing
|
31
|
-
Exception: For any other unexpected errors during the email sending process
|
32
|
-
|
33
|
-
Tags:
|
34
|
-
send, email, api, communication, important
|
35
|
-
"""
|
36
|
-
try:
|
37
|
-
url = f"{self.base_api_url}/messages/send"
|
38
|
-
|
39
|
-
# Create email in base64 encoded format
|
40
|
-
raw_message = self._create_message(to, subject, body)
|
41
|
-
|
42
|
-
# Format the data as expected by Gmail API
|
43
|
-
email_data = {"raw": raw_message}
|
44
|
-
|
45
|
-
logger.info(f"Sending email to {to}")
|
46
|
-
|
47
|
-
response = self._post(url, email_data)
|
48
|
-
|
49
|
-
if response.status_code == 200:
|
50
|
-
return f"Successfully sent email to {to}"
|
51
|
-
else:
|
52
|
-
logger.error(
|
53
|
-
f"Gmail API Error: {response.status_code} - {response.text}"
|
54
|
-
)
|
55
|
-
return f"Error sending email: {response.status_code} - {response.text}"
|
56
|
-
except NotAuthorizedError as e:
|
57
|
-
# Return the authorization message directly
|
58
|
-
logger.warning(f"Gmail authorization required: {e.message}")
|
59
|
-
return e.message
|
60
|
-
except KeyError as key_error:
|
61
|
-
logger.error(f"Missing key error: {str(key_error)}")
|
62
|
-
return f"Configuration error: Missing required key - {str(key_error)}"
|
63
|
-
except Exception as e:
|
64
|
-
logger.exception(f"Error sending email: {type(e).__name__} - {str(e)}")
|
65
|
-
return f"Error sending email: {type(e).__name__} - {str(e)}"
|
66
|
-
|
67
|
-
def _create_message(self, to, subject, body):
|
68
|
-
try:
|
69
|
-
message = EmailMessage()
|
70
|
-
message["to"] = to
|
71
|
-
message["subject"] = subject
|
72
|
-
message.set_content(body)
|
73
|
-
|
74
|
-
# Use "me" as the default sender
|
75
|
-
message["from"] = "me"
|
76
|
-
|
77
|
-
# Encode as base64 string
|
78
|
-
raw = base64.urlsafe_b64encode(message.as_bytes()).decode()
|
79
|
-
return raw
|
80
|
-
except Exception as e:
|
81
|
-
logger.error(f"Error creating message: {str(e)}")
|
82
|
-
raise
|
83
|
-
|
84
|
-
def create_draft(self, to: str, subject: str, body: str) -> str:
|
85
|
-
"""
|
86
|
-
Creates a draft email message in Gmail using the Gmail API and returns a confirmation status.
|
87
|
-
|
88
|
-
Args:
|
89
|
-
to: The email address of the recipient
|
90
|
-
subject: The subject line of the draft email
|
91
|
-
body: The main content/message of the draft email
|
92
|
-
|
93
|
-
Returns:
|
94
|
-
A string containing either a success message with the draft ID or an error message describing the failure
|
95
|
-
|
96
|
-
Raises:
|
97
|
-
NotAuthorizedError: When the user's Gmail API authorization is invalid or expired
|
98
|
-
KeyError: When required configuration keys are missing
|
99
|
-
Exception: For general API errors, network issues, or other unexpected problems
|
100
|
-
|
101
|
-
Tags:
|
102
|
-
create, email, draft, gmail, api, important
|
103
|
-
"""
|
104
|
-
try:
|
105
|
-
url = f"{self.base_api_url}/drafts"
|
106
|
-
|
107
|
-
raw_message = self._create_message(to, subject, body)
|
108
|
-
|
109
|
-
draft_data = {"message": {"raw": raw_message}}
|
110
|
-
|
111
|
-
logger.info(f"Creating draft email to {to}")
|
112
|
-
|
113
|
-
response = self._post(url, draft_data)
|
114
|
-
|
115
|
-
if response.status_code == 200:
|
116
|
-
draft_id = response.json().get("id", "unknown")
|
117
|
-
return f"Successfully created draft email with ID: {draft_id}"
|
118
|
-
else:
|
119
|
-
logger.error(
|
120
|
-
f"Gmail API Error: {response.status_code} - {response.text}"
|
121
|
-
)
|
122
|
-
return f"Error creating draft: {response.status_code} - {response.text}"
|
123
|
-
except NotAuthorizedError as e:
|
124
|
-
logger.warning(f"Gmail authorization required: {e.message}")
|
125
|
-
return e.message
|
126
|
-
except KeyError as key_error:
|
127
|
-
logger.error(f"Missing key error: {str(key_error)}")
|
128
|
-
return f"Configuration error: Missing required key - {str(key_error)}"
|
129
|
-
except Exception as e:
|
130
|
-
logger.exception(f"Error creating draft: {type(e).__name__} - {str(e)}")
|
131
|
-
return f"Error creating draft: {type(e).__name__} - {str(e)}"
|
132
|
-
|
133
|
-
def send_draft(self, draft_id: str) -> str:
|
134
|
-
"""
|
135
|
-
Sends an existing draft email using the Gmail API and returns a confirmation message.
|
136
|
-
|
137
|
-
Args:
|
138
|
-
draft_id: The unique identifier of the Gmail draft to be sent
|
139
|
-
|
140
|
-
Returns:
|
141
|
-
A string containing either a success message with the sent message ID or an error message detailing the failure reason
|
142
|
-
|
143
|
-
Raises:
|
144
|
-
NotAuthorizedError: When the user's Gmail API authorization is invalid or expired
|
145
|
-
KeyError: When required configuration keys are missing from the API response
|
146
|
-
Exception: For other unexpected errors during the API request or response handling
|
147
|
-
|
148
|
-
Tags:
|
149
|
-
send, email, api, communication, important, draft
|
150
|
-
"""
|
151
|
-
try:
|
152
|
-
url = f"{self.base_api_url}/drafts/send"
|
153
|
-
|
154
|
-
draft_data = {"id": draft_id}
|
155
|
-
|
156
|
-
logger.info(f"Sending draft email with ID: {draft_id}")
|
157
|
-
|
158
|
-
response = self._post(url, draft_data)
|
159
|
-
|
160
|
-
if response.status_code == 200:
|
161
|
-
message_id = response.json().get("id", "unknown")
|
162
|
-
return f"Successfully sent draft email. Message ID: {message_id}"
|
163
|
-
else:
|
164
|
-
logger.error(
|
165
|
-
f"Gmail API Error: {response.status_code} - {response.text}"
|
166
|
-
)
|
167
|
-
return f"Error sending draft: {response.status_code} - {response.text}"
|
168
|
-
except NotAuthorizedError as e:
|
169
|
-
logger.warning(f"Gmail authorization required: {e.message}")
|
170
|
-
return e.message
|
171
|
-
except KeyError as key_error:
|
172
|
-
logger.error(f"Missing key error: {str(key_error)}")
|
173
|
-
return f"Configuration error: Missing required key - {str(key_error)}"
|
174
|
-
except Exception as e:
|
175
|
-
logger.exception(f"Error sending draft: {type(e).__name__} - {str(e)}")
|
176
|
-
return f"Error sending draft: {type(e).__name__} - {str(e)}"
|
177
|
-
|
178
|
-
def get_draft(self, draft_id: str, format: str = "full") -> str:
|
179
|
-
"""
|
180
|
-
Retrieves and formats a specific draft email from Gmail by its ID
|
181
|
-
|
182
|
-
Args:
|
183
|
-
draft_id: String identifier of the draft email to retrieve
|
184
|
-
format: Output format of the draft (options: minimal, full, raw, metadata). Defaults to 'full'
|
185
|
-
|
186
|
-
Returns:
|
187
|
-
A formatted string containing the draft email details (ID, recipient, subject) or an error message if retrieval fails
|
188
|
-
|
189
|
-
Raises:
|
190
|
-
NotAuthorizedError: When the user's Gmail authorization is invalid or expired
|
191
|
-
KeyError: When required configuration keys or response data fields are missing
|
192
|
-
Exception: For any other unexpected errors during draft retrieval
|
193
|
-
|
194
|
-
Tags:
|
195
|
-
retrieve, email, gmail, draft, api, format, important
|
196
|
-
"""
|
197
|
-
try:
|
198
|
-
url = f"{self.base_api_url}/drafts/{draft_id}"
|
199
|
-
|
200
|
-
# Add format parameter as query param
|
201
|
-
params = {"format": format}
|
202
|
-
|
203
|
-
logger.info(f"Retrieving draft with ID: {draft_id}")
|
204
|
-
|
205
|
-
response = self._get(url, params=params)
|
206
|
-
|
207
|
-
if response.status_code == 200:
|
208
|
-
draft_data = response.json()
|
209
|
-
|
210
|
-
# Format the response in a readable way
|
211
|
-
message = draft_data.get("message", {})
|
212
|
-
headers = {}
|
213
|
-
|
214
|
-
# Extract headers if they exist
|
215
|
-
for header in message.get("payload", {}).get("headers", []):
|
216
|
-
name = header.get("name", "")
|
217
|
-
value = header.get("value", "")
|
218
|
-
headers[name] = value
|
219
|
-
|
220
|
-
to = headers.get("To", "Unknown recipient")
|
221
|
-
subject = headers.get("Subject", "No subject")
|
222
|
-
|
223
|
-
result = f"Draft ID: {draft_id}\nTo: {to}\nSubject: {subject}\n"
|
224
|
-
|
225
|
-
return result
|
226
|
-
else:
|
227
|
-
logger.error(
|
228
|
-
f"Gmail API Error: {response.status_code} - {response.text}"
|
229
|
-
)
|
230
|
-
return (
|
231
|
-
f"Error retrieving draft: {response.status_code} - {response.text}"
|
232
|
-
)
|
233
|
-
except NotAuthorizedError as e:
|
234
|
-
logger.warning(f"Gmail authorization required: {e.message}")
|
235
|
-
return e.message
|
236
|
-
except KeyError as key_error:
|
237
|
-
logger.error(f"Missing key error: {str(key_error)}")
|
238
|
-
return f"Configuration error: Missing required key - {str(key_error)}"
|
239
|
-
except Exception as e:
|
240
|
-
logger.exception(f"Error retrieving draft: {type(e).__name__} - {str(e)}")
|
241
|
-
return f"Error retrieving draft: {type(e).__name__} - {str(e)}"
|
242
|
-
|
243
|
-
def list_drafts(
|
244
|
-
self, max_results: int = 20, q: str = None, include_spam_trash: bool = False
|
245
|
-
) -> str:
|
246
|
-
"""
|
247
|
-
Retrieves and formats a list of email drafts from the user's Gmail mailbox with optional filtering and pagination.
|
248
|
-
|
249
|
-
Args:
|
250
|
-
max_results: Maximum number of drafts to return (max 500, default 20)
|
251
|
-
q: Search query string to filter drafts using Gmail search syntax (default None)
|
252
|
-
include_spam_trash: Boolean flag to include drafts from spam and trash folders (default False)
|
253
|
-
|
254
|
-
Returns:
|
255
|
-
A formatted string containing the list of draft IDs and count information, or an error message if the request fails
|
256
|
-
|
257
|
-
Raises:
|
258
|
-
NotAuthorizedError: When the Gmail API authentication is missing or invalid
|
259
|
-
KeyError: When required configuration keys are missing
|
260
|
-
Exception: For general errors during API communication or data processing
|
261
|
-
|
262
|
-
Tags:
|
263
|
-
list, email, drafts, gmail, api, search, query, pagination, important
|
264
|
-
"""
|
265
|
-
try:
|
266
|
-
url = f"{self.base_api_url}/drafts"
|
267
|
-
|
268
|
-
# Build query parameters
|
269
|
-
params = {"maxResults": max_results}
|
270
|
-
|
271
|
-
if q:
|
272
|
-
params["q"] = q
|
273
|
-
|
274
|
-
if include_spam_trash:
|
275
|
-
params["includeSpamTrash"] = "true"
|
276
|
-
|
277
|
-
logger.info(f"Retrieving drafts list with params: {params}")
|
278
|
-
|
279
|
-
response = self._get(url, params=params)
|
280
|
-
|
281
|
-
if response.status_code == 200:
|
282
|
-
data = response.json()
|
283
|
-
drafts = data.get("drafts", [])
|
284
|
-
result_size = data.get("resultSizeEstimate", 0)
|
285
|
-
|
286
|
-
if not drafts:
|
287
|
-
return "No drafts found."
|
288
|
-
|
289
|
-
result = (
|
290
|
-
f"Found {len(drafts)} drafts (estimated total: {result_size}):\n\n"
|
291
|
-
)
|
292
|
-
|
293
|
-
for i, draft in enumerate(drafts, 1):
|
294
|
-
draft_id = draft.get("id", "Unknown ID")
|
295
|
-
# The message field only contains id and threadId at this level
|
296
|
-
result += f"{i}. Draft ID: {draft_id}\n"
|
297
|
-
|
298
|
-
if "nextPageToken" in data:
|
299
|
-
result += "\nMore drafts available. Use page token to see more."
|
300
|
-
|
301
|
-
return result
|
302
|
-
else:
|
303
|
-
logger.error(
|
304
|
-
f"Gmail API Error: {response.status_code} - {response.text}"
|
305
|
-
)
|
306
|
-
return f"Error listing drafts: {response.status_code} - {response.text}"
|
307
|
-
except NotAuthorizedError as e:
|
308
|
-
logger.warning(f"Gmail authorization required: {e.message}")
|
309
|
-
return e.message
|
310
|
-
except KeyError as key_error:
|
311
|
-
logger.error(f"Missing key error: {str(key_error)}")
|
312
|
-
return f"Configuration error: Missing required key - {str(key_error)}"
|
313
|
-
except Exception as e:
|
314
|
-
logger.exception(f"Error listing drafts: {type(e).__name__} - {str(e)}")
|
315
|
-
return f"Error listing drafts: {type(e).__name__} - {str(e)}"
|
316
|
-
|
317
|
-
def get_message(self, message_id: str) -> str:
|
318
|
-
"""
|
319
|
-
Retrieves and formats a specific email message from Gmail API by its ID, including sender, recipient, date, subject, and message preview.
|
320
|
-
|
321
|
-
Args:
|
322
|
-
message_id: The unique identifier of the Gmail message to retrieve
|
323
|
-
|
324
|
-
Returns:
|
325
|
-
A formatted string containing the message details (ID, From, To, Date, Subject, Preview) or an error message if the retrieval fails
|
326
|
-
|
327
|
-
Raises:
|
328
|
-
NotAuthorizedError: When the request lacks proper Gmail API authorization
|
329
|
-
KeyError: When required configuration keys or message fields are missing
|
330
|
-
Exception: For general API communication errors or unexpected issues
|
331
|
-
|
332
|
-
Tags:
|
333
|
-
retrieve, email, format, api, gmail, message, important
|
334
|
-
"""
|
335
|
-
try:
|
336
|
-
url = f"{self.base_api_url}/messages/{message_id}"
|
337
|
-
|
338
|
-
logger.info(f"Retrieving message with ID: {message_id}")
|
339
|
-
|
340
|
-
response = self._get(url)
|
341
|
-
|
342
|
-
if response.status_code == 200:
|
343
|
-
message_data = response.json()
|
344
|
-
|
345
|
-
# Extract basic message metadata
|
346
|
-
headers = {}
|
347
|
-
|
348
|
-
# Extract headers if they exist
|
349
|
-
for header in message_data.get("payload", {}).get("headers", []):
|
350
|
-
name = header.get("name", "")
|
351
|
-
value = header.get("value", "")
|
352
|
-
headers[name] = value
|
353
|
-
|
354
|
-
from_addr = headers.get("From", "Unknown sender")
|
355
|
-
to = headers.get("To", "Unknown recipient")
|
356
|
-
subject = headers.get("Subject", "No subject")
|
357
|
-
date = headers.get("Date", "Unknown date")
|
358
|
-
|
359
|
-
# Format the result
|
360
|
-
result = (
|
361
|
-
f"Message ID: {message_id}\n"
|
362
|
-
f"From: {from_addr}\n"
|
363
|
-
f"To: {to}\n"
|
364
|
-
f"Date: {date}\n"
|
365
|
-
f"Subject: {subject}\n\n"
|
366
|
-
)
|
367
|
-
|
368
|
-
# Include snippet as preview of message content
|
369
|
-
if "snippet" in message_data:
|
370
|
-
result += f"Preview: {message_data['snippet']}\n"
|
371
|
-
|
372
|
-
return result
|
373
|
-
else:
|
374
|
-
logger.error(
|
375
|
-
f"Gmail API Error: {response.status_code} - {response.text}"
|
376
|
-
)
|
377
|
-
return f"Error retrieving message: {response.status_code} - {response.text}"
|
378
|
-
except NotAuthorizedError as e:
|
379
|
-
logger.warning(f"Gmail authorization required: {e.message}")
|
380
|
-
return e.message
|
381
|
-
except KeyError as key_error:
|
382
|
-
logger.error(f"Missing key error: {str(key_error)}")
|
383
|
-
return f"Configuration error: Missing required key - {str(key_error)}"
|
384
|
-
except Exception as e:
|
385
|
-
logger.exception(f"Error retrieving message: {type(e).__name__} - {str(e)}")
|
386
|
-
return f"Error retrieving message: {type(e).__name__} - {str(e)}"
|
387
|
-
|
388
|
-
def list_messages(
|
389
|
-
self, max_results: int = 20, q: str = None, include_spam_trash: bool = False
|
390
|
-
) -> str:
|
391
|
-
"""
|
392
|
-
Retrieves and formats a list of messages from the user's Gmail mailbox with optional filtering and pagination support.
|
393
|
-
|
394
|
-
Args:
|
395
|
-
max_results: Maximum number of messages to return (max 500, default 20)
|
396
|
-
q: Search query string to filter messages using Gmail search syntax
|
397
|
-
include_spam_trash: Boolean flag to include messages from spam and trash folders (default False)
|
398
|
-
|
399
|
-
Returns:
|
400
|
-
A formatted string containing the list of message IDs and thread IDs, or an error message if the operation fails
|
401
|
-
|
402
|
-
Raises:
|
403
|
-
NotAuthorizedError: When the Gmail API authentication is invalid or missing
|
404
|
-
KeyError: When required configuration keys are missing
|
405
|
-
Exception: For general API errors, network issues, or other unexpected problems
|
406
|
-
|
407
|
-
Tags:
|
408
|
-
list, messages, gmail, search, query, pagination, important
|
409
|
-
"""
|
410
|
-
try:
|
411
|
-
url = f"{self.base_api_url}/messages"
|
412
|
-
|
413
|
-
# Build query parameters
|
414
|
-
params = {"maxResults": max_results}
|
415
|
-
|
416
|
-
if q:
|
417
|
-
params["q"] = q
|
418
|
-
|
419
|
-
if include_spam_trash:
|
420
|
-
params["includeSpamTrash"] = "true"
|
421
|
-
|
422
|
-
logger.info(f"Retrieving messages list with params: {params}")
|
423
|
-
|
424
|
-
response = self._get(url, params=params)
|
425
|
-
|
426
|
-
if response.status_code == 200:
|
427
|
-
data = response.json()
|
428
|
-
messages = data.get("messages", [])
|
429
|
-
result_size = data.get("resultSizeEstimate", 0)
|
430
|
-
|
431
|
-
if not messages:
|
432
|
-
return "No messages found matching the criteria."
|
433
|
-
|
434
|
-
result = f"Found {len(messages)} messages (estimated total: {result_size}):\n\n"
|
435
|
-
|
436
|
-
# Just list message IDs without fetching additional details
|
437
|
-
for i, msg in enumerate(messages, 1):
|
438
|
-
message_id = msg.get("id", "Unknown ID")
|
439
|
-
thread_id = msg.get("threadId", "Unknown Thread")
|
440
|
-
result += f"{i}. Message ID: {message_id} (Thread: {thread_id})\n"
|
441
|
-
|
442
|
-
# Add a note about how to get message details
|
443
|
-
result += "\nUse get_message(message_id) to view the contents of a specific message."
|
444
|
-
|
445
|
-
if "nextPageToken" in data:
|
446
|
-
result += "\nMore messages available. Use page token to see more."
|
447
|
-
|
448
|
-
return result
|
449
|
-
else:
|
450
|
-
logger.error(
|
451
|
-
f"Gmail API Error: {response.status_code} - {response.text}"
|
452
|
-
)
|
453
|
-
return (
|
454
|
-
f"Error listing messages: {response.status_code} - {response.text}"
|
455
|
-
)
|
456
|
-
except NotAuthorizedError as e:
|
457
|
-
logger.warning(f"Gmail authorization required: {e.message}")
|
458
|
-
return e.message
|
459
|
-
except KeyError as key_error:
|
460
|
-
logger.error(f"Missing key error: {str(key_error)}")
|
461
|
-
return f"Configuration error: Missing required key - {str(key_error)}"
|
462
|
-
except Exception as e:
|
463
|
-
logger.exception(f"Error listing messages: {type(e).__name__} - {str(e)}")
|
464
|
-
return f"Error listing messages: {type(e).__name__} - {str(e)}"
|
465
|
-
|
466
|
-
def list_labels(self) -> str:
|
467
|
-
"""
|
468
|
-
Retrieves and formats a list of all labels (both system and user-created) from the user's Gmail account, organizing them by type and sorting them alphabetically.
|
469
|
-
|
470
|
-
Args:
|
471
|
-
None: This method takes no arguments
|
472
|
-
|
473
|
-
Returns:
|
474
|
-
A formatted string containing a list of Gmail labels categorized by type (system and user), with their IDs, or an error message if the operation fails.
|
475
|
-
|
476
|
-
Raises:
|
477
|
-
NotAuthorizedError: Raised when the user's Gmail authorization is invalid or missing
|
478
|
-
Exception: Raised when any other unexpected error occurs during the API request or data processing
|
479
|
-
|
480
|
-
Tags:
|
481
|
-
list, gmail, labels, fetch, organize, important, management
|
482
|
-
"""
|
483
|
-
try:
|
484
|
-
url = f"{self.base_api_url}/labels"
|
485
|
-
|
486
|
-
logger.info("Retrieving Gmail labels")
|
487
|
-
|
488
|
-
response = self._get(url)
|
489
|
-
|
490
|
-
if response.status_code == 200:
|
491
|
-
data = response.json()
|
492
|
-
labels = data.get("labels", [])
|
493
|
-
|
494
|
-
if not labels:
|
495
|
-
return "No labels found in your Gmail account."
|
496
|
-
|
497
|
-
# Sort labels by type (system first, then user) and then by name
|
498
|
-
system_labels = []
|
499
|
-
user_labels = []
|
500
|
-
|
501
|
-
for label in labels:
|
502
|
-
label_id = label.get("id", "Unknown ID")
|
503
|
-
label_name = label.get("name", "Unknown Name")
|
504
|
-
label_type = label.get("type", "Unknown Type")
|
505
|
-
|
506
|
-
if label_type == "system":
|
507
|
-
system_labels.append((label_id, label_name))
|
508
|
-
else:
|
509
|
-
user_labels.append((label_id, label_name))
|
510
|
-
|
511
|
-
# Sort by name within each category
|
512
|
-
system_labels.sort(key=lambda x: x[1])
|
513
|
-
user_labels.sort(key=lambda x: x[1])
|
514
|
-
|
515
|
-
result = f"Found {len(labels)} Gmail labels:\n\n"
|
516
|
-
|
517
|
-
# System labels
|
518
|
-
if system_labels:
|
519
|
-
result += "System Labels:\n"
|
520
|
-
for label_id, label_name in system_labels:
|
521
|
-
result += f"- {label_name} (ID: {label_id})\n"
|
522
|
-
result += "\n"
|
523
|
-
|
524
|
-
# User labels
|
525
|
-
if user_labels:
|
526
|
-
result += "User Labels:\n"
|
527
|
-
for label_id, label_name in user_labels:
|
528
|
-
result += f"- {label_name} (ID: {label_id})\n"
|
529
|
-
|
530
|
-
# Add note about using labels
|
531
|
-
result += "\nThese label IDs can be used with list_messages to filter emails by label."
|
532
|
-
|
533
|
-
return result
|
534
|
-
else:
|
535
|
-
logger.error(
|
536
|
-
f"Gmail API Error: {response.status_code} - {response.text}"
|
537
|
-
)
|
538
|
-
return f"Error listing labels: {response.status_code} - {response.text}"
|
539
|
-
except NotAuthorizedError as e:
|
540
|
-
logger.warning(f"Gmail authorization required: {e.message}")
|
541
|
-
return e.message
|
542
|
-
except Exception as e:
|
543
|
-
logger.exception(f"Error listing labels: {type(e).__name__} - {str(e)}")
|
544
|
-
return f"Error listing labels: {type(e).__name__} - {str(e)}"
|
545
|
-
|
546
|
-
def create_label(self, name: str) -> str:
|
547
|
-
"""
|
548
|
-
Creates a new Gmail label with specified visibility settings and returns creation status details.
|
549
|
-
|
550
|
-
Args:
|
551
|
-
name: The display name of the label to create
|
552
|
-
|
553
|
-
Returns:
|
554
|
-
A formatted string containing the creation status, including the new label's name and ID if successful, or an error message if the creation fails
|
555
|
-
|
556
|
-
Raises:
|
557
|
-
NotAuthorizedError: Raised when the request lacks proper Gmail API authorization
|
558
|
-
Exception: Raised for any other unexpected errors during label creation
|
559
|
-
|
560
|
-
Tags:
|
561
|
-
create, label, gmail, management, important
|
562
|
-
"""
|
563
|
-
try:
|
564
|
-
url = f"{self.base_api_url}/labels"
|
565
|
-
|
566
|
-
# Create the label data with just the essential fields
|
567
|
-
label_data = {
|
568
|
-
"name": name,
|
569
|
-
"labelListVisibility": "labelShow", # Show in label list
|
570
|
-
"messageListVisibility": "show", # Show in message list
|
571
|
-
}
|
572
|
-
|
573
|
-
logger.info(f"Creating new Gmail label: {name}")
|
574
|
-
|
575
|
-
response = self._post(url, label_data)
|
576
|
-
|
577
|
-
if response.status_code in [200, 201]:
|
578
|
-
new_label = response.json()
|
579
|
-
label_id = new_label.get("id", "Unknown")
|
580
|
-
label_name = new_label.get("name", name)
|
581
|
-
|
582
|
-
result = "Successfully created new label:\n"
|
583
|
-
result += f"- Name: {label_name}\n"
|
584
|
-
result += f"- ID: {label_id}\n"
|
585
|
-
|
586
|
-
return result
|
587
|
-
else:
|
588
|
-
logger.error(
|
589
|
-
f"Gmail API Error: {response.status_code} - {response.text}"
|
590
|
-
)
|
591
|
-
return f"Error creating label: {response.status_code} - {response.text}"
|
592
|
-
except NotAuthorizedError as e:
|
593
|
-
logger.warning(f"Gmail authorization required: {e.message}")
|
594
|
-
return e.message
|
595
|
-
except Exception as e:
|
596
|
-
logger.exception(f"Error creating label: {type(e).__name__} - {str(e)}")
|
597
|
-
return f"Error creating label: {type(e).__name__} - {str(e)}"
|
598
|
-
|
599
|
-
def get_profile(self) -> str:
|
600
|
-
"""
|
601
|
-
Retrieves and formats the user's Gmail profile information including email address, message count, thread count, and history ID.
|
602
|
-
|
603
|
-
Args:
|
604
|
-
None: This method takes no arguments besides self
|
605
|
-
|
606
|
-
Returns:
|
607
|
-
A formatted string containing the user's Gmail profile information or an error message if the request fails
|
608
|
-
|
609
|
-
Raises:
|
610
|
-
NotAuthorizedError: Raised when the user's credentials are invalid or authorization is required
|
611
|
-
Exception: Raised for any other unexpected errors during the API request or data processing
|
612
|
-
|
613
|
-
Tags:
|
614
|
-
fetch, profile, gmail, user-info, api-request, important
|
615
|
-
"""
|
616
|
-
try:
|
617
|
-
url = f"{self.base_api_url}/profile"
|
618
|
-
|
619
|
-
logger.info("Retrieving Gmail user profile")
|
620
|
-
|
621
|
-
response = self._get(url)
|
622
|
-
|
623
|
-
if response.status_code == 200:
|
624
|
-
profile_data = response.json()
|
625
|
-
|
626
|
-
# Extract profile information
|
627
|
-
email_address = profile_data.get("emailAddress", "Unknown")
|
628
|
-
messages_total = profile_data.get("messagesTotal", 0)
|
629
|
-
threads_total = profile_data.get("threadsTotal", 0)
|
630
|
-
history_id = profile_data.get("historyId", "Unknown")
|
631
|
-
|
632
|
-
# Format the response
|
633
|
-
result = "Gmail Profile Information:\n"
|
634
|
-
result += f"- Email Address: {email_address}\n"
|
635
|
-
result += f"- Total Messages: {messages_total:,}\n"
|
636
|
-
result += f"- Total Threads: {threads_total:,}\n"
|
637
|
-
result += f"- History ID: {history_id}\n"
|
638
|
-
|
639
|
-
return result
|
640
|
-
else:
|
641
|
-
logger.error(
|
642
|
-
f"Gmail API Error: {response.status_code} - {response.text}"
|
643
|
-
)
|
644
|
-
return f"Error retrieving profile: {response.status_code} - {response.text}"
|
645
|
-
except NotAuthorizedError as e:
|
646
|
-
logger.warning(f"Gmail authorization required: {e.message}")
|
647
|
-
return e.message
|
648
|
-
except Exception as e:
|
649
|
-
logger.exception(f"Error retrieving profile: {type(e).__name__} - {str(e)}")
|
650
|
-
return f"Error retrieving profile: {type(e).__name__} - {str(e)}"
|
651
|
-
|
652
|
-
def list_tools(self):
|
653
|
-
return [
|
654
|
-
self.send_email,
|
655
|
-
self.create_draft,
|
656
|
-
self.send_draft,
|
657
|
-
self.get_draft,
|
658
|
-
self.list_drafts,
|
659
|
-
self.get_message,
|
660
|
-
self.list_messages,
|
661
|
-
self.list_labels,
|
662
|
-
self.create_label,
|
663
|
-
self.get_profile,
|
664
|
-
]
|