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.
@@ -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) -> dict[str, Any]:
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
- Presentation data dictionary or raises error.
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
- # Return raw service result
42
- return result
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) -> dict[str, Any]:
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
- A dictionary containing the list of slides or an error message.
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
- return {"message": "No slides found in this presentation."}
93
+ slides = []
71
94
 
72
- # Return raw service result
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
- ) -> dict[str, Any]:
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
- Created presentation data or raises error.
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 result
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
- ) -> dict[str, Any]:
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
- Response data confirming slide creation or raises error.
147
+ SlidesCreateSlideOutput containing response data confirming slide creation.
122
148
  """
123
- logger.info(f"Executing create_slide in presentation '{presentation_id}' with layout '{layout}'")
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 result
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
- ) -> dict[str, Any]:
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
- Response data confirming text addition or raises error.
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(f"Invalid shape_type '{shape_type}' provided. Must be one of {valid_shape_types}.")
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 result
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
- ) -> dict[str, Any]:
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
- Response data confirming text addition or raises error.
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 result
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
- ) -> dict[str, Any]:
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
- Response data confirming list addition or raises error.
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 result
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
- ) -> dict[str, Any]:
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
- Response data confirming table addition or raises error.
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 result
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
- ) -> dict[str, Any]:
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
- Response data confirming notes addition or raises error.
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 result
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
- ) -> dict[str, Any]:
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
- Response data with the new slide ID or raises error.
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 result
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
- ) -> dict[str, Any]:
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
- Response data confirming slide deletion or raises error.
496
+ SlidesDeleteSlideOutput containing response data confirming slide deletion.
436
497
  """
437
- logger.info(f"Executing delete_slide: slide '{slide_id}' from presentation '{presentation_id}'")
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(presentation_id=presentation_id, slide_id=slide_id)
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 result
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
- ) -> dict[str, Any]:
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
- Created presentation data or raises error.
535
+ SlidesCreateFromMarkdownOutput containing created presentation data.
467
536
  """
468
537
  logger.info(f"Executing create_presentation_from_markdown with title '{title}'")
469
- if not title or not title.strip() or not markdown_content or not markdown_content.strip():
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(title=title, markdown_content=markdown_content)
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(result.get("message", "Error creating presentation from Markdown"))
595
+ raise ValueError(
596
+ result.get("message", "Failed to share presentation with domain.")
597
+ )
477
598
 
478
- return result
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.5
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.4
15
- Requires-Dist: mcp>=1.7.0
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=_FvgDKfMvFWIamDHOimY3cVK2zPk0o7iUbbKeJehaHY,8370
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=9ReL9XkRLpK7ZS1KngHVv2ZGykeOo4N5ncp-gSYcAEA,16804
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=67glYCTuQynka_vAXxCsM2U5SMuRNXXpTff2G-X-fT0,18068
25
- google_workspace_mcp/services/slides.py,sha256=qfXh6GBr2JkCGPgVmwvV4NapLlzF7A1_WlYQR8EXMEE,37310
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=KX42ZHNMcO69GuU-Re-37HY1Kk0jGM0dwUSrkexi_So,7890
28
- google_workspace_mcp/tools/docs_tools.py,sha256=L4zgjBAZxGYEnvNzUj_-leNXc1tIghzmDBinng2uMjI,12677
29
- google_workspace_mcp/tools/drive.py,sha256=HUL_ozcTvWOIXKM0eQpLLYY6Yu_V2aFyhPVWW8GTpyw,7132
30
- google_workspace_mcp/tools/gmail.py,sha256=nkbN6IC8yhSxrNwxUVRh9qAakomKJjGP4-MEKJoTnpA,11269
31
- google_workspace_mcp/tools/sheets_tools.py,sha256=1lC_6oeXCZQDFI4f_hHrOZOnu-t6iVeQ2ErYwhS172Q,11760
32
- google_workspace_mcp/tools/slides.py,sha256=GTtkIaQf6GMtzaxu8wlAemWcY5n5Y6RMEwWILPoiMFo,15382
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.5.dist-info/METADATA,sha256=lJBTaKQvTD2WIqBRRhdQ2whSkJLx7pkchqKofMf6xNQ,25104
36
- google_workspace_mcp-1.0.5.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
37
- google_workspace_mcp-1.0.5.dist-info/entry_points.txt,sha256=t9eBYTnGzEBpiiRx_SCTqforubwM6Ebf5mszIj-MjeA,79
38
- google_workspace_mcp-1.0.5.dist-info/RECORD,,
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,,