google-workspace-mcp 1.0.4__py3-none-any.whl → 1.1.5__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.
@@ -16,7 +16,6 @@ logger = logging.getLogger(__name__)
16
16
 
17
17
  # @mcp.tool(
18
18
  # name="list_calendars",
19
- # description="Lists all calendars accessible by the user.",
20
19
  # )
21
20
  # async def list_calendars() -> dict[str, Any]:
22
21
  # """
@@ -40,10 +39,9 @@ logger = logging.getLogger(__name__)
40
39
  # return {"count": len(calendars), "calendars": calendars}
41
40
 
42
41
 
43
- @mcp.tool(
44
- name="calendar_get_events",
45
- description="Retrieve calendar events within a specified time range.",
46
- )
42
+ # @mcp.tool(
43
+ # name="calendar_get_events",
44
+ # )
47
45
  async def calendar_get_events(
48
46
  time_min: str,
49
47
  time_max: str,
@@ -92,10 +90,9 @@ async def calendar_get_events(
92
90
  return {"count": len(events), "events": events}
93
91
 
94
92
 
95
- @mcp.tool(
96
- name="calendar_get_event_details",
97
- description="Retrieves detailed information for a specific calendar event by its ID.",
98
- )
93
+ # @mcp.tool(
94
+ # name="calendar_get_event_details",
95
+ # )
99
96
  async def calendar_get_event_details(event_id: str, calendar_id: str = "primary") -> dict[str, Any]:
100
97
  """
101
98
  Retrieves details for a specific event in a Google Calendar.
@@ -126,10 +123,9 @@ async def calendar_get_event_details(event_id: str, calendar_id: str = "primary"
126
123
  return event_details
127
124
 
128
125
 
129
- @mcp.tool(
130
- name="create_calendar_event",
131
- description="Creates a new event in a specified Google Calendar.",
132
- )
126
+ # @mcp.tool(
127
+ # name="create_calendar_event",
128
+ # )
133
129
  async def create_calendar_event(
134
130
  summary: str,
135
131
  start_time: str,
@@ -184,10 +180,9 @@ async def create_calendar_event(
184
180
  return result
185
181
 
186
182
 
187
- @mcp.tool(
188
- name="delete_calendar_event",
189
- description="Deletes an event from Google Calendar by its event ID.",
190
- )
183
+ # @mcp.tool(
184
+ # name="delete_calendar_event",
185
+ # )
191
186
  async def delete_calendar_event(
192
187
  event_id: str,
193
188
  calendar_id: str = "primary",
@@ -11,10 +11,9 @@ from google_workspace_mcp.services.docs_service import DocsService
11
11
  logger = logging.getLogger(__name__)
12
12
 
13
13
 
14
- @mcp.tool(
15
- name="docs_create_document",
16
- description="Creates a new Google Document with a specified title.",
17
- )
14
+ # @mcp.tool(
15
+ # name="docs_create_document",
16
+ # )
18
17
  async def docs_create_document(title: str) -> dict[str, Any]:
19
18
  """
20
19
  Creates a new, empty Google Document.
@@ -44,10 +43,9 @@ async def docs_create_document(title: str) -> dict[str, Any]:
44
43
  return result
45
44
 
46
45
 
47
- @mcp.tool(
48
- name="docs_get_document_metadata",
49
- description="Retrieves metadata (like title and ID) for a Google Document.",
50
- )
46
+ # @mcp.tool(
47
+ # name="docs_get_document_metadata",
48
+ # )
51
49
  async def docs_get_document_metadata(document_id: str) -> dict[str, Any]:
52
50
  """
53
51
  Retrieves metadata for a specific Google Document.
@@ -77,10 +75,9 @@ async def docs_get_document_metadata(document_id: str) -> dict[str, Any]:
77
75
  return result
78
76
 
79
77
 
80
- @mcp.tool(
81
- name="docs_get_content_as_markdown",
82
- description="Retrieves the content of a Google Document, attempting to convert it to Markdown. Note: Direct Markdown export from Google Docs via Drive API is not officially guaranteed for all document complexities and may result in errors or suboptimal formatting. For critical conversions, consider exporting as HTML and using a dedicated Markdown conversion library.",
83
- )
78
+ # @mcp.tool(
79
+ # name="docs_get_content_as_markdown",
80
+ # )
84
81
  async def docs_get_content_as_markdown(document_id: str) -> dict[str, Any]:
85
82
  """
86
83
  Retrieves the main textual content of a Google Document, converted to Markdown.
@@ -114,10 +111,9 @@ async def docs_get_content_as_markdown(document_id: str) -> dict[str, Any]:
114
111
  return result
115
112
 
116
113
 
117
- @mcp.tool(
118
- name="docs_append_text",
119
- description="Appends text to the end of a specified Google Document.",
120
- )
114
+ # @mcp.tool(
115
+ # name="docs_append_text",
116
+ # )
121
117
  async def docs_append_text(
122
118
  document_id: str, text: str, ensure_newline: bool = True
123
119
  ) -> dict[str, Any]:
@@ -152,10 +148,9 @@ async def docs_append_text(
152
148
  return result
153
149
 
154
150
 
155
- @mcp.tool(
156
- name="docs_prepend_text",
157
- description="Prepends text to the beginning of a specified Google Document.",
158
- )
151
+ # @mcp.tool(
152
+ # name="docs_prepend_text",
153
+ # )
159
154
  async def docs_prepend_text(
160
155
  document_id: str, text: str, ensure_newline: bool = True
161
156
  ) -> dict[str, Any]:
@@ -190,10 +185,9 @@ async def docs_prepend_text(
190
185
  return result
191
186
 
192
187
 
193
- @mcp.tool(
194
- name="docs_insert_text",
195
- description="Inserts text at a specified location in a Google Document. For simple appends or prepends, use 'docs_append_text' or 'docs_prepend_text'.",
196
- )
188
+ # @mcp.tool(
189
+ # name="docs_insert_text",
190
+ # )
197
191
  async def docs_insert_text(
198
192
  document_id: str, text: str, index: int | None = None, segment_id: str | None = None
199
193
  ) -> dict[str, Any]:
@@ -234,10 +228,9 @@ async def docs_insert_text(
234
228
  return result
235
229
 
236
230
 
237
- @mcp.tool(
238
- name="docs_batch_update",
239
- description="Applies a list of raw Google Docs API update requests to a document. For advanced users familiar with Docs API request structures.",
240
- )
231
+ # @mcp.tool(
232
+ # name="docs_batch_update",
233
+ # )
241
234
  async def docs_batch_update(document_id: str, requests: list[dict]) -> dict[str, Any]:
242
235
  """
243
236
  Applies a list of Google Docs API update requests to the specified document.
@@ -277,10 +270,9 @@ async def docs_batch_update(document_id: str, requests: list[dict]) -> dict[str,
277
270
  return result # Return the full response which includes documentId and replies
278
271
 
279
272
 
280
- @mcp.tool(
281
- name="docs_insert_image",
282
- description="Inserts an image into a Google Document from a URL at a specific index. The image URL must be publicly accessible and in PNG, JPEG, or GIF format.",
283
- )
273
+ # @mcp.tool(
274
+ # name="docs_insert_image",
275
+ # )
284
276
  async def docs_insert_image(
285
277
  document_id: str,
286
278
  image_url: str,
@@ -16,34 +16,70 @@ logger = logging.getLogger(__name__)
16
16
 
17
17
  @mcp.tool(
18
18
  name="drive_search_files",
19
- description="Search for files in Google Drive with optional shared drive support.",
20
19
  )
21
20
  async def drive_search_files(
22
21
  query: str,
23
22
  page_size: int = 10,
24
23
  shared_drive_id: str | None = None,
24
+ include_shared_drives: bool = True,
25
+ include_trashed: bool = False,
25
26
  ) -> dict[str, Any]:
26
27
  """
27
- Search for files in Google Drive, optionally within a specific shared drive.
28
+ Search for files in Google Drive with optional shared drive support. Trashed files are excluded by default.
29
+
30
+
31
+ Examples:
32
+ - "budget report" → works as-is
33
+ - "John's Documents" → automatically handled
28
34
 
29
35
  Args:
30
36
  query: Search query string. Can be a simple text search or complex query with operators.
37
+ Apostrophes are automatically escaped for you.
31
38
  page_size: Maximum number of files to return (1 to 1000, default 10).
32
39
  shared_drive_id: Optional shared drive ID to search within a specific shared drive.
40
+ include_shared_drives: Whether to include shared drives and folders in search (default True).
41
+ Set to False to search only personal files.
42
+ include_trashed: Whether to include trashed files in search results (default False).
33
43
 
34
44
  Returns:
35
45
  A dictionary containing a list of files or an error message.
36
46
  """
37
47
  logger.info(
38
- f"Executing drive_search_files with query: '{query}', page_size: {page_size}, shared_drive_id: {shared_drive_id}"
48
+ f"Executing drive_search_files with query: '{query}', page_size: {page_size}, "
49
+ f"shared_drive_id: {shared_drive_id}, include_shared_drives: {include_shared_drives}, "
50
+ f"include_trashed: {include_trashed}"
39
51
  )
40
52
 
41
53
  if not query or not query.strip():
42
54
  raise ValueError("Query cannot be empty")
43
55
 
56
+ # Logic to build a robust query
57
+ # If the query looks like a simple term (no spaces, no operators), wrap it.
58
+ # Otherwise, assume the user has provided a full query expression.
59
+ clean_query = query.strip()
60
+ if (
61
+ " " not in clean_query
62
+ and ":" not in clean_query
63
+ and "=" not in clean_query
64
+ and ">" not in clean_query
65
+ and "<" not in clean_query
66
+ ):
67
+ # This is likely a simple term, wrap it for a full-text search.
68
+ final_query = f"fullText contains '{clean_query.replace("'", "\\'")}'"
69
+ else:
70
+ # Assume it's a complex query and use it as-is.
71
+ final_query = clean_query.replace("'", "\\'")
72
+
73
+ # Append the trashed filter
74
+ if not include_trashed:
75
+ final_query = f"{final_query} and trashed=false"
76
+
44
77
  drive_service = DriveService()
45
78
  files = drive_service.search_files(
46
- query=query, page_size=page_size, shared_drive_id=shared_drive_id
79
+ query=final_query,
80
+ page_size=page_size,
81
+ shared_drive_id=shared_drive_id,
82
+ include_shared_drives=include_shared_drives,
47
83
  )
48
84
 
49
85
  if isinstance(files, dict) and files.get("error"):
@@ -52,10 +88,9 @@ async def drive_search_files(
52
88
  return {"files": files}
53
89
 
54
90
 
55
- @mcp.tool(
56
- name="drive_read_file_content",
57
- description="Read the content of a file from Google Drive.",
58
- )
91
+ # @mcp.tool(
92
+ # name="drive_read_file_content",
93
+ # )
59
94
  async def drive_read_file_content(file_id: str) -> dict[str, Any]:
60
95
  """
61
96
  Read the content of a file from Google Drive.
@@ -82,10 +117,9 @@ async def drive_read_file_content(file_id: str) -> dict[str, Any]:
82
117
  return result
83
118
 
84
119
 
85
- @mcp.tool(
86
- name="drive_upload_file",
87
- description="Uploads a file to Google Drive by providing its content directly.",
88
- )
120
+ # @mcp.tool(
121
+ # name="drive_upload_file",
122
+ # )
89
123
  async def drive_upload_file(
90
124
  filename: str,
91
125
  content_base64: str,
@@ -126,10 +160,9 @@ async def drive_upload_file(
126
160
  return result
127
161
 
128
162
 
129
- @mcp.tool(
130
- name="drive_create_folder",
131
- description="Create a new folder in Google Drive.",
132
- )
163
+ # @mcp.tool(
164
+ # name="drive_create_folder",
165
+ # )
133
166
  async def drive_create_folder(
134
167
  folder_name: str,
135
168
  parent_folder_id: str | None = None,
@@ -168,10 +201,9 @@ async def drive_create_folder(
168
201
  return result
169
202
 
170
203
 
171
- @mcp.tool(
172
- name="drive_delete_file",
173
- description="Delete a file from Google Drive using its file ID.",
174
- )
204
+ # @mcp.tool(
205
+ # name="drive_delete_file",
206
+ # )
175
207
  async def drive_delete_file(
176
208
  file_id: str,
177
209
  ) -> dict[str, Any]:
@@ -199,7 +231,6 @@ async def drive_delete_file(
199
231
 
200
232
  @mcp.tool(
201
233
  name="drive_list_shared_drives",
202
- description="Lists shared drives accessible by the user.",
203
234
  )
204
235
  async def drive_list_shared_drives(page_size: int = 100) -> dict[str, Any]:
205
236
  """
@@ -224,3 +255,215 @@ async def drive_list_shared_drives(page_size: int = 100) -> dict[str, Any]:
224
255
  return {"message": "No shared drives found or accessible."}
225
256
 
226
257
  return {"count": len(drives), "shared_drives": drives}
258
+
259
+
260
+ @mcp.tool(
261
+ name="drive_search_files_in_folder",
262
+ )
263
+ async def drive_search_files_in_folder(
264
+ folder_id: str,
265
+ query: str = "",
266
+ page_size: int = 10,
267
+ ) -> dict[str, Any]:
268
+ """
269
+ Search for files or folders within a specific folder ID. Trashed files are excluded.
270
+ This works for both regular folders and Shared Drives (when using the Shared Drive's ID as the folder_id).
271
+
272
+ Args:
273
+ folder_id: The ID of the folder or Shared Drive to search within.
274
+ query: Optional search query string, following Google Drive API syntax.
275
+ If empty, returns all items.
276
+ Example to find only sub-folders: "mimeType = 'application/vnd.google-apps.folder'"
277
+ page_size: Maximum number of files to return (1 to 1000, default 10).
278
+
279
+ Returns:
280
+ A dictionary containing a list of files and folders.
281
+ """
282
+ logger.info(
283
+ f"Executing drive_search_files_in_folder with folder_id: '{folder_id}', "
284
+ f"query: '{query}', page_size: {page_size}"
285
+ )
286
+
287
+ if not folder_id or not folder_id.strip():
288
+ raise ValueError("Folder ID cannot be empty")
289
+
290
+ # Build the search query to search within the specific folder
291
+ folder_query = f"'{folder_id}' in parents and trashed=false"
292
+ if query and query.strip():
293
+ # Automatically escape apostrophes in user query
294
+ escaped_query = query.strip().replace("'", "\\'")
295
+ # Combine folder constraint with user query
296
+ combined_query = f"{escaped_query} and {folder_query}"
297
+ else:
298
+ combined_query = folder_query
299
+
300
+ drive_service = DriveService()
301
+ files = drive_service.search_files(
302
+ query=combined_query,
303
+ page_size=page_size,
304
+ include_shared_drives=True, # Always include shared drives for folder searches
305
+ )
306
+
307
+ if isinstance(files, dict) and files.get("error"):
308
+ raise ValueError(
309
+ f"Folder search failed: {files.get('message', 'Unknown error')}"
310
+ )
311
+
312
+ return {"folder_id": folder_id, "files": files}
313
+
314
+
315
+ # @mcp.tool(
316
+ # name="drive_get_folder_info",
317
+ # )
318
+ async def drive_get_folder_info(folder_id: str) -> dict[str, Any]:
319
+ """
320
+ Get detailed information about a folder in Google Drive.
321
+
322
+ Useful for understanding folder permissions and hierarchy.
323
+
324
+ Args:
325
+ folder_id: The ID of the folder to get information about.
326
+
327
+ Returns:
328
+ A dictionary containing folder metadata or an error message.
329
+ """
330
+ logger.info(f"Executing drive_get_folder_info with folder_id: '{folder_id}'")
331
+
332
+ if not folder_id or not folder_id.strip():
333
+ raise ValueError("Folder ID cannot be empty")
334
+
335
+ drive_service = DriveService()
336
+ folder_info = drive_service.get_file_metadata(file_id=folder_id)
337
+
338
+ if isinstance(folder_info, dict) and folder_info.get("error"):
339
+ raise ValueError(
340
+ f"Failed to get folder info: {folder_info.get('message', 'Unknown error')}"
341
+ )
342
+
343
+ # Verify it's actually a folder
344
+ if folder_info.get("mimeType") != "application/vnd.google-apps.folder":
345
+ raise ValueError(
346
+ f"ID '{folder_id}' is not a folder (mimeType: {folder_info.get('mimeType')})"
347
+ )
348
+
349
+ return folder_info
350
+
351
+
352
+ @mcp.tool(
353
+ name="drive_find_folder_by_name",
354
+ )
355
+ async def drive_find_folder_by_name(
356
+ folder_name: str,
357
+ include_files: bool = False,
358
+ file_query: str = "",
359
+ page_size: int = 10,
360
+ shared_drive_id: str | None = None,
361
+ ) -> dict[str, Any]:
362
+ """
363
+ Finds folders by name using a two-step search: first an exact match, then a partial match.
364
+ Automatically handles apostrophes in folder names and search queries. Trashed items are excluded.
365
+
366
+ Crucial Note: This tool finds **regular folders** within "My Drive" or a Shared Drive.
367
+ It **does not** find Shared Drives themselves. To list available Shared Drives,
368
+ use the `drive_list_shared_drives` tool.
369
+
370
+ Args:
371
+ folder_name: The name of the folder to search for.
372
+ include_files: Whether to also search for files within the found folder (default False).
373
+ file_query: Optional search query for files within the folder. Only used if include_files=True.
374
+ page_size: Maximum number of files to return (1 to 1000, default 10).
375
+ shared_drive_id: Optional shared drive ID to search within a specific shared drive.
376
+
377
+ Returns:
378
+ A dictionary containing folders_found and, if requested, file search results.
379
+ """
380
+ logger.info(
381
+ f"Executing drive_find_folder_by_name with folder_name: '{folder_name}', "
382
+ f"include_files: {include_files}, file_query: '{file_query}', "
383
+ f"page_size: {page_size}, shared_drive_id: {shared_drive_id}"
384
+ )
385
+
386
+ if not folder_name or not folder_name.strip():
387
+ raise ValueError("Folder name cannot be empty")
388
+
389
+ drive_service = DriveService()
390
+ escaped_folder_name = folder_name.strip().replace("'", "\\'")
391
+
392
+ # --- Step 1: Attempt Exact Match ---
393
+ logger.info(f"Step 1: Searching for exact folder name: '{escaped_folder_name}'")
394
+ exact_query = f"name = '{escaped_folder_name}' and mimeType='application/vnd.google-apps.folder' and trashed=false"
395
+ folders = drive_service.search_files(
396
+ query=exact_query,
397
+ page_size=5,
398
+ shared_drive_id=shared_drive_id,
399
+ include_shared_drives=True,
400
+ )
401
+
402
+ # If no exact match, fall back to partial match
403
+ if not folders:
404
+ logger.info(
405
+ f"No exact match found. Step 2: Searching for folder name containing '{escaped_folder_name}'"
406
+ )
407
+ contains_query = f"name contains '{escaped_folder_name}' and mimeType='application/vnd.google-apps.folder' and trashed=false"
408
+ folders = drive_service.search_files(
409
+ query=contains_query,
410
+ page_size=5,
411
+ shared_drive_id=shared_drive_id,
412
+ include_shared_drives=True,
413
+ )
414
+
415
+ if isinstance(folders, dict) and folders.get("error"):
416
+ raise ValueError(
417
+ f"Folder search failed: {folders.get('message', 'Unknown error')}"
418
+ )
419
+
420
+ result = {
421
+ "folder_name": folder_name,
422
+ "folders_found": folders,
423
+ "folder_count": len(folders) if folders else 0,
424
+ }
425
+
426
+ if not include_files:
427
+ return result
428
+
429
+ if not folders:
430
+ result["message"] = f"No folders found with name matching '{folder_name}'"
431
+ return result
432
+
433
+ target_folder = folders[0]
434
+ folder_id = target_folder["id"]
435
+
436
+ # Build the search query for files within the folder
437
+ folder_constraint = f"'{folder_id}' in parents and trashed=false"
438
+
439
+ if file_query and file_query.strip():
440
+ # Use the same smart query logic as drive_search_files
441
+ clean_file_query = file_query.strip()
442
+ if (
443
+ " " not in clean_file_query
444
+ and ":" not in clean_file_query
445
+ and "=" not in clean_file_query
446
+ ):
447
+ wrapped_file_query = (
448
+ f"fullText contains '{clean_file_query.replace("'", "\\'")}'"
449
+ )
450
+ else:
451
+ wrapped_file_query = clean_file_query.replace("'", "\\'")
452
+ combined_query = f"{wrapped_file_query} and {folder_constraint}"
453
+ else:
454
+ combined_query = folder_constraint
455
+
456
+ files = drive_service.search_files(
457
+ query=combined_query, page_size=page_size, include_shared_drives=True
458
+ )
459
+
460
+ if isinstance(files, dict) and files.get("error"):
461
+ raise ValueError(
462
+ f"File search in folder failed: {files.get('message', 'Unknown error')}"
463
+ )
464
+
465
+ result["target_folder"] = target_folder
466
+ result["files"] = files
467
+ result["file_count"] = len(files) if files else 0
468
+
469
+ return result
@@ -14,10 +14,9 @@ logger = logging.getLogger(__name__)
14
14
  # --- Gmail Tool Functions --- #
15
15
 
16
16
 
17
- @mcp.tool(
18
- name="query_gmail_emails",
19
- description="Query Gmail emails based on a search query.",
20
- )
17
+ # @mcp.tool(
18
+ # name="query_gmail_emails",
19
+ # )
21
20
  async def query_gmail_emails(query: str, max_results: int = 100) -> dict[str, Any]:
22
21
  """
23
22
  Searches for Gmail emails using Gmail query syntax.
@@ -45,10 +44,9 @@ async def query_gmail_emails(query: str, max_results: int = 100) -> dict[str, An
45
44
  return {"count": len(emails), "emails": emails}
46
45
 
47
46
 
48
- @mcp.tool(
49
- name="gmail_get_message_details",
50
- description="Retrieves a complete Gmail email message by its ID.",
51
- )
47
+ # @mcp.tool(
48
+ # name="gmail_get_message_details",
49
+ # )
52
50
  async def gmail_get_message_details(email_id: str) -> dict[str, Any]:
53
51
  """
54
52
  Retrieves a complete Gmail email message by its ID.
@@ -77,10 +75,9 @@ async def gmail_get_message_details(email_id: str) -> dict[str, Any]:
77
75
  return result
78
76
 
79
77
 
80
- @mcp.tool(
81
- name="gmail_get_attachment_content",
82
- description="Retrieves a specific attachment from a Gmail message.",
83
- )
78
+ # @mcp.tool(
79
+ # name="gmail_get_attachment_content",
80
+ # )
84
81
  async def gmail_get_attachment_content(message_id: str, attachment_id: str) -> dict[str, Any]:
85
82
  """
86
83
  Retrieves a specific attachment from a Gmail message.
@@ -109,10 +106,9 @@ async def gmail_get_attachment_content(message_id: str, attachment_id: str) -> d
109
106
  return result
110
107
 
111
108
 
112
- @mcp.tool(
113
- name="create_gmail_draft",
114
- description="Creates a draft email message in Gmail.",
115
- )
109
+ # @mcp.tool(
110
+ # name="create_gmail_draft",
111
+ # )
116
112
  async def create_gmail_draft(
117
113
  to: str,
118
114
  subject: str,
@@ -150,10 +146,9 @@ async def create_gmail_draft(
150
146
  return result
151
147
 
152
148
 
153
- @mcp.tool(
154
- name="delete_gmail_draft",
155
- description="Deletes a Gmail draft email by its draft ID.",
156
- )
149
+ # @mcp.tool(
150
+ # name="delete_gmail_draft",
151
+ # )
157
152
  async def delete_gmail_draft(
158
153
  draft_id: str,
159
154
  ) -> dict[str, Any]:
@@ -189,10 +184,9 @@ async def delete_gmail_draft(
189
184
  }
190
185
 
191
186
 
192
- @mcp.tool(
193
- name="gmail_send_draft",
194
- description="Sends an existing draft email from Gmail.",
195
- )
187
+ # @mcp.tool(
188
+ # name="gmail_send_draft",
189
+ # )
196
190
  async def gmail_send_draft(draft_id: str) -> dict[str, Any]:
197
191
  """
198
192
  Sends a specific draft email.
@@ -219,10 +213,9 @@ async def gmail_send_draft(draft_id: str) -> dict[str, Any]:
219
213
  return result
220
214
 
221
215
 
222
- @mcp.tool(
223
- name="gmail_reply_to_email",
224
- description="Create a reply to an existing email. Can be sent or saved as draft.",
225
- )
216
+ # @mcp.tool(
217
+ # name="gmail_reply_to_email",
218
+ # )
226
219
  async def gmail_reply_to_email(
227
220
  email_id: str,
228
221
  reply_body: str,
@@ -262,10 +255,9 @@ async def gmail_reply_to_email(
262
255
  return result
263
256
 
264
257
 
265
- @mcp.tool(
266
- name="gmail_bulk_delete_messages",
267
- description="Delete multiple emails at once by providing a list of message IDs.",
268
- )
258
+ # @mcp.tool(
259
+ # name="gmail_bulk_delete_messages",
260
+ # )
269
261
  async def gmail_bulk_delete_messages(
270
262
  message_ids: list[str],
271
263
  ) -> dict[str, Any]:
@@ -300,10 +292,9 @@ async def gmail_bulk_delete_messages(
300
292
  return result
301
293
 
302
294
 
303
- @mcp.tool(
304
- name="gmail_send_email",
305
- description="Composes and sends an email directly.",
306
- )
295
+ # @mcp.tool(
296
+ # name="gmail_send_email",
297
+ # )
307
298
  async def gmail_send_email(
308
299
  to: list[str],
309
300
  subject: str,