google-workspace-mcp 1.0.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/models.py +486 -0
- google_workspace_mcp/services/calendar.py +14 -4
- google_workspace_mcp/services/drive.py +268 -18
- google_workspace_mcp/services/sheets_service.py +273 -35
- google_workspace_mcp/services/slides.py +242 -53
- google_workspace_mcp/tools/calendar.py +99 -88
- google_workspace_mcp/tools/docs_tools.py +67 -33
- google_workspace_mcp/tools/drive.py +288 -25
- google_workspace_mcp/tools/gmail.py +95 -39
- google_workspace_mcp/tools/sheets_tools.py +112 -46
- google_workspace_mcp/tools/slides.py +317 -46
- {google_workspace_mcp-1.0.5.dist-info → google_workspace_mcp-1.2.0.dist-info}/METADATA +4 -3
- {google_workspace_mcp-1.0.5.dist-info → google_workspace_mcp-1.2.0.dist-info}/RECORD +15 -14
- {google_workspace_mcp-1.0.5.dist-info → google_workspace_mcp-1.2.0.dist-info}/WHEEL +0 -0
- {google_workspace_mcp-1.0.5.dist-info → google_workspace_mcp-1.2.0.dist-info}/entry_points.txt +0 -0
@@ -6,6 +6,24 @@ import logging
|
|
6
6
|
from typing import Any
|
7
7
|
|
8
8
|
from google_workspace_mcp.app import mcp # Import from central app module
|
9
|
+
from google_workspace_mcp.models import (
|
10
|
+
SlidesAddFormattedTextOutput,
|
11
|
+
SlidesAddListOutput,
|
12
|
+
SlidesAddNotesOutput,
|
13
|
+
SlidesAddTableOutput,
|
14
|
+
SlidesAddTextOutput,
|
15
|
+
SlidesCreateFromMarkdownOutput,
|
16
|
+
SlidesCreatePresentationOutput,
|
17
|
+
SlidesCreateSlideOutput,
|
18
|
+
SlidesDeleteSlideOutput,
|
19
|
+
SlidesDuplicateSlideOutput,
|
20
|
+
SlidesGetPresentationOutput,
|
21
|
+
SlidesGetSlidesOutput,
|
22
|
+
SlidesInsertChartOutput,
|
23
|
+
SlidesSharePresentationOutput,
|
24
|
+
)
|
25
|
+
from google_workspace_mcp.services.drive import DriveService
|
26
|
+
from google_workspace_mcp.services.sheets_service import SheetsService
|
9
27
|
from google_workspace_mcp.services.slides import SlidesService
|
10
28
|
|
11
29
|
logger = logging.getLogger(__name__)
|
@@ -18,7 +36,7 @@ logger = logging.getLogger(__name__)
|
|
18
36
|
name="get_presentation",
|
19
37
|
description="Get a presentation by ID with its metadata and content.",
|
20
38
|
)
|
21
|
-
async def get_presentation(presentation_id: str) ->
|
39
|
+
async def get_presentation(presentation_id: str) -> SlidesGetPresentationOutput:
|
22
40
|
"""
|
23
41
|
Get presentation information including all slides and content.
|
24
42
|
|
@@ -26,7 +44,7 @@ async def get_presentation(presentation_id: str) -> dict[str, Any]:
|
|
26
44
|
presentation_id: The ID of the presentation.
|
27
45
|
|
28
46
|
Returns:
|
29
|
-
|
47
|
+
SlidesGetPresentationOutput containing presentation data.
|
30
48
|
"""
|
31
49
|
logger.info(f"Executing get_presentation tool with ID: '{presentation_id}'")
|
32
50
|
if not presentation_id or not presentation_id.strip():
|
@@ -38,15 +56,20 @@ async def get_presentation(presentation_id: str) -> dict[str, Any]:
|
|
38
56
|
if isinstance(result, dict) and result.get("error"):
|
39
57
|
raise ValueError(result.get("message", "Error getting presentation"))
|
40
58
|
|
41
|
-
|
42
|
-
|
59
|
+
return SlidesGetPresentationOutput(
|
60
|
+
presentation_id=result.get("presentationId", presentation_id),
|
61
|
+
title=result.get("title", ""),
|
62
|
+
slides=result.get("slides", []),
|
63
|
+
masters=result.get("masters", []),
|
64
|
+
layouts=result.get("layouts", []),
|
65
|
+
)
|
43
66
|
|
44
67
|
|
45
68
|
@mcp.tool(
|
46
69
|
name="get_slides",
|
47
70
|
description="Retrieves all slides from a presentation with their elements and notes.",
|
48
71
|
)
|
49
|
-
async def get_slides(presentation_id: str) ->
|
72
|
+
async def get_slides(presentation_id: str) -> SlidesGetSlidesOutput:
|
50
73
|
"""
|
51
74
|
Retrieves all slides from a presentation.
|
52
75
|
|
@@ -54,7 +77,7 @@ async def get_slides(presentation_id: str) -> dict[str, Any]:
|
|
54
77
|
presentation_id: The ID of the presentation.
|
55
78
|
|
56
79
|
Returns:
|
57
|
-
|
80
|
+
SlidesGetSlidesOutput containing the list of slides.
|
58
81
|
"""
|
59
82
|
logger.info(f"Executing get_slides tool from presentation: '{presentation_id}'")
|
60
83
|
if not presentation_id or not presentation_id.strip():
|
@@ -67,10 +90,9 @@ async def get_slides(presentation_id: str) -> dict[str, Any]:
|
|
67
90
|
raise ValueError(slides.get("message", "Error getting slides"))
|
68
91
|
|
69
92
|
if not slides:
|
70
|
-
|
93
|
+
slides = []
|
71
94
|
|
72
|
-
|
73
|
-
return {"count": len(slides), "slides": slides}
|
95
|
+
return SlidesGetSlidesOutput(count=len(slides), slides=slides)
|
74
96
|
|
75
97
|
|
76
98
|
@mcp.tool(
|
@@ -79,7 +101,7 @@ async def get_slides(presentation_id: str) -> dict[str, Any]:
|
|
79
101
|
)
|
80
102
|
async def create_presentation(
|
81
103
|
title: str,
|
82
|
-
) ->
|
104
|
+
) -> SlidesCreatePresentationOutput:
|
83
105
|
"""
|
84
106
|
Create a new presentation.
|
85
107
|
|
@@ -87,7 +109,7 @@ async def create_presentation(
|
|
87
109
|
title: The title for the new presentation.
|
88
110
|
|
89
111
|
Returns:
|
90
|
-
|
112
|
+
SlidesCreatePresentationOutput containing created presentation data.
|
91
113
|
"""
|
92
114
|
logger.info(f"Executing create_presentation with title: '{title}'")
|
93
115
|
if not title or not title.strip():
|
@@ -99,7 +121,11 @@ async def create_presentation(
|
|
99
121
|
if isinstance(result, dict) and result.get("error"):
|
100
122
|
raise ValueError(result.get("message", "Error creating presentation"))
|
101
123
|
|
102
|
-
return
|
124
|
+
return SlidesCreatePresentationOutput(
|
125
|
+
presentation_id=result["presentation_id"],
|
126
|
+
title=result["title"],
|
127
|
+
presentation_url=result["presentation_url"],
|
128
|
+
)
|
103
129
|
|
104
130
|
|
105
131
|
@mcp.tool(
|
@@ -109,7 +135,7 @@ async def create_presentation(
|
|
109
135
|
async def create_slide(
|
110
136
|
presentation_id: str,
|
111
137
|
layout: str = "TITLE_AND_BODY",
|
112
|
-
) ->
|
138
|
+
) -> SlidesCreateSlideOutput:
|
113
139
|
"""
|
114
140
|
Add a new slide to a presentation.
|
115
141
|
|
@@ -118,9 +144,11 @@ async def create_slide(
|
|
118
144
|
layout: The layout for the new slide (e.g., TITLE_AND_BODY, TITLE_ONLY, BLANK).
|
119
145
|
|
120
146
|
Returns:
|
121
|
-
|
147
|
+
SlidesCreateSlideOutput containing response data confirming slide creation.
|
122
148
|
"""
|
123
|
-
logger.info(
|
149
|
+
logger.info(
|
150
|
+
f"Executing create_slide in presentation '{presentation_id}' with layout '{layout}'"
|
151
|
+
)
|
124
152
|
if not presentation_id or not presentation_id.strip():
|
125
153
|
raise ValueError("Presentation ID cannot be empty")
|
126
154
|
# Optional: Validate layout against known predefined layouts?
|
@@ -131,7 +159,9 @@ async def create_slide(
|
|
131
159
|
if isinstance(result, dict) and result.get("error"):
|
132
160
|
raise ValueError(result.get("message", "Error creating slide"))
|
133
161
|
|
134
|
-
return
|
162
|
+
return SlidesCreateSlideOutput(
|
163
|
+
slide_id=result["slide_id"], presentation_id=presentation_id, layout=layout
|
164
|
+
)
|
135
165
|
|
136
166
|
|
137
167
|
@mcp.tool(
|
@@ -147,7 +177,7 @@ async def add_text_to_slide(
|
|
147
177
|
position_y: float = 100.0,
|
148
178
|
size_width: float = 400.0,
|
149
179
|
size_height: float = 100.0,
|
150
|
-
) ->
|
180
|
+
) -> SlidesAddTextOutput:
|
151
181
|
"""
|
152
182
|
Add text to a slide by creating a text box.
|
153
183
|
|
@@ -162,7 +192,7 @@ async def add_text_to_slide(
|
|
162
192
|
size_height: Height of the text box (default 100.0 PT).
|
163
193
|
|
164
194
|
Returns:
|
165
|
-
|
195
|
+
SlidesAddTextOutput containing response data confirming text addition.
|
166
196
|
"""
|
167
197
|
logger.info(f"Executing add_text_to_slide on slide '{slide_id}'")
|
168
198
|
if not presentation_id or not slide_id or text is None:
|
@@ -171,7 +201,9 @@ async def add_text_to_slide(
|
|
171
201
|
# Validate shape_type
|
172
202
|
valid_shape_types = {"TEXT_BOX"}
|
173
203
|
if shape_type not in valid_shape_types:
|
174
|
-
raise ValueError(
|
204
|
+
raise ValueError(
|
205
|
+
f"Invalid shape_type '{shape_type}' provided. Must be one of {valid_shape_types}."
|
206
|
+
)
|
175
207
|
|
176
208
|
slides_service = SlidesService()
|
177
209
|
result = slides_service.add_text(
|
@@ -186,7 +218,11 @@ async def add_text_to_slide(
|
|
186
218
|
if isinstance(result, dict) and result.get("error"):
|
187
219
|
raise ValueError(result.get("message", "Error adding text to slide"))
|
188
220
|
|
189
|
-
return
|
221
|
+
return SlidesAddTextOutput(
|
222
|
+
element_id=result.get("element_id", ""),
|
223
|
+
presentation_id=presentation_id,
|
224
|
+
slide_id=slide_id,
|
225
|
+
)
|
190
226
|
|
191
227
|
|
192
228
|
@mcp.tool(
|
@@ -201,7 +237,7 @@ async def add_formatted_text_to_slide(
|
|
201
237
|
position_y: float = 100.0,
|
202
238
|
size_width: float = 400.0,
|
203
239
|
size_height: float = 100.0,
|
204
|
-
) ->
|
240
|
+
) -> SlidesAddFormattedTextOutput:
|
205
241
|
"""
|
206
242
|
Add formatted text to a slide with markdown-style formatting.
|
207
243
|
|
@@ -215,7 +251,7 @@ async def add_formatted_text_to_slide(
|
|
215
251
|
size_height: Height of the text box (default 100.0 PT).
|
216
252
|
|
217
253
|
Returns:
|
218
|
-
|
254
|
+
SlidesAddFormattedTextOutput containing response data confirming text addition.
|
219
255
|
"""
|
220
256
|
logger.info(f"Executing add_formatted_text_to_slide on slide '{slide_id}'")
|
221
257
|
if not presentation_id or not slide_id or text is None:
|
@@ -233,7 +269,12 @@ async def add_formatted_text_to_slide(
|
|
233
269
|
if isinstance(result, dict) and result.get("error"):
|
234
270
|
raise ValueError(result.get("message", "Error adding formatted text to slide"))
|
235
271
|
|
236
|
-
return
|
272
|
+
return SlidesAddFormattedTextOutput(
|
273
|
+
element_id=result.get("element_id", ""),
|
274
|
+
presentation_id=presentation_id,
|
275
|
+
slide_id=slide_id,
|
276
|
+
formatting_applied=result.get("formatting_applied", True),
|
277
|
+
)
|
237
278
|
|
238
279
|
|
239
280
|
@mcp.tool(
|
@@ -248,7 +289,7 @@ async def add_bulleted_list_to_slide(
|
|
248
289
|
position_y: float = 100.0,
|
249
290
|
size_width: float = 400.0,
|
250
291
|
size_height: float = 200.0,
|
251
|
-
) ->
|
292
|
+
) -> SlidesAddListOutput:
|
252
293
|
"""
|
253
294
|
Add a bulleted list to a slide.
|
254
295
|
|
@@ -262,7 +303,7 @@ async def add_bulleted_list_to_slide(
|
|
262
303
|
size_height: Height of the text box (default 200.0 PT).
|
263
304
|
|
264
305
|
Returns:
|
265
|
-
|
306
|
+
SlidesAddListOutput containing response data confirming list addition.
|
266
307
|
"""
|
267
308
|
logger.info(f"Executing add_bulleted_list_to_slide on slide '{slide_id}'")
|
268
309
|
if not presentation_id or not slide_id or not items:
|
@@ -280,7 +321,12 @@ async def add_bulleted_list_to_slide(
|
|
280
321
|
if isinstance(result, dict) and result.get("error"):
|
281
322
|
raise ValueError(result.get("message", "Error adding bulleted list to slide"))
|
282
323
|
|
283
|
-
return
|
324
|
+
return SlidesAddListOutput(
|
325
|
+
element_id=result.get("element_id", ""),
|
326
|
+
presentation_id=presentation_id,
|
327
|
+
slide_id=slide_id,
|
328
|
+
items_count=len(items),
|
329
|
+
)
|
284
330
|
|
285
331
|
|
286
332
|
@mcp.tool(
|
@@ -297,7 +343,7 @@ async def add_table_to_slide(
|
|
297
343
|
position_y: float = 100.0,
|
298
344
|
size_width: float = 400.0,
|
299
345
|
size_height: float = 200.0,
|
300
|
-
) ->
|
346
|
+
) -> SlidesAddTableOutput:
|
301
347
|
"""
|
302
348
|
Add a table to a slide.
|
303
349
|
|
@@ -313,7 +359,7 @@ async def add_table_to_slide(
|
|
313
359
|
size_height: Height of the table (default 200.0 PT).
|
314
360
|
|
315
361
|
Returns:
|
316
|
-
|
362
|
+
SlidesAddTableOutput containing response data confirming table addition.
|
317
363
|
"""
|
318
364
|
logger.info(f"Executing add_table_to_slide on slide '{slide_id}'")
|
319
365
|
if not presentation_id or not slide_id:
|
@@ -339,7 +385,13 @@ async def add_table_to_slide(
|
|
339
385
|
if isinstance(result, dict) and result.get("error"):
|
340
386
|
raise ValueError(result.get("message", "Error adding table to slide"))
|
341
387
|
|
342
|
-
return
|
388
|
+
return SlidesAddTableOutput(
|
389
|
+
element_id=result.get("element_id", ""),
|
390
|
+
presentation_id=presentation_id,
|
391
|
+
slide_id=slide_id,
|
392
|
+
rows=rows,
|
393
|
+
columns=columns,
|
394
|
+
)
|
343
395
|
|
344
396
|
|
345
397
|
@mcp.tool(
|
@@ -350,7 +402,7 @@ async def add_slide_notes(
|
|
350
402
|
presentation_id: str,
|
351
403
|
slide_id: str,
|
352
404
|
notes: str,
|
353
|
-
) ->
|
405
|
+
) -> SlidesAddNotesOutput:
|
354
406
|
"""
|
355
407
|
Add presenter notes to a slide.
|
356
408
|
|
@@ -360,7 +412,7 @@ async def add_slide_notes(
|
|
360
412
|
notes: The notes content to add.
|
361
413
|
|
362
414
|
Returns:
|
363
|
-
|
415
|
+
SlidesAddNotesOutput containing response data confirming notes addition.
|
364
416
|
"""
|
365
417
|
logger.info(f"Executing add_slide_notes on slide '{slide_id}'")
|
366
418
|
if not presentation_id or not slide_id or not notes:
|
@@ -376,7 +428,12 @@ async def add_slide_notes(
|
|
376
428
|
if isinstance(result, dict) and result.get("error"):
|
377
429
|
raise ValueError(result.get("message", "Error adding notes to slide"))
|
378
430
|
|
379
|
-
return
|
431
|
+
return SlidesAddNotesOutput(
|
432
|
+
success=result.get("success", True),
|
433
|
+
presentation_id=presentation_id,
|
434
|
+
slide_id=slide_id,
|
435
|
+
notes_length=len(notes),
|
436
|
+
)
|
380
437
|
|
381
438
|
|
382
439
|
@mcp.tool(
|
@@ -387,7 +444,7 @@ async def duplicate_slide(
|
|
387
444
|
presentation_id: str,
|
388
445
|
slide_id: str,
|
389
446
|
insert_at_index: int | None = None,
|
390
|
-
) ->
|
447
|
+
) -> SlidesDuplicateSlideOutput:
|
391
448
|
"""
|
392
449
|
Duplicate a slide in a presentation.
|
393
450
|
|
@@ -397,7 +454,7 @@ async def duplicate_slide(
|
|
397
454
|
insert_at_index: Optional index where to insert the duplicated slide.
|
398
455
|
|
399
456
|
Returns:
|
400
|
-
|
457
|
+
SlidesDuplicateSlideOutput containing response data with the new slide ID.
|
401
458
|
"""
|
402
459
|
logger.info(f"Executing duplicate_slide for slide '{slide_id}'")
|
403
460
|
if not presentation_id or not slide_id:
|
@@ -413,7 +470,11 @@ async def duplicate_slide(
|
|
413
470
|
if isinstance(result, dict) and result.get("error"):
|
414
471
|
raise ValueError(result.get("message", "Error duplicating slide"))
|
415
472
|
|
416
|
-
return
|
473
|
+
return SlidesDuplicateSlideOutput(
|
474
|
+
new_slide_id=result["new_slide_id"],
|
475
|
+
presentation_id=presentation_id,
|
476
|
+
source_slide_id=slide_id,
|
477
|
+
)
|
417
478
|
|
418
479
|
|
419
480
|
@mcp.tool(
|
@@ -423,7 +484,7 @@ async def duplicate_slide(
|
|
423
484
|
async def delete_slide(
|
424
485
|
presentation_id: str,
|
425
486
|
slide_id: str,
|
426
|
-
) ->
|
487
|
+
) -> SlidesDeleteSlideOutput:
|
427
488
|
"""
|
428
489
|
Delete a slide from a presentation.
|
429
490
|
|
@@ -432,19 +493,27 @@ async def delete_slide(
|
|
432
493
|
slide_id: The ID of the slide to delete.
|
433
494
|
|
434
495
|
Returns:
|
435
|
-
|
496
|
+
SlidesDeleteSlideOutput containing response data confirming slide deletion.
|
436
497
|
"""
|
437
|
-
logger.info(
|
498
|
+
logger.info(
|
499
|
+
f"Executing delete_slide: slide '{slide_id}' from presentation '{presentation_id}'"
|
500
|
+
)
|
438
501
|
if not presentation_id or not slide_id:
|
439
502
|
raise ValueError("Presentation ID and Slide ID are required")
|
440
503
|
|
441
504
|
slides_service = SlidesService()
|
442
|
-
result = slides_service.delete_slide(
|
505
|
+
result = slides_service.delete_slide(
|
506
|
+
presentation_id=presentation_id, slide_id=slide_id
|
507
|
+
)
|
443
508
|
|
444
509
|
if isinstance(result, dict) and result.get("error"):
|
445
510
|
raise ValueError(result.get("message", "Error deleting slide"))
|
446
511
|
|
447
|
-
return
|
512
|
+
return SlidesDeleteSlideOutput(
|
513
|
+
success=result.get("success", True),
|
514
|
+
presentation_id=presentation_id,
|
515
|
+
deleted_slide_id=slide_id,
|
516
|
+
)
|
448
517
|
|
449
518
|
|
450
519
|
@mcp.tool(
|
@@ -454,7 +523,7 @@ async def delete_slide(
|
|
454
523
|
async def create_presentation_from_markdown(
|
455
524
|
title: str,
|
456
525
|
markdown_content: str,
|
457
|
-
) ->
|
526
|
+
) -> SlidesCreateFromMarkdownOutput:
|
458
527
|
"""
|
459
528
|
Create a Google Slides presentation from Markdown using the markdowndeck library.
|
460
529
|
|
@@ -463,16 +532,218 @@ async def create_presentation_from_markdown(
|
|
463
532
|
markdown_content: Markdown content structured for slides.
|
464
533
|
|
465
534
|
Returns:
|
466
|
-
|
535
|
+
SlidesCreateFromMarkdownOutput containing created presentation data.
|
467
536
|
"""
|
468
537
|
logger.info(f"Executing create_presentation_from_markdown with title '{title}'")
|
469
|
-
if
|
538
|
+
if (
|
539
|
+
not title
|
540
|
+
or not title.strip()
|
541
|
+
or not markdown_content
|
542
|
+
or not markdown_content.strip()
|
543
|
+
):
|
470
544
|
raise ValueError("Title and markdown content are required")
|
471
545
|
|
472
546
|
slides_service = SlidesService()
|
473
|
-
result = slides_service.create_presentation_from_markdown(
|
547
|
+
result = slides_service.create_presentation_from_markdown(
|
548
|
+
title=title, markdown_content=markdown_content
|
549
|
+
)
|
550
|
+
|
551
|
+
if isinstance(result, dict) and result.get("error"):
|
552
|
+
raise ValueError(
|
553
|
+
result.get("message", "Error creating presentation from Markdown")
|
554
|
+
)
|
555
|
+
|
556
|
+
return SlidesCreateFromMarkdownOutput(
|
557
|
+
presentation_id=result["presentation_id"],
|
558
|
+
title=result["title"],
|
559
|
+
presentation_url=result["presentation_url"],
|
560
|
+
slides_created=result.get("slides_created", 0),
|
561
|
+
)
|
562
|
+
|
563
|
+
|
564
|
+
@mcp.tool(name="share_presentation_with_domain")
|
565
|
+
async def share_presentation_with_domain(
|
566
|
+
presentation_id: str,
|
567
|
+
) -> SlidesSharePresentationOutput:
|
568
|
+
"""
|
569
|
+
Shares a Google Slides presentation with the entire organization domain.
|
570
|
+
The domain is configured by the server administrator.
|
571
|
+
|
572
|
+
This tool makes the presentation viewable by anyone in the organization.
|
573
|
+
|
574
|
+
Args:
|
575
|
+
presentation_id: The ID of the Google Slides presentation to share.
|
576
|
+
|
577
|
+
Returns:
|
578
|
+
SlidesSharePresentationOutput containing response data confirming the sharing operation.
|
579
|
+
"""
|
580
|
+
logger.info(
|
581
|
+
f"Executing share_presentation_with_domain for presentation ID: '{presentation_id}'"
|
582
|
+
)
|
583
|
+
|
584
|
+
if not presentation_id or not presentation_id.strip():
|
585
|
+
raise ValueError("Presentation ID cannot be empty.")
|
586
|
+
|
587
|
+
sharing_domain = "rizzbuzz.com"
|
588
|
+
|
589
|
+
drive_service = DriveService()
|
590
|
+
result = drive_service.share_file_with_domain(
|
591
|
+
file_id=presentation_id, domain=sharing_domain, role="reader"
|
592
|
+
)
|
474
593
|
|
475
594
|
if isinstance(result, dict) and result.get("error"):
|
476
|
-
raise ValueError(
|
595
|
+
raise ValueError(
|
596
|
+
result.get("message", "Failed to share presentation with domain.")
|
597
|
+
)
|
477
598
|
|
478
|
-
|
599
|
+
# Construct the shareable link
|
600
|
+
presentation_link = f"https://docs.google.com/presentation/d/{presentation_id}/"
|
601
|
+
|
602
|
+
return SlidesSharePresentationOutput(
|
603
|
+
success=True,
|
604
|
+
message=f"Presentation successfully shared with the '{sharing_domain}' domain.",
|
605
|
+
presentation_id=presentation_id,
|
606
|
+
presentation_link=presentation_link,
|
607
|
+
domain=sharing_domain,
|
608
|
+
role="reader",
|
609
|
+
)
|
610
|
+
|
611
|
+
|
612
|
+
# @mcp.tool(name="insert_chart_from_data")
|
613
|
+
async def insert_chart_from_data(
|
614
|
+
presentation_id: str,
|
615
|
+
slide_id: str,
|
616
|
+
chart_type: str,
|
617
|
+
data: list[list[Any]],
|
618
|
+
title: str,
|
619
|
+
position_x: float = 50.0,
|
620
|
+
position_y: float = 50.0,
|
621
|
+
size_width: float = 480.0,
|
622
|
+
size_height: float = 320.0,
|
623
|
+
) -> SlidesInsertChartOutput:
|
624
|
+
"""
|
625
|
+
Creates and embeds a native, theme-aware Google Chart into a slide from a data table.
|
626
|
+
This tool handles the entire process: creating a data sheet in a dedicated Drive folder,
|
627
|
+
generating the chart, and embedding it into the slide.
|
628
|
+
|
629
|
+
Supported `chart_type` values:
|
630
|
+
- 'BAR': For bar charts. The API creates a vertical column chart.
|
631
|
+
- 'LINE': For line charts.
|
632
|
+
- 'PIE': For pie charts.
|
633
|
+
- 'COLUMN': For vertical column charts (identical to 'BAR').
|
634
|
+
|
635
|
+
Required `data` format:
|
636
|
+
The data must be a list of lists, where the first inner list contains the column headers.
|
637
|
+
Example: [["Month", "Revenue"], ["Jan", 2500], ["Feb", 3100], ["Mar", 2800]]
|
638
|
+
|
639
|
+
Args:
|
640
|
+
presentation_id: The ID of the presentation to add the chart to.
|
641
|
+
slide_id: The ID of the slide where the chart will be placed.
|
642
|
+
chart_type: The type of chart to create ('BAR', 'LINE', 'PIE', 'COLUMN').
|
643
|
+
data: A list of lists containing the chart data, with headers in the first row.
|
644
|
+
title: The title that will appear on the chart.
|
645
|
+
position_x: The X-coordinate for the chart's top-left corner on the slide (in points).
|
646
|
+
position_y: The Y-coordinate for the chart's top-left corner on the slide (in points).
|
647
|
+
size_width: The width of the chart on the slide (in points).
|
648
|
+
size_height: The height of the chart on the slide (in points).
|
649
|
+
|
650
|
+
Returns:
|
651
|
+
SlidesInsertChartOutput containing response data confirming the chart creation and embedding.
|
652
|
+
"""
|
653
|
+
logger.info(
|
654
|
+
f"Executing insert_chart_from_data: type='{chart_type}', title='{title}'"
|
655
|
+
)
|
656
|
+
sheets_service = SheetsService()
|
657
|
+
slides_service = SlidesService()
|
658
|
+
drive_service = DriveService()
|
659
|
+
|
660
|
+
spreadsheet_id = None
|
661
|
+
try:
|
662
|
+
# 1. Get the dedicated folder for storing data sheets
|
663
|
+
data_folder_id = drive_service._get_or_create_data_folder()
|
664
|
+
|
665
|
+
# 2. Create a temporary Google Sheet for the data
|
666
|
+
sheet_title = f"[Chart Data] - {title}"
|
667
|
+
sheet_result = sheets_service.create_spreadsheet(title=sheet_title)
|
668
|
+
if not sheet_result or sheet_result.get("error"):
|
669
|
+
raise RuntimeError(
|
670
|
+
f"Failed to create data sheet: {sheet_result.get('message')}"
|
671
|
+
)
|
672
|
+
|
673
|
+
spreadsheet_id = sheet_result["spreadsheet_id"]
|
674
|
+
|
675
|
+
# Move the new sheet to the correct folder and remove it from root
|
676
|
+
drive_service.service.files().update(
|
677
|
+
fileId=spreadsheet_id,
|
678
|
+
addParents=data_folder_id,
|
679
|
+
removeParents="root",
|
680
|
+
fields="id, parents",
|
681
|
+
).execute()
|
682
|
+
logger.info(f"Moved data sheet {spreadsheet_id} to folder {data_folder_id}")
|
683
|
+
|
684
|
+
# 3. Write the data to the temporary sheet
|
685
|
+
num_rows = len(data)
|
686
|
+
num_cols = len(data[0]) if data else 0
|
687
|
+
if num_rows == 0 or num_cols < 2:
|
688
|
+
raise ValueError(
|
689
|
+
"Data must have at least one header row and one data column."
|
690
|
+
)
|
691
|
+
|
692
|
+
range_a1 = f"Sheet1!A1:{chr(ord('A') + num_cols - 1)}{num_rows}"
|
693
|
+
write_result = sheets_service.write_range(spreadsheet_id, range_a1, data)
|
694
|
+
if not write_result or write_result.get("error"):
|
695
|
+
raise RuntimeError(
|
696
|
+
f"Failed to write data to sheet: {write_result.get('message')}"
|
697
|
+
)
|
698
|
+
|
699
|
+
# 4. Create the chart object within the sheet
|
700
|
+
metadata = sheets_service.get_spreadsheet_metadata(spreadsheet_id)
|
701
|
+
sheet_id_numeric = metadata["sheets"][0]["properties"]["sheetId"]
|
702
|
+
|
703
|
+
# --- START OF FIX: Map user-friendly chart type to API-specific chart type ---
|
704
|
+
chart_type_upper = chart_type.upper()
|
705
|
+
if chart_type_upper in ["BAR", "COLUMN"]:
|
706
|
+
api_chart_type = "COLUMN"
|
707
|
+
elif chart_type_upper == "PIE":
|
708
|
+
api_chart_type = "PIE_CHART"
|
709
|
+
else:
|
710
|
+
api_chart_type = chart_type_upper
|
711
|
+
# --- END OF FIX ---
|
712
|
+
|
713
|
+
chart_result = sheets_service.create_chart_on_sheet(
|
714
|
+
spreadsheet_id, sheet_id_numeric, api_chart_type, num_rows, num_cols, title
|
715
|
+
)
|
716
|
+
if not chart_result or chart_result.get("error"):
|
717
|
+
raise RuntimeError(
|
718
|
+
f"Failed to create chart in sheet: {chart_result.get('message')}"
|
719
|
+
)
|
720
|
+
chart_id = chart_result["chartId"]
|
721
|
+
|
722
|
+
# 5. Embed the chart into the Google Slide
|
723
|
+
embed_result = slides_service.embed_sheets_chart(
|
724
|
+
presentation_id,
|
725
|
+
slide_id,
|
726
|
+
spreadsheet_id,
|
727
|
+
chart_id,
|
728
|
+
position=(position_x, position_y),
|
729
|
+
size=(size_width, size_height),
|
730
|
+
)
|
731
|
+
if not embed_result or embed_result.get("error"):
|
732
|
+
raise RuntimeError(
|
733
|
+
f"Failed to embed chart into slide: {embed_result.get('message')}"
|
734
|
+
)
|
735
|
+
|
736
|
+
return SlidesInsertChartOutput(
|
737
|
+
success=True,
|
738
|
+
message=f"Successfully added native '{title}' chart to slide.",
|
739
|
+
presentation_id=presentation_id,
|
740
|
+
slide_id=slide_id,
|
741
|
+
chart_element_id=embed_result.get("element_id"),
|
742
|
+
)
|
743
|
+
|
744
|
+
except Exception as e:
|
745
|
+
logger.error(f"Chart creation workflow failed: {e}", exc_info=True)
|
746
|
+
# Re-raise to ensure the MCP framework catches it and reports an error
|
747
|
+
raise RuntimeError(
|
748
|
+
f"An error occurred during the chart creation process: {e}"
|
749
|
+
) from e
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: google-workspace-mcp
|
3
|
-
Version: 1.0
|
3
|
+
Version: 1.2.0
|
4
4
|
Summary: MCP server for Google Workspace integration
|
5
5
|
Author-email: Arclio Team <info@arclio.com>
|
6
6
|
License: MIT
|
@@ -11,8 +11,9 @@ Requires-Dist: google-auth-httplib2>=0.1.0
|
|
11
11
|
Requires-Dist: google-auth-oauthlib>=1.0.0
|
12
12
|
Requires-Dist: google-auth>=2.22.0
|
13
13
|
Requires-Dist: markdown>=3.5.0
|
14
|
-
Requires-Dist: markdowndeck>=0.1.
|
15
|
-
Requires-Dist: mcp>=1.
|
14
|
+
Requires-Dist: markdowndeck>=0.1.5
|
15
|
+
Requires-Dist: mcp>=1.12.0
|
16
|
+
Requires-Dist: pyhumps>=3.8.0
|
16
17
|
Requires-Dist: python-dotenv>=1.0.0
|
17
18
|
Requires-Dist: pytz>=2023.3
|
18
19
|
Provides-Extra: dev
|
@@ -2,6 +2,7 @@ google_workspace_mcp/__init__.py,sha256=CppIQ682LvBlR1IPwBOsryB6rk-P4tDy9bPK1OYt
|
|
2
2
|
google_workspace_mcp/__main__.py,sha256=Hue-McVAyaxJTJxTR8Iu_oMeoslRjybaTEyjDv3XT_8,1710
|
3
3
|
google_workspace_mcp/app.py,sha256=ajasCHBQ8dWiBoLKDibrkj6SsGyt1pNupwQO9fcsZzE,160
|
4
4
|
google_workspace_mcp/config.py,sha256=DNac38UANs0A2RX4fTi1sMaJ5mTrOaaWnPEusL6Id_M,2578
|
5
|
+
google_workspace_mcp/models.py,sha256=wDqlkNbLNqFRfNt3Lhgo2b0GMpXnZrFcRMylUiHVp-o,18573
|
5
6
|
google_workspace_mcp/auth/__init__.py,sha256=4SNTCWZqUJGn20vj8j5WekpwNnm87-KeiKFfOGG4b1M,126
|
6
7
|
google_workspace_mcp/auth/gauth.py,sha256=2-djqz3hb6_QIhgcUTk9vxBlVH3pCjQYz_pPqOogf2E,2355
|
7
8
|
google_workspace_mcp/prompts/__init__.py,sha256=BG6Ol0ZgE5e85jHOd6MlvpbqkNwhRcuZaEGpDvjEvi4,61
|
@@ -17,22 +18,22 @@ google_workspace_mcp/resources/sheets_resources.py,sha256=kGdVFgcm56ebN7XGx92975
|
|
17
18
|
google_workspace_mcp/resources/slides.py,sha256=m2KVCYUOJ9uKMVWXXw9lXNem3OFT438X04UxZ7IUZJo,14233
|
18
19
|
google_workspace_mcp/services/__init__.py,sha256=crw4YinYFi7QDfJZUCcUqPliRtlViSE7QSqAX8j33eE,473
|
19
20
|
google_workspace_mcp/services/base.py,sha256=uLtFB158kaY_n3JWKgW2kxQ2tx9SP9zxX-PWuPxydFI,2378
|
20
|
-
google_workspace_mcp/services/calendar.py,sha256=
|
21
|
+
google_workspace_mcp/services/calendar.py,sha256=KfclfXF_70MWEhMsDpjlTtoLHSgx2lpXMy78PtRqsuc,8510
|
21
22
|
google_workspace_mcp/services/docs_service.py,sha256=e67bvDb0lAmDbSXYRxPAIH93rXPbHS0Fq07c-eBcy08,25286
|
22
|
-
google_workspace_mcp/services/drive.py,sha256=
|
23
|
+
google_workspace_mcp/services/drive.py,sha256=Tjj_6Q7EgHE6SxA_yKP6OTkIgrxdWUrlhQVvJQTl900,26471
|
23
24
|
google_workspace_mcp/services/gmail.py,sha256=M6trjL9uFOe5WnBORyic4Y7lU4Z0X9VnEq-yU7RY-No,24846
|
24
|
-
google_workspace_mcp/services/sheets_service.py,sha256=
|
25
|
-
google_workspace_mcp/services/slides.py,sha256=
|
25
|
+
google_workspace_mcp/services/sheets_service.py,sha256=mivK9gfywyhT7VUhXqQllQYZ3nQ440bNjk8ExWddgv8,25601
|
26
|
+
google_workspace_mcp/services/slides.py,sha256=HdL0xDh8nKffXNoiXSHMx-7lcdOPtpQRau_bnu72bN0,42247
|
26
27
|
google_workspace_mcp/tools/__init__.py,sha256=vwa7hV8HwrLqs3Sf7RTrt1MlVZ-KjptXuFDSJt5tWzA,107
|
27
|
-
google_workspace_mcp/tools/calendar.py,sha256=
|
28
|
-
google_workspace_mcp/tools/docs_tools.py,sha256=
|
29
|
-
google_workspace_mcp/tools/drive.py,sha256=
|
30
|
-
google_workspace_mcp/tools/gmail.py,sha256=
|
31
|
-
google_workspace_mcp/tools/sheets_tools.py,sha256=
|
32
|
-
google_workspace_mcp/tools/slides.py,sha256=
|
28
|
+
google_workspace_mcp/tools/calendar.py,sha256=ltM_9tziMRXl-o-Md0AsmUA4lA-KuHPKibs8ebNE2l8,7803
|
29
|
+
google_workspace_mcp/tools/docs_tools.py,sha256=Lbd5mZC1EyW4VW2VNbtfIvEHB6a0eFUHZc1wDxJPKAU,13869
|
30
|
+
google_workspace_mcp/tools/drive.py,sha256=NWec8SjyhPqrApxNioFIUIV3XaEoQHjW-qPcJExBo7U,16740
|
31
|
+
google_workspace_mcp/tools/gmail.py,sha256=V54H45Okukd6wbhMVSj4Y5b-laVOL1dSacWS4oaU5Oo,12872
|
32
|
+
google_workspace_mcp/tools/sheets_tools.py,sha256=z7yJ0V2KciBIKV1yKEIxuFucQM4Fl3XbggPRW4FQt9s,13139
|
33
|
+
google_workspace_mcp/tools/slides.py,sha256=zAY1iVeNUJrrURHq5MbduUU08idiBn_hoGj4CzIEtbY,25612
|
33
34
|
google_workspace_mcp/utils/__init__.py,sha256=jKEfO2DhR_lwsRSoUgceixDwzkGVlp_vmbrz_6AHOk0,50
|
34
35
|
google_workspace_mcp/utils/markdown_slides.py,sha256=G7EbK3DDki90hNilGzbczs2e345v-HjYB5GWhFbEtw4,21015
|
35
|
-
google_workspace_mcp-1.0.
|
36
|
-
google_workspace_mcp-1.0.
|
37
|
-
google_workspace_mcp-1.0.
|
38
|
-
google_workspace_mcp-1.0.
|
36
|
+
google_workspace_mcp-1.2.0.dist-info/METADATA,sha256=U-9r5_KcGGd-tFo9Qtc4s_a8y7fWga4UXpHbY1tNGEo,25135
|
37
|
+
google_workspace_mcp-1.2.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
38
|
+
google_workspace_mcp-1.2.0.dist-info/entry_points.txt,sha256=t9eBYTnGzEBpiiRx_SCTqforubwM6Ebf5mszIj-MjeA,79
|
39
|
+
google_workspace_mcp-1.2.0.dist-info/RECORD,,
|