universal-mcp 0.1.1__py3-none-any.whl → 0.1.2rc1__py3-none-any.whl

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