workspace-mcp 1.0.0__py3-none-any.whl → 1.0.2__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.
- auth/service_decorator.py +31 -32
- core/server.py +3 -10
- core/utils.py +36 -0
- gcalendar/calendar_tools.py +308 -258
- gchat/chat_tools.py +131 -158
- gdocs/docs_tools.py +121 -149
- gdrive/drive_tools.py +168 -171
- gforms/forms_tools.py +118 -157
- gmail/gmail_tools.py +319 -400
- gsheets/sheets_tools.py +144 -197
- gslides/slides_tools.py +113 -157
- main.py +31 -25
- workspace_mcp-1.0.2.dist-info/METADATA +422 -0
- workspace_mcp-1.0.2.dist-info/RECORD +32 -0
- workspace_mcp-1.0.0.dist-info/METADATA +0 -29
- workspace_mcp-1.0.0.dist-info/RECORD +0 -32
- {workspace_mcp-1.0.0.dist-info → workspace_mcp-1.0.2.dist-info}/WHEEL +0 -0
- {workspace_mcp-1.0.0.dist-info → workspace_mcp-1.0.2.dist-info}/entry_points.txt +0 -0
- {workspace_mcp-1.0.0.dist-info → workspace_mcp-1.0.2.dist-info}/licenses/LICENSE +0 -0
- {workspace_mcp-1.0.0.dist-info → workspace_mcp-1.0.2.dist-info}/top_level.txt +0 -0
gsheets/sheets_tools.py
CHANGED
@@ -13,6 +13,7 @@ from googleapiclient.errors import HttpError
|
|
13
13
|
|
14
14
|
from auth.service_decorator import require_google_service
|
15
15
|
from core.server import server
|
16
|
+
from core.utils import handle_http_errors
|
16
17
|
|
17
18
|
# Configure module logger
|
18
19
|
logger = logging.getLogger(__name__)
|
@@ -20,6 +21,7 @@ logger = logging.getLogger(__name__)
|
|
20
21
|
|
21
22
|
@server.tool()
|
22
23
|
@require_google_service("drive", "drive_read")
|
24
|
+
@handle_http_errors("list_spreadsheets")
|
23
25
|
async def list_spreadsheets(
|
24
26
|
service,
|
25
27
|
user_google_email: str,
|
@@ -37,47 +39,38 @@ async def list_spreadsheets(
|
|
37
39
|
"""
|
38
40
|
logger.info(f"[list_spreadsheets] Invoked. Email: '{user_google_email}'")
|
39
41
|
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
.
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
orderBy="modifiedTime desc",
|
48
|
-
)
|
49
|
-
.execute
|
42
|
+
files_response = await asyncio.to_thread(
|
43
|
+
service.files()
|
44
|
+
.list(
|
45
|
+
q="mimeType='application/vnd.google-apps.spreadsheet'",
|
46
|
+
pageSize=max_results,
|
47
|
+
fields="files(id,name,modifiedTime,webViewLink)",
|
48
|
+
orderBy="modifiedTime desc",
|
50
49
|
)
|
50
|
+
.execute
|
51
|
+
)
|
51
52
|
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
spreadsheets_list = [
|
57
|
-
f"- \"{file['name']}\" (ID: {file['id']}) | Modified: {file.get('modifiedTime', 'Unknown')} | Link: {file.get('webViewLink', 'No link')}"
|
58
|
-
for file in files
|
59
|
-
]
|
53
|
+
files = files_response.get("files", [])
|
54
|
+
if not files:
|
55
|
+
return f"No spreadsheets found for {user_google_email}."
|
60
56
|
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
57
|
+
spreadsheets_list = [
|
58
|
+
f"- \"{file['name']}\" (ID: {file['id']}) | Modified: {file.get('modifiedTime', 'Unknown')} | Link: {file.get('webViewLink', 'No link')}"
|
59
|
+
for file in files
|
60
|
+
]
|
65
61
|
|
66
|
-
|
67
|
-
|
62
|
+
text_output = (
|
63
|
+
f"Successfully listed {len(files)} spreadsheets for {user_google_email}:\n"
|
64
|
+
+ "\n".join(spreadsheets_list)
|
65
|
+
)
|
68
66
|
|
69
|
-
|
70
|
-
|
71
|
-
logger.error(message, exc_info=True)
|
72
|
-
raise Exception(message)
|
73
|
-
except Exception as e:
|
74
|
-
message = f"Unexpected error listing spreadsheets: {e}."
|
75
|
-
logger.exception(message)
|
76
|
-
raise Exception(message)
|
67
|
+
logger.info(f"Successfully listed {len(files)} spreadsheets for {user_google_email}.")
|
68
|
+
return text_output
|
77
69
|
|
78
70
|
|
79
71
|
@server.tool()
|
80
72
|
@require_google_service("sheets", "sheets_read")
|
73
|
+
@handle_http_errors("get_spreadsheet_info")
|
81
74
|
async def get_spreadsheet_info(
|
82
75
|
service,
|
83
76
|
user_google_email: str,
|
@@ -95,48 +88,39 @@ async def get_spreadsheet_info(
|
|
95
88
|
"""
|
96
89
|
logger.info(f"[get_spreadsheet_info] Invoked. Email: '{user_google_email}', Spreadsheet ID: {spreadsheet_id}")
|
97
90
|
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
)
|
102
|
-
|
103
|
-
title = spreadsheet.get("properties", {}).get("title", "Unknown")
|
104
|
-
sheets = spreadsheet.get("sheets", [])
|
91
|
+
spreadsheet = await asyncio.to_thread(
|
92
|
+
service.spreadsheets().get(spreadsheetId=spreadsheet_id).execute
|
93
|
+
)
|
105
94
|
|
106
|
-
|
107
|
-
|
108
|
-
sheet_props = sheet.get("properties", {})
|
109
|
-
sheet_name = sheet_props.get("title", "Unknown")
|
110
|
-
sheet_id = sheet_props.get("sheetId", "Unknown")
|
111
|
-
grid_props = sheet_props.get("gridProperties", {})
|
112
|
-
rows = grid_props.get("rowCount", "Unknown")
|
113
|
-
cols = grid_props.get("columnCount", "Unknown")
|
95
|
+
title = spreadsheet.get("properties", {}).get("title", "Unknown")
|
96
|
+
sheets = spreadsheet.get("sheets", [])
|
114
97
|
|
115
|
-
|
116
|
-
|
117
|
-
|
98
|
+
sheets_info = []
|
99
|
+
for sheet in sheets:
|
100
|
+
sheet_props = sheet.get("properties", {})
|
101
|
+
sheet_name = sheet_props.get("title", "Unknown")
|
102
|
+
sheet_id = sheet_props.get("sheetId", "Unknown")
|
103
|
+
grid_props = sheet_props.get("gridProperties", {})
|
104
|
+
rows = grid_props.get("rowCount", "Unknown")
|
105
|
+
cols = grid_props.get("columnCount", "Unknown")
|
118
106
|
|
119
|
-
|
120
|
-
f"
|
121
|
-
f"Sheets ({len(sheets)}):\n"
|
122
|
-
+ "\n".join(sheets_info) if sheets_info else " No sheets found"
|
107
|
+
sheets_info.append(
|
108
|
+
f" - \"{sheet_name}\" (ID: {sheet_id}) | Size: {rows}x{cols}"
|
123
109
|
)
|
124
110
|
|
125
|
-
|
126
|
-
|
111
|
+
text_output = (
|
112
|
+
f"Spreadsheet: \"{title}\" (ID: {spreadsheet_id})\n"
|
113
|
+
f"Sheets ({len(sheets)}):\n"
|
114
|
+
+ "\n".join(sheets_info) if sheets_info else " No sheets found"
|
115
|
+
)
|
127
116
|
|
128
|
-
|
129
|
-
|
130
|
-
logger.error(message, exc_info=True)
|
131
|
-
raise Exception(message)
|
132
|
-
except Exception as e:
|
133
|
-
message = f"Unexpected error getting spreadsheet info: {e}."
|
134
|
-
logger.exception(message)
|
135
|
-
raise Exception(message)
|
117
|
+
logger.info(f"Successfully retrieved info for spreadsheet {spreadsheet_id} for {user_google_email}.")
|
118
|
+
return text_output
|
136
119
|
|
137
120
|
|
138
121
|
@server.tool()
|
139
122
|
@require_google_service("sheets", "sheets_read")
|
123
|
+
@handle_http_errors("read_sheet_values")
|
140
124
|
async def read_sheet_values(
|
141
125
|
service,
|
142
126
|
user_google_email: str,
|
@@ -156,46 +140,37 @@ async def read_sheet_values(
|
|
156
140
|
"""
|
157
141
|
logger.info(f"[read_sheet_values] Invoked. Email: '{user_google_email}', Spreadsheet: {spreadsheet_id}, Range: {range_name}")
|
158
142
|
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
)
|
166
|
-
|
167
|
-
values = result.get("values", [])
|
168
|
-
if not values:
|
169
|
-
return f"No data found in range '{range_name}' for {user_google_email}."
|
143
|
+
result = await asyncio.to_thread(
|
144
|
+
service.spreadsheets()
|
145
|
+
.values()
|
146
|
+
.get(spreadsheetId=spreadsheet_id, range=range_name)
|
147
|
+
.execute
|
148
|
+
)
|
170
149
|
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
# Pad row with empty strings to show structure
|
175
|
-
padded_row = row + [""] * max(0, len(values[0]) - len(row)) if values else row
|
176
|
-
formatted_rows.append(f"Row {i:2d}: {padded_row}")
|
150
|
+
values = result.get("values", [])
|
151
|
+
if not values:
|
152
|
+
return f"No data found in range '{range_name}' for {user_google_email}."
|
177
153
|
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
)
|
154
|
+
# Format the output as a readable table
|
155
|
+
formatted_rows = []
|
156
|
+
for i, row in enumerate(values, 1):
|
157
|
+
# Pad row with empty strings to show structure
|
158
|
+
padded_row = row + [""] * max(0, len(values[0]) - len(row)) if values else row
|
159
|
+
formatted_rows.append(f"Row {i:2d}: {padded_row}")
|
183
160
|
|
184
|
-
|
185
|
-
|
161
|
+
text_output = (
|
162
|
+
f"Successfully read {len(values)} rows from range '{range_name}' in spreadsheet {spreadsheet_id} for {user_google_email}:\n"
|
163
|
+
+ "\n".join(formatted_rows[:50]) # Limit to first 50 rows for readability
|
164
|
+
+ (f"\n... and {len(values) - 50} more rows" if len(values) > 50 else "")
|
165
|
+
)
|
186
166
|
|
187
|
-
|
188
|
-
|
189
|
-
logger.error(message, exc_info=True)
|
190
|
-
raise Exception(message)
|
191
|
-
except Exception as e:
|
192
|
-
message = f"Unexpected error reading sheet values: {e}."
|
193
|
-
logger.exception(message)
|
194
|
-
raise Exception(message)
|
167
|
+
logger.info(f"Successfully read {len(values)} rows for {user_google_email}.")
|
168
|
+
return text_output
|
195
169
|
|
196
170
|
|
197
171
|
@server.tool()
|
198
172
|
@require_google_service("sheets", "sheets_write")
|
173
|
+
@handle_http_errors("modify_sheet_values")
|
199
174
|
async def modify_sheet_values(
|
200
175
|
service,
|
201
176
|
user_google_email: str,
|
@@ -225,57 +200,48 @@ async def modify_sheet_values(
|
|
225
200
|
if not clear_values and not values:
|
226
201
|
raise Exception("Either 'values' must be provided or 'clear_values' must be True.")
|
227
202
|
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
)
|
236
|
-
|
237
|
-
cleared_range = result.get("clearedRange", range_name)
|
238
|
-
text_output = f"Successfully cleared range '{cleared_range}' in spreadsheet {spreadsheet_id} for {user_google_email}."
|
239
|
-
logger.info(f"Successfully cleared range '{cleared_range}' for {user_google_email}.")
|
240
|
-
else:
|
241
|
-
body = {"values": values}
|
242
|
-
|
243
|
-
result = await asyncio.to_thread(
|
244
|
-
service.spreadsheets()
|
245
|
-
.values()
|
246
|
-
.update(
|
247
|
-
spreadsheetId=spreadsheet_id,
|
248
|
-
range=range_name,
|
249
|
-
valueInputOption=value_input_option,
|
250
|
-
body=body,
|
251
|
-
)
|
252
|
-
.execute
|
253
|
-
)
|
203
|
+
if clear_values:
|
204
|
+
result = await asyncio.to_thread(
|
205
|
+
service.spreadsheets()
|
206
|
+
.values()
|
207
|
+
.clear(spreadsheetId=spreadsheet_id, range=range_name)
|
208
|
+
.execute
|
209
|
+
)
|
254
210
|
|
255
|
-
|
256
|
-
|
257
|
-
|
211
|
+
cleared_range = result.get("clearedRange", range_name)
|
212
|
+
text_output = f"Successfully cleared range '{cleared_range}' in spreadsheet {spreadsheet_id} for {user_google_email}."
|
213
|
+
logger.info(f"Successfully cleared range '{cleared_range}' for {user_google_email}.")
|
214
|
+
else:
|
215
|
+
body = {"values": values}
|
258
216
|
|
259
|
-
|
260
|
-
|
261
|
-
|
217
|
+
result = await asyncio.to_thread(
|
218
|
+
service.spreadsheets()
|
219
|
+
.values()
|
220
|
+
.update(
|
221
|
+
spreadsheetId=spreadsheet_id,
|
222
|
+
range=range_name,
|
223
|
+
valueInputOption=value_input_option,
|
224
|
+
body=body,
|
262
225
|
)
|
263
|
-
|
226
|
+
.execute
|
227
|
+
)
|
228
|
+
|
229
|
+
updated_cells = result.get("updatedCells", 0)
|
230
|
+
updated_rows = result.get("updatedRows", 0)
|
231
|
+
updated_columns = result.get("updatedColumns", 0)
|
264
232
|
|
265
|
-
|
233
|
+
text_output = (
|
234
|
+
f"Successfully updated range '{range_name}' in spreadsheet {spreadsheet_id} for {user_google_email}. "
|
235
|
+
f"Updated: {updated_cells} cells, {updated_rows} rows, {updated_columns} columns."
|
236
|
+
)
|
237
|
+
logger.info(f"Successfully updated {updated_cells} cells for {user_google_email}.")
|
266
238
|
|
267
|
-
|
268
|
-
message = f"API error modifying sheet values: {error}. You might need to re-authenticate. LLM: Try 'start_google_auth' with user's email and service_name='Google Sheets'."
|
269
|
-
logger.error(message, exc_info=True)
|
270
|
-
raise Exception(message)
|
271
|
-
except Exception as e:
|
272
|
-
message = f"Unexpected error modifying sheet values: {e}."
|
273
|
-
logger.exception(message)
|
274
|
-
raise Exception(message)
|
239
|
+
return text_output
|
275
240
|
|
276
241
|
|
277
242
|
@server.tool()
|
278
243
|
@require_google_service("sheets", "sheets_write")
|
244
|
+
@handle_http_errors("create_spreadsheet")
|
279
245
|
async def create_spreadsheet(
|
280
246
|
service,
|
281
247
|
user_google_email: str,
|
@@ -295,45 +261,36 @@ async def create_spreadsheet(
|
|
295
261
|
"""
|
296
262
|
logger.info(f"[create_spreadsheet] Invoked. Email: '{user_google_email}', Title: {title}")
|
297
263
|
|
298
|
-
|
299
|
-
|
300
|
-
"
|
301
|
-
"title": title
|
302
|
-
}
|
264
|
+
spreadsheet_body = {
|
265
|
+
"properties": {
|
266
|
+
"title": title
|
303
267
|
}
|
268
|
+
}
|
304
269
|
|
305
|
-
|
306
|
-
|
307
|
-
|
308
|
-
|
309
|
-
|
310
|
-
spreadsheet = await asyncio.to_thread(
|
311
|
-
service.spreadsheets().create(body=spreadsheet_body).execute
|
312
|
-
)
|
270
|
+
if sheet_names:
|
271
|
+
spreadsheet_body["sheets"] = [
|
272
|
+
{"properties": {"title": sheet_name}} for sheet_name in sheet_names
|
273
|
+
]
|
313
274
|
|
314
|
-
|
315
|
-
|
275
|
+
spreadsheet = await asyncio.to_thread(
|
276
|
+
service.spreadsheets().create(body=spreadsheet_body).execute
|
277
|
+
)
|
316
278
|
|
317
|
-
|
318
|
-
|
319
|
-
f"ID: {spreadsheet_id} | URL: {spreadsheet_url}"
|
320
|
-
)
|
279
|
+
spreadsheet_id = spreadsheet.get("spreadsheetId")
|
280
|
+
spreadsheet_url = spreadsheet.get("spreadsheetUrl")
|
321
281
|
|
322
|
-
|
323
|
-
|
282
|
+
text_output = (
|
283
|
+
f"Successfully created spreadsheet '{title}' for {user_google_email}. "
|
284
|
+
f"ID: {spreadsheet_id} | URL: {spreadsheet_url}"
|
285
|
+
)
|
324
286
|
|
325
|
-
|
326
|
-
|
327
|
-
logger.error(message, exc_info=True)
|
328
|
-
raise Exception(message)
|
329
|
-
except Exception as e:
|
330
|
-
message = f"Unexpected error creating spreadsheet: {e}."
|
331
|
-
logger.exception(message)
|
332
|
-
raise Exception(message)
|
287
|
+
logger.info(f"Successfully created spreadsheet for {user_google_email}. ID: {spreadsheet_id}")
|
288
|
+
return text_output
|
333
289
|
|
334
290
|
|
335
291
|
@server.tool()
|
336
292
|
@require_google_service("sheets", "sheets_write")
|
293
|
+
@handle_http_errors("create_sheet")
|
337
294
|
async def create_sheet(
|
338
295
|
service,
|
339
296
|
user_google_email: str,
|
@@ -353,41 +310,31 @@ async def create_sheet(
|
|
353
310
|
"""
|
354
311
|
logger.info(f"[create_sheet] Invoked. Email: '{user_google_email}', Spreadsheet: {spreadsheet_id}, Sheet: {sheet_name}")
|
355
312
|
|
356
|
-
|
357
|
-
|
358
|
-
|
359
|
-
{
|
360
|
-
"
|
361
|
-
"
|
362
|
-
"title": sheet_name
|
363
|
-
}
|
313
|
+
request_body = {
|
314
|
+
"requests": [
|
315
|
+
{
|
316
|
+
"addSheet": {
|
317
|
+
"properties": {
|
318
|
+
"title": sheet_name
|
364
319
|
}
|
365
320
|
}
|
366
|
-
|
367
|
-
|
321
|
+
}
|
322
|
+
]
|
323
|
+
}
|
368
324
|
|
369
|
-
|
370
|
-
|
371
|
-
|
372
|
-
|
373
|
-
|
325
|
+
response = await asyncio.to_thread(
|
326
|
+
service.spreadsheets()
|
327
|
+
.batchUpdate(spreadsheetId=spreadsheet_id, body=request_body)
|
328
|
+
.execute
|
329
|
+
)
|
374
330
|
|
375
|
-
|
331
|
+
sheet_id = response["replies"][0]["addSheet"]["properties"]["sheetId"]
|
376
332
|
|
377
|
-
|
378
|
-
|
379
|
-
|
333
|
+
text_output = (
|
334
|
+
f"Successfully created sheet '{sheet_name}' (ID: {sheet_id}) in spreadsheet {spreadsheet_id} for {user_google_email}."
|
335
|
+
)
|
380
336
|
|
381
|
-
|
382
|
-
|
383
|
-
|
384
|
-
except HttpError as error:
|
385
|
-
message = f"API error creating sheet: {error}. You might need to re-authenticate. LLM: Try 'start_google_auth' with user's email and service_name='Google Sheets'."
|
386
|
-
logger.error(message, exc_info=True)
|
387
|
-
raise Exception(message)
|
388
|
-
except Exception as e:
|
389
|
-
message = f"Unexpected error creating sheet: {e}."
|
390
|
-
logger.exception(message)
|
391
|
-
raise Exception(message)
|
337
|
+
logger.info(f"Successfully created sheet for {user_google_email}. Sheet ID: {sheet_id}")
|
338
|
+
return text_output
|
392
339
|
|
393
340
|
|