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,322 @@
|
|
1
|
+
"""
|
2
|
+
Google Sheets 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.sheets_service import SheetsService
|
10
|
+
|
11
|
+
logger = logging.getLogger(__name__)
|
12
|
+
|
13
|
+
|
14
|
+
@mcp.tool(
|
15
|
+
name="sheets_create_spreadsheet",
|
16
|
+
description="Creates a new Google Spreadsheet with a specified title.",
|
17
|
+
)
|
18
|
+
async def sheets_create_spreadsheet(title: str) -> dict[str, Any]:
|
19
|
+
"""
|
20
|
+
Creates a new, empty Google Spreadsheet.
|
21
|
+
|
22
|
+
Args:
|
23
|
+
title: The title for the new Google Spreadsheet.
|
24
|
+
|
25
|
+
Returns:
|
26
|
+
A dictionary containing the 'spreadsheet_id', 'title', and 'spreadsheet_url'
|
27
|
+
of the created spreadsheet, or an error message.
|
28
|
+
"""
|
29
|
+
logger.info(f"Executing sheets_create_spreadsheet tool with title: '{title}'")
|
30
|
+
if not title or not title.strip():
|
31
|
+
raise ValueError("Spreadsheet title cannot be empty.")
|
32
|
+
|
33
|
+
sheets_service = SheetsService()
|
34
|
+
result = sheets_service.create_spreadsheet(title=title)
|
35
|
+
|
36
|
+
if isinstance(result, dict) and result.get("error"):
|
37
|
+
raise ValueError(result.get("message", "Error creating spreadsheet"))
|
38
|
+
|
39
|
+
if not result or not result.get("spreadsheet_id"):
|
40
|
+
raise ValueError(
|
41
|
+
f"Failed to create spreadsheet '{title}' or did not receive a spreadsheet ID."
|
42
|
+
)
|
43
|
+
|
44
|
+
return result
|
45
|
+
|
46
|
+
|
47
|
+
@mcp.tool(
|
48
|
+
name="sheets_read_range",
|
49
|
+
description="Reads data from a specified range in a Google Spreadsheet (e.g., 'Sheet1!A1:B5').",
|
50
|
+
)
|
51
|
+
async def sheets_read_range(spreadsheet_id: str, range_a1: str) -> dict[str, Any]:
|
52
|
+
"""
|
53
|
+
Reads data from a given A1 notation range in a Google Spreadsheet.
|
54
|
+
|
55
|
+
Args:
|
56
|
+
spreadsheet_id: The ID of the spreadsheet.
|
57
|
+
range_a1: The A1 notation of the range to read (e.g., "Sheet1!A1:B5", or "A1:B5" if referring
|
58
|
+
to the first visible sheet or if sheet name is part of it).
|
59
|
+
|
60
|
+
Returns:
|
61
|
+
A dictionary containing the range and a list of lists representing the cell values,
|
62
|
+
or an error message.
|
63
|
+
"""
|
64
|
+
logger.info(
|
65
|
+
f"Executing sheets_read_range tool for spreadsheet_id: '{spreadsheet_id}', range: '{range_a1}'"
|
66
|
+
)
|
67
|
+
if not spreadsheet_id or not spreadsheet_id.strip():
|
68
|
+
raise ValueError("Spreadsheet ID cannot be empty.")
|
69
|
+
if not range_a1 or not range_a1.strip():
|
70
|
+
raise ValueError("Range (A1 notation) cannot be empty.")
|
71
|
+
|
72
|
+
sheets_service = SheetsService()
|
73
|
+
result = sheets_service.read_range(spreadsheet_id=spreadsheet_id, range_a1=range_a1)
|
74
|
+
|
75
|
+
if isinstance(result, dict) and result.get("error"):
|
76
|
+
raise ValueError(result.get("message", "Error reading range from spreadsheet"))
|
77
|
+
|
78
|
+
if (
|
79
|
+
not result or "values" not in result
|
80
|
+
): # Check for 'values' as it's key for successful read
|
81
|
+
raise ValueError(
|
82
|
+
f"Failed to read range '{range_a1}' from spreadsheet '{spreadsheet_id}'."
|
83
|
+
)
|
84
|
+
|
85
|
+
return result
|
86
|
+
|
87
|
+
|
88
|
+
@mcp.tool(
|
89
|
+
name="sheets_write_range",
|
90
|
+
description="Writes data to a specified range in a Google Spreadsheet (e.g., 'Sheet1!A1:B5').",
|
91
|
+
)
|
92
|
+
async def sheets_write_range(
|
93
|
+
spreadsheet_id: str,
|
94
|
+
range_a1: str,
|
95
|
+
values: list[list[Any]],
|
96
|
+
value_input_option: str = "USER_ENTERED",
|
97
|
+
) -> dict[str, Any]:
|
98
|
+
"""
|
99
|
+
Writes data (list of lists) to a given A1 notation range in a Google Spreadsheet.
|
100
|
+
|
101
|
+
Args:
|
102
|
+
spreadsheet_id: The ID of the spreadsheet.
|
103
|
+
range_a1: The A1 notation of the range to write to (e.g., "Sheet1!A1:B2").
|
104
|
+
values: A list of lists representing the data rows to write.
|
105
|
+
Example: [["Name", "Score"], ["Alice", 100], ["Bob", 90]]
|
106
|
+
value_input_option: How input data should be interpreted.
|
107
|
+
"USER_ENTERED": Values parsed as if typed by user (e.g., formulas).
|
108
|
+
"RAW": Values taken literally. (Default: "USER_ENTERED")
|
109
|
+
Returns:
|
110
|
+
A dictionary detailing the update (updated range, number of cells, etc.),
|
111
|
+
or an error message.
|
112
|
+
"""
|
113
|
+
logger.info(
|
114
|
+
f"Executing sheets_write_range tool for spreadsheet_id: '{spreadsheet_id}', range: '{range_a1}'"
|
115
|
+
)
|
116
|
+
if not spreadsheet_id or not spreadsheet_id.strip():
|
117
|
+
raise ValueError("Spreadsheet ID cannot be empty.")
|
118
|
+
if not range_a1 or not range_a1.strip():
|
119
|
+
raise ValueError("Range (A1 notation) cannot be empty.")
|
120
|
+
if not isinstance(values, list) or not all(isinstance(row, list) for row in values):
|
121
|
+
raise ValueError("Values must be a list of lists.")
|
122
|
+
if value_input_option not in ["USER_ENTERED", "RAW"]:
|
123
|
+
raise ValueError("value_input_option must be either 'USER_ENTERED' or 'RAW'.")
|
124
|
+
|
125
|
+
sheets_service = SheetsService()
|
126
|
+
result = sheets_service.write_range(
|
127
|
+
spreadsheet_id=spreadsheet_id,
|
128
|
+
range_a1=range_a1,
|
129
|
+
values=values,
|
130
|
+
value_input_option=value_input_option,
|
131
|
+
)
|
132
|
+
|
133
|
+
if isinstance(result, dict) and result.get("error"):
|
134
|
+
raise ValueError(result.get("message", "Error writing to range in spreadsheet"))
|
135
|
+
|
136
|
+
if not result or not result.get("updated_range"):
|
137
|
+
raise ValueError(
|
138
|
+
f"Failed to write to range '{range_a1}' in spreadsheet '{spreadsheet_id}'."
|
139
|
+
)
|
140
|
+
|
141
|
+
return result
|
142
|
+
|
143
|
+
|
144
|
+
@mcp.tool(
|
145
|
+
name="sheets_append_rows",
|
146
|
+
description="Appends rows of data to a sheet or table in a Google Spreadsheet (e.g., to 'Sheet1').",
|
147
|
+
)
|
148
|
+
async def sheets_append_rows(
|
149
|
+
spreadsheet_id: str,
|
150
|
+
range_a1: str,
|
151
|
+
values: list[list[Any]],
|
152
|
+
value_input_option: str = "USER_ENTERED",
|
153
|
+
insert_data_option: str = "INSERT_ROWS",
|
154
|
+
) -> dict[str, Any]:
|
155
|
+
"""
|
156
|
+
Appends rows of data to a sheet or table in a Google Spreadsheet.
|
157
|
+
|
158
|
+
Args:
|
159
|
+
spreadsheet_id: The ID of the spreadsheet.
|
160
|
+
range_a1: The A1 notation of the sheet or table to append to (e.g., "Sheet1" or "MyNamedRange").
|
161
|
+
Data will be appended after the last row of data in this range.
|
162
|
+
values: A list of lists representing the data rows to append.
|
163
|
+
value_input_option: How input data should be interpreted ("USER_ENTERED" or "RAW"). Default: "USER_ENTERED".
|
164
|
+
insert_data_option: How new data should be inserted ("INSERT_ROWS" or "OVERWRITE"). Default: "INSERT_ROWS".
|
165
|
+
|
166
|
+
Returns:
|
167
|
+
A dictionary detailing the append operation (e.g., range of appended data),
|
168
|
+
or an error message.
|
169
|
+
"""
|
170
|
+
logger.info(
|
171
|
+
f"Executing sheets_append_rows tool for spreadsheet_id: '{spreadsheet_id}', range: '{range_a1}'"
|
172
|
+
)
|
173
|
+
if not spreadsheet_id or not spreadsheet_id.strip():
|
174
|
+
raise ValueError("Spreadsheet ID cannot be empty.")
|
175
|
+
if not range_a1 or not range_a1.strip():
|
176
|
+
raise ValueError("Range (A1 notation) cannot be empty.")
|
177
|
+
if not isinstance(values, list) or not all(isinstance(row, list) for row in values):
|
178
|
+
raise ValueError("Values must be a non-empty list of lists.")
|
179
|
+
if not values: # Ensure values is not an empty list
|
180
|
+
raise ValueError("Values list cannot be empty.")
|
181
|
+
if value_input_option not in ["USER_ENTERED", "RAW"]:
|
182
|
+
raise ValueError("value_input_option must be either 'USER_ENTERED' or 'RAW'.")
|
183
|
+
if insert_data_option not in ["INSERT_ROWS", "OVERWRITE"]:
|
184
|
+
raise ValueError(
|
185
|
+
"insert_data_option must be either 'INSERT_ROWS' or 'OVERWRITE'."
|
186
|
+
)
|
187
|
+
|
188
|
+
sheets_service = SheetsService()
|
189
|
+
result = sheets_service.append_rows(
|
190
|
+
spreadsheet_id=spreadsheet_id,
|
191
|
+
range_a1=range_a1,
|
192
|
+
values=values,
|
193
|
+
value_input_option=value_input_option,
|
194
|
+
insert_data_option=insert_data_option,
|
195
|
+
)
|
196
|
+
|
197
|
+
if isinstance(result, dict) and result.get("error"):
|
198
|
+
raise ValueError(result.get("message", "Error appending rows to spreadsheet"))
|
199
|
+
|
200
|
+
if not result: # Check for empty or None result as well
|
201
|
+
raise ValueError(
|
202
|
+
f"Failed to append rows to range '{range_a1}' in spreadsheet '{spreadsheet_id}'."
|
203
|
+
)
|
204
|
+
|
205
|
+
return result
|
206
|
+
|
207
|
+
|
208
|
+
@mcp.tool(
|
209
|
+
name="sheets_clear_range",
|
210
|
+
description="Clears values from a specified range in a Google Spreadsheet (e.g., 'Sheet1!A1:B5').",
|
211
|
+
)
|
212
|
+
async def sheets_clear_range(spreadsheet_id: str, range_a1: str) -> dict[str, Any]:
|
213
|
+
"""
|
214
|
+
Clears all values from a given A1 notation range in a Google Spreadsheet.
|
215
|
+
Note: This usually clears only the values, not formatting.
|
216
|
+
|
217
|
+
Args:
|
218
|
+
spreadsheet_id: The ID of the spreadsheet.
|
219
|
+
range_a1: The A1 notation of the range to clear (e.g., "Sheet1!A1:B5").
|
220
|
+
|
221
|
+
Returns:
|
222
|
+
A dictionary confirming the cleared range, or an error message.
|
223
|
+
"""
|
224
|
+
logger.info(
|
225
|
+
f"Executing sheets_clear_range tool for spreadsheet_id: '{spreadsheet_id}', range: '{range_a1}'"
|
226
|
+
)
|
227
|
+
if not spreadsheet_id or not spreadsheet_id.strip():
|
228
|
+
raise ValueError("Spreadsheet ID cannot be empty.")
|
229
|
+
if not range_a1 or not range_a1.strip():
|
230
|
+
raise ValueError("Range (A1 notation) cannot be empty.")
|
231
|
+
|
232
|
+
sheets_service = SheetsService()
|
233
|
+
result = sheets_service.clear_range(
|
234
|
+
spreadsheet_id=spreadsheet_id, range_a1=range_a1
|
235
|
+
)
|
236
|
+
|
237
|
+
if isinstance(result, dict) and result.get("error"):
|
238
|
+
raise ValueError(result.get("message", "Error clearing range in spreadsheet"))
|
239
|
+
|
240
|
+
if not result or not result.get("cleared_range"):
|
241
|
+
raise ValueError(
|
242
|
+
f"Failed to clear range '{range_a1}' in spreadsheet '{spreadsheet_id}'."
|
243
|
+
)
|
244
|
+
|
245
|
+
return result
|
246
|
+
|
247
|
+
|
248
|
+
@mcp.tool(
|
249
|
+
name="sheets_add_sheet",
|
250
|
+
description="Adds a new sheet (tab) to an existing Google Spreadsheet.",
|
251
|
+
)
|
252
|
+
async def sheets_add_sheet(spreadsheet_id: str, title: str) -> dict[str, Any]:
|
253
|
+
"""
|
254
|
+
Adds a new sheet with the given title to the specified spreadsheet.
|
255
|
+
|
256
|
+
Args:
|
257
|
+
spreadsheet_id: The ID of the spreadsheet.
|
258
|
+
title: The title for the new sheet.
|
259
|
+
|
260
|
+
Returns:
|
261
|
+
A dictionary containing properties of the newly created sheet (like sheetId, title, index),
|
262
|
+
or an error message.
|
263
|
+
"""
|
264
|
+
logger.info(
|
265
|
+
f"Executing sheets_add_sheet tool for spreadsheet_id: '{spreadsheet_id}', title: '{title}'"
|
266
|
+
)
|
267
|
+
if not spreadsheet_id or not spreadsheet_id.strip():
|
268
|
+
raise ValueError("Spreadsheet ID cannot be empty.")
|
269
|
+
if not title or not title.strip():
|
270
|
+
raise ValueError("Sheet title cannot be empty.")
|
271
|
+
|
272
|
+
sheets_service = SheetsService()
|
273
|
+
result = sheets_service.add_sheet(spreadsheet_id=spreadsheet_id, title=title)
|
274
|
+
|
275
|
+
if isinstance(result, dict) and result.get("error"):
|
276
|
+
raise ValueError(result.get("message", "Error adding sheet to spreadsheet"))
|
277
|
+
|
278
|
+
if not result or not result.get("sheet_properties"):
|
279
|
+
raise ValueError(
|
280
|
+
f"Failed to add sheet '{title}' to spreadsheet '{spreadsheet_id}'."
|
281
|
+
)
|
282
|
+
|
283
|
+
return result
|
284
|
+
|
285
|
+
|
286
|
+
@mcp.tool(
|
287
|
+
name="sheets_delete_sheet",
|
288
|
+
description="Deletes a specific sheet (tab) from a Google Spreadsheet using its numeric sheet ID.",
|
289
|
+
)
|
290
|
+
async def sheets_delete_sheet(spreadsheet_id: str, sheet_id: int) -> dict[str, Any]:
|
291
|
+
"""
|
292
|
+
Deletes a sheet from the specified spreadsheet using its numeric ID.
|
293
|
+
|
294
|
+
Args:
|
295
|
+
spreadsheet_id: The ID of the spreadsheet.
|
296
|
+
sheet_id: The numeric ID of the sheet to delete.
|
297
|
+
|
298
|
+
Returns:
|
299
|
+
A dictionary confirming the deletion or an error message.
|
300
|
+
"""
|
301
|
+
logger.info(
|
302
|
+
f"Executing sheets_delete_sheet tool for spreadsheet_id: '{spreadsheet_id}', sheet_id: {sheet_id}"
|
303
|
+
)
|
304
|
+
if not spreadsheet_id or not spreadsheet_id.strip():
|
305
|
+
raise ValueError("Spreadsheet ID cannot be empty.")
|
306
|
+
if not isinstance(sheet_id, int):
|
307
|
+
raise ValueError("Sheet ID must be an integer.")
|
308
|
+
|
309
|
+
sheets_service = SheetsService()
|
310
|
+
result = sheets_service.delete_sheet(
|
311
|
+
spreadsheet_id=spreadsheet_id, sheet_id=sheet_id
|
312
|
+
)
|
313
|
+
|
314
|
+
if isinstance(result, dict) and result.get("error"):
|
315
|
+
raise ValueError(result.get("message", "Error deleting sheet from spreadsheet"))
|
316
|
+
|
317
|
+
if not result or not result.get("success"):
|
318
|
+
raise ValueError(
|
319
|
+
f"Failed to delete sheet ID '{sheet_id}' from spreadsheet '{spreadsheet_id}'."
|
320
|
+
)
|
321
|
+
|
322
|
+
return result
|