workspace-mcp 1.1.5__py3-none-any.whl → 1.1.7__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.
gmail/gmail_tools.py CHANGED
@@ -111,28 +111,31 @@ def _format_gmail_results_plain(messages: list, query: str) -> str:
111
111
  message_url = _generate_gmail_web_url(msg["id"])
112
112
  thread_url = _generate_gmail_web_url(msg["threadId"])
113
113
 
114
- lines.extend([
115
- f" {i}. Message ID: {msg['id']}",
116
- f" Web Link: {message_url}",
117
- f" Thread ID: {msg['threadId']}",
118
- f" Thread Link: {thread_url}",
119
- ""
120
- ])
121
-
122
- lines.extend([
123
- "💡 USAGE:",
124
- " • Pass the Message IDs **as a list** to get_gmail_messages_content_batch()",
125
- " e.g. get_gmail_messages_content_batch(message_ids=[...])",
126
- " Pass the Thread IDs to get_gmail_thread_content() (single) _or_",
127
- " get_gmail_threads_content_batch() (coming soon)"
128
- ])
114
+ lines.extend(
115
+ [
116
+ f" {i}. Message ID: {msg['id']}",
117
+ f" Web Link: {message_url}",
118
+ f" Thread ID: {msg['threadId']}",
119
+ f" Thread Link: {thread_url}",
120
+ "",
121
+ ]
122
+ )
123
+
124
+ lines.extend(
125
+ [
126
+ "💡 USAGE:",
127
+ " Pass the Message IDs **as a list** to get_gmail_messages_content_batch()",
128
+ " e.g. get_gmail_messages_content_batch(message_ids=[...])",
129
+ " • Pass the Thread IDs to get_gmail_thread_content() (single) or get_gmail_threads_content_batch() (batch)",
130
+ ]
131
+ )
129
132
 
130
133
  return "\n".join(lines)
131
134
 
132
135
 
133
136
  @server.tool()
137
+ @handle_http_errors("search_gmail_messages", is_read_only=True)
134
138
  @require_google_service("gmail", "gmail_read")
135
- @handle_http_errors("search_gmail_messages")
136
139
  async def search_gmail_messages(
137
140
  service, query: str, user_google_email: str, page_size: int = 10
138
141
  ) -> str:
@@ -148,7 +151,9 @@ async def search_gmail_messages(
148
151
  Returns:
149
152
  str: LLM-friendly structured results with Message IDs, Thread IDs, and clickable Gmail web interface URLs for each found message.
150
153
  """
151
- logger.info(f"[search_gmail_messages] Email: '{user_google_email}', Query: '{query}'")
154
+ logger.info(
155
+ f"[search_gmail_messages] Email: '{user_google_email}', Query: '{query}'"
156
+ )
152
157
 
153
158
  response = await asyncio.to_thread(
154
159
  service.users()
@@ -164,8 +169,8 @@ async def search_gmail_messages(
164
169
 
165
170
 
166
171
  @server.tool()
172
+ @handle_http_errors("get_gmail_message_content", is_read_only=True)
167
173
  @require_google_service("gmail", "gmail_read")
168
- @handle_http_errors("get_gmail_message_content")
169
174
  async def get_gmail_message_content(
170
175
  service, message_id: str, user_google_email: str
171
176
  ) -> str:
@@ -232,8 +237,8 @@ async def get_gmail_message_content(
232
237
 
233
238
 
234
239
  @server.tool()
240
+ @handle_http_errors("get_gmail_messages_content_batch", is_read_only=True)
235
241
  @require_google_service("gmail", "gmail_read")
236
- @handle_http_errors("get_gmail_messages_content_batch")
237
242
  async def get_gmail_messages_content_batch(
238
243
  service,
239
244
  message_ids: List[str],
@@ -263,7 +268,7 @@ async def get_gmail_messages_content_batch(
263
268
 
264
269
  # Process in chunks of 100 (Gmail batch limit)
265
270
  for chunk_start in range(0, len(message_ids), 100):
266
- chunk_ids = message_ids[chunk_start:chunk_start + 100]
271
+ chunk_ids = message_ids[chunk_start : chunk_start + 100]
267
272
  results: Dict[str, Dict] = {}
268
273
 
269
274
  def _batch_callback(request_id, response, exception):
@@ -276,17 +281,21 @@ async def get_gmail_messages_content_batch(
276
281
 
277
282
  for mid in chunk_ids:
278
283
  if format == "metadata":
279
- req = service.users().messages().get(
280
- userId="me",
281
- id=mid,
282
- format="metadata",
283
- metadataHeaders=["Subject", "From"]
284
+ req = (
285
+ service.users()
286
+ .messages()
287
+ .get(
288
+ userId="me",
289
+ id=mid,
290
+ format="metadata",
291
+ metadataHeaders=["Subject", "From"],
292
+ )
284
293
  )
285
294
  else:
286
- req = service.users().messages().get(
287
- userId="me",
288
- id=mid,
289
- format="full"
295
+ req = (
296
+ service.users()
297
+ .messages()
298
+ .get(userId="me", id=mid, format="full")
290
299
  )
291
300
  batch.add(req, request_id=mid)
292
301
 
@@ -303,20 +312,22 @@ async def get_gmail_messages_content_batch(
303
312
  try:
304
313
  if format == "metadata":
305
314
  msg = await asyncio.to_thread(
306
- service.users().messages().get(
315
+ service.users()
316
+ .messages()
317
+ .get(
307
318
  userId="me",
308
319
  id=mid,
309
320
  format="metadata",
310
- metadataHeaders=["Subject", "From"]
311
- ).execute
321
+ metadataHeaders=["Subject", "From"],
322
+ )
323
+ .execute
312
324
  )
313
325
  else:
314
326
  msg = await asyncio.to_thread(
315
- service.users().messages().get(
316
- userId="me",
317
- id=mid,
318
- format="full"
319
- ).execute
327
+ service.users()
328
+ .messages()
329
+ .get(userId="me", id=mid, format="full")
330
+ .execute
320
331
  )
321
332
  return mid, msg, None
322
333
  except Exception as e:
@@ -324,8 +335,7 @@ async def get_gmail_messages_content_batch(
324
335
 
325
336
  # Fetch all messages in parallel
326
337
  fetch_results = await asyncio.gather(
327
- *[fetch_message(mid) for mid in chunk_ids],
328
- return_exceptions=False
338
+ *[fetch_message(mid) for mid in chunk_ids], return_exceptions=False
329
339
  )
330
340
 
331
341
  # Convert to results format
@@ -337,15 +347,11 @@ async def get_gmail_messages_content_batch(
337
347
  entry = results.get(mid, {"data": None, "error": "No result"})
338
348
 
339
349
  if entry["error"]:
340
- output_messages.append(
341
- f"⚠️ Message {mid}: {entry['error']}\n"
342
- )
350
+ output_messages.append(f"⚠️ Message {mid}: {entry['error']}\n")
343
351
  else:
344
352
  message = entry["data"]
345
353
  if not message:
346
- output_messages.append(
347
- f"⚠️ Message {mid}: No data returned\n"
348
- )
354
+ output_messages.append(f"⚠️ Message {mid}: No data returned\n")
349
355
  continue
350
356
 
351
357
  # Extract content based on format
@@ -385,8 +391,8 @@ async def get_gmail_messages_content_batch(
385
391
 
386
392
 
387
393
  @server.tool()
388
- @require_google_service("gmail", GMAIL_SEND_SCOPE)
389
394
  @handle_http_errors("send_gmail_message")
395
+ @require_google_service("gmail", GMAIL_SEND_SCOPE)
390
396
  async def send_gmail_message(
391
397
  service,
392
398
  user_google_email: str,
@@ -422,8 +428,8 @@ async def send_gmail_message(
422
428
 
423
429
 
424
430
  @server.tool()
425
- @require_google_service("gmail", GMAIL_COMPOSE_SCOPE)
426
431
  @handle_http_errors("draft_gmail_message")
432
+ @require_google_service("gmail", GMAIL_COMPOSE_SCOPE)
427
433
  async def draft_gmail_message(
428
434
  service,
429
435
  user_google_email: str,
@@ -468,35 +474,18 @@ async def draft_gmail_message(
468
474
  return f"Draft created! Draft ID: {draft_id}"
469
475
 
470
476
 
471
- @server.tool()
472
- @require_google_service("gmail", "gmail_read")
473
- @handle_http_errors("get_gmail_thread_content")
474
- async def get_gmail_thread_content(
475
- service, thread_id: str, user_google_email: str
476
- ) -> str:
477
+ def _format_thread_content(thread_data: dict, thread_id: str) -> str:
477
478
  """
478
- Retrieves the complete content of a Gmail conversation thread, including all messages.
479
+ Helper function to format thread content from Gmail API response.
479
480
 
480
481
  Args:
481
- thread_id (str): The unique ID of the Gmail thread to retrieve.
482
- user_google_email (str): The user's Google email address. Required.
482
+ thread_data (dict): Thread data from Gmail API
483
+ thread_id (str): Thread ID for display
483
484
 
484
485
  Returns:
485
- str: The complete thread content with all messages formatted for reading.
486
+ str: Formatted thread content
486
487
  """
487
- logger.info(
488
- f"[get_gmail_thread_content] Invoked. Thread ID: '{thread_id}', Email: '{user_google_email}'"
489
- )
490
-
491
- # Fetch the complete thread with all messages
492
- thread_response = await asyncio.to_thread(
493
- service.users()
494
- .threads()
495
- .get(userId="me", id=thread_id, format="full")
496
- .execute
497
- )
498
-
499
- messages = thread_response.get("messages", [])
488
+ messages = thread_data.get("messages", [])
500
489
  if not messages:
501
490
  return f"No messages found in thread '{thread_id}'."
502
491
 
@@ -520,8 +509,7 @@ async def get_gmail_thread_content(
520
509
  for i, message in enumerate(messages, 1):
521
510
  # Extract headers
522
511
  headers = {
523
- h["name"]: h["value"]
524
- for h in message.get("payload", {}).get("headers", [])
512
+ h["name"]: h["value"] for h in message.get("payload", {}).get("headers", [])
525
513
  }
526
514
 
527
515
  sender = headers.get("From", "(unknown sender)")
@@ -553,13 +541,134 @@ async def get_gmail_thread_content(
553
541
  ]
554
542
  )
555
543
 
556
- content_text = "\n".join(content_lines)
557
- return content_text
544
+ return "\n".join(content_lines)
558
545
 
559
546
 
560
547
  @server.tool()
561
548
  @require_google_service("gmail", "gmail_read")
562
- @handle_http_errors("list_gmail_labels")
549
+ @handle_http_errors("get_gmail_thread_content", is_read_only=True)
550
+ async def get_gmail_thread_content(
551
+ service, thread_id: str, user_google_email: str
552
+ ) -> str:
553
+ """
554
+ Retrieves the complete content of a Gmail conversation thread, including all messages.
555
+
556
+ Args:
557
+ thread_id (str): The unique ID of the Gmail thread to retrieve.
558
+ user_google_email (str): The user's Google email address. Required.
559
+
560
+ Returns:
561
+ str: The complete thread content with all messages formatted for reading.
562
+ """
563
+ logger.info(
564
+ f"[get_gmail_thread_content] Invoked. Thread ID: '{thread_id}', Email: '{user_google_email}'"
565
+ )
566
+
567
+ # Fetch the complete thread with all messages
568
+ thread_response = await asyncio.to_thread(
569
+ service.users().threads().get(userId="me", id=thread_id, format="full").execute
570
+ )
571
+
572
+ return _format_thread_content(thread_response, thread_id)
573
+
574
+
575
+ @server.tool()
576
+ @require_google_service("gmail", "gmail_read")
577
+ @handle_http_errors("get_gmail_threads_content_batch", is_read_only=True)
578
+ async def get_gmail_threads_content_batch(
579
+ service,
580
+ thread_ids: List[str],
581
+ user_google_email: str,
582
+ ) -> str:
583
+ """
584
+ Retrieves the content of multiple Gmail threads in a single batch request.
585
+ Supports up to 100 threads per request using Google's batch API.
586
+
587
+ Args:
588
+ thread_ids (List[str]): A list of Gmail thread IDs to retrieve. The function will automatically batch requests in chunks of 100.
589
+ user_google_email (str): The user's Google email address. Required.
590
+
591
+ Returns:
592
+ str: A formatted list of thread contents with separators.
593
+ """
594
+ logger.info(
595
+ f"[get_gmail_threads_content_batch] Invoked. Thread count: {len(thread_ids)}, Email: '{user_google_email}'"
596
+ )
597
+
598
+ if not thread_ids:
599
+ raise ValueError("No thread IDs provided")
600
+
601
+ output_threads = []
602
+
603
+ def _batch_callback(request_id, response, exception):
604
+ """Callback for batch requests"""
605
+ results[request_id] = {"data": response, "error": exception}
606
+
607
+ # Process in chunks of 100 (Gmail batch limit)
608
+ for chunk_start in range(0, len(thread_ids), 100):
609
+ chunk_ids = thread_ids[chunk_start : chunk_start + 100]
610
+ results: Dict[str, Dict] = {}
611
+
612
+ # Try to use batch API
613
+ try:
614
+ batch = service.new_batch_http_request(callback=_batch_callback)
615
+
616
+ for tid in chunk_ids:
617
+ req = service.users().threads().get(userId="me", id=tid, format="full")
618
+ batch.add(req, request_id=tid)
619
+
620
+ # Execute batch request
621
+ await asyncio.to_thread(batch.execute)
622
+
623
+ except Exception as batch_error:
624
+ # Fallback to asyncio.gather if batch API fails
625
+ logger.warning(
626
+ f"[get_gmail_threads_content_batch] Batch API failed, falling back to asyncio.gather: {batch_error}"
627
+ )
628
+
629
+ async def fetch_thread(tid: str):
630
+ try:
631
+ thread = await asyncio.to_thread(
632
+ service.users()
633
+ .threads()
634
+ .get(userId="me", id=tid, format="full")
635
+ .execute
636
+ )
637
+ return tid, thread, None
638
+ except Exception as e:
639
+ return tid, None, e
640
+
641
+ # Fetch all threads in parallel
642
+ fetch_results = await asyncio.gather(
643
+ *[fetch_thread(tid) for tid in chunk_ids], return_exceptions=False
644
+ )
645
+
646
+ # Convert to results format
647
+ for tid, thread, error in fetch_results:
648
+ results[tid] = {"data": thread, "error": error}
649
+
650
+ # Process results for this chunk
651
+ for tid in chunk_ids:
652
+ entry = results.get(tid, {"data": None, "error": "No result"})
653
+
654
+ if entry["error"]:
655
+ output_threads.append(f"⚠️ Thread {tid}: {entry['error']}\n")
656
+ else:
657
+ thread = entry["data"]
658
+ if not thread:
659
+ output_threads.append(f"⚠️ Thread {tid}: No data returned\n")
660
+ continue
661
+
662
+ output_threads.append(_format_thread_content(thread, tid))
663
+
664
+ # Combine all threads with separators
665
+ header = f"Retrieved {len(thread_ids)} threads:"
666
+ return header + "\n\n" + "\n---\n\n".join(output_threads)
667
+
668
+
669
+ @server.tool()
670
+ @handle_http_errors("list_gmail_labels", is_read_only=True)
671
+ @require_google_service("gmail", "gmail_read")
563
672
  async def list_gmail_labels(service, user_google_email: str) -> str:
564
673
  """
565
674
  Lists all labels in the user's Gmail account.
@@ -606,8 +715,8 @@ async def list_gmail_labels(service, user_google_email: str) -> str:
606
715
 
607
716
 
608
717
  @server.tool()
609
- @require_google_service("gmail", GMAIL_LABELS_SCOPE)
610
718
  @handle_http_errors("manage_gmail_label")
719
+ @require_google_service("gmail", GMAIL_LABELS_SCOPE)
611
720
  async def manage_gmail_label(
612
721
  service,
613
722
  user_google_email: str,
@@ -631,7 +740,9 @@ async def manage_gmail_label(
631
740
  Returns:
632
741
  str: Confirmation message of the label operation.
633
742
  """
634
- logger.info(f"[manage_gmail_label] Invoked. Email: '{user_google_email}', Action: '{action}'")
743
+ logger.info(
744
+ f"[manage_gmail_label] Invoked. Email: '{user_google_email}', Action: '{action}'"
745
+ )
635
746
 
636
747
  if action == "create" and not name:
637
748
  raise Exception("Label name is required for create action.")
@@ -663,7 +774,10 @@ async def manage_gmail_label(
663
774
  }
664
775
 
665
776
  updated_label = await asyncio.to_thread(
666
- service.users().labels().update(userId="me", id=label_id, body=label_object).execute
777
+ service.users()
778
+ .labels()
779
+ .update(userId="me", id=label_id, body=label_object)
780
+ .execute
667
781
  )
668
782
  return f"Label updated successfully!\nName: {updated_label['name']}\nID: {updated_label['id']}"
669
783
 
@@ -680,8 +794,8 @@ async def manage_gmail_label(
680
794
 
681
795
 
682
796
  @server.tool()
683
- @require_google_service("gmail", GMAIL_MODIFY_SCOPE)
684
797
  @handle_http_errors("modify_gmail_message_labels")
798
+ @require_google_service("gmail", GMAIL_MODIFY_SCOPE)
685
799
  async def modify_gmail_message_labels(
686
800
  service,
687
801
  user_google_email: str,
@@ -701,10 +815,14 @@ async def modify_gmail_message_labels(
701
815
  Returns:
702
816
  str: Confirmation message of the label changes applied to the message.
703
817
  """
704
- logger.info(f"[modify_gmail_message_labels] Invoked. Email: '{user_google_email}', Message ID: '{message_id}'")
818
+ logger.info(
819
+ f"[modify_gmail_message_labels] Invoked. Email: '{user_google_email}', Message ID: '{message_id}'"
820
+ )
705
821
 
706
822
  if not add_label_ids and not remove_label_ids:
707
- raise Exception("At least one of add_label_ids or remove_label_ids must be provided.")
823
+ raise Exception(
824
+ "At least one of add_label_ids or remove_label_ids must be provided."
825
+ )
708
826
 
709
827
  body = {}
710
828
  if add_label_ids:
gsheets/sheets_tools.py CHANGED
@@ -21,8 +21,8 @@ logger = logging.getLogger(__name__)
21
21
 
22
22
 
23
23
  @server.tool()
24
+ @handle_http_errors("list_spreadsheets", is_read_only=True)
24
25
  @require_google_service("drive", "drive_read")
25
- @handle_http_errors("list_spreadsheets")
26
26
  async def list_spreadsheets(
27
27
  service,
28
28
  user_google_email: str,
@@ -70,8 +70,8 @@ async def list_spreadsheets(
70
70
 
71
71
 
72
72
  @server.tool()
73
+ @handle_http_errors("get_spreadsheet_info", is_read_only=True)
73
74
  @require_google_service("sheets", "sheets_read")
74
- @handle_http_errors("get_spreadsheet_info")
75
75
  async def get_spreadsheet_info(
76
76
  service,
77
77
  user_google_email: str,
@@ -120,8 +120,8 @@ async def get_spreadsheet_info(
120
120
 
121
121
 
122
122
  @server.tool()
123
+ @handle_http_errors("read_sheet_values", is_read_only=True)
123
124
  @require_google_service("sheets", "sheets_read")
124
- @handle_http_errors("read_sheet_values")
125
125
  async def read_sheet_values(
126
126
  service,
127
127
  user_google_email: str,
@@ -170,8 +170,8 @@ async def read_sheet_values(
170
170
 
171
171
 
172
172
  @server.tool()
173
- @require_google_service("sheets", "sheets_write")
174
173
  @handle_http_errors("modify_sheet_values")
174
+ @require_google_service("sheets", "sheets_write")
175
175
  async def modify_sheet_values(
176
176
  service,
177
177
  user_google_email: str,
@@ -241,8 +241,8 @@ async def modify_sheet_values(
241
241
 
242
242
 
243
243
  @server.tool()
244
- @require_google_service("sheets", "sheets_write")
245
244
  @handle_http_errors("create_spreadsheet")
245
+ @require_google_service("sheets", "sheets_write")
246
246
  async def create_spreadsheet(
247
247
  service,
248
248
  user_google_email: str,
@@ -290,8 +290,8 @@ async def create_spreadsheet(
290
290
 
291
291
 
292
292
  @server.tool()
293
- @require_google_service("sheets", "sheets_write")
294
293
  @handle_http_errors("create_sheet")
294
+ @require_google_service("sheets", "sheets_write")
295
295
  async def create_sheet(
296
296
  service,
297
297
  user_google_email: str,
@@ -344,7 +344,7 @@ _comment_tools = create_comment_tools("spreadsheet", "spreadsheet_id")
344
344
 
345
345
  # Extract and register the functions
346
346
  read_sheet_comments = _comment_tools['read_comments']
347
- create_sheet_comment = _comment_tools['create_comment']
347
+ create_sheet_comment = _comment_tools['create_comment']
348
348
  reply_to_sheet_comment = _comment_tools['reply_to_comment']
349
349
  resolve_sheet_comment = _comment_tools['resolve_comment']
350
350
 
gslides/slides_tools.py CHANGED
@@ -20,8 +20,8 @@ logger = logging.getLogger(__name__)
20
20
 
21
21
 
22
22
  @server.tool()
23
- @require_google_service("slides", "slides")
24
23
  @handle_http_errors("create_presentation")
24
+ @require_google_service("slides", "slides")
25
25
  async def create_presentation(
26
26
  service,
27
27
  user_google_email: str,
@@ -42,27 +42,27 @@ async def create_presentation(
42
42
  body = {
43
43
  'title': title
44
44
  }
45
-
45
+
46
46
  result = await asyncio.to_thread(
47
47
  service.presentations().create(body=body).execute
48
48
  )
49
-
49
+
50
50
  presentation_id = result.get('presentationId')
51
51
  presentation_url = f"https://docs.google.com/presentation/d/{presentation_id}/edit"
52
-
52
+
53
53
  confirmation_message = f"""Presentation Created Successfully for {user_google_email}:
54
54
  - Title: {title}
55
55
  - Presentation ID: {presentation_id}
56
56
  - URL: {presentation_url}
57
57
  - Slides: {len(result.get('slides', []))} slide(s) created"""
58
-
58
+
59
59
  logger.info(f"Presentation created successfully for {user_google_email}")
60
60
  return confirmation_message
61
61
 
62
62
 
63
63
  @server.tool()
64
+ @handle_http_errors("get_presentation", is_read_only=True)
64
65
  @require_google_service("slides", "slides_read")
65
- @handle_http_errors("get_presentation")
66
66
  async def get_presentation(
67
67
  service,
68
68
  user_google_email: str,
@@ -83,17 +83,17 @@ async def get_presentation(
83
83
  result = await asyncio.to_thread(
84
84
  service.presentations().get(presentationId=presentation_id).execute
85
85
  )
86
-
86
+
87
87
  title = result.get('title', 'Untitled')
88
88
  slides = result.get('slides', [])
89
89
  page_size = result.get('pageSize', {})
90
-
90
+
91
91
  slides_info = []
92
92
  for i, slide in enumerate(slides, 1):
93
93
  slide_id = slide.get('objectId', 'Unknown')
94
94
  page_elements = slide.get('pageElements', [])
95
95
  slides_info.append(f" Slide {i}: ID {slide_id}, {len(page_elements)} element(s)")
96
-
96
+
97
97
  confirmation_message = f"""Presentation Details for {user_google_email}:
98
98
  - Title: {title}
99
99
  - Presentation ID: {presentation_id}
@@ -103,14 +103,14 @@ async def get_presentation(
103
103
 
104
104
  Slides Breakdown:
105
105
  {chr(10).join(slides_info) if slides_info else ' No slides found'}"""
106
-
106
+
107
107
  logger.info(f"Presentation retrieved successfully for {user_google_email}")
108
108
  return confirmation_message
109
109
 
110
110
 
111
111
  @server.tool()
112
- @require_google_service("slides", "slides")
113
112
  @handle_http_errors("batch_update_presentation")
113
+ @require_google_service("slides", "slides")
114
114
  async def batch_update_presentation(
115
115
  service,
116
116
  user_google_email: str,
@@ -133,22 +133,22 @@ async def batch_update_presentation(
133
133
  body = {
134
134
  'requests': requests
135
135
  }
136
-
136
+
137
137
  result = await asyncio.to_thread(
138
138
  service.presentations().batchUpdate(
139
139
  presentationId=presentation_id,
140
140
  body=body
141
141
  ).execute
142
142
  )
143
-
143
+
144
144
  replies = result.get('replies', [])
145
-
145
+
146
146
  confirmation_message = f"""Batch Update Completed for {user_google_email}:
147
147
  - Presentation ID: {presentation_id}
148
148
  - URL: https://docs.google.com/presentation/d/{presentation_id}/edit
149
149
  - Requests Applied: {len(requests)}
150
150
  - Replies Received: {len(replies)}"""
151
-
151
+
152
152
  if replies:
153
153
  confirmation_message += "\n\nUpdate Results:"
154
154
  for i, reply in enumerate(replies, 1):
@@ -160,14 +160,14 @@ async def batch_update_presentation(
160
160
  confirmation_message += f"\n Request {i}: Created shape with ID {shape_id}"
161
161
  else:
162
162
  confirmation_message += f"\n Request {i}: Operation completed"
163
-
163
+
164
164
  logger.info(f"Batch update completed successfully for {user_google_email}")
165
165
  return confirmation_message
166
166
 
167
167
 
168
168
  @server.tool()
169
+ @handle_http_errors("get_page", is_read_only=True)
169
170
  @require_google_service("slides", "slides_read")
170
- @handle_http_errors("get_page")
171
171
  async def get_page(
172
172
  service,
173
173
  user_google_email: str,
@@ -193,10 +193,10 @@ async def get_page(
193
193
  pageObjectId=page_object_id
194
194
  ).execute
195
195
  )
196
-
196
+
197
197
  page_type = result.get('pageType', 'Unknown')
198
198
  page_elements = result.get('pageElements', [])
199
-
199
+
200
200
  elements_info = []
201
201
  for element in page_elements:
202
202
  element_id = element.get('objectId', 'Unknown')
@@ -213,7 +213,7 @@ async def get_page(
213
213
  elements_info.append(f" Line: ID {element_id}, Type: {line_type}")
214
214
  else:
215
215
  elements_info.append(f" Element: ID {element_id}, Type: Unknown")
216
-
216
+
217
217
  confirmation_message = f"""Page Details for {user_google_email}:
218
218
  - Presentation ID: {presentation_id}
219
219
  - Page ID: {page_object_id}
@@ -222,14 +222,14 @@ async def get_page(
222
222
 
223
223
  Page Elements:
224
224
  {chr(10).join(elements_info) if elements_info else ' No elements found'}"""
225
-
225
+
226
226
  logger.info(f"Page retrieved successfully for {user_google_email}")
227
227
  return confirmation_message
228
228
 
229
229
 
230
230
  @server.tool()
231
+ @handle_http_errors("get_page_thumbnail", is_read_only=True)
231
232
  @require_google_service("slides", "slides_read")
232
- @handle_http_errors("get_page_thumbnail")
233
233
  async def get_page_thumbnail(
234
234
  service,
235
235
  user_google_email: str,
@@ -258,9 +258,9 @@ async def get_page_thumbnail(
258
258
  thumbnailPropertiesImageSize=thumbnail_size
259
259
  ).execute
260
260
  )
261
-
261
+
262
262
  thumbnail_url = result.get('contentUrl', '')
263
-
263
+
264
264
  confirmation_message = f"""Thumbnail Generated for {user_google_email}:
265
265
  - Presentation ID: {presentation_id}
266
266
  - Page ID: {page_object_id}
@@ -268,7 +268,7 @@ async def get_page_thumbnail(
268
268
  - Thumbnail URL: {thumbnail_url}
269
269
 
270
270
  You can view or download the thumbnail using the provided URL."""
271
-
271
+
272
272
  logger.info(f"Thumbnail generated successfully for {user_google_email}")
273
273
  return confirmation_message
274
274