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.
- universal_mcp/applications/__init__.py +23 -28
- universal_mcp/applications/application.py +13 -8
- universal_mcp/applications/e2b/app.py +74 -0
- universal_mcp/applications/firecrawl/app.py +381 -0
- universal_mcp/applications/github/README.md +35 -0
- universal_mcp/applications/github/app.py +133 -100
- universal_mcp/applications/google_calendar/app.py +170 -139
- universal_mcp/applications/google_mail/app.py +185 -160
- universal_mcp/applications/markitdown/app.py +32 -0
- universal_mcp/applications/reddit/app.py +112 -71
- universal_mcp/applications/resend/app.py +3 -8
- universal_mcp/applications/serp/app.py +84 -0
- universal_mcp/applications/tavily/app.py +11 -10
- universal_mcp/applications/zenquotes/app.py +3 -3
- universal_mcp/cli.py +98 -16
- universal_mcp/config.py +20 -3
- universal_mcp/exceptions.py +1 -3
- universal_mcp/integrations/__init__.py +6 -2
- universal_mcp/integrations/agentr.py +26 -24
- universal_mcp/integrations/integration.py +72 -35
- universal_mcp/servers/__init__.py +21 -1
- universal_mcp/servers/server.py +77 -44
- universal_mcp/stores/__init__.py +15 -2
- universal_mcp/stores/store.py +123 -13
- universal_mcp/utils/__init__.py +1 -0
- universal_mcp/utils/api_generator.py +269 -0
- universal_mcp/utils/docgen.py +360 -0
- universal_mcp/utils/installation.py +17 -2
- universal_mcp/utils/openapi.py +202 -104
- {universal_mcp-0.1.1.dist-info → universal_mcp-0.1.2rc1.dist-info}/METADATA +22 -5
- universal_mcp-0.1.2rc1.dist-info/RECORD +37 -0
- universal_mcp-0.1.1.dist-info/RECORD +0 -29
- {universal_mcp-0.1.1.dist-info → universal_mcp-0.1.2rc1.dist-info}/WHEEL +0 -0
- {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
|
-
|
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="
|
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
|
-
|
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
|
-
|
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(
|
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[
|
75
|
-
message[
|
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[
|
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
|
-
|
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(
|
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
|
-
|
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(
|
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
|
-
|
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(
|
211
|
-
|
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(
|
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
|
-
|
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 =
|
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(
|
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(
|
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(
|
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
|
-
|
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(
|
397
|
-
|
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(
|
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"
|
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 =
|
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(
|
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(
|
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 [
|
564
|
-
|
565
|
-
|
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
|
+
]
|