universal-mcp-applications 0.1.33__py3-none-any.whl → 0.1.39rc8__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.

Potentially problematic release.


This version of universal-mcp-applications might be problematic. Click here for more details.

Files changed (113) hide show
  1. universal_mcp/applications/ahrefs/app.py +92 -238
  2. universal_mcp/applications/airtable/app.py +23 -122
  3. universal_mcp/applications/apollo/app.py +122 -475
  4. universal_mcp/applications/asana/app.py +605 -1755
  5. universal_mcp/applications/aws_s3/app.py +36 -103
  6. universal_mcp/applications/bill/app.py +644 -2055
  7. universal_mcp/applications/box/app.py +1246 -4159
  8. universal_mcp/applications/braze/app.py +410 -1476
  9. universal_mcp/applications/browser_use/README.md +15 -1
  10. universal_mcp/applications/browser_use/__init__.py +1 -0
  11. universal_mcp/applications/browser_use/app.py +86 -24
  12. universal_mcp/applications/cal_com_v2/app.py +207 -625
  13. universal_mcp/applications/calendly/app.py +103 -242
  14. universal_mcp/applications/canva/app.py +75 -140
  15. universal_mcp/applications/clickup/app.py +331 -798
  16. universal_mcp/applications/coda/app.py +240 -520
  17. universal_mcp/applications/confluence/app.py +497 -1285
  18. universal_mcp/applications/contentful/app.py +36 -151
  19. universal_mcp/applications/crustdata/app.py +42 -121
  20. universal_mcp/applications/dialpad/app.py +451 -924
  21. universal_mcp/applications/digitalocean/app.py +2071 -6082
  22. universal_mcp/applications/domain_checker/app.py +3 -54
  23. universal_mcp/applications/e2b/app.py +14 -64
  24. universal_mcp/applications/elevenlabs/app.py +9 -47
  25. universal_mcp/applications/exa/README.md +8 -4
  26. universal_mcp/applications/exa/app.py +408 -186
  27. universal_mcp/applications/falai/app.py +24 -101
  28. universal_mcp/applications/figma/app.py +91 -175
  29. universal_mcp/applications/file_system/app.py +2 -13
  30. universal_mcp/applications/firecrawl/app.py +186 -163
  31. universal_mcp/applications/fireflies/app.py +59 -281
  32. universal_mcp/applications/fpl/app.py +92 -529
  33. universal_mcp/applications/fpl/utils/fixtures.py +15 -49
  34. universal_mcp/applications/fpl/utils/helper.py +25 -89
  35. universal_mcp/applications/fpl/utils/league_utils.py +20 -64
  36. universal_mcp/applications/ghost_content/app.py +66 -175
  37. universal_mcp/applications/github/app.py +28 -65
  38. universal_mcp/applications/gong/app.py +140 -300
  39. universal_mcp/applications/google_calendar/app.py +26 -78
  40. universal_mcp/applications/google_docs/app.py +98 -202
  41. universal_mcp/applications/google_drive/app.py +194 -793
  42. universal_mcp/applications/google_gemini/app.py +27 -62
  43. universal_mcp/applications/google_mail/README.md +1 -0
  44. universal_mcp/applications/google_mail/app.py +93 -214
  45. universal_mcp/applications/google_searchconsole/app.py +25 -58
  46. universal_mcp/applications/google_sheet/app.py +171 -624
  47. universal_mcp/applications/google_sheet/helper.py +26 -53
  48. universal_mcp/applications/hashnode/app.py +57 -269
  49. universal_mcp/applications/heygen/app.py +77 -155
  50. universal_mcp/applications/http_tools/app.py +10 -32
  51. universal_mcp/applications/hubspot/README.md +1 -1
  52. universal_mcp/applications/hubspot/app.py +7508 -99
  53. universal_mcp/applications/jira/app.py +2419 -8334
  54. universal_mcp/applications/klaviyo/app.py +737 -1619
  55. universal_mcp/applications/linkedin/README.md +5 -0
  56. universal_mcp/applications/linkedin/app.py +332 -227
  57. universal_mcp/applications/mailchimp/app.py +696 -1851
  58. universal_mcp/applications/markitdown/app.py +8 -20
  59. universal_mcp/applications/miro/app.py +333 -815
  60. universal_mcp/applications/ms_teams/app.py +85 -207
  61. universal_mcp/applications/neon/app.py +144 -250
  62. universal_mcp/applications/notion/app.py +36 -51
  63. universal_mcp/applications/onedrive/app.py +26 -48
  64. universal_mcp/applications/openai/app.py +42 -165
  65. universal_mcp/applications/outlook/README.md +22 -9
  66. universal_mcp/applications/outlook/app.py +403 -141
  67. universal_mcp/applications/perplexity/README.md +2 -1
  68. universal_mcp/applications/perplexity/app.py +162 -20
  69. universal_mcp/applications/pipedrive/app.py +1021 -3331
  70. universal_mcp/applications/posthog/app.py +272 -541
  71. universal_mcp/applications/reddit/app.py +61 -160
  72. universal_mcp/applications/resend/app.py +41 -107
  73. universal_mcp/applications/retell/app.py +23 -50
  74. universal_mcp/applications/rocketlane/app.py +250 -963
  75. universal_mcp/applications/scraper/app.py +67 -125
  76. universal_mcp/applications/semanticscholar/app.py +36 -78
  77. universal_mcp/applications/semrush/app.py +43 -77
  78. universal_mcp/applications/sendgrid/app.py +826 -1576
  79. universal_mcp/applications/sentry/app.py +444 -1079
  80. universal_mcp/applications/serpapi/app.py +40 -143
  81. universal_mcp/applications/sharepoint/app.py +27 -49
  82. universal_mcp/applications/shopify/app.py +1743 -4479
  83. universal_mcp/applications/shortcut/app.py +272 -534
  84. universal_mcp/applications/slack/app.py +41 -123
  85. universal_mcp/applications/spotify/app.py +206 -405
  86. universal_mcp/applications/supabase/app.py +174 -283
  87. universal_mcp/applications/tavily/app.py +2 -2
  88. universal_mcp/applications/trello/app.py +853 -2816
  89. universal_mcp/applications/twilio/app.py +14 -50
  90. universal_mcp/applications/twitter/api_segments/compliance_api.py +4 -14
  91. universal_mcp/applications/twitter/api_segments/dm_conversations_api.py +6 -18
  92. universal_mcp/applications/twitter/api_segments/likes_api.py +1 -3
  93. universal_mcp/applications/twitter/api_segments/lists_api.py +5 -15
  94. universal_mcp/applications/twitter/api_segments/trends_api.py +1 -3
  95. universal_mcp/applications/twitter/api_segments/tweets_api.py +9 -31
  96. universal_mcp/applications/twitter/api_segments/usage_api.py +1 -5
  97. universal_mcp/applications/twitter/api_segments/users_api.py +14 -42
  98. universal_mcp/applications/whatsapp/app.py +35 -186
  99. universal_mcp/applications/whatsapp/audio.py +2 -6
  100. universal_mcp/applications/whatsapp/whatsapp.py +17 -51
  101. universal_mcp/applications/whatsapp_business/app.py +86 -299
  102. universal_mcp/applications/wrike/app.py +80 -153
  103. universal_mcp/applications/yahoo_finance/app.py +19 -65
  104. universal_mcp/applications/youtube/app.py +120 -306
  105. universal_mcp/applications/zenquotes/app.py +3 -3
  106. {universal_mcp_applications-0.1.33.dist-info → universal_mcp_applications-0.1.39rc8.dist-info}/METADATA +4 -2
  107. {universal_mcp_applications-0.1.33.dist-info → universal_mcp_applications-0.1.39rc8.dist-info}/RECORD +109 -113
  108. {universal_mcp_applications-0.1.33.dist-info → universal_mcp_applications-0.1.39rc8.dist-info}/WHEEL +1 -1
  109. universal_mcp/applications/hubspot/api_segments/__init__.py +0 -0
  110. universal_mcp/applications/hubspot/api_segments/api_segment_base.py +0 -54
  111. universal_mcp/applications/hubspot/api_segments/crm_api.py +0 -7337
  112. universal_mcp/applications/hubspot/api_segments/marketing_api.py +0 -1467
  113. {universal_mcp_applications-0.1.33.dist-info → universal_mcp_applications-0.1.39rc8.dist-info}/licenses/LICENSE +0 -0
@@ -1,8 +1,7 @@
1
1
  import base64
2
- import concurrent.futures
2
+ import asyncio
3
3
  from email.message import EmailMessage
4
4
  from typing import Any
5
-
6
5
  from loguru import logger
7
6
  from universal_mcp.applications.application import APIApplication
8
7
  from universal_mcp.integrations import Integration
@@ -14,14 +13,7 @@ class GoogleMailApp(APIApplication):
14
13
  self.base_api_url = "https://gmail.googleapis.com/gmail/v1/users/me"
15
14
  self.base_url = "https://gmail.googleapis.com"
16
15
 
17
- def send_email(
18
- self,
19
- to: str,
20
- subject: str,
21
- body: str,
22
- body_type: str = "plain",
23
- thread_id: str | None = None,
24
- ) -> dict[str, Any]:
16
+ async def send_email(self, to: str, subject: str, body: str, body_type: str = "plain", thread_id: str | None = None) -> dict[str, Any]:
25
17
  """
26
18
  Composes and immediately sends an email message via the Gmail API. It can function as a reply within an existing conversation if a `thread_id` is provided. This action is distinct from `send_draft`, which sends a previously saved draft message, or `create_draft`, which only saves an email.
27
19
 
@@ -43,17 +35,12 @@ class GoogleMailApp(APIApplication):
43
35
  Tags:
44
36
  send, email, api, communication, important, thread, reply, openWorldHint
45
37
  """
46
-
47
38
  url = f"{self.base_api_url}/messages/send"
48
39
  raw_message = self._create_message(to, subject, body, body_type)
49
40
  email_data = {"raw": raw_message}
50
-
51
- # Add threadId to make it a proper reply if thread_id is provided
52
41
  if thread_id:
53
42
  email_data["threadId"] = thread_id
54
-
55
- response = self._post(url, email_data)
56
-
43
+ response = await self._apost(url, email_data)
57
44
  return self._handle_response(response)
58
45
 
59
46
  def _create_message(self, to, subject, body, body_type="plain"):
@@ -69,13 +56,8 @@ class GoogleMailApp(APIApplication):
69
56
  logger.error(f"Error creating message: {str(e)}")
70
57
  raise
71
58
 
72
- def create_draft(
73
- self,
74
- to: str,
75
- subject: str,
76
- body: str,
77
- body_type: str = "plain",
78
- thread_id: str | None = None,
59
+ async def create_draft(
60
+ self, to: str, subject: str, body: str, body_type: str = "plain", thread_id: str | None = None
79
61
  ) -> dict[str, Any]:
80
62
  """
81
63
  Saves a new email draft in Gmail with a specified recipient, subject, and body. An optional thread ID can create the draft as a reply within an existing conversation, distinguishing it from `send_email`, which sends immediately.
@@ -98,24 +80,16 @@ class GoogleMailApp(APIApplication):
98
80
  Tags:
99
81
  create, email, draft, gmail, api, important, thread, reply, html
100
82
  """
101
-
102
83
  url = f"{self.base_api_url}/drafts"
103
-
104
84
  raw_message = self._create_message(to, subject, body, body_type)
105
-
106
85
  draft_data = {"message": {"raw": raw_message}}
107
-
108
- # Add threadId to make it a proper reply if thread_id is provided
109
86
  if thread_id:
110
87
  draft_data["message"]["threadId"] = thread_id
111
-
112
88
  logger.info(f"Creating draft email to {to}")
113
-
114
- response = self._post(url, draft_data)
115
-
89
+ response = await self._apost(url, draft_data)
116
90
  return self._handle_response(response)
117
91
 
118
- def send_draft(self, draft_id: str) -> dict[str, Any]:
92
+ async def send_draft(self, draft_id: str) -> dict[str, Any]:
119
93
  """
120
94
  Sends a pre-existing Gmail draft identified by its unique ID. It posts to the `/drafts/send` endpoint, converting a saved draft into a sent message. This function acts on drafts from `create_draft` and differs from `send_email`, which composes and sends an email in one step.
121
95
 
@@ -133,18 +107,13 @@ class GoogleMailApp(APIApplication):
133
107
  Tags:
134
108
  send, email, api, communication, important, draft
135
109
  """
136
-
137
110
  url = f"{self.base_api_url}/drafts/send"
138
-
139
111
  draft_data = {"id": draft_id}
140
-
141
112
  logger.info(f"Sending draft email with ID: {draft_id}")
142
-
143
- response = self._post(url, draft_data)
144
-
113
+ response = await self._apost(url, draft_data)
145
114
  return self._handle_response(response)
146
115
 
147
- def get_draft(self, draft_id: str, format: str = "full") -> dict[str, Any]:
116
+ async def get_draft(self, draft_id: str, format: str = "full") -> dict[str, Any]:
148
117
  """
149
118
  Retrieves a specific Gmail draft by its unique ID. This function allows specifying the output format (e.g., full, raw) to control the response detail. Unlike `list_drafts`, it fetches a single, known draft rather than a collection of multiple drafts.
150
119
 
@@ -163,24 +132,13 @@ class GoogleMailApp(APIApplication):
163
132
  Tags:
164
133
  retrieve, email, gmail, draft, api, format, important
165
134
  """
166
-
167
135
  url = f"{self.base_api_url}/drafts/{draft_id}"
168
-
169
- # Add format parameter as query param
170
136
  params = {"format": format}
171
-
172
137
  logger.info(f"Retrieving draft with ID: {draft_id}")
173
-
174
- response = self._get(url, params=params)
175
-
138
+ response = await self._aget(url, params=params)
176
139
  return self._handle_response(response)
177
140
 
178
- def list_drafts(
179
- self,
180
- max_results: int = 20,
181
- q: str | None = None,
182
- include_spam_trash: bool = False,
183
- ) -> dict[str, Any]:
141
+ async def list_drafts(self, max_results: int = 20, q: str | None = None, include_spam_trash: bool = False) -> dict[str, Any]:
184
142
  """
185
143
  Fetches a list of email drafts, allowing filtering by a search query and limiting results. It can optionally include drafts from spam and trash, returning a collection of draft objects. This is distinct from `get_draft`, which retrieves only a single, specific draft by its ID.
186
144
 
@@ -200,25 +158,17 @@ class GoogleMailApp(APIApplication):
200
158
  Tags:
201
159
  list, email, drafts, gmail, api, search, query, pagination, important
202
160
  """
203
-
204
161
  url = f"{self.base_api_url}/drafts"
205
-
206
- # Build query parameters
207
162
  params: dict[str, Any] = {"maxResults": max_results}
208
-
209
163
  if q:
210
164
  params["q"] = q
211
-
212
165
  if include_spam_trash:
213
166
  params["includeSpamTrash"] = "true"
214
-
215
167
  logger.info(f"Retrieving drafts list with params: {params}")
216
-
217
- response = self._get(url, params=params)
218
-
168
+ response = await self._aget(url, params=params)
219
169
  return self._handle_response(response)
220
170
 
221
- def get_message_details(self, message_id: str) -> dict[str, Any]:
171
+ async def get_message_details(self, message_id: str) -> dict[str, Any]:
222
172
  """
223
173
  Retrieves a specific email from Gmail by its ID. It parses the API response to extract and format key details—including sender, subject, body, and attachments—into a structured dictionary. This function provides detailed data for a single message, distinguishing it from `list_messages` which fetches multiple messages.
224
174
 
@@ -226,40 +176,42 @@ class GoogleMailApp(APIApplication):
226
176
  message_id: The unique identifier of the Gmail message to retrieve
227
177
 
228
178
  Returns:
229
- A dictionary containing the cleaned message details (serializable as JSON)
230
-
179
+ A dictionary containing the message details with the following keys:
180
+ - message_id (str): The unique ID of the email.
181
+ - from (str): The sender's email address or "Unknown sender" if unavailable.
182
+ - to (str): The recipient(s) email address(es) or "Unknown recipient" if unavailable.
183
+ - date (str): The date the email was sent or "Unknown date" if unavailable.
184
+ - subject (str): The subject of the email or "No subject" if unavailable.
185
+ - body (str): The plain text content of the email or a preview/snippet.
186
+ - thread_id (str | None): The ID of the email thread this message belongs to.
187
+ - attachments (list[dict]): A list of attachments, each represented as a dictionary
188
+ with metadata (e.g., filename, MIME type, attachment ID).
189
+
231
190
  Tags:
232
191
  retrieve, email, format, api, gmail, message, important, body, content, attachments
233
192
  """
234
193
  url = f"{self.base_api_url}/messages/{message_id}"
235
- response = self._get(url)
194
+ response = await self._aget(url)
236
195
  raw_data = self._handle_response(response)
237
-
238
- # Extract headers
239
196
  headers = {}
240
197
  for header in raw_data.get("payload", {}).get("headers", []):
241
198
  name = header.get("name", "")
242
199
  value = header.get("value", "")
243
200
  headers[name] = value
244
-
245
- # Extract body content
246
201
  body_content = self._extract_email_body(raw_data.get("payload", {}))
247
202
  if not body_content:
248
203
  if "snippet" in raw_data:
249
204
  body_content = f"Preview: {raw_data['snippet']}"
250
205
  else:
251
206
  body_content = "No content available"
252
-
253
- # Extract attachments
254
207
  attachments = self._extract_attachments(raw_data.get("payload", {}))
255
-
256
208
  return {
257
209
  "message_id": message_id,
258
- "from_addr": headers.get("From", "Unknown sender"),
210
+ "from": headers.get("From", "Unknown sender"),
259
211
  "to": headers.get("To", "Unknown recipient"),
260
212
  "date": headers.get("Date", "Unknown date"),
261
213
  "subject": headers.get("Subject", "No subject"),
262
- "body_content": body_content,
214
+ "body": body_content,
263
215
  "thread_id": raw_data.get("threadId"),
264
216
  "attachments": attachments,
265
217
  }
@@ -275,45 +227,30 @@ class GoogleMailApp(APIApplication):
275
227
  str: The email body content (plain text preferred, HTML as fallback)
276
228
  """
277
229
  try:
278
- # Handle single part message
279
230
  if payload.get("body") and payload.get("body", {}).get("data"):
280
231
  return self._decode_base64(payload["body"]["data"])
281
-
282
- # Handle multipart message
283
232
  parts = payload.get("parts", [])
284
233
  if not parts:
285
234
  return ""
286
-
287
235
  plain_text_body = ""
288
236
  html_body = ""
289
-
290
237
  for part in parts:
291
238
  mime_type = part.get("mimeType", "")
292
-
293
- # Extract plain text
294
239
  if mime_type == "text/plain":
295
240
  if part.get("body") and part.get("body", {}).get("data"):
296
241
  plain_text_body = self._decode_base64(part["body"]["data"])
297
-
298
- # Extract HTML content
299
242
  elif mime_type == "text/html":
300
243
  if part.get("body") and part.get("body", {}).get("data"):
301
244
  html_body = self._decode_base64(part["body"]["data"])
302
-
303
- # Handle nested multipart (recursive)
304
245
  elif mime_type.startswith("multipart/") and part.get("parts"):
305
246
  nested_body = self._extract_email_body(part)
306
- if nested_body and not plain_text_body:
247
+ if nested_body and (not plain_text_body):
307
248
  plain_text_body = nested_body
308
-
309
- # Prefer plain text, fallback to HTML
310
249
  if plain_text_body:
311
250
  return plain_text_body
312
251
  elif html_body:
313
252
  return f"[HTML Content]\n{html_body}"
314
-
315
253
  return ""
316
-
317
254
  except Exception as e:
318
255
  logger.error(f"Error extracting email body: {str(e)}")
319
256
  return ""
@@ -329,7 +266,6 @@ class GoogleMailApp(APIApplication):
329
266
  list: List of attachment dictionaries with attachment_id, filename, mime_type, and size
330
267
  """
331
268
  attachments = []
332
-
333
269
  try:
334
270
  if payload.get("filename") and payload.get("body", {}).get("attachmentId"):
335
271
  attachments.append(
@@ -340,7 +276,6 @@ class GoogleMailApp(APIApplication):
340
276
  "size": payload.get("body", {}).get("size", 0),
341
277
  }
342
278
  )
343
-
344
279
  parts = payload.get("parts", [])
345
280
  for part in parts:
346
281
  if part.get("filename") and part.get("body", {}).get("attachmentId"):
@@ -352,14 +287,11 @@ class GoogleMailApp(APIApplication):
352
287
  "size": part.get("body", {}).get("size", 0),
353
288
  }
354
289
  )
355
-
356
290
  elif part.get("parts"):
357
291
  nested_attachments = self._extract_attachments(part)
358
292
  attachments.extend(nested_attachments)
359
-
360
293
  except Exception as e:
361
294
  logger.error(f"Error extracting attachments: {str(e)}")
362
-
363
295
  return attachments
364
296
 
365
297
  def _decode_base64(self, data):
@@ -373,19 +305,14 @@ class GoogleMailApp(APIApplication):
373
305
  str: Decoded string content
374
306
  """
375
307
  try:
376
- # Gmail API uses URL-safe base64 encoding
377
308
  decoded_bytes = base64.urlsafe_b64decode(data)
378
309
  return decoded_bytes.decode("utf-8")
379
310
  except Exception as e:
380
311
  logger.error(f"Error decoding base64 data: {str(e)}")
381
312
  return f"[Unable to decode content: {str(e)}]"
382
313
 
383
- def list_messages(
384
- self,
385
- max_results: int = 10,
386
- q: str | None = None,
387
- include_spam_trash: bool = False,
388
- page_token: str | None = None,
314
+ async def list_messages(
315
+ self, max_results: int = 10, q: str | None = None, include_spam_trash: bool = False, page_token: str | None = None
389
316
  ) -> dict[str, Any]:
390
317
  """
391
318
  Fetches a paginated list of detailed email messages using optional search queries. It concurrently retrieves full content (sender, subject, body) for each message, returning the results and a pagination token. This differs from `get_message_details`, which fetches only a single message.
@@ -412,7 +339,17 @@ class GoogleMailApp(APIApplication):
412
339
  include_spam_trash: Boolean flag to include messages from spam and trash folders (default False)
413
340
 
414
341
  Returns:
415
- A dictionary containing the list of messages and next page token for pagination
342
+ A dictionary with the following keys:
343
+ - messages (list[dict]): A list of detailed message dictionaries. Each dictionary contains:
344
+ - message_id (str): Unique ID of the email.
345
+ - from (str): Sender's email address or "Unknown sender" if unavailable.
346
+ - to (str): Recipient(s) email address(es) or "Unknown recipient" if unavailable.
347
+ - date (str): Date the email was sent or "Unknown date" if unavailable.
348
+ - subject (str): Subject of the email or "No subject" if unavailable.
349
+ - body (str): Plain text content of the email or a preview/snippet.
350
+ - thread_id (str | None): ID of the email thread this message belongs to.
351
+ - attachments (list[dict]): List of attachments with metadata (e.g., filename, MIME type, attachment ID).
352
+ - next_page_token (str | None): Token to retrieve the next page of messages; None if there are no further pages.
416
353
 
417
354
  Raises:
418
355
  NotAuthorizedError: When the Gmail API authentication is invalid or missing
@@ -423,54 +360,30 @@ class GoogleMailApp(APIApplication):
423
360
  list, messages, gmail, search, query, pagination, important
424
361
  """
425
362
  url = f"{self.base_api_url}/messages?format=metadata"
426
-
427
- # Build query parameters
428
363
  params: dict[str, Any] = {"maxResults": max_results}
429
-
430
364
  if q:
431
365
  params["q"] = q
432
-
433
366
  if include_spam_trash:
434
367
  params["includeSpamTrash"] = "true"
435
-
436
368
  if page_token:
437
369
  params["pageToken"] = page_token
438
-
439
370
  logger.info(f"Retrieving messages list with params: {params}")
440
-
441
- response = self._get(url, params=params)
371
+ response = await self._aget(url, params=params)
442
372
  data = self._handle_response(response)
443
-
444
- # Extract message IDs
445
373
  messages = data.get("messages", [])
446
374
  message_ids = [msg.get("id") for msg in messages if msg.get("id")]
447
-
448
- # Use ThreadPoolExecutor to get detailed information for each message in parallel
449
375
  detailed_messages = []
450
376
  if message_ids:
451
- with concurrent.futures.ThreadPoolExecutor(max_workers=10) as executor:
452
- # Submit all get_message_details calls
453
- future_to_message_id = {
454
- executor.submit(self.get_message_details, message_id): message_id
455
- for message_id in message_ids
456
- }
457
-
458
- # Collect results as they complete
459
- for future in concurrent.futures.as_completed(future_to_message_id):
460
- message_id = future_to_message_id[future]
461
- try:
462
- result = future.result()
463
- detailed_messages.append(result)
464
- except Exception as e:
465
- logger.error(f"Error retrieving message {message_id}: {str(e)}")
466
- # Skip failed messages rather than including error strings
467
-
468
- return {
469
- "messages": detailed_messages,
470
- "next_page_token": data.get("nextPageToken"),
471
- }
472
-
473
- def get_email_thread(self, thread_id: str) -> dict[str, Any]:
377
+ tasks = [self.get_message_details(message_id) for message_id in message_ids]
378
+ results = await asyncio.gather(*tasks, return_exceptions=True)
379
+ for i, result in enumerate(results):
380
+ if isinstance(result, Exception):
381
+ logger.error(f"Error retrieving message {message_ids[i]}: {str(result)}")
382
+ else:
383
+ detailed_messages.append(result)
384
+ return {"messages": detailed_messages, "next_page_token": data.get("nextPageToken")}
385
+
386
+ async def get_email_thread(self, thread_id: str) -> dict[str, Any]:
474
387
  """
475
388
  Fetches a complete email conversation by its unique thread ID. Unlike `get_message_details`, which retrieves a single message, this returns all messages and metadata for the entire thread, providing the full context of the conversation.
476
389
 
@@ -490,10 +403,10 @@ class GoogleMailApp(APIApplication):
490
403
  """
491
404
  url = f"{self.base_api_url}/threads/{thread_id}"
492
405
  logger.info(f"Retrieving thread {thread_id}")
493
- response = self._get(url)
406
+ response = await self._aget(url)
494
407
  return self._handle_response(response)
495
408
 
496
- def list_labels(self) -> dict[str, Any]:
409
+ async def list_labels(self) -> dict[str, Any]:
497
410
  """
498
411
  Fetches a complete list of all available labels from the user's Gmail account via the API. It retrieves both system-defined (e.g., INBOX) and user-created labels, returning their names and IDs, complementing management functions like `create_label` and `update_label`.
499
412
 
@@ -510,16 +423,12 @@ class GoogleMailApp(APIApplication):
510
423
  Tags:
511
424
  list, gmail, labels, fetch, organize, important, management
512
425
  """
513
-
514
426
  url = f"{self.base_api_url}/labels"
515
-
516
427
  logger.info("Retrieving Gmail labels")
517
-
518
- response = self._get(url)
519
-
428
+ response = await self._aget(url)
520
429
  return self._handle_response(response)
521
430
 
522
- def create_label(self, name: str) -> dict[str, Any]:
431
+ async def create_label(self, name: str) -> dict[str, Any]:
523
432
  """
524
433
  Creates a new Gmail label with a specified name, hardcoding its visibility to ensure it appears in both label and message lists. This function complements `update_label` and `delete_label` by adding new organizational tags to the user's account via the API.
525
434
 
@@ -536,23 +445,13 @@ class GoogleMailApp(APIApplication):
536
445
  Tags:
537
446
  create, label, gmail, management, important
538
447
  """
539
-
540
448
  url = f"{self.base_api_url}/labels"
541
-
542
- # Create the label data with just the essential fields
543
- label_data = {
544
- "name": name,
545
- "labelListVisibility": "labelShow", # Show in label list
546
- "messageListVisibility": "show", # Show in message list
547
- }
548
-
449
+ label_data = {"name": name, "labelListVisibility": "labelShow", "messageListVisibility": "show"}
549
450
  logger.info(f"Creating new Gmail label: {name}")
550
-
551
- response = self._post(url, label_data)
552
-
451
+ response = await self._apost(url, label_data)
553
452
  return self._handle_response(response)
554
453
 
555
- def get_profile(self) -> dict[str, Any]:
454
+ async def get_profile(self) -> dict[str, Any]:
556
455
  """
557
456
  Retrieves the authenticated user's Gmail profile from the API. The profile includes the user's email address, total message and thread counts, and the mailbox's history ID, offering a high-level summary of the account's state.
558
457
 
@@ -569,15 +468,12 @@ class GoogleMailApp(APIApplication):
569
468
  Tags:
570
469
  fetch, profile, gmail, user-info, api-request, important
571
470
  """
572
-
573
471
  url = f"{self.base_api_url}/profile"
574
-
575
472
  logger.info("Retrieving Gmail user profile")
576
-
577
- response = self._get(url)
473
+ response = await self._aget(url)
578
474
  return self._handle_response(response)
579
475
 
580
- def update_draft(
476
+ async def update_draft(
581
477
  self,
582
478
  userId,
583
479
  id,
@@ -670,10 +566,7 @@ class GoogleMailApp(APIApplication):
670
566
  raise ValueError("Missing required parameter 'userId'")
671
567
  if id is None:
672
568
  raise ValueError("Missing required parameter 'id'")
673
- request_body = {
674
- "id": id,
675
- "message": message,
676
- }
569
+ request_body = {"id": id, "message": message}
677
570
  request_body = {k: v for k, v in request_body.items() if v is not None}
678
571
  url = f"{self.base_url}/gmail/v1/users/{userId}/drafts/{id}"
679
572
  query_params = {
@@ -693,11 +586,10 @@ class GoogleMailApp(APIApplication):
693
586
  ]
694
587
  if v is not None
695
588
  }
696
- response = self._put(url, data=request_body, params=query_params)
697
- response.raise_for_status()
698
- return response.json()
589
+ response = await self._aput(url, data=request_body, params=query_params)
590
+ return self._handle_response(response)
699
591
 
700
- def trash_message(
592
+ async def trash_message(
701
593
  self,
702
594
  userId,
703
595
  id,
@@ -759,11 +651,10 @@ class GoogleMailApp(APIApplication):
759
651
  ]
760
652
  if v is not None
761
653
  }
762
- response = self._post(url, data={}, params=query_params)
763
- response.raise_for_status()
764
- return response.json()
654
+ response = await self._apost(url, data={}, params=query_params)
655
+ return self._handle_response(response)
765
656
 
766
- def untrash_message(
657
+ async def untrash_message(
767
658
  self,
768
659
  userId,
769
660
  id,
@@ -825,11 +716,10 @@ class GoogleMailApp(APIApplication):
825
716
  ]
826
717
  if v is not None
827
718
  }
828
- response = self._post(url, data={}, params=query_params)
829
- response.raise_for_status()
830
- return response.json()
719
+ response = await self._apost(url, data={}, params=query_params)
720
+ return self._handle_response(response)
831
721
 
832
- def get_attachment(
722
+ async def get_attachment(
833
723
  self,
834
724
  userId,
835
725
  messageId,
@@ -895,11 +785,10 @@ class GoogleMailApp(APIApplication):
895
785
  ]
896
786
  if v is not None
897
787
  }
898
- response = self._get(url, params=query_params)
899
- response.raise_for_status()
900
- return response.json()
788
+ response = await self._aget(url, params=query_params)
789
+ return self._handle_response(response)
901
790
 
902
- def update_label(
791
+ async def update_label(
903
792
  self,
904
793
  userId,
905
794
  id,
@@ -1010,11 +899,10 @@ class GoogleMailApp(APIApplication):
1010
899
  ]
1011
900
  if v is not None
1012
901
  }
1013
- response = self._put(url, data=request_body, params=query_params)
1014
- response.raise_for_status()
1015
- return response.json()
902
+ response = await self._aput(url, data=request_body, params=query_params)
903
+ return self._handle_response(response)
1016
904
 
1017
- def delete_label(
905
+ async def delete_label(
1018
906
  self,
1019
907
  userId,
1020
908
  id,
@@ -1076,11 +964,10 @@ class GoogleMailApp(APIApplication):
1076
964
  ]
1077
965
  if v is not None
1078
966
  }
1079
- response = self._delete(url, params=query_params)
1080
- response.raise_for_status()
1081
- return response.json()
967
+ response = await self._adelete(url, params=query_params)
968
+ return self._handle_response(response)
1082
969
 
1083
- def get_filter(
970
+ async def get_filter(
1084
971
  self,
1085
972
  userId,
1086
973
  id,
@@ -1142,11 +1029,10 @@ class GoogleMailApp(APIApplication):
1142
1029
  ]
1143
1030
  if v is not None
1144
1031
  }
1145
- response = self._get(url, params=query_params)
1146
- response.raise_for_status()
1147
- return response.json()
1032
+ response = await self._aget(url, params=query_params)
1033
+ return self._handle_response(response)
1148
1034
 
1149
- def delete_filter(
1035
+ async def delete_filter(
1150
1036
  self,
1151
1037
  userId,
1152
1038
  id,
@@ -1208,11 +1094,10 @@ class GoogleMailApp(APIApplication):
1208
1094
  ]
1209
1095
  if v is not None
1210
1096
  }
1211
- response = self._delete(url, params=query_params)
1212
- response.raise_for_status()
1213
- return response.json()
1097
+ response = await self._adelete(url, params=query_params)
1098
+ return self._handle_response(response)
1214
1099
 
1215
- def get_all_filters(
1100
+ async def get_all_filters(
1216
1101
  self,
1217
1102
  userId,
1218
1103
  access_token=None,
@@ -1270,11 +1155,10 @@ class GoogleMailApp(APIApplication):
1270
1155
  ]
1271
1156
  if v is not None
1272
1157
  }
1273
- response = self._get(url, params=query_params)
1274
- response.raise_for_status()
1275
- return response.json()
1158
+ response = await self._aget(url, params=query_params)
1159
+ return self._handle_response(response)
1276
1160
 
1277
- def create_filter(
1161
+ async def create_filter(
1278
1162
  self,
1279
1163
  userId,
1280
1164
  access_token=None,
@@ -1348,11 +1232,7 @@ class GoogleMailApp(APIApplication):
1348
1232
  """
1349
1233
  if userId is None:
1350
1234
  raise ValueError("Missing required parameter 'userId'")
1351
- request_body = {
1352
- "action": action,
1353
- "criteria": criteria,
1354
- "id": id,
1355
- }
1235
+ request_body = {"action": action, "criteria": criteria, "id": id}
1356
1236
  request_body = {k: v for k, v in request_body.items() if v is not None}
1357
1237
  url = f"{self.base_url}/gmail/v1/users/{userId}/settings/filters"
1358
1238
  query_params = {
@@ -1372,9 +1252,8 @@ class GoogleMailApp(APIApplication):
1372
1252
  ]
1373
1253
  if v is not None
1374
1254
  }
1375
- response = self._post(url, data=request_body, params=query_params)
1376
- response.raise_for_status()
1377
- return response.json()
1255
+ response = await self._apost(url, data=request_body, params=query_params)
1256
+ return self._handle_response(response)
1378
1257
 
1379
1258
  def list_tools(self):
1380
1259
  return [
@@ -1385,10 +1264,10 @@ class GoogleMailApp(APIApplication):
1385
1264
  self.list_drafts,
1386
1265
  self.get_message_details,
1387
1266
  self.list_messages,
1267
+ self.get_email_thread,
1388
1268
  self.list_labels,
1389
1269
  self.create_label,
1390
1270
  self.get_profile,
1391
- # Auto Generated from openapi spec
1392
1271
  self.update_draft,
1393
1272
  self.trash_message,
1394
1273
  self.untrash_message,