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