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.
Files changed (109) hide show
  1. universal_mcp/applications/__init__.py +51 -7
  2. universal_mcp/cli.py +109 -17
  3. universal_mcp/integrations/__init__.py +1 -1
  4. universal_mcp/integrations/integration.py +79 -0
  5. universal_mcp/servers/README.md +79 -0
  6. universal_mcp/servers/server.py +17 -29
  7. universal_mcp/stores/README.md +74 -0
  8. universal_mcp/stores/store.py +0 -2
  9. universal_mcp/templates/README.md.j2 +93 -0
  10. universal_mcp/templates/api_client.py.j2 +27 -0
  11. universal_mcp/tools/README.md +86 -0
  12. universal_mcp/tools/tools.py +1 -1
  13. universal_mcp/utils/agentr.py +90 -0
  14. universal_mcp/utils/api_generator.py +166 -208
  15. universal_mcp/utils/openapi.py +221 -321
  16. universal_mcp/utils/singleton.py +23 -0
  17. {universal_mcp-0.1.12.dist-info → universal_mcp-0.1.13rc2.dist-info}/METADATA +16 -41
  18. universal_mcp-0.1.13rc2.dist-info/RECORD +38 -0
  19. universal_mcp/applications/ahrefs/README.md +0 -76
  20. universal_mcp/applications/ahrefs/__init__.py +0 -0
  21. universal_mcp/applications/ahrefs/app.py +0 -2291
  22. universal_mcp/applications/cal_com_v2/README.md +0 -175
  23. universal_mcp/applications/cal_com_v2/__init__.py +0 -0
  24. universal_mcp/applications/cal_com_v2/app.py +0 -5390
  25. universal_mcp/applications/calendly/README.md +0 -78
  26. universal_mcp/applications/calendly/__init__.py +0 -0
  27. universal_mcp/applications/calendly/app.py +0 -1195
  28. universal_mcp/applications/clickup/README.md +0 -160
  29. universal_mcp/applications/clickup/__init__.py +0 -0
  30. universal_mcp/applications/clickup/app.py +0 -5009
  31. universal_mcp/applications/coda/README.md +0 -133
  32. universal_mcp/applications/coda/__init__.py +0 -0
  33. universal_mcp/applications/coda/app.py +0 -3671
  34. universal_mcp/applications/e2b/README.md +0 -37
  35. universal_mcp/applications/e2b/app.py +0 -65
  36. universal_mcp/applications/elevenlabs/README.md +0 -84
  37. universal_mcp/applications/elevenlabs/__init__.py +0 -0
  38. universal_mcp/applications/elevenlabs/app.py +0 -1402
  39. universal_mcp/applications/falai/README.md +0 -42
  40. universal_mcp/applications/falai/__init__.py +0 -0
  41. universal_mcp/applications/falai/app.py +0 -332
  42. universal_mcp/applications/figma/README.md +0 -74
  43. universal_mcp/applications/figma/__init__.py +0 -0
  44. universal_mcp/applications/figma/app.py +0 -1261
  45. universal_mcp/applications/firecrawl/README.md +0 -45
  46. universal_mcp/applications/firecrawl/app.py +0 -268
  47. universal_mcp/applications/github/README.md +0 -47
  48. universal_mcp/applications/github/app.py +0 -429
  49. universal_mcp/applications/gong/README.md +0 -88
  50. universal_mcp/applications/gong/__init__.py +0 -0
  51. universal_mcp/applications/gong/app.py +0 -2297
  52. universal_mcp/applications/google_calendar/app.py +0 -442
  53. universal_mcp/applications/google_docs/README.md +0 -40
  54. universal_mcp/applications/google_docs/app.py +0 -88
  55. universal_mcp/applications/google_drive/README.md +0 -44
  56. universal_mcp/applications/google_drive/app.py +0 -286
  57. universal_mcp/applications/google_mail/README.md +0 -47
  58. universal_mcp/applications/google_mail/app.py +0 -664
  59. universal_mcp/applications/google_sheet/README.md +0 -42
  60. universal_mcp/applications/google_sheet/app.py +0 -150
  61. universal_mcp/applications/hashnode/app.py +0 -81
  62. universal_mcp/applications/hashnode/prompt.md +0 -23
  63. universal_mcp/applications/heygen/README.md +0 -69
  64. universal_mcp/applications/heygen/__init__.py +0 -0
  65. universal_mcp/applications/heygen/app.py +0 -956
  66. universal_mcp/applications/mailchimp/README.md +0 -306
  67. universal_mcp/applications/mailchimp/__init__.py +0 -0
  68. universal_mcp/applications/mailchimp/app.py +0 -10937
  69. universal_mcp/applications/markitdown/app.py +0 -44
  70. universal_mcp/applications/notion/README.md +0 -55
  71. universal_mcp/applications/notion/__init__.py +0 -0
  72. universal_mcp/applications/notion/app.py +0 -527
  73. universal_mcp/applications/perplexity/README.md +0 -37
  74. universal_mcp/applications/perplexity/app.py +0 -65
  75. universal_mcp/applications/reddit/README.md +0 -45
  76. universal_mcp/applications/reddit/app.py +0 -379
  77. universal_mcp/applications/replicate/README.md +0 -65
  78. universal_mcp/applications/replicate/__init__.py +0 -0
  79. universal_mcp/applications/replicate/app.py +0 -980
  80. universal_mcp/applications/resend/README.md +0 -38
  81. universal_mcp/applications/resend/app.py +0 -37
  82. universal_mcp/applications/retell_ai/README.md +0 -46
  83. universal_mcp/applications/retell_ai/__init__.py +0 -0
  84. universal_mcp/applications/retell_ai/app.py +0 -333
  85. universal_mcp/applications/rocketlane/README.md +0 -42
  86. universal_mcp/applications/rocketlane/__init__.py +0 -0
  87. universal_mcp/applications/rocketlane/app.py +0 -194
  88. universal_mcp/applications/serpapi/README.md +0 -37
  89. universal_mcp/applications/serpapi/app.py +0 -73
  90. universal_mcp/applications/spotify/README.md +0 -116
  91. universal_mcp/applications/spotify/__init__.py +0 -0
  92. universal_mcp/applications/spotify/app.py +0 -2526
  93. universal_mcp/applications/supabase/README.md +0 -112
  94. universal_mcp/applications/supabase/__init__.py +0 -0
  95. universal_mcp/applications/supabase/app.py +0 -2970
  96. universal_mcp/applications/tavily/README.md +0 -38
  97. universal_mcp/applications/tavily/app.py +0 -51
  98. universal_mcp/applications/wrike/README.md +0 -71
  99. universal_mcp/applications/wrike/__init__.py +0 -0
  100. universal_mcp/applications/wrike/app.py +0 -1372
  101. universal_mcp/applications/youtube/README.md +0 -82
  102. universal_mcp/applications/youtube/__init__.py +0 -0
  103. universal_mcp/applications/youtube/app.py +0 -1428
  104. universal_mcp/applications/zenquotes/README.md +0 -37
  105. universal_mcp/applications/zenquotes/app.py +0 -31
  106. universal_mcp/integrations/agentr.py +0 -112
  107. universal_mcp-0.1.12.dist-info/RECORD +0 -119
  108. {universal_mcp-0.1.12.dist-info → universal_mcp-0.1.13rc2.dist-info}/WHEEL +0 -0
  109. {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
- ]