google-workspace-mcp 1.0.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/__init__.py +3 -0
- google_workspace_mcp/__main__.py +43 -0
- google_workspace_mcp/app.py +8 -0
- google_workspace_mcp/auth/__init__.py +7 -0
- google_workspace_mcp/auth/gauth.py +62 -0
- google_workspace_mcp/config.py +60 -0
- google_workspace_mcp/prompts/__init__.py +3 -0
- google_workspace_mcp/prompts/calendar.py +36 -0
- google_workspace_mcp/prompts/drive.py +18 -0
- google_workspace_mcp/prompts/gmail.py +65 -0
- google_workspace_mcp/prompts/slides.py +40 -0
- google_workspace_mcp/resources/__init__.py +13 -0
- google_workspace_mcp/resources/calendar.py +79 -0
- google_workspace_mcp/resources/drive.py +93 -0
- google_workspace_mcp/resources/gmail.py +58 -0
- google_workspace_mcp/resources/sheets_resources.py +92 -0
- google_workspace_mcp/resources/slides.py +421 -0
- google_workspace_mcp/services/__init__.py +21 -0
- google_workspace_mcp/services/base.py +73 -0
- google_workspace_mcp/services/calendar.py +256 -0
- google_workspace_mcp/services/docs_service.py +388 -0
- google_workspace_mcp/services/drive.py +454 -0
- google_workspace_mcp/services/gmail.py +676 -0
- google_workspace_mcp/services/sheets_service.py +466 -0
- google_workspace_mcp/services/slides.py +959 -0
- google_workspace_mcp/tools/__init__.py +7 -0
- google_workspace_mcp/tools/calendar.py +229 -0
- google_workspace_mcp/tools/docs_tools.py +277 -0
- google_workspace_mcp/tools/drive.py +221 -0
- google_workspace_mcp/tools/gmail.py +344 -0
- google_workspace_mcp/tools/sheets_tools.py +322 -0
- google_workspace_mcp/tools/slides.py +478 -0
- google_workspace_mcp/utils/__init__.py +1 -0
- google_workspace_mcp/utils/markdown_slides.py +504 -0
- google_workspace_mcp-1.0.0.dist-info/METADATA +547 -0
- google_workspace_mcp-1.0.0.dist-info/RECORD +38 -0
- google_workspace_mcp-1.0.0.dist-info/WHEEL +4 -0
- google_workspace_mcp-1.0.0.dist-info/entry_points.txt +2 -0
@@ -0,0 +1,229 @@
|
|
1
|
+
"""
|
2
|
+
Google Calendar tool handlers for Google Workspace MCP.
|
3
|
+
"""
|
4
|
+
|
5
|
+
import logging
|
6
|
+
from typing import Any
|
7
|
+
|
8
|
+
from google_workspace_mcp.app import mcp # Import from central app module
|
9
|
+
from google_workspace_mcp.services.calendar import CalendarService
|
10
|
+
|
11
|
+
logger = logging.getLogger(__name__)
|
12
|
+
|
13
|
+
|
14
|
+
# --- Calendar Tool Functions --- #
|
15
|
+
|
16
|
+
|
17
|
+
# @mcp.tool(
|
18
|
+
# name="list_calendars",
|
19
|
+
# description="Lists all calendars accessible by the user.",
|
20
|
+
# )
|
21
|
+
# async def list_calendars() -> dict[str, Any]:
|
22
|
+
# """
|
23
|
+
# Lists all calendars accessible by the user.
|
24
|
+
|
25
|
+
# Returns:
|
26
|
+
# A dictionary containing the list of calendars or an error message.
|
27
|
+
# """
|
28
|
+
# logger.info("Executing list_calendars tool")
|
29
|
+
|
30
|
+
# calendar_service = CalendarService()
|
31
|
+
# calendars = calendar_service.list_calendars()
|
32
|
+
|
33
|
+
# if isinstance(calendars, dict) and calendars.get("error"):
|
34
|
+
# raise ValueError(calendars.get("message", "Error listing calendars"))
|
35
|
+
|
36
|
+
# if not calendars:
|
37
|
+
# return {"message": "No calendars found."}
|
38
|
+
|
39
|
+
# # Return raw service result
|
40
|
+
# return {"count": len(calendars), "calendars": calendars}
|
41
|
+
|
42
|
+
|
43
|
+
@mcp.tool(
|
44
|
+
name="calendar_get_events",
|
45
|
+
description="Retrieve calendar events within a specified time range.",
|
46
|
+
)
|
47
|
+
async def calendar_get_events(
|
48
|
+
time_min: str,
|
49
|
+
time_max: str,
|
50
|
+
calendar_id: str = "primary",
|
51
|
+
max_results: int = 250,
|
52
|
+
show_deleted: bool = False,
|
53
|
+
) -> dict[str, Any]:
|
54
|
+
"""
|
55
|
+
Retrieve calendar events within a specified time range.
|
56
|
+
|
57
|
+
Args:
|
58
|
+
time_min: Start time in RFC3339 format (e.g., "2024-01-01T00:00:00Z").
|
59
|
+
time_max: End time in RFC3339 format (e.g., "2024-01-01T23:59:59Z").
|
60
|
+
calendar_id: ID of the calendar (defaults to 'primary').
|
61
|
+
max_results: Maximum number of events to return (default: 250).
|
62
|
+
show_deleted: Whether to include deleted events (default: False).
|
63
|
+
|
64
|
+
Returns:
|
65
|
+
A dictionary containing the list of events or an error message.
|
66
|
+
"""
|
67
|
+
logger.info(f"Executing calendar_get_events tool on calendar '{calendar_id}' between {time_min} and {time_max}")
|
68
|
+
|
69
|
+
if not calendar_id:
|
70
|
+
raise ValueError("calendar_id parameter is required")
|
71
|
+
if not time_min:
|
72
|
+
raise ValueError("time_min parameter is required")
|
73
|
+
if not time_max:
|
74
|
+
raise ValueError("time_max parameter is required")
|
75
|
+
|
76
|
+
calendar_service = CalendarService()
|
77
|
+
events = calendar_service.get_events(
|
78
|
+
calendar_id=calendar_id,
|
79
|
+
time_min=time_min,
|
80
|
+
time_max=time_max,
|
81
|
+
max_results=max_results,
|
82
|
+
show_deleted=show_deleted,
|
83
|
+
)
|
84
|
+
|
85
|
+
if isinstance(events, dict) and events.get("error"):
|
86
|
+
raise ValueError(events.get("message", "Error getting calendar events"))
|
87
|
+
|
88
|
+
if not events:
|
89
|
+
return {"message": "No events found for the specified time range."}
|
90
|
+
|
91
|
+
# Return raw service result
|
92
|
+
return {"count": len(events), "events": events}
|
93
|
+
|
94
|
+
|
95
|
+
@mcp.tool(
|
96
|
+
name="calendar_get_event_details",
|
97
|
+
description="Retrieves detailed information for a specific calendar event by its ID.",
|
98
|
+
)
|
99
|
+
async def calendar_get_event_details(event_id: str, calendar_id: str = "primary") -> dict[str, Any]:
|
100
|
+
"""
|
101
|
+
Retrieves details for a specific event in a Google Calendar.
|
102
|
+
|
103
|
+
Args:
|
104
|
+
event_id: The ID of the event to retrieve.
|
105
|
+
calendar_id: The ID of the calendar containing the event. Defaults to 'primary'.
|
106
|
+
|
107
|
+
Returns:
|
108
|
+
A dictionary containing the event details (summary, start, end, description, attendees, etc.),
|
109
|
+
or an error message.
|
110
|
+
"""
|
111
|
+
logger.info(f"Executing calendar_get_event_details tool for event_id: '{event_id}', calendar_id: '{calendar_id}'")
|
112
|
+
if not event_id or not event_id.strip():
|
113
|
+
raise ValueError("Event ID cannot be empty.")
|
114
|
+
if not calendar_id or not calendar_id.strip():
|
115
|
+
raise ValueError("Calendar ID cannot be empty.") # Ensure calendar_id is also validated
|
116
|
+
|
117
|
+
calendar_service = CalendarService()
|
118
|
+
event_details = calendar_service.get_event_details(event_id=event_id, calendar_id=calendar_id)
|
119
|
+
|
120
|
+
if isinstance(event_details, dict) and event_details.get("error"):
|
121
|
+
raise ValueError(event_details.get("message", "Error retrieving event details"))
|
122
|
+
|
123
|
+
if not event_details: # Should be caught by error dict check
|
124
|
+
raise ValueError(f"Failed to retrieve details for event '{event_id}'")
|
125
|
+
|
126
|
+
return event_details
|
127
|
+
|
128
|
+
|
129
|
+
@mcp.tool(
|
130
|
+
name="create_calendar_event",
|
131
|
+
description="Creates a new event in a specified Google Calendar.",
|
132
|
+
)
|
133
|
+
async def create_calendar_event(
|
134
|
+
summary: str,
|
135
|
+
start_time: str,
|
136
|
+
end_time: str,
|
137
|
+
calendar_id: str = "primary",
|
138
|
+
location: str | None = None,
|
139
|
+
description: str | None = None,
|
140
|
+
attendees: list[str] | None = None,
|
141
|
+
send_notifications: bool = True,
|
142
|
+
timezone: str | None = None,
|
143
|
+
) -> dict[str, Any]:
|
144
|
+
"""
|
145
|
+
Create a new calendar event.
|
146
|
+
|
147
|
+
Args:
|
148
|
+
summary: Title of the event.
|
149
|
+
start_time: Start time in RFC3339 format (e.g. 2024-12-01T10:00:00Z).
|
150
|
+
end_time: End time in RFC3339 format (e.g. 2024-12-01T11:00:00Z).
|
151
|
+
calendar_id: Calendar ID (defaults to "primary").
|
152
|
+
location: Location of the event (optional).
|
153
|
+
description: Description or notes (optional).
|
154
|
+
attendees: List of attendee email addresses (optional).
|
155
|
+
send_notifications: Whether to send notifications to attendees (default True).
|
156
|
+
timezone: Timezone for the event (e.g., 'America/New_York', defaults to UTC).
|
157
|
+
|
158
|
+
Returns:
|
159
|
+
A dictionary containing the created event details.
|
160
|
+
"""
|
161
|
+
logger.info(f"Executing create_calendar_event on calendar '{calendar_id}'")
|
162
|
+
if not summary or not start_time or not end_time:
|
163
|
+
raise ValueError("Summary, start_time, and end_time are required")
|
164
|
+
|
165
|
+
calendar_service = CalendarService()
|
166
|
+
result = calendar_service.create_event(
|
167
|
+
summary=summary,
|
168
|
+
start_time=start_time,
|
169
|
+
end_time=end_time,
|
170
|
+
location=location,
|
171
|
+
description=description,
|
172
|
+
attendees=attendees,
|
173
|
+
send_notifications=send_notifications,
|
174
|
+
timezone=timezone,
|
175
|
+
calendar_id=calendar_id,
|
176
|
+
)
|
177
|
+
|
178
|
+
if not result or (isinstance(result, dict) and result.get("error")):
|
179
|
+
error_msg = "Error creating calendar event"
|
180
|
+
if isinstance(result, dict):
|
181
|
+
error_msg = result.get("message", error_msg)
|
182
|
+
raise ValueError(error_msg)
|
183
|
+
|
184
|
+
return result
|
185
|
+
|
186
|
+
|
187
|
+
@mcp.tool(
|
188
|
+
name="delete_calendar_event",
|
189
|
+
description="Deletes an event from Google Calendar by its event ID.",
|
190
|
+
)
|
191
|
+
async def delete_calendar_event(
|
192
|
+
event_id: str,
|
193
|
+
calendar_id: str = "primary",
|
194
|
+
send_notifications: bool = True,
|
195
|
+
) -> dict[str, Any]:
|
196
|
+
"""
|
197
|
+
Delete a calendar event by its ID.
|
198
|
+
|
199
|
+
Args:
|
200
|
+
event_id: The ID of the event to delete.
|
201
|
+
calendar_id: Calendar ID containing the event (defaults to "primary").
|
202
|
+
send_notifications: Whether to send cancellation notifications (default True).
|
203
|
+
|
204
|
+
Returns:
|
205
|
+
A dictionary confirming the deletion.
|
206
|
+
"""
|
207
|
+
logger.info(f"Executing delete_calendar_event on calendar '{calendar_id}', event '{event_id}'")
|
208
|
+
if not event_id:
|
209
|
+
raise ValueError("Event ID is required")
|
210
|
+
|
211
|
+
calendar_service = CalendarService()
|
212
|
+
success = calendar_service.delete_event(
|
213
|
+
event_id=event_id,
|
214
|
+
send_notifications=send_notifications,
|
215
|
+
calendar_id=calendar_id,
|
216
|
+
)
|
217
|
+
|
218
|
+
if not success:
|
219
|
+
# Attempt to check if the service returned an error dict
|
220
|
+
error_info = getattr(calendar_service, "last_error", None) # Hypothetical
|
221
|
+
error_msg = "Failed to delete calendar event"
|
222
|
+
if isinstance(error_info, dict) and error_info.get("error"):
|
223
|
+
error_msg = error_info.get("message", error_msg)
|
224
|
+
raise ValueError(error_msg)
|
225
|
+
|
226
|
+
return {
|
227
|
+
"message": f"Event with ID '{event_id}' deleted successfully from calendar '{calendar_id}'.",
|
228
|
+
"success": True,
|
229
|
+
}
|
@@ -0,0 +1,277 @@
|
|
1
|
+
"""
|
2
|
+
Google Docs tool handlers for Google Workspace MCP.
|
3
|
+
"""
|
4
|
+
|
5
|
+
import logging
|
6
|
+
from typing import Any
|
7
|
+
|
8
|
+
from google_workspace_mcp.app import mcp
|
9
|
+
from google_workspace_mcp.services.docs_service import DocsService
|
10
|
+
|
11
|
+
logger = logging.getLogger(__name__)
|
12
|
+
|
13
|
+
|
14
|
+
@mcp.tool(
|
15
|
+
name="docs_create_document",
|
16
|
+
description="Creates a new Google Document with a specified title.",
|
17
|
+
)
|
18
|
+
async def docs_create_document(title: str) -> dict[str, Any]:
|
19
|
+
"""
|
20
|
+
Creates a new, empty Google Document.
|
21
|
+
|
22
|
+
Args:
|
23
|
+
title: The title for the new Google Document.
|
24
|
+
|
25
|
+
Returns:
|
26
|
+
A dictionary containing the 'document_id', 'title', and 'document_link' of the created document,
|
27
|
+
or an error message.
|
28
|
+
"""
|
29
|
+
logger.info(f"Executing docs_create_document tool with title: '{title}'")
|
30
|
+
if not title or not title.strip():
|
31
|
+
raise ValueError("Document title cannot be empty.")
|
32
|
+
|
33
|
+
docs_service = DocsService()
|
34
|
+
result = docs_service.create_document(title=title)
|
35
|
+
|
36
|
+
if isinstance(result, dict) and result.get("error"):
|
37
|
+
raise ValueError(result.get("message", "Error creating document"))
|
38
|
+
|
39
|
+
if not result or not result.get("document_id"):
|
40
|
+
raise ValueError(
|
41
|
+
f"Failed to create document '{title}' or did not receive a document ID."
|
42
|
+
)
|
43
|
+
|
44
|
+
return result
|
45
|
+
|
46
|
+
|
47
|
+
@mcp.tool(
|
48
|
+
name="docs_get_document_metadata",
|
49
|
+
description="Retrieves metadata (like title and ID) for a Google Document.",
|
50
|
+
)
|
51
|
+
async def docs_get_document_metadata(document_id: str) -> dict[str, Any]:
|
52
|
+
"""
|
53
|
+
Retrieves metadata for a specific Google Document.
|
54
|
+
|
55
|
+
Args:
|
56
|
+
document_id: The ID of the Google Document.
|
57
|
+
|
58
|
+
Returns:
|
59
|
+
A dictionary containing the document's 'document_id', 'title', and 'document_link',
|
60
|
+
or an error message.
|
61
|
+
"""
|
62
|
+
logger.info(
|
63
|
+
f"Executing docs_get_document_metadata tool for document_id: '{document_id}'"
|
64
|
+
)
|
65
|
+
if not document_id or not document_id.strip():
|
66
|
+
raise ValueError("Document ID cannot be empty.")
|
67
|
+
|
68
|
+
docs_service = DocsService()
|
69
|
+
result = docs_service.get_document_metadata(document_id=document_id)
|
70
|
+
|
71
|
+
if isinstance(result, dict) and result.get("error"):
|
72
|
+
raise ValueError(result.get("message", "Error retrieving document metadata"))
|
73
|
+
|
74
|
+
if not result or not result.get("document_id"):
|
75
|
+
raise ValueError(f"Failed to retrieve metadata for document '{document_id}'.")
|
76
|
+
|
77
|
+
return result
|
78
|
+
|
79
|
+
|
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
|
+
)
|
84
|
+
async def docs_get_content_as_markdown(document_id: str) -> dict[str, Any]:
|
85
|
+
"""
|
86
|
+
Retrieves the main textual content of a Google Document, converted to Markdown.
|
87
|
+
|
88
|
+
Args:
|
89
|
+
document_id: The ID of the Google Document.
|
90
|
+
|
91
|
+
Returns:
|
92
|
+
A dictionary containing the 'document_id' and 'markdown_content',
|
93
|
+
or an error message.
|
94
|
+
"""
|
95
|
+
logger.info(
|
96
|
+
f"Executing docs_get_content_as_markdown tool for document_id: '{document_id}'"
|
97
|
+
)
|
98
|
+
if not document_id or not document_id.strip():
|
99
|
+
raise ValueError("Document ID cannot be empty.")
|
100
|
+
|
101
|
+
docs_service = DocsService()
|
102
|
+
result = docs_service.get_document_content_as_markdown(document_id=document_id)
|
103
|
+
|
104
|
+
if isinstance(result, dict) and result.get("error"):
|
105
|
+
raise ValueError(
|
106
|
+
result.get("message", "Error retrieving document content as Markdown")
|
107
|
+
)
|
108
|
+
|
109
|
+
if not result or "markdown_content" not in result:
|
110
|
+
raise ValueError(
|
111
|
+
f"Failed to retrieve Markdown content for document '{document_id}'."
|
112
|
+
)
|
113
|
+
|
114
|
+
return result
|
115
|
+
|
116
|
+
|
117
|
+
@mcp.tool(
|
118
|
+
name="docs_append_text",
|
119
|
+
description="Appends text to the end of a specified Google Document.",
|
120
|
+
)
|
121
|
+
async def docs_append_text(
|
122
|
+
document_id: str, text: str, ensure_newline: bool = True
|
123
|
+
) -> dict[str, Any]:
|
124
|
+
"""
|
125
|
+
Appends the given text to the end of the specified Google Document.
|
126
|
+
|
127
|
+
Args:
|
128
|
+
document_id: The ID of the Google Document.
|
129
|
+
text: The text to append.
|
130
|
+
ensure_newline: If True, prepends a newline to the text before appending,
|
131
|
+
if the document is not empty, to ensure the new text starts on a new line. (Default: True)
|
132
|
+
|
133
|
+
Returns:
|
134
|
+
A dictionary indicating success or an error message.
|
135
|
+
"""
|
136
|
+
logger.info(f"Executing docs_append_text tool for document_id: '{document_id}'")
|
137
|
+
if not document_id or not document_id.strip():
|
138
|
+
raise ValueError("Document ID cannot be empty.")
|
139
|
+
# Text can be empty, that's fine.
|
140
|
+
|
141
|
+
docs_service = DocsService()
|
142
|
+
result = docs_service.append_text(
|
143
|
+
document_id=document_id, text=text, ensure_newline=ensure_newline
|
144
|
+
)
|
145
|
+
|
146
|
+
if isinstance(result, dict) and result.get("error"):
|
147
|
+
raise ValueError(result.get("message", "Error appending text to document"))
|
148
|
+
|
149
|
+
if not result or not result.get("success"):
|
150
|
+
raise ValueError(f"Failed to append text to document '{document_id}'.")
|
151
|
+
|
152
|
+
return result
|
153
|
+
|
154
|
+
|
155
|
+
@mcp.tool(
|
156
|
+
name="docs_prepend_text",
|
157
|
+
description="Prepends text to the beginning of a specified Google Document.",
|
158
|
+
)
|
159
|
+
async def docs_prepend_text(
|
160
|
+
document_id: str, text: str, ensure_newline: bool = True
|
161
|
+
) -> dict[str, Any]:
|
162
|
+
"""
|
163
|
+
Prepends the given text to the beginning of the specified Google Document.
|
164
|
+
|
165
|
+
Args:
|
166
|
+
document_id: The ID of the Google Document.
|
167
|
+
text: The text to prepend.
|
168
|
+
ensure_newline: If True, appends a newline to the text after prepending,
|
169
|
+
if the document was not empty, to ensure existing content starts on a new line. (Default: True)
|
170
|
+
|
171
|
+
Returns:
|
172
|
+
A dictionary indicating success or an error message.
|
173
|
+
"""
|
174
|
+
logger.info(f"Executing docs_prepend_text tool for document_id: '{document_id}'")
|
175
|
+
if not document_id or not document_id.strip():
|
176
|
+
raise ValueError("Document ID cannot be empty.")
|
177
|
+
# Text can be empty, that's fine.
|
178
|
+
|
179
|
+
docs_service = DocsService()
|
180
|
+
result = docs_service.prepend_text(
|
181
|
+
document_id=document_id, text=text, ensure_newline=ensure_newline
|
182
|
+
)
|
183
|
+
|
184
|
+
if isinstance(result, dict) and result.get("error"):
|
185
|
+
raise ValueError(result.get("message", "Error prepending text to document"))
|
186
|
+
|
187
|
+
if not result or not result.get("success"):
|
188
|
+
raise ValueError(f"Failed to prepend text to document '{document_id}'.")
|
189
|
+
|
190
|
+
return result
|
191
|
+
|
192
|
+
|
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
|
+
)
|
197
|
+
async def docs_insert_text(
|
198
|
+
document_id: str, text: str, index: int | None = None, segment_id: str | None = None
|
199
|
+
) -> dict[str, Any]:
|
200
|
+
"""
|
201
|
+
Inserts the given text at a specific location within the specified Google Document.
|
202
|
+
|
203
|
+
Args:
|
204
|
+
document_id: The ID of the Google Document.
|
205
|
+
text: The text to insert.
|
206
|
+
index: Optional. The 0-based index where the text should be inserted within the segment.
|
207
|
+
For the main body, an index of 1 typically refers to the beginning of the content.
|
208
|
+
Consult Google Docs API documentation for details on indexing if precise placement is needed.
|
209
|
+
If omitted, defaults to a sensible starting position (e.g., beginning of the body).
|
210
|
+
segment_id: Optional. The ID of a specific document segment (e.g., header, footer).
|
211
|
+
If omitted, the text is inserted into the main document body.
|
212
|
+
|
213
|
+
Returns:
|
214
|
+
A dictionary indicating success or an error message.
|
215
|
+
"""
|
216
|
+
logger.info(
|
217
|
+
f"Executing docs_insert_text tool for document_id: '{document_id}' at index: {index}"
|
218
|
+
)
|
219
|
+
if not document_id or not document_id.strip():
|
220
|
+
raise ValueError("Document ID cannot be empty.")
|
221
|
+
# Text can be empty, that's a valid insertion (though perhaps not useful).
|
222
|
+
|
223
|
+
docs_service = DocsService()
|
224
|
+
result = docs_service.insert_text(
|
225
|
+
document_id=document_id, text=text, index=index, segment_id=segment_id
|
226
|
+
)
|
227
|
+
|
228
|
+
if isinstance(result, dict) and result.get("error"):
|
229
|
+
raise ValueError(result.get("message", "Error inserting text into document"))
|
230
|
+
|
231
|
+
if not result or not result.get("success"):
|
232
|
+
raise ValueError(f"Failed to insert text into document '{document_id}'.")
|
233
|
+
|
234
|
+
return result
|
235
|
+
|
236
|
+
|
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
|
+
)
|
241
|
+
async def docs_batch_update(document_id: str, requests: list[dict]) -> dict[str, Any]:
|
242
|
+
"""
|
243
|
+
Applies a list of Google Docs API update requests to the specified document.
|
244
|
+
This is an advanced tool; requests must conform to the Google Docs API format.
|
245
|
+
See: https://developers.google.com/docs/api/reference/rest/v1/documents/request
|
246
|
+
|
247
|
+
Args:
|
248
|
+
document_id: The ID of the Google Document.
|
249
|
+
requests: A list of request objects (as dictionaries) to apply.
|
250
|
+
Example request: {"insertText": {"location": {"index": 1}, "text": "Hello"}}
|
251
|
+
|
252
|
+
Returns:
|
253
|
+
A dictionary containing the API response from the batchUpdate call (includes replies for each request),
|
254
|
+
or an error message.
|
255
|
+
"""
|
256
|
+
logger.info(
|
257
|
+
f"Executing docs_batch_update tool for document_id: '{document_id}' with {len(requests)} requests."
|
258
|
+
)
|
259
|
+
if not document_id or not document_id.strip():
|
260
|
+
raise ValueError("Document ID cannot be empty.")
|
261
|
+
if not isinstance(requests, list):
|
262
|
+
raise ValueError("Requests must be a list.")
|
263
|
+
# Further validation of individual request structures is complex here and usually
|
264
|
+
# left to the API, but basic check for list is good.
|
265
|
+
|
266
|
+
docs_service = DocsService()
|
267
|
+
result = docs_service.batch_update(document_id=document_id, requests=requests)
|
268
|
+
|
269
|
+
if isinstance(result, dict) and result.get("error"):
|
270
|
+
raise ValueError(
|
271
|
+
result.get("message", "Error executing batch update on document")
|
272
|
+
)
|
273
|
+
|
274
|
+
if not result: # Should be caught by error dict check
|
275
|
+
raise ValueError(f"Failed to execute batch update on document '{document_id}'.")
|
276
|
+
|
277
|
+
return result # Return the full response which includes documentId and replies
|