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