workspace-mcp 1.0.3__tar.gz → 1.0.4__tar.gz
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.
- {workspace_mcp-1.0.3 → workspace_mcp-1.0.4}/PKG-INFO +32 -14
- {workspace_mcp-1.0.3 → workspace_mcp-1.0.4}/README.md +31 -13
- workspace_mcp-1.0.4/core/comments.py +257 -0
- {workspace_mcp-1.0.3 → workspace_mcp-1.0.4}/core/server.py +2 -1
- {workspace_mcp-1.0.3 → workspace_mcp-1.0.4}/gdocs/docs_tools.py +8 -175
- {workspace_mcp-1.0.3 → workspace_mcp-1.0.4}/gsheets/sheets_tools.py +11 -0
- {workspace_mcp-1.0.3 → workspace_mcp-1.0.4}/gslides/slides_tools.py +16 -1
- {workspace_mcp-1.0.3 → workspace_mcp-1.0.4}/pyproject.toml +1 -1
- {workspace_mcp-1.0.3 → workspace_mcp-1.0.4}/workspace_mcp.egg-info/PKG-INFO +32 -14
- {workspace_mcp-1.0.3 → workspace_mcp-1.0.4}/workspace_mcp.egg-info/SOURCES.txt +1 -0
- {workspace_mcp-1.0.3 → workspace_mcp-1.0.4}/LICENSE +0 -0
- {workspace_mcp-1.0.3 → workspace_mcp-1.0.4}/auth/__init__.py +0 -0
- {workspace_mcp-1.0.3 → workspace_mcp-1.0.4}/auth/google_auth.py +0 -0
- {workspace_mcp-1.0.3 → workspace_mcp-1.0.4}/auth/oauth_callback_server.py +0 -0
- {workspace_mcp-1.0.3 → workspace_mcp-1.0.4}/auth/oauth_responses.py +0 -0
- {workspace_mcp-1.0.3 → workspace_mcp-1.0.4}/auth/scopes.py +0 -0
- {workspace_mcp-1.0.3 → workspace_mcp-1.0.4}/auth/service_decorator.py +0 -0
- {workspace_mcp-1.0.3 → workspace_mcp-1.0.4}/core/__init__.py +0 -0
- {workspace_mcp-1.0.3 → workspace_mcp-1.0.4}/core/context.py +0 -0
- {workspace_mcp-1.0.3 → workspace_mcp-1.0.4}/core/utils.py +0 -0
- {workspace_mcp-1.0.3 → workspace_mcp-1.0.4}/gcalendar/__init__.py +0 -0
- {workspace_mcp-1.0.3 → workspace_mcp-1.0.4}/gcalendar/calendar_tools.py +0 -0
- {workspace_mcp-1.0.3 → workspace_mcp-1.0.4}/gchat/__init__.py +0 -0
- {workspace_mcp-1.0.3 → workspace_mcp-1.0.4}/gchat/chat_tools.py +0 -0
- {workspace_mcp-1.0.3 → workspace_mcp-1.0.4}/gdocs/__init__.py +0 -0
- {workspace_mcp-1.0.3 → workspace_mcp-1.0.4}/gdrive/__init__.py +0 -0
- {workspace_mcp-1.0.3 → workspace_mcp-1.0.4}/gdrive/drive_tools.py +0 -0
- {workspace_mcp-1.0.3 → workspace_mcp-1.0.4}/gforms/__init__.py +0 -0
- {workspace_mcp-1.0.3 → workspace_mcp-1.0.4}/gforms/forms_tools.py +0 -0
- {workspace_mcp-1.0.3 → workspace_mcp-1.0.4}/gmail/__init__.py +0 -0
- {workspace_mcp-1.0.3 → workspace_mcp-1.0.4}/gmail/gmail_tools.py +0 -0
- {workspace_mcp-1.0.3 → workspace_mcp-1.0.4}/gsheets/__init__.py +0 -0
- {workspace_mcp-1.0.3 → workspace_mcp-1.0.4}/gslides/__init__.py +0 -0
- {workspace_mcp-1.0.3 → workspace_mcp-1.0.4}/main.py +0 -0
- {workspace_mcp-1.0.3 → workspace_mcp-1.0.4}/setup.cfg +0 -0
- {workspace_mcp-1.0.3 → workspace_mcp-1.0.4}/tests/test_auth.py +0 -0
- {workspace_mcp-1.0.3 → workspace_mcp-1.0.4}/workspace_mcp.egg-info/dependency_links.txt +0 -0
- {workspace_mcp-1.0.3 → workspace_mcp-1.0.4}/workspace_mcp.egg-info/entry_points.txt +0 -0
- {workspace_mcp-1.0.3 → workspace_mcp-1.0.4}/workspace_mcp.egg-info/requires.txt +0 -0
- {workspace_mcp-1.0.3 → workspace_mcp-1.0.4}/workspace_mcp.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: workspace-mcp
|
3
|
-
Version: 1.0.
|
3
|
+
Version: 1.0.4
|
4
4
|
Summary: Comprehensive, highly performant Google Workspace Streamable HTTP & SSE MCP Server for Calendar, Gmail, Docs, Sheets, Slides & Drive
|
5
5
|
Author-email: Taylor Wilsdon <taylor@taylorwilsdon.com>
|
6
6
|
License: MIT
|
@@ -76,20 +76,14 @@ Dynamic: license-file
|
|
76
76
|
|
77
77
|
---
|
78
78
|
|
79
|
-
|
79
|
+
### A quick plug for AI-Enhanced Docs
|
80
80
|
|
81
81
|
> **This README was crafted with AI assistance, and here's why that matters**
|
82
82
|
>
|
83
|
-
>
|
83
|
+
> As a solo developer building open source tools that may only ever serve my own needs, comprehensive documentation often wouldn't happen without AI help. Using agentic dev tools like **Roo** & **Claude Code** that understand the entire codebase, AI doesn't just regurgitate generic content - it extracts real implementation details and creates accurate, specific documentation.
|
84
|
+
>
|
85
|
+
> In this case, Sonnet 4 took a pass & a human (me) verified them 6/28/25.
|
84
86
|
|
85
|
-
As a solo developer building open source tools that may only ever serve my own needs, comprehensive documentation often wouldn't happen without AI help. When done right—using agents like **Roo** or **Claude Code** that understand the entire codebase, AI doesn't just regurgitate generic content - it extracts real implementation details and creates accurate, specific documentation.
|
86
|
-
|
87
|
-
**The alternative? No docs at all.**
|
88
|
-
|
89
|
-
I hope the community can appreciate these tools for what they enable: solo developers maintaining professional documentation standards while focusing on building great software.
|
90
|
-
|
91
|
-
---
|
92
|
-
*This documentation was enhanced by AI with full codebase context. The result? You're reading docs that otherwise might not exist.*
|
93
87
|
|
94
88
|
## 🌐 Overview
|
95
89
|
|
@@ -101,9 +95,9 @@ A production-ready MCP server that integrates all major Google Workspace service
|
|
101
95
|
- **📅 Google Calendar**: Full calendar management with event CRUD operations
|
102
96
|
- **📁 Google Drive**: File operations with native Microsoft Office format support (.docx, .xlsx)
|
103
97
|
- **📧 Gmail**: Complete email management with search, send, and draft capabilities
|
104
|
-
- **📄 Google Docs**: Document operations including content extraction and
|
105
|
-
- **📊 Google Sheets**: Comprehensive spreadsheet management with flexible cell operations
|
106
|
-
- **🖼️ Google Slides**: Presentation management with slide creation, updates, and
|
98
|
+
- **📄 Google Docs**: Document operations including content extraction, creation, and comment management
|
99
|
+
- **📊 Google Sheets**: Comprehensive spreadsheet management with flexible cell operations and comment management
|
100
|
+
- **🖼️ Google Slides**: Presentation management with slide creation, updates, content manipulation, and comment management
|
107
101
|
- **📝 Google Forms**: Form creation, retrieval, publish settings, and response management
|
108
102
|
- **💬 Google Chat**: Space management and messaging capabilities
|
109
103
|
- **🔄 Multiple Transports**: HTTP with SSE fallback, OpenAPI compatibility via `mcpo`
|
@@ -185,12 +179,14 @@ uv run main.py
|
|
185
179
|
2. **Environment**:
|
186
180
|
```bash
|
187
181
|
export OAUTHLIB_INSECURE_TRANSPORT=1 # Development only
|
182
|
+
export USER_GOOGLE_EMAIL=your.email@gmail.com # Optional: Default email for auth - use this for single user setups and you won't need to set your email in system prompt for magic auth
|
188
183
|
```
|
189
184
|
|
190
185
|
3. **Server Configuration**:
|
191
186
|
The server's base URL and port can be customized using environment variables:
|
192
187
|
- `WORKSPACE_MCP_BASE_URI`: Sets the base URI for the server (default: http://localhost). This affects the server_url used for Gemini native function calling and the OAUTH_REDIRECT_URI.
|
193
188
|
- `WORKSPACE_MCP_PORT`: Sets the port the server listens on (default: 8000). This affects the server_url, port, and OAUTH_REDIRECT_URI.
|
189
|
+
- `USER_GOOGLE_EMAIL`: Optional default email for authentication flows. If set, the LLM won't need to specify your email when calling `start_google_auth`.
|
194
190
|
|
195
191
|
### Start the Server
|
196
192
|
|
@@ -338,6 +334,10 @@ When calling a tool:
|
|
338
334
|
| `get_doc_content` | Extract document text |
|
339
335
|
| `list_docs_in_folder` | List docs in folder |
|
340
336
|
| `create_doc` | Create new documents |
|
337
|
+
| `read_doc_comments` | Read all comments and replies |
|
338
|
+
| `create_doc_comment` | Create new comments |
|
339
|
+
| `reply_to_comment` | Reply to existing comments |
|
340
|
+
| `resolve_comment` | Resolve comments |
|
341
341
|
|
342
342
|
### 📊 Google Sheets ([`sheets_tools.py`](gsheets/sheets_tools.py))
|
343
343
|
|
@@ -349,6 +349,24 @@ When calling a tool:
|
|
349
349
|
| `modify_sheet_values` | Write/update/clear cells |
|
350
350
|
| `create_spreadsheet` | Create new spreadsheets |
|
351
351
|
| `create_sheet` | Add sheets to existing files |
|
352
|
+
| `read_sheet_comments` | Read all comments and replies |
|
353
|
+
| `create_sheet_comment` | Create new comments |
|
354
|
+
| `reply_to_sheet_comment` | Reply to existing comments |
|
355
|
+
| `resolve_sheet_comment` | Resolve comments |
|
356
|
+
|
357
|
+
### 🖼️ Google Slides ([`slides_tools.py`](gslides/slides_tools.py))
|
358
|
+
|
359
|
+
| Tool | Description |
|
360
|
+
|------|-------------|
|
361
|
+
| `create_presentation` | Create new presentations |
|
362
|
+
| `get_presentation` | Retrieve presentation details |
|
363
|
+
| `batch_update_presentation` | Apply multiple updates at once |
|
364
|
+
| `get_page` | Get specific slide information |
|
365
|
+
| `get_page_thumbnail` | Generate slide thumbnails |
|
366
|
+
| `read_presentation_comments` | Read all comments and replies |
|
367
|
+
| `create_presentation_comment` | Create new comments |
|
368
|
+
| `reply_to_presentation_comment` | Reply to existing comments |
|
369
|
+
| `resolve_presentation_comment` | Resolve comments |
|
352
370
|
|
353
371
|
### 📝 Google Forms ([`forms_tools.py`](gforms/forms_tools.py))
|
354
372
|
|
@@ -34,20 +34,14 @@
|
|
34
34
|
|
35
35
|
---
|
36
36
|
|
37
|
-
|
37
|
+
### A quick plug for AI-Enhanced Docs
|
38
38
|
|
39
39
|
> **This README was crafted with AI assistance, and here's why that matters**
|
40
40
|
>
|
41
|
-
>
|
41
|
+
> As a solo developer building open source tools that may only ever serve my own needs, comprehensive documentation often wouldn't happen without AI help. Using agentic dev tools like **Roo** & **Claude Code** that understand the entire codebase, AI doesn't just regurgitate generic content - it extracts real implementation details and creates accurate, specific documentation.
|
42
|
+
>
|
43
|
+
> In this case, Sonnet 4 took a pass & a human (me) verified them 6/28/25.
|
42
44
|
|
43
|
-
As a solo developer building open source tools that may only ever serve my own needs, comprehensive documentation often wouldn't happen without AI help. When done right—using agents like **Roo** or **Claude Code** that understand the entire codebase, AI doesn't just regurgitate generic content - it extracts real implementation details and creates accurate, specific documentation.
|
44
|
-
|
45
|
-
**The alternative? No docs at all.**
|
46
|
-
|
47
|
-
I hope the community can appreciate these tools for what they enable: solo developers maintaining professional documentation standards while focusing on building great software.
|
48
|
-
|
49
|
-
---
|
50
|
-
*This documentation was enhanced by AI with full codebase context. The result? You're reading docs that otherwise might not exist.*
|
51
45
|
|
52
46
|
## 🌐 Overview
|
53
47
|
|
@@ -59,9 +53,9 @@ A production-ready MCP server that integrates all major Google Workspace service
|
|
59
53
|
- **📅 Google Calendar**: Full calendar management with event CRUD operations
|
60
54
|
- **📁 Google Drive**: File operations with native Microsoft Office format support (.docx, .xlsx)
|
61
55
|
- **📧 Gmail**: Complete email management with search, send, and draft capabilities
|
62
|
-
- **📄 Google Docs**: Document operations including content extraction and
|
63
|
-
- **📊 Google Sheets**: Comprehensive spreadsheet management with flexible cell operations
|
64
|
-
- **🖼️ Google Slides**: Presentation management with slide creation, updates, and
|
56
|
+
- **📄 Google Docs**: Document operations including content extraction, creation, and comment management
|
57
|
+
- **📊 Google Sheets**: Comprehensive spreadsheet management with flexible cell operations and comment management
|
58
|
+
- **🖼️ Google Slides**: Presentation management with slide creation, updates, content manipulation, and comment management
|
65
59
|
- **📝 Google Forms**: Form creation, retrieval, publish settings, and response management
|
66
60
|
- **💬 Google Chat**: Space management and messaging capabilities
|
67
61
|
- **🔄 Multiple Transports**: HTTP with SSE fallback, OpenAPI compatibility via `mcpo`
|
@@ -143,12 +137,14 @@ uv run main.py
|
|
143
137
|
2. **Environment**:
|
144
138
|
```bash
|
145
139
|
export OAUTHLIB_INSECURE_TRANSPORT=1 # Development only
|
140
|
+
export USER_GOOGLE_EMAIL=your.email@gmail.com # Optional: Default email for auth - use this for single user setups and you won't need to set your email in system prompt for magic auth
|
146
141
|
```
|
147
142
|
|
148
143
|
3. **Server Configuration**:
|
149
144
|
The server's base URL and port can be customized using environment variables:
|
150
145
|
- `WORKSPACE_MCP_BASE_URI`: Sets the base URI for the server (default: http://localhost). This affects the server_url used for Gemini native function calling and the OAUTH_REDIRECT_URI.
|
151
146
|
- `WORKSPACE_MCP_PORT`: Sets the port the server listens on (default: 8000). This affects the server_url, port, and OAUTH_REDIRECT_URI.
|
147
|
+
- `USER_GOOGLE_EMAIL`: Optional default email for authentication flows. If set, the LLM won't need to specify your email when calling `start_google_auth`.
|
152
148
|
|
153
149
|
### Start the Server
|
154
150
|
|
@@ -296,6 +292,10 @@ When calling a tool:
|
|
296
292
|
| `get_doc_content` | Extract document text |
|
297
293
|
| `list_docs_in_folder` | List docs in folder |
|
298
294
|
| `create_doc` | Create new documents |
|
295
|
+
| `read_doc_comments` | Read all comments and replies |
|
296
|
+
| `create_doc_comment` | Create new comments |
|
297
|
+
| `reply_to_comment` | Reply to existing comments |
|
298
|
+
| `resolve_comment` | Resolve comments |
|
299
299
|
|
300
300
|
### 📊 Google Sheets ([`sheets_tools.py`](gsheets/sheets_tools.py))
|
301
301
|
|
@@ -307,6 +307,24 @@ When calling a tool:
|
|
307
307
|
| `modify_sheet_values` | Write/update/clear cells |
|
308
308
|
| `create_spreadsheet` | Create new spreadsheets |
|
309
309
|
| `create_sheet` | Add sheets to existing files |
|
310
|
+
| `read_sheet_comments` | Read all comments and replies |
|
311
|
+
| `create_sheet_comment` | Create new comments |
|
312
|
+
| `reply_to_sheet_comment` | Reply to existing comments |
|
313
|
+
| `resolve_sheet_comment` | Resolve comments |
|
314
|
+
|
315
|
+
### 🖼️ Google Slides ([`slides_tools.py`](gslides/slides_tools.py))
|
316
|
+
|
317
|
+
| Tool | Description |
|
318
|
+
|------|-------------|
|
319
|
+
| `create_presentation` | Create new presentations |
|
320
|
+
| `get_presentation` | Retrieve presentation details |
|
321
|
+
| `batch_update_presentation` | Apply multiple updates at once |
|
322
|
+
| `get_page` | Get specific slide information |
|
323
|
+
| `get_page_thumbnail` | Generate slide thumbnails |
|
324
|
+
| `read_presentation_comments` | Read all comments and replies |
|
325
|
+
| `create_presentation_comment` | Create new comments |
|
326
|
+
| `reply_to_presentation_comment` | Reply to existing comments |
|
327
|
+
| `resolve_presentation_comment` | Resolve comments |
|
310
328
|
|
311
329
|
### 📝 Google Forms ([`forms_tools.py`](gforms/forms_tools.py))
|
312
330
|
|
@@ -0,0 +1,257 @@
|
|
1
|
+
"""
|
2
|
+
Core Comments Module
|
3
|
+
|
4
|
+
This module provides reusable comment management functions for Google Workspace applications.
|
5
|
+
All Google Workspace apps (Docs, Sheets, Slides) use the Drive API for comment operations.
|
6
|
+
"""
|
7
|
+
|
8
|
+
import logging
|
9
|
+
import asyncio
|
10
|
+
from typing import Dict, Any
|
11
|
+
|
12
|
+
from mcp import types
|
13
|
+
from googleapiclient.errors import HttpError
|
14
|
+
|
15
|
+
from auth.service_decorator import require_google_service
|
16
|
+
from core.server import server
|
17
|
+
from core.utils import handle_http_errors
|
18
|
+
|
19
|
+
logger = logging.getLogger(__name__)
|
20
|
+
|
21
|
+
|
22
|
+
def create_comment_tools(app_name: str, file_id_param: str):
|
23
|
+
"""
|
24
|
+
Factory function to create comment management tools for a specific Google Workspace app.
|
25
|
+
|
26
|
+
Args:
|
27
|
+
app_name: Name of the app (e.g., "document", "spreadsheet", "presentation")
|
28
|
+
file_id_param: Parameter name for the file ID (e.g., "document_id", "spreadsheet_id", "presentation_id")
|
29
|
+
|
30
|
+
Returns:
|
31
|
+
Dict containing the four comment management functions with unique names
|
32
|
+
"""
|
33
|
+
|
34
|
+
# Create unique function names based on the app type
|
35
|
+
read_func_name = f"read_{app_name}_comments"
|
36
|
+
create_func_name = f"create_{app_name}_comment"
|
37
|
+
reply_func_name = f"reply_to_{app_name}_comment"
|
38
|
+
resolve_func_name = f"resolve_{app_name}_comment"
|
39
|
+
|
40
|
+
# Create read comments function
|
41
|
+
if file_id_param == "document_id":
|
42
|
+
@server.tool()
|
43
|
+
@require_google_service("drive", "drive_read")
|
44
|
+
@handle_http_errors(read_func_name)
|
45
|
+
async def read_comments(service, user_google_email: str, document_id: str) -> str:
|
46
|
+
"""Read all comments from a Google Slide, Sheet or Doc."""
|
47
|
+
return await _read_comments_impl(service, app_name, document_id)
|
48
|
+
|
49
|
+
@server.tool()
|
50
|
+
@require_google_service("drive", "drive_file")
|
51
|
+
@handle_http_errors(create_func_name)
|
52
|
+
async def create_comment(service, user_google_email: str, document_id: str, comment_content: str) -> str:
|
53
|
+
"""Create a new comment on a Google Slide, Sheet or Doc."""
|
54
|
+
return await _create_comment_impl(service, app_name, document_id, comment_content)
|
55
|
+
|
56
|
+
@server.tool()
|
57
|
+
@require_google_service("drive", "drive_file")
|
58
|
+
@handle_http_errors(reply_func_name)
|
59
|
+
async def reply_to_comment(service, user_google_email: str, document_id: str, comment_id: str, reply_content: str) -> str:
|
60
|
+
"""Reply to a specific comment in a Google Document."""
|
61
|
+
return await _reply_to_comment_impl(service, app_name, document_id, comment_id, reply_content)
|
62
|
+
|
63
|
+
@server.tool()
|
64
|
+
@require_google_service("drive", "drive_file")
|
65
|
+
@handle_http_errors(resolve_func_name)
|
66
|
+
async def resolve_comment(service, user_google_email: str, document_id: str, comment_id: str) -> str:
|
67
|
+
"""Resolve a comment in a Google Slide, Sheet or Doc."""
|
68
|
+
return await _resolve_comment_impl(service, app_name, document_id, comment_id)
|
69
|
+
|
70
|
+
elif file_id_param == "spreadsheet_id":
|
71
|
+
@server.tool()
|
72
|
+
@require_google_service("drive", "drive_read")
|
73
|
+
@handle_http_errors(read_func_name)
|
74
|
+
async def read_comments(service, user_google_email: str, spreadsheet_id: str) -> str:
|
75
|
+
"""Read all comments from a Google Slide, Sheet or Doc."""
|
76
|
+
return await _read_comments_impl(service, app_name, spreadsheet_id)
|
77
|
+
|
78
|
+
@server.tool()
|
79
|
+
@require_google_service("drive", "drive_file")
|
80
|
+
@handle_http_errors(create_func_name)
|
81
|
+
async def create_comment(service, user_google_email: str, spreadsheet_id: str, comment_content: str) -> str:
|
82
|
+
"""Create a new comment on a Google Slide, Sheet or Doc."""
|
83
|
+
return await _create_comment_impl(service, app_name, spreadsheet_id, comment_content)
|
84
|
+
|
85
|
+
@server.tool()
|
86
|
+
@require_google_service("drive", "drive_file")
|
87
|
+
@handle_http_errors(reply_func_name)
|
88
|
+
async def reply_to_comment(service, user_google_email: str, spreadsheet_id: str, comment_id: str, reply_content: str) -> str:
|
89
|
+
"""Reply to a specific comment in a Google Slide, Sheet or Doc."""
|
90
|
+
return await _reply_to_comment_impl(service, app_name, spreadsheet_id, comment_id, reply_content)
|
91
|
+
|
92
|
+
@server.tool()
|
93
|
+
@require_google_service("drive", "drive_file")
|
94
|
+
@handle_http_errors(resolve_func_name)
|
95
|
+
async def resolve_comment(service, user_google_email: str, spreadsheet_id: str, comment_id: str) -> str:
|
96
|
+
"""Resolve a comment in a Google Slide, Sheet or Doc."""
|
97
|
+
return await _resolve_comment_impl(service, app_name, spreadsheet_id, comment_id)
|
98
|
+
|
99
|
+
elif file_id_param == "presentation_id":
|
100
|
+
@server.tool()
|
101
|
+
@require_google_service("drive", "drive_read")
|
102
|
+
@handle_http_errors(read_func_name)
|
103
|
+
async def read_comments(service, user_google_email: str, presentation_id: str) -> str:
|
104
|
+
"""Read all comments from a Google Slide, Sheet or Doc."""
|
105
|
+
return await _read_comments_impl(service, app_name, presentation_id)
|
106
|
+
|
107
|
+
@server.tool()
|
108
|
+
@require_google_service("drive", "drive_file")
|
109
|
+
@handle_http_errors(create_func_name)
|
110
|
+
async def create_comment(service, user_google_email: str, presentation_id: str, comment_content: str) -> str:
|
111
|
+
"""Create a new comment on a Google Slide, Sheet or Doc."""
|
112
|
+
return await _create_comment_impl(service, app_name, presentation_id, comment_content)
|
113
|
+
|
114
|
+
@server.tool()
|
115
|
+
@require_google_service("drive", "drive_file")
|
116
|
+
@handle_http_errors(reply_func_name)
|
117
|
+
async def reply_to_comment(service, user_google_email: str, presentation_id: str, comment_id: str, reply_content: str) -> str:
|
118
|
+
"""Reply to a specific comment in a Google Slide, Sheet or Doc."""
|
119
|
+
return await _reply_to_comment_impl(service, app_name, presentation_id, comment_id, reply_content)
|
120
|
+
|
121
|
+
@server.tool()
|
122
|
+
@require_google_service("drive", "drive_file")
|
123
|
+
@handle_http_errors(resolve_func_name)
|
124
|
+
async def resolve_comment(service, user_google_email: str, presentation_id: str, comment_id: str) -> str:
|
125
|
+
"""Resolve a comment in a Google Slide, Sheet or Doc."""
|
126
|
+
return await _resolve_comment_impl(service, app_name, presentation_id, comment_id)
|
127
|
+
|
128
|
+
# Set the proper function names for MCP registration
|
129
|
+
read_comments.__name__ = read_func_name
|
130
|
+
create_comment.__name__ = create_func_name
|
131
|
+
reply_to_comment.__name__ = reply_func_name
|
132
|
+
resolve_comment.__name__ = resolve_func_name
|
133
|
+
|
134
|
+
return {
|
135
|
+
'read_comments': read_comments,
|
136
|
+
'create_comment': create_comment,
|
137
|
+
'reply_to_comment': reply_to_comment,
|
138
|
+
'resolve_comment': resolve_comment
|
139
|
+
}
|
140
|
+
|
141
|
+
|
142
|
+
async def _read_comments_impl(service, app_name: str, file_id: str) -> str:
|
143
|
+
"""Implementation for reading comments from any Google Workspace file."""
|
144
|
+
logger.info(f"[read_{app_name}_comments] Reading comments for {app_name} {file_id}")
|
145
|
+
|
146
|
+
response = await asyncio.to_thread(
|
147
|
+
service.comments().list(
|
148
|
+
fileId=file_id,
|
149
|
+
fields="comments(id,content,author,createdTime,modifiedTime,resolved,replies(content,author,id,createdTime,modifiedTime))"
|
150
|
+
).execute
|
151
|
+
)
|
152
|
+
|
153
|
+
comments = response.get('comments', [])
|
154
|
+
|
155
|
+
if not comments:
|
156
|
+
return f"No comments found in {app_name} {file_id}"
|
157
|
+
|
158
|
+
output = [f"Found {len(comments)} comments in {app_name} {file_id}:\\n"]
|
159
|
+
|
160
|
+
for comment in comments:
|
161
|
+
author = comment.get('author', {}).get('displayName', 'Unknown')
|
162
|
+
content = comment.get('content', '')
|
163
|
+
created = comment.get('createdTime', '')
|
164
|
+
resolved = comment.get('resolved', False)
|
165
|
+
comment_id = comment.get('id', '')
|
166
|
+
status = " [RESOLVED]" if resolved else ""
|
167
|
+
|
168
|
+
output.append(f"Comment ID: {comment_id}")
|
169
|
+
output.append(f"Author: {author}")
|
170
|
+
output.append(f"Created: {created}{status}")
|
171
|
+
output.append(f"Content: {content}")
|
172
|
+
|
173
|
+
# Add replies if any
|
174
|
+
replies = comment.get('replies', [])
|
175
|
+
if replies:
|
176
|
+
output.append(f" Replies ({len(replies)}):")
|
177
|
+
for reply in replies:
|
178
|
+
reply_author = reply.get('author', {}).get('displayName', 'Unknown')
|
179
|
+
reply_content = reply.get('content', '')
|
180
|
+
reply_created = reply.get('createdTime', '')
|
181
|
+
reply_id = reply.get('id', '')
|
182
|
+
output.append(f" Reply ID: {reply_id}")
|
183
|
+
output.append(f" Author: {reply_author}")
|
184
|
+
output.append(f" Created: {reply_created}")
|
185
|
+
output.append(f" Content: {reply_content}")
|
186
|
+
|
187
|
+
output.append("") # Empty line between comments
|
188
|
+
|
189
|
+
return "\\n".join(output)
|
190
|
+
|
191
|
+
|
192
|
+
async def _create_comment_impl(service, app_name: str, file_id: str, comment_content: str) -> str:
|
193
|
+
"""Implementation for creating a comment on any Google Workspace file."""
|
194
|
+
logger.info(f"[create_{app_name}_comment] Creating comment in {app_name} {file_id}")
|
195
|
+
|
196
|
+
body = {"content": comment_content}
|
197
|
+
|
198
|
+
comment = await asyncio.to_thread(
|
199
|
+
service.comments().create(
|
200
|
+
fileId=file_id,
|
201
|
+
body=body,
|
202
|
+
fields="id,content,author,createdTime,modifiedTime"
|
203
|
+
).execute
|
204
|
+
)
|
205
|
+
|
206
|
+
comment_id = comment.get('id', '')
|
207
|
+
author = comment.get('author', {}).get('displayName', 'Unknown')
|
208
|
+
created = comment.get('createdTime', '')
|
209
|
+
|
210
|
+
return f"Comment created successfully!\\nComment ID: {comment_id}\\nAuthor: {author}\\nCreated: {created}\\nContent: {comment_content}"
|
211
|
+
|
212
|
+
|
213
|
+
async def _reply_to_comment_impl(service, app_name: str, file_id: str, comment_id: str, reply_content: str) -> str:
|
214
|
+
"""Implementation for replying to a comment on any Google Workspace file."""
|
215
|
+
logger.info(f"[reply_to_{app_name}_comment] Replying to comment {comment_id} in {app_name} {file_id}")
|
216
|
+
|
217
|
+
body = {'content': reply_content}
|
218
|
+
|
219
|
+
reply = await asyncio.to_thread(
|
220
|
+
service.replies().create(
|
221
|
+
fileId=file_id,
|
222
|
+
commentId=comment_id,
|
223
|
+
body=body,
|
224
|
+
fields="id,content,author,createdTime,modifiedTime"
|
225
|
+
).execute
|
226
|
+
)
|
227
|
+
|
228
|
+
reply_id = reply.get('id', '')
|
229
|
+
author = reply.get('author', {}).get('displayName', 'Unknown')
|
230
|
+
created = reply.get('createdTime', '')
|
231
|
+
|
232
|
+
return f"Reply posted successfully!\\nReply ID: {reply_id}\\nAuthor: {author}\\nCreated: {created}\\nContent: {reply_content}"
|
233
|
+
|
234
|
+
|
235
|
+
async def _resolve_comment_impl(service, app_name: str, file_id: str, comment_id: str) -> str:
|
236
|
+
"""Implementation for resolving a comment on any Google Workspace file."""
|
237
|
+
logger.info(f"[resolve_{app_name}_comment] Resolving comment {comment_id} in {app_name} {file_id}")
|
238
|
+
|
239
|
+
body = {
|
240
|
+
"content": "This comment has been resolved.",
|
241
|
+
"action": "resolve"
|
242
|
+
}
|
243
|
+
|
244
|
+
reply = await asyncio.to_thread(
|
245
|
+
service.replies().create(
|
246
|
+
fileId=file_id,
|
247
|
+
commentId=comment_id,
|
248
|
+
body=body,
|
249
|
+
fields="id,content,author,createdTime,modifiedTime"
|
250
|
+
).execute
|
251
|
+
)
|
252
|
+
|
253
|
+
reply_id = reply.get('id', '')
|
254
|
+
author = reply.get('author', {}).get('displayName', 'Unknown')
|
255
|
+
created = reply.get('createdTime', '')
|
256
|
+
|
257
|
+
return f"Comment {comment_id} has been resolved successfully.\\nResolve reply ID: {reply_id}\\nAuthor: {author}\\nCreated: {created}"
|
@@ -58,6 +58,7 @@ logger = logging.getLogger(__name__)
|
|
58
58
|
|
59
59
|
WORKSPACE_MCP_PORT = int(os.getenv("PORT", os.getenv("WORKSPACE_MCP_PORT", 8000)))
|
60
60
|
WORKSPACE_MCP_BASE_URI = os.getenv("WORKSPACE_MCP_BASE_URI", "http://localhost")
|
61
|
+
USER_GOOGLE_EMAIL = os.getenv("USER_GOOGLE_EMAIL", None)
|
61
62
|
|
62
63
|
# Transport mode detection (will be set by main.py)
|
63
64
|
_current_transport_mode = "stdio" # Default to stdio
|
@@ -155,8 +156,8 @@ async def oauth2_callback(request: Request) -> HTMLResponse:
|
|
155
156
|
|
156
157
|
@server.tool()
|
157
158
|
async def start_google_auth(
|
158
|
-
user_google_email: str,
|
159
159
|
service_name: str,
|
160
|
+
user_google_email: str = USER_GOOGLE_EMAIL,
|
160
161
|
mcp_session_id: Optional[str] = Header(None, alias="Mcp-Session-Id")
|
161
162
|
) -> str:
|
162
163
|
"""
|
@@ -16,6 +16,7 @@ from googleapiclient.http import MediaIoBaseDownload
|
|
16
16
|
from auth.service_decorator import require_google_service, require_multiple_services
|
17
17
|
from core.utils import extract_office_xml_text, handle_http_errors
|
18
18
|
from core.server import server
|
19
|
+
from core.comments import create_comment_tools
|
19
20
|
|
20
21
|
logger = logging.getLogger(__name__)
|
21
22
|
|
@@ -216,179 +217,11 @@ async def create_doc(
|
|
216
217
|
return msg
|
217
218
|
|
218
219
|
|
219
|
-
|
220
|
-
|
221
|
-
@handle_http_errors("read_doc_comments")
|
222
|
-
async def read_doc_comments(
|
223
|
-
service,
|
224
|
-
user_google_email: str,
|
225
|
-
document_id: str,
|
226
|
-
) -> str:
|
227
|
-
"""
|
228
|
-
Read all comments from a Google Doc.
|
229
|
-
|
230
|
-
Args:
|
231
|
-
document_id: The ID of the Google Document
|
232
|
-
|
233
|
-
Returns:
|
234
|
-
str: A formatted list of all comments and replies in the document.
|
235
|
-
"""
|
236
|
-
logger.info(f"[read_doc_comments] Reading comments for document {document_id}")
|
237
|
-
|
238
|
-
response = await asyncio.to_thread(
|
239
|
-
service.comments().list(
|
240
|
-
fileId=document_id,
|
241
|
-
fields="comments(id,content,author,createdTime,modifiedTime,resolved,replies(content,author,id,createdTime,modifiedTime))"
|
242
|
-
).execute
|
243
|
-
)
|
244
|
-
|
245
|
-
comments = response.get('comments', [])
|
246
|
-
|
247
|
-
if not comments:
|
248
|
-
return f"No comments found in document {document_id}"
|
249
|
-
|
250
|
-
output = [f"Found {len(comments)} comments in document {document_id}:\n"]
|
251
|
-
|
252
|
-
for comment in comments:
|
253
|
-
author = comment.get('author', {}).get('displayName', 'Unknown')
|
254
|
-
content = comment.get('content', '')
|
255
|
-
created = comment.get('createdTime', '')
|
256
|
-
resolved = comment.get('resolved', False)
|
257
|
-
comment_id = comment.get('id', '')
|
258
|
-
status = " [RESOLVED]" if resolved else ""
|
259
|
-
|
260
|
-
output.append(f"Comment ID: {comment_id}")
|
261
|
-
output.append(f"Author: {author}")
|
262
|
-
output.append(f"Created: {created}{status}")
|
263
|
-
output.append(f"Content: {content}")
|
264
|
-
|
265
|
-
# Add replies if any
|
266
|
-
replies = comment.get('replies', [])
|
267
|
-
if replies:
|
268
|
-
output.append(f" Replies ({len(replies)}):")
|
269
|
-
for reply in replies:
|
270
|
-
reply_author = reply.get('author', {}).get('displayName', 'Unknown')
|
271
|
-
reply_content = reply.get('content', '')
|
272
|
-
reply_created = reply.get('createdTime', '')
|
273
|
-
reply_id = reply.get('id', '')
|
274
|
-
output.append(f" Reply ID: {reply_id}")
|
275
|
-
output.append(f" Author: {reply_author}")
|
276
|
-
output.append(f" Created: {reply_created}")
|
277
|
-
output.append(f" Content: {reply_content}")
|
278
|
-
|
279
|
-
output.append("") # Empty line between comments
|
280
|
-
|
281
|
-
return "\n".join(output)
|
282
|
-
|
283
|
-
|
284
|
-
@server.tool()
|
285
|
-
@require_google_service("drive", "drive_file")
|
286
|
-
@handle_http_errors("reply_to_comment")
|
287
|
-
async def reply_to_comment(
|
288
|
-
service,
|
289
|
-
user_google_email: str,
|
290
|
-
document_id: str,
|
291
|
-
comment_id: str,
|
292
|
-
reply_content: str,
|
293
|
-
) -> str:
|
294
|
-
"""
|
295
|
-
Reply to a specific comment in a Google Doc.
|
296
|
-
|
297
|
-
Args:
|
298
|
-
document_id: The ID of the Google Document
|
299
|
-
comment_id: The ID of the comment to reply to
|
300
|
-
reply_content: The content of the reply
|
301
|
-
|
302
|
-
Returns:
|
303
|
-
str: Confirmation message with reply details.
|
304
|
-
"""
|
305
|
-
logger.info(f"[reply_to_comment] Replying to comment {comment_id} in document {document_id}")
|
306
|
-
|
307
|
-
body = {'content': reply_content}
|
308
|
-
|
309
|
-
reply = await asyncio.to_thread(
|
310
|
-
service.replies().create(
|
311
|
-
fileId=document_id,
|
312
|
-
commentId=comment_id,
|
313
|
-
body=body,
|
314
|
-
fields="id,content,author,createdTime,modifiedTime"
|
315
|
-
).execute
|
316
|
-
)
|
317
|
-
|
318
|
-
reply_id = reply.get('id', '')
|
319
|
-
author = reply.get('author', {}).get('displayName', 'Unknown')
|
320
|
-
created = reply.get('createdTime', '')
|
321
|
-
|
322
|
-
return f"Reply posted successfully!\nReply ID: {reply_id}\nAuthor: {author}\nCreated: {created}\nContent: {reply_content}"
|
323
|
-
|
324
|
-
|
325
|
-
@server.tool()
|
326
|
-
@require_google_service("drive", "drive_file")
|
327
|
-
@handle_http_errors("create_doc_comment")
|
328
|
-
async def create_doc_comment(
|
329
|
-
service,
|
330
|
-
user_google_email: str,
|
331
|
-
document_id: str,
|
332
|
-
comment_content: str,
|
333
|
-
) -> str:
|
334
|
-
"""
|
335
|
-
Create a new comment on a Google Doc.
|
336
|
-
|
337
|
-
Args:
|
338
|
-
document_id: The ID of the Google Document
|
339
|
-
comment_content: The content of the comment
|
340
|
-
|
341
|
-
Returns:
|
342
|
-
str: Confirmation message with comment details.
|
343
|
-
"""
|
344
|
-
logger.info(f"[create_doc_comment] Creating comment in document {document_id}")
|
345
|
-
|
346
|
-
body = {"content": comment_content}
|
347
|
-
|
348
|
-
comment = await asyncio.to_thread(
|
349
|
-
service.comments().create(
|
350
|
-
fileId=document_id,
|
351
|
-
body=body,
|
352
|
-
fields="id,content,author,createdTime,modifiedTime"
|
353
|
-
).execute
|
354
|
-
)
|
355
|
-
|
356
|
-
comment_id = comment.get('id', '')
|
357
|
-
author = comment.get('author', {}).get('displayName', 'Unknown')
|
358
|
-
created = comment.get('createdTime', '')
|
359
|
-
|
360
|
-
return f"Comment created successfully!\nComment ID: {comment_id}\nAuthor: {author}\nCreated: {created}\nContent: {comment_content}"
|
361
|
-
|
220
|
+
# Create comment management tools for documents
|
221
|
+
_comment_tools = create_comment_tools("document", "document_id")
|
362
222
|
|
363
|
-
|
364
|
-
|
365
|
-
|
366
|
-
|
367
|
-
|
368
|
-
user_google_email: str,
|
369
|
-
document_id: str,
|
370
|
-
comment_id: str,
|
371
|
-
) -> str:
|
372
|
-
"""
|
373
|
-
Resolve a comment in a Google Doc.
|
374
|
-
|
375
|
-
Args:
|
376
|
-
document_id: The ID of the Google Document
|
377
|
-
comment_id: The ID of the comment to resolve
|
378
|
-
|
379
|
-
Returns:
|
380
|
-
str: Confirmation message.
|
381
|
-
"""
|
382
|
-
logger.info(f"[resolve_comment] Resolving comment {comment_id} in document {document_id}")
|
383
|
-
|
384
|
-
body = {"resolved": True}
|
385
|
-
|
386
|
-
await asyncio.to_thread(
|
387
|
-
service.comments().update(
|
388
|
-
fileId=document_id,
|
389
|
-
commentId=comment_id,
|
390
|
-
body=body
|
391
|
-
).execute
|
392
|
-
)
|
393
|
-
|
394
|
-
return f"Comment {comment_id} has been resolved successfully."
|
223
|
+
# Extract and register the functions
|
224
|
+
read_doc_comments = _comment_tools['read_comments']
|
225
|
+
create_doc_comment = _comment_tools['create_comment']
|
226
|
+
reply_to_comment = _comment_tools['reply_to_comment']
|
227
|
+
resolve_comment = _comment_tools['resolve_comment']
|
@@ -14,6 +14,7 @@ from googleapiclient.errors import HttpError
|
|
14
14
|
from auth.service_decorator import require_google_service
|
15
15
|
from core.server import server
|
16
16
|
from core.utils import handle_http_errors
|
17
|
+
from core.comments import create_comment_tools
|
17
18
|
|
18
19
|
# Configure module logger
|
19
20
|
logger = logging.getLogger(__name__)
|
@@ -338,3 +339,13 @@ async def create_sheet(
|
|
338
339
|
return text_output
|
339
340
|
|
340
341
|
|
342
|
+
# Create comment management tools for sheets
|
343
|
+
_comment_tools = create_comment_tools("spreadsheet", "spreadsheet_id")
|
344
|
+
|
345
|
+
# Extract and register the functions
|
346
|
+
read_sheet_comments = _comment_tools['read_comments']
|
347
|
+
create_sheet_comment = _comment_tools['create_comment']
|
348
|
+
reply_to_sheet_comment = _comment_tools['reply_to_comment']
|
349
|
+
resolve_sheet_comment = _comment_tools['resolve_comment']
|
350
|
+
|
351
|
+
|
@@ -14,6 +14,7 @@ from googleapiclient.errors import HttpError
|
|
14
14
|
from auth.service_decorator import require_google_service
|
15
15
|
from core.server import server
|
16
16
|
from core.utils import handle_http_errors
|
17
|
+
from core.comments import create_comment_tools
|
17
18
|
|
18
19
|
logger = logging.getLogger(__name__)
|
19
20
|
|
@@ -269,4 +270,18 @@ async def get_page_thumbnail(
|
|
269
270
|
You can view or download the thumbnail using the provided URL."""
|
270
271
|
|
271
272
|
logger.info(f"Thumbnail generated successfully for {user_google_email}")
|
272
|
-
return confirmation_message
|
273
|
+
return confirmation_message
|
274
|
+
|
275
|
+
|
276
|
+
# Create comment management tools for slides
|
277
|
+
_comment_tools = create_comment_tools("presentation", "presentation_id")
|
278
|
+
read_presentation_comments = _comment_tools['read_comments']
|
279
|
+
create_presentation_comment = _comment_tools['create_comment']
|
280
|
+
reply_to_presentation_comment = _comment_tools['reply_to_comment']
|
281
|
+
resolve_presentation_comment = _comment_tools['resolve_comment']
|
282
|
+
|
283
|
+
# Aliases for backwards compatibility and intuitive naming
|
284
|
+
read_slide_comments = read_presentation_comments
|
285
|
+
create_slide_comment = create_presentation_comment
|
286
|
+
reply_to_slide_comment = reply_to_presentation_comment
|
287
|
+
resolve_slide_comment = resolve_presentation_comment
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
4
4
|
|
5
5
|
[project]
|
6
6
|
name = "workspace-mcp"
|
7
|
-
version = "1.0.
|
7
|
+
version = "1.0.4"
|
8
8
|
description = "Comprehensive, highly performant Google Workspace Streamable HTTP & SSE MCP Server for Calendar, Gmail, Docs, Sheets, Slides & Drive"
|
9
9
|
readme = "README.md"
|
10
10
|
keywords = [ "mcp", "google", "workspace", "llm", "ai", "claude", "model", "context", "protocol", "server"]
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: workspace-mcp
|
3
|
-
Version: 1.0.
|
3
|
+
Version: 1.0.4
|
4
4
|
Summary: Comprehensive, highly performant Google Workspace Streamable HTTP & SSE MCP Server for Calendar, Gmail, Docs, Sheets, Slides & Drive
|
5
5
|
Author-email: Taylor Wilsdon <taylor@taylorwilsdon.com>
|
6
6
|
License: MIT
|
@@ -76,20 +76,14 @@ Dynamic: license-file
|
|
76
76
|
|
77
77
|
---
|
78
78
|
|
79
|
-
|
79
|
+
### A quick plug for AI-Enhanced Docs
|
80
80
|
|
81
81
|
> **This README was crafted with AI assistance, and here's why that matters**
|
82
82
|
>
|
83
|
-
>
|
83
|
+
> As a solo developer building open source tools that may only ever serve my own needs, comprehensive documentation often wouldn't happen without AI help. Using agentic dev tools like **Roo** & **Claude Code** that understand the entire codebase, AI doesn't just regurgitate generic content - it extracts real implementation details and creates accurate, specific documentation.
|
84
|
+
>
|
85
|
+
> In this case, Sonnet 4 took a pass & a human (me) verified them 6/28/25.
|
84
86
|
|
85
|
-
As a solo developer building open source tools that may only ever serve my own needs, comprehensive documentation often wouldn't happen without AI help. When done right—using agents like **Roo** or **Claude Code** that understand the entire codebase, AI doesn't just regurgitate generic content - it extracts real implementation details and creates accurate, specific documentation.
|
86
|
-
|
87
|
-
**The alternative? No docs at all.**
|
88
|
-
|
89
|
-
I hope the community can appreciate these tools for what they enable: solo developers maintaining professional documentation standards while focusing on building great software.
|
90
|
-
|
91
|
-
---
|
92
|
-
*This documentation was enhanced by AI with full codebase context. The result? You're reading docs that otherwise might not exist.*
|
93
87
|
|
94
88
|
## 🌐 Overview
|
95
89
|
|
@@ -101,9 +95,9 @@ A production-ready MCP server that integrates all major Google Workspace service
|
|
101
95
|
- **📅 Google Calendar**: Full calendar management with event CRUD operations
|
102
96
|
- **📁 Google Drive**: File operations with native Microsoft Office format support (.docx, .xlsx)
|
103
97
|
- **📧 Gmail**: Complete email management with search, send, and draft capabilities
|
104
|
-
- **📄 Google Docs**: Document operations including content extraction and
|
105
|
-
- **📊 Google Sheets**: Comprehensive spreadsheet management with flexible cell operations
|
106
|
-
- **🖼️ Google Slides**: Presentation management with slide creation, updates, and
|
98
|
+
- **📄 Google Docs**: Document operations including content extraction, creation, and comment management
|
99
|
+
- **📊 Google Sheets**: Comprehensive spreadsheet management with flexible cell operations and comment management
|
100
|
+
- **🖼️ Google Slides**: Presentation management with slide creation, updates, content manipulation, and comment management
|
107
101
|
- **📝 Google Forms**: Form creation, retrieval, publish settings, and response management
|
108
102
|
- **💬 Google Chat**: Space management and messaging capabilities
|
109
103
|
- **🔄 Multiple Transports**: HTTP with SSE fallback, OpenAPI compatibility via `mcpo`
|
@@ -185,12 +179,14 @@ uv run main.py
|
|
185
179
|
2. **Environment**:
|
186
180
|
```bash
|
187
181
|
export OAUTHLIB_INSECURE_TRANSPORT=1 # Development only
|
182
|
+
export USER_GOOGLE_EMAIL=your.email@gmail.com # Optional: Default email for auth - use this for single user setups and you won't need to set your email in system prompt for magic auth
|
188
183
|
```
|
189
184
|
|
190
185
|
3. **Server Configuration**:
|
191
186
|
The server's base URL and port can be customized using environment variables:
|
192
187
|
- `WORKSPACE_MCP_BASE_URI`: Sets the base URI for the server (default: http://localhost). This affects the server_url used for Gemini native function calling and the OAUTH_REDIRECT_URI.
|
193
188
|
- `WORKSPACE_MCP_PORT`: Sets the port the server listens on (default: 8000). This affects the server_url, port, and OAUTH_REDIRECT_URI.
|
189
|
+
- `USER_GOOGLE_EMAIL`: Optional default email for authentication flows. If set, the LLM won't need to specify your email when calling `start_google_auth`.
|
194
190
|
|
195
191
|
### Start the Server
|
196
192
|
|
@@ -338,6 +334,10 @@ When calling a tool:
|
|
338
334
|
| `get_doc_content` | Extract document text |
|
339
335
|
| `list_docs_in_folder` | List docs in folder |
|
340
336
|
| `create_doc` | Create new documents |
|
337
|
+
| `read_doc_comments` | Read all comments and replies |
|
338
|
+
| `create_doc_comment` | Create new comments |
|
339
|
+
| `reply_to_comment` | Reply to existing comments |
|
340
|
+
| `resolve_comment` | Resolve comments |
|
341
341
|
|
342
342
|
### 📊 Google Sheets ([`sheets_tools.py`](gsheets/sheets_tools.py))
|
343
343
|
|
@@ -349,6 +349,24 @@ When calling a tool:
|
|
349
349
|
| `modify_sheet_values` | Write/update/clear cells |
|
350
350
|
| `create_spreadsheet` | Create new spreadsheets |
|
351
351
|
| `create_sheet` | Add sheets to existing files |
|
352
|
+
| `read_sheet_comments` | Read all comments and replies |
|
353
|
+
| `create_sheet_comment` | Create new comments |
|
354
|
+
| `reply_to_sheet_comment` | Reply to existing comments |
|
355
|
+
| `resolve_sheet_comment` | Resolve comments |
|
356
|
+
|
357
|
+
### 🖼️ Google Slides ([`slides_tools.py`](gslides/slides_tools.py))
|
358
|
+
|
359
|
+
| Tool | Description |
|
360
|
+
|------|-------------|
|
361
|
+
| `create_presentation` | Create new presentations |
|
362
|
+
| `get_presentation` | Retrieve presentation details |
|
363
|
+
| `batch_update_presentation` | Apply multiple updates at once |
|
364
|
+
| `get_page` | Get specific slide information |
|
365
|
+
| `get_page_thumbnail` | Generate slide thumbnails |
|
366
|
+
| `read_presentation_comments` | Read all comments and replies |
|
367
|
+
| `create_presentation_comment` | Create new comments |
|
368
|
+
| `reply_to_presentation_comment` | Reply to existing comments |
|
369
|
+
| `resolve_presentation_comment` | Resolve comments |
|
352
370
|
|
353
371
|
### 📝 Google Forms ([`forms_tools.py`](gforms/forms_tools.py))
|
354
372
|
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|