google-workspace-mcp 1.1.5__py3-none-any.whl → 1.2.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- google_workspace_mcp/__main__.py +5 -6
- google_workspace_mcp/models.py +486 -0
- google_workspace_mcp/services/calendar.py +14 -4
- google_workspace_mcp/services/drive.py +237 -14
- google_workspace_mcp/services/sheets_service.py +273 -35
- google_workspace_mcp/services/slides.py +42 -1829
- google_workspace_mcp/tools/calendar.py +116 -100
- google_workspace_mcp/tools/docs_tools.py +99 -57
- google_workspace_mcp/tools/drive.py +112 -92
- google_workspace_mcp/tools/gmail.py +131 -66
- google_workspace_mcp/tools/sheets_tools.py +137 -64
- google_workspace_mcp/tools/slides.py +295 -743
- {google_workspace_mcp-1.1.5.dist-info → google_workspace_mcp-1.2.0.dist-info}/METADATA +3 -2
- {google_workspace_mcp-1.1.5.dist-info → google_workspace_mcp-1.2.0.dist-info}/RECORD +16 -17
- google_workspace_mcp/tools/add_image.py +0 -1781
- google_workspace_mcp/utils/unit_conversion.py +0 -201
- {google_workspace_mcp-1.1.5.dist-info → google_workspace_mcp-1.2.0.dist-info}/WHEEL +0 -0
- {google_workspace_mcp-1.1.5.dist-info → google_workspace_mcp-1.2.0.dist-info}/entry_points.txt +0 -0
@@ -3,9 +3,19 @@ Gmail tools for Google Workspace MCP operations.
|
|
3
3
|
"""
|
4
4
|
|
5
5
|
import logging
|
6
|
-
from typing import Any
|
7
6
|
|
8
7
|
from google_workspace_mcp.app import mcp
|
8
|
+
from google_workspace_mcp.models import (
|
9
|
+
GmailAttachmentOutput,
|
10
|
+
GmailBulkDeleteOutput,
|
11
|
+
GmailDraftCreationOutput,
|
12
|
+
GmailDraftDeletionOutput,
|
13
|
+
GmailDraftSendOutput,
|
14
|
+
GmailEmailSearchOutput,
|
15
|
+
GmailMessageDetailsOutput,
|
16
|
+
GmailReplyOutput,
|
17
|
+
GmailSendOutput,
|
18
|
+
)
|
9
19
|
from google_workspace_mcp.services.gmail import GmailService
|
10
20
|
|
11
21
|
logger = logging.getLogger(__name__)
|
@@ -14,10 +24,13 @@ logger = logging.getLogger(__name__)
|
|
14
24
|
# --- Gmail Tool Functions --- #
|
15
25
|
|
16
26
|
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
27
|
+
@mcp.tool(
|
28
|
+
name="query_gmail_emails",
|
29
|
+
description="Query Gmail emails based on a search query.",
|
30
|
+
)
|
31
|
+
async def query_gmail_emails(
|
32
|
+
query: str, max_results: int = 100
|
33
|
+
) -> GmailEmailSearchOutput:
|
21
34
|
"""
|
22
35
|
Searches for Gmail emails using Gmail query syntax.
|
23
36
|
|
@@ -26,7 +39,7 @@ async def query_gmail_emails(query: str, max_results: int = 100) -> dict[str, An
|
|
26
39
|
max_results: Maximum number of emails to return.
|
27
40
|
|
28
41
|
Returns:
|
29
|
-
|
42
|
+
GmailEmailSearchOutput containing the list of matching emails.
|
30
43
|
"""
|
31
44
|
logger.info(f"Executing query_gmail_emails tool with query: '{query}'")
|
32
45
|
|
@@ -39,15 +52,16 @@ async def query_gmail_emails(query: str, max_results: int = 100) -> dict[str, An
|
|
39
52
|
|
40
53
|
# Return appropriate message if no results
|
41
54
|
if not emails:
|
42
|
-
|
55
|
+
emails = []
|
43
56
|
|
44
|
-
return
|
57
|
+
return GmailEmailSearchOutput(count=len(emails), emails=emails)
|
45
58
|
|
46
59
|
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
60
|
+
@mcp.tool(
|
61
|
+
name="gmail_get_message_details",
|
62
|
+
description="Retrieves a complete Gmail email message by its ID.",
|
63
|
+
)
|
64
|
+
async def gmail_get_message_details(email_id: str) -> GmailMessageDetailsOutput:
|
51
65
|
"""
|
52
66
|
Retrieves a complete Gmail email message by its ID.
|
53
67
|
|
@@ -55,7 +69,7 @@ async def gmail_get_message_details(email_id: str) -> dict[str, Any]:
|
|
55
69
|
email_id: The ID of the Gmail message to retrieve.
|
56
70
|
|
57
71
|
Returns:
|
58
|
-
|
72
|
+
GmailMessageDetailsOutput containing the email details and attachments.
|
59
73
|
"""
|
60
74
|
logger.info(f"Executing gmail_get_message_details tool with email_id: '{email_id}'")
|
61
75
|
if not email_id or not email_id.strip():
|
@@ -72,13 +86,25 @@ async def gmail_get_message_details(email_id: str) -> dict[str, Any]:
|
|
72
86
|
if not result:
|
73
87
|
raise ValueError(f"Failed to retrieve email with ID: {email_id}")
|
74
88
|
|
75
|
-
return
|
89
|
+
return GmailMessageDetailsOutput(
|
90
|
+
id=result["id"],
|
91
|
+
thread_id=result.get("thread_id", ""),
|
92
|
+
subject=result.get("subject", ""),
|
93
|
+
from_email=result.get("from_email", ""),
|
94
|
+
to_email=result.get("to_email", []),
|
95
|
+
date=result.get("date", ""),
|
96
|
+
body=result.get("body", ""),
|
97
|
+
attachments=result.get("attachments"),
|
98
|
+
)
|
76
99
|
|
77
100
|
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
101
|
+
@mcp.tool(
|
102
|
+
name="gmail_get_attachment_content",
|
103
|
+
description="Retrieves a specific attachment from a Gmail message.",
|
104
|
+
)
|
105
|
+
async def gmail_get_attachment_content(
|
106
|
+
message_id: str, attachment_id: str
|
107
|
+
) -> GmailAttachmentOutput:
|
82
108
|
"""
|
83
109
|
Retrieves a specific attachment from a Gmail message.
|
84
110
|
|
@@ -87,14 +113,18 @@ async def gmail_get_attachment_content(message_id: str, attachment_id: str) -> d
|
|
87
113
|
attachment_id: The ID of the attachment to retrieve.
|
88
114
|
|
89
115
|
Returns:
|
90
|
-
|
116
|
+
GmailAttachmentOutput containing filename, mimeType, size, and base64 data.
|
91
117
|
"""
|
92
|
-
logger.info(
|
118
|
+
logger.info(
|
119
|
+
f"Executing gmail_get_attachment_content tool - Msg: {message_id}, Attach: {attachment_id}"
|
120
|
+
)
|
93
121
|
if not message_id or not attachment_id:
|
94
122
|
raise ValueError("Message ID and attachment ID are required")
|
95
123
|
|
96
124
|
gmail_service = GmailService()
|
97
|
-
result = gmail_service.get_attachment_content(
|
125
|
+
result = gmail_service.get_attachment_content(
|
126
|
+
message_id=message_id, attachment_id=attachment_id
|
127
|
+
)
|
98
128
|
|
99
129
|
if not result or (isinstance(result, dict) and result.get("error")):
|
100
130
|
error_msg = "Error getting attachment"
|
@@ -102,20 +132,25 @@ async def gmail_get_attachment_content(message_id: str, attachment_id: str) -> d
|
|
102
132
|
error_msg = result.get("message", error_msg)
|
103
133
|
raise ValueError(error_msg)
|
104
134
|
|
105
|
-
|
106
|
-
|
135
|
+
return GmailAttachmentOutput(
|
136
|
+
filename=result["filename"],
|
137
|
+
mime_type=result["mime_type"],
|
138
|
+
size=result["size"],
|
139
|
+
data=result["data"],
|
140
|
+
)
|
107
141
|
|
108
142
|
|
109
|
-
|
110
|
-
|
111
|
-
|
143
|
+
@mcp.tool(
|
144
|
+
name="create_gmail_draft",
|
145
|
+
description="Creates a draft email message in Gmail.",
|
146
|
+
)
|
112
147
|
async def create_gmail_draft(
|
113
148
|
to: str,
|
114
149
|
subject: str,
|
115
150
|
body: str,
|
116
151
|
cc: list[str] | None = None,
|
117
152
|
bcc: list[str] | None = None,
|
118
|
-
) ->
|
153
|
+
) -> GmailDraftCreationOutput:
|
119
154
|
"""
|
120
155
|
Creates a draft email message in Gmail.
|
121
156
|
|
@@ -127,7 +162,7 @@ async def create_gmail_draft(
|
|
127
162
|
bcc: Optional list of email addresses to BCC.
|
128
163
|
|
129
164
|
Returns:
|
130
|
-
|
165
|
+
GmailDraftCreationOutput containing the created draft details.
|
131
166
|
"""
|
132
167
|
logger.info("Executing create_gmail_draft")
|
133
168
|
if not to or not subject or not body: # Check for empty strings
|
@@ -135,7 +170,9 @@ async def create_gmail_draft(
|
|
135
170
|
|
136
171
|
gmail_service = GmailService()
|
137
172
|
# Pass bcc parameter even though service may not use it (for test compatibility)
|
138
|
-
result = gmail_service.create_draft(
|
173
|
+
result = gmail_service.create_draft(
|
174
|
+
to=to, subject=subject, body=body, cc=cc, bcc=bcc
|
175
|
+
)
|
139
176
|
|
140
177
|
if not result or (isinstance(result, dict) and result.get("error")):
|
141
178
|
error_msg = "Error creating draft"
|
@@ -143,15 +180,16 @@ async def create_gmail_draft(
|
|
143
180
|
error_msg = result.get("message", error_msg)
|
144
181
|
raise ValueError(error_msg)
|
145
182
|
|
146
|
-
return result
|
183
|
+
return GmailDraftCreationOutput(id=result["id"], message=result.get("message", {}))
|
147
184
|
|
148
185
|
|
149
|
-
|
150
|
-
|
151
|
-
|
186
|
+
@mcp.tool(
|
187
|
+
name="delete_gmail_draft",
|
188
|
+
description="Deletes a Gmail draft email by its draft ID.",
|
189
|
+
)
|
152
190
|
async def delete_gmail_draft(
|
153
191
|
draft_id: str,
|
154
|
-
) ->
|
192
|
+
) -> GmailDraftDeletionOutput:
|
155
193
|
"""
|
156
194
|
Deletes a specific draft email from Gmail.
|
157
195
|
|
@@ -159,7 +197,7 @@ async def delete_gmail_draft(
|
|
159
197
|
draft_id: The ID of the draft to delete.
|
160
198
|
|
161
199
|
Returns:
|
162
|
-
|
200
|
+
GmailDraftDeletionOutput confirming the deletion.
|
163
201
|
"""
|
164
202
|
logger.info(f"Executing delete_gmail_draft with draft_id: '{draft_id}'")
|
165
203
|
if not draft_id or not draft_id.strip():
|
@@ -172,22 +210,24 @@ async def delete_gmail_draft(
|
|
172
210
|
# Attempt to check if the service returned an error dict
|
173
211
|
# (Assuming handle_api_error might return dict or False/None)
|
174
212
|
# This part might need adjustment based on actual service error handling
|
175
|
-
error_info = getattr(
|
213
|
+
error_info = getattr(
|
214
|
+
gmail_service, "last_error", None
|
215
|
+
) # Hypothetical error capture
|
176
216
|
error_msg = "Failed to delete draft"
|
177
217
|
if isinstance(error_info, dict) and error_info.get("error"):
|
178
218
|
error_msg = error_info.get("message", error_msg)
|
179
219
|
raise ValueError(error_msg)
|
180
220
|
|
181
|
-
return
|
182
|
-
|
183
|
-
|
184
|
-
}
|
221
|
+
return GmailDraftDeletionOutput(
|
222
|
+
message=f"Draft with ID '{draft_id}' deleted successfully.", success=True
|
223
|
+
)
|
185
224
|
|
186
225
|
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
226
|
+
@mcp.tool(
|
227
|
+
name="gmail_send_draft",
|
228
|
+
description="Sends an existing draft email from Gmail.",
|
229
|
+
)
|
230
|
+
async def gmail_send_draft(draft_id: str) -> GmailDraftSendOutput:
|
191
231
|
"""
|
192
232
|
Sends a specific draft email.
|
193
233
|
|
@@ -195,7 +235,7 @@ async def gmail_send_draft(draft_id: str) -> dict[str, Any]:
|
|
195
235
|
draft_id: The ID of the draft to send.
|
196
236
|
|
197
237
|
Returns:
|
198
|
-
|
238
|
+
GmailDraftSendOutput containing the details of the sent message.
|
199
239
|
"""
|
200
240
|
logger.info(f"Executing gmail_send_draft tool for draft_id: '{draft_id}'")
|
201
241
|
if not draft_id or not draft_id.strip():
|
@@ -210,18 +250,23 @@ async def gmail_send_draft(draft_id: str) -> dict[str, Any]:
|
|
210
250
|
if not result: # Should be caught by error dict check
|
211
251
|
raise ValueError(f"Failed to send draft '{draft_id}'")
|
212
252
|
|
213
|
-
return
|
253
|
+
return GmailDraftSendOutput(
|
254
|
+
id=result["id"],
|
255
|
+
thread_id=result.get("thread_id", ""),
|
256
|
+
label_ids=result.get("label_ids", []),
|
257
|
+
)
|
214
258
|
|
215
259
|
|
216
|
-
|
217
|
-
|
218
|
-
|
260
|
+
@mcp.tool(
|
261
|
+
name="gmail_reply_to_email",
|
262
|
+
description="Create a reply to an existing email. Can be sent or saved as draft.",
|
263
|
+
)
|
219
264
|
async def gmail_reply_to_email(
|
220
265
|
email_id: str,
|
221
266
|
reply_body: str,
|
222
267
|
send: bool = False,
|
223
268
|
reply_all: bool = False,
|
224
|
-
) ->
|
269
|
+
) -> GmailReplyOutput:
|
225
270
|
"""
|
226
271
|
Creates a reply to an existing email thread.
|
227
272
|
|
@@ -232,7 +277,7 @@ async def gmail_reply_to_email(
|
|
232
277
|
reply_all: If True, reply to all recipients. If False, reply to sender only.
|
233
278
|
|
234
279
|
Returns:
|
235
|
-
|
280
|
+
GmailReplyOutput containing the sent message or created draft details.
|
236
281
|
"""
|
237
282
|
logger.info(f"Executing gmail_reply_to_email to message: '{email_id}'")
|
238
283
|
if not email_id or not reply_body:
|
@@ -252,15 +297,18 @@ async def gmail_reply_to_email(
|
|
252
297
|
error_msg = result.get("message", error_msg)
|
253
298
|
raise ValueError(error_msg)
|
254
299
|
|
255
|
-
return
|
300
|
+
return GmailReplyOutput(
|
301
|
+
id=result["id"], thread_id=result.get("thread_id", ""), in_reply_to=email_id
|
302
|
+
)
|
256
303
|
|
257
304
|
|
258
|
-
|
259
|
-
|
260
|
-
|
305
|
+
@mcp.tool(
|
306
|
+
name="gmail_bulk_delete_messages",
|
307
|
+
description="Delete multiple emails at once by providing a list of message IDs.",
|
308
|
+
)
|
261
309
|
async def gmail_bulk_delete_messages(
|
262
310
|
message_ids: list[str],
|
263
|
-
) ->
|
311
|
+
) -> GmailBulkDeleteOutput:
|
264
312
|
"""
|
265
313
|
Deletes multiple Gmail emails using a list of message IDs.
|
266
314
|
|
@@ -268,7 +316,7 @@ async def gmail_bulk_delete_messages(
|
|
268
316
|
message_ids: A list of email message IDs to delete.
|
269
317
|
|
270
318
|
Returns:
|
271
|
-
|
319
|
+
GmailBulkDeleteOutput summarizing the deletion result.
|
272
320
|
"""
|
273
321
|
# Validation first - check if it's a list
|
274
322
|
if not isinstance(message_ids, list):
|
@@ -289,19 +337,26 @@ async def gmail_bulk_delete_messages(
|
|
289
337
|
error_msg = result.get("message", error_msg)
|
290
338
|
raise ValueError(error_msg)
|
291
339
|
|
292
|
-
return
|
340
|
+
return GmailBulkDeleteOutput(
|
341
|
+
deleted_count=result.get("deleted_count", len(message_ids)),
|
342
|
+
success=result.get("success", True),
|
343
|
+
message=result.get(
|
344
|
+
"message", f"Successfully deleted {len(message_ids)} messages"
|
345
|
+
),
|
346
|
+
)
|
293
347
|
|
294
348
|
|
295
|
-
|
296
|
-
|
297
|
-
|
349
|
+
@mcp.tool(
|
350
|
+
name="gmail_send_email",
|
351
|
+
description="Composes and sends an email directly.",
|
352
|
+
)
|
298
353
|
async def gmail_send_email(
|
299
354
|
to: list[str],
|
300
355
|
subject: str,
|
301
356
|
body: str,
|
302
357
|
cc: list[str] | None = None,
|
303
358
|
bcc: list[str] | None = None,
|
304
|
-
) ->
|
359
|
+
) -> GmailSendOutput:
|
305
360
|
"""
|
306
361
|
Composes and sends an email message.
|
307
362
|
|
@@ -313,14 +368,20 @@ async def gmail_send_email(
|
|
313
368
|
bcc: Optional. A list of BCC recipient email addresses.
|
314
369
|
|
315
370
|
Returns:
|
316
|
-
|
371
|
+
GmailSendOutput containing the details of the sent message.
|
317
372
|
"""
|
318
373
|
logger.info(f"Executing gmail_send_email tool to: {to}, subject: '{subject}'")
|
319
|
-
if
|
374
|
+
if (
|
375
|
+
not to
|
376
|
+
or not isinstance(to, list)
|
377
|
+
or not all(isinstance(email, str) and email.strip() for email in to)
|
378
|
+
):
|
320
379
|
raise ValueError("Recipients 'to' must be a non-empty list of email strings.")
|
321
380
|
if not subject or not subject.strip():
|
322
381
|
raise ValueError("Subject cannot be empty.")
|
323
|
-
if
|
382
|
+
if (
|
383
|
+
body is None
|
384
|
+
): # Allow empty string for body, but not None if it implies missing arg.
|
324
385
|
raise ValueError("Body cannot be None (can be an empty string).")
|
325
386
|
|
326
387
|
gmail_service = GmailService()
|
@@ -332,4 +393,8 @@ async def gmail_send_email(
|
|
332
393
|
if not result:
|
333
394
|
raise ValueError("Failed to send email")
|
334
395
|
|
335
|
-
return
|
396
|
+
return GmailSendOutput(
|
397
|
+
id=result["id"],
|
398
|
+
thread_id=result.get("thread_id", ""),
|
399
|
+
label_ids=result.get("label_ids", []),
|
400
|
+
)
|