Merge branch 'main' of https://github.com/taylorwilsdon/google_workspace_mcp into claude/fix-cors-middleware-error-sPbo6

This commit is contained in:
Taylor Wilsdon
2026-03-01 17:47:02 -05:00
25 changed files with 2075 additions and 2200 deletions

View File

@@ -7,7 +7,7 @@ All Google Workspace apps (Docs, Sheets, Slides) use the Drive API for comment o
import logging
import asyncio
from typing import Optional
from auth.service_decorator import require_google_service
from core.server import server
@@ -16,6 +16,38 @@ from core.utils import handle_http_errors
logger = logging.getLogger(__name__)
async def _manage_comment_dispatch(
service,
app_name: str,
file_id: str,
action: str,
comment_content: Optional[str] = None,
comment_id: Optional[str] = None,
) -> str:
"""Route comment management actions to the appropriate implementation."""
action_lower = action.lower().strip()
if action_lower == "create":
if not comment_content:
raise ValueError("comment_content is required for create action")
return await _create_comment_impl(service, app_name, file_id, comment_content)
elif action_lower == "reply":
if not comment_id or not comment_content:
raise ValueError(
"comment_id and comment_content are required for reply action"
)
return await _reply_to_comment_impl(
service, app_name, file_id, comment_id, comment_content
)
elif action_lower == "resolve":
if not comment_id:
raise ValueError("comment_id is required for resolve action")
return await _resolve_comment_impl(service, app_name, file_id, comment_id)
else:
raise ValueError(
f"Invalid action '{action_lower}'. Must be 'create', 'reply', or 'resolve'."
)
def create_comment_tools(app_name: str, file_id_param: str):
"""
Factory function to create comment management tools for a specific Google Workspace app.
@@ -25,165 +57,114 @@ def create_comment_tools(app_name: str, file_id_param: str):
file_id_param: Parameter name for the file ID (e.g., "document_id", "spreadsheet_id", "presentation_id")
Returns:
Dict containing the four comment management functions with unique names
Dict containing the comment management functions with unique names
"""
# Create unique function names based on the app type
read_func_name = f"read_{app_name}_comments"
create_func_name = f"create_{app_name}_comment"
reply_func_name = f"reply_to_{app_name}_comment"
resolve_func_name = f"resolve_{app_name}_comment"
# --- Consolidated tools ---
list_func_name = f"list_{app_name}_comments"
manage_func_name = f"manage_{app_name}_comment"
# Create functions without decorators first, then apply decorators with proper names
if file_id_param == "document_id":
@require_google_service("drive", "drive_read")
@handle_http_errors(read_func_name, service_type="drive")
async def read_comments(
@handle_http_errors(list_func_name, service_type="drive")
async def list_comments(
service, user_google_email: str, document_id: str
) -> str:
"""Read all comments from a Google Document."""
"""List all comments from a Google Document."""
return await _read_comments_impl(service, app_name, document_id)
@require_google_service("drive", "drive_file")
@handle_http_errors(create_func_name, service_type="drive")
async def create_comment(
service, user_google_email: str, document_id: str, comment_content: str
) -> str:
"""Create a new comment on a Google Document."""
return await _create_comment_impl(
service, app_name, document_id, comment_content
)
@require_google_service("drive", "drive_file")
@handle_http_errors(reply_func_name, service_type="drive")
async def reply_to_comment(
@handle_http_errors(manage_func_name, service_type="drive")
async def manage_comment(
service,
user_google_email: str,
document_id: str,
comment_id: str,
reply_content: str,
action: str,
comment_content: Optional[str] = None,
comment_id: Optional[str] = None,
) -> str:
"""Reply to a specific comment in a Google Document."""
return await _reply_to_comment_impl(
service, app_name, document_id, comment_id, reply_content
)
"""Manage comments on a Google Document.
@require_google_service("drive", "drive_file")
@handle_http_errors(resolve_func_name, service_type="drive")
async def resolve_comment(
service, user_google_email: str, document_id: str, comment_id: str
) -> str:
"""Resolve a comment in a Google Document."""
return await _resolve_comment_impl(
service, app_name, document_id, comment_id
Actions:
- create: Create a new comment. Requires comment_content.
- reply: Reply to a comment. Requires comment_id and comment_content.
- resolve: Resolve a comment. Requires comment_id.
"""
return await _manage_comment_dispatch(
service, app_name, document_id, action, comment_content, comment_id
)
elif file_id_param == "spreadsheet_id":
@require_google_service("drive", "drive_read")
@handle_http_errors(read_func_name, service_type="drive")
async def read_comments(
@handle_http_errors(list_func_name, service_type="drive")
async def list_comments(
service, user_google_email: str, spreadsheet_id: str
) -> str:
"""Read all comments from a Google Spreadsheet."""
"""List all comments from a Google Spreadsheet."""
return await _read_comments_impl(service, app_name, spreadsheet_id)
@require_google_service("drive", "drive_file")
@handle_http_errors(create_func_name, service_type="drive")
async def create_comment(
service, user_google_email: str, spreadsheet_id: str, comment_content: str
) -> str:
"""Create a new comment on a Google Spreadsheet."""
return await _create_comment_impl(
service, app_name, spreadsheet_id, comment_content
)
@require_google_service("drive", "drive_file")
@handle_http_errors(reply_func_name, service_type="drive")
async def reply_to_comment(
@handle_http_errors(manage_func_name, service_type="drive")
async def manage_comment(
service,
user_google_email: str,
spreadsheet_id: str,
comment_id: str,
reply_content: str,
action: str,
comment_content: Optional[str] = None,
comment_id: Optional[str] = None,
) -> str:
"""Reply to a specific comment in a Google Spreadsheet."""
return await _reply_to_comment_impl(
service, app_name, spreadsheet_id, comment_id, reply_content
)
"""Manage comments on a Google Spreadsheet.
@require_google_service("drive", "drive_file")
@handle_http_errors(resolve_func_name, service_type="drive")
async def resolve_comment(
service, user_google_email: str, spreadsheet_id: str, comment_id: str
) -> str:
"""Resolve a comment in a Google Spreadsheet."""
return await _resolve_comment_impl(
service, app_name, spreadsheet_id, comment_id
Actions:
- create: Create a new comment. Requires comment_content.
- reply: Reply to a comment. Requires comment_id and comment_content.
- resolve: Resolve a comment. Requires comment_id.
"""
return await _manage_comment_dispatch(
service, app_name, spreadsheet_id, action, comment_content, comment_id
)
elif file_id_param == "presentation_id":
@require_google_service("drive", "drive_read")
@handle_http_errors(read_func_name, service_type="drive")
async def read_comments(
@handle_http_errors(list_func_name, service_type="drive")
async def list_comments(
service, user_google_email: str, presentation_id: str
) -> str:
"""Read all comments from a Google Presentation."""
"""List all comments from a Google Presentation."""
return await _read_comments_impl(service, app_name, presentation_id)
@require_google_service("drive", "drive_file")
@handle_http_errors(create_func_name, service_type="drive")
async def create_comment(
service, user_google_email: str, presentation_id: str, comment_content: str
) -> str:
"""Create a new comment on a Google Presentation."""
return await _create_comment_impl(
service, app_name, presentation_id, comment_content
)
@require_google_service("drive", "drive_file")
@handle_http_errors(reply_func_name, service_type="drive")
async def reply_to_comment(
@handle_http_errors(manage_func_name, service_type="drive")
async def manage_comment(
service,
user_google_email: str,
presentation_id: str,
comment_id: str,
reply_content: str,
action: str,
comment_content: Optional[str] = None,
comment_id: Optional[str] = None,
) -> str:
"""Reply to a specific comment in a Google Presentation."""
return await _reply_to_comment_impl(
service, app_name, presentation_id, comment_id, reply_content
"""Manage comments on a Google Presentation.
Actions:
- create: Create a new comment. Requires comment_content.
- reply: Reply to a comment. Requires comment_id and comment_content.
- resolve: Resolve a comment. Requires comment_id.
"""
return await _manage_comment_dispatch(
service, app_name, presentation_id, action, comment_content, comment_id
)
@require_google_service("drive", "drive_file")
@handle_http_errors(resolve_func_name, service_type="drive")
async def resolve_comment(
service, user_google_email: str, presentation_id: str, comment_id: str
) -> str:
"""Resolve a comment in a Google Presentation."""
return await _resolve_comment_impl(
service, app_name, presentation_id, comment_id
)
# Set the proper function names and register with server
read_comments.__name__ = read_func_name
create_comment.__name__ = create_func_name
reply_to_comment.__name__ = reply_func_name
resolve_comment.__name__ = resolve_func_name
# Register tools with the server using the proper names
server.tool()(read_comments)
server.tool()(create_comment)
server.tool()(reply_to_comment)
server.tool()(resolve_comment)
list_comments.__name__ = list_func_name
manage_comment.__name__ = manage_func_name
server.tool()(list_comments)
server.tool()(manage_comment)
return {
"read_comments": read_comments,
"create_comment": create_comment,
"reply_to_comment": reply_to_comment,
"resolve_comment": resolve_comment,
"list_comments": list_comments,
"manage_comment": manage_comment,
}

View File

@@ -13,8 +13,7 @@ gmail:
- manage_gmail_label
- draft_gmail_message
- list_gmail_filters
- create_gmail_filter
- delete_gmail_filter
- manage_gmail_filter
complete:
- get_gmail_threads_content_batch
@@ -29,16 +28,12 @@ drive:
- create_drive_file
- create_drive_folder
- import_to_google_doc
- share_drive_file
- get_drive_shareable_link
extended:
- list_drive_items
- copy_drive_file
- update_drive_file
- update_drive_permission
- remove_drive_permission
- transfer_drive_ownership
- batch_share_drive_file
- manage_drive_access
- set_drive_file_permissions
complete:
- get_drive_file_permissions
@@ -48,10 +43,8 @@ calendar:
core:
- list_calendars
- get_events
- create_event
- modify_event
- manage_event
extended:
- delete_event
- query_freebusy
complete: []
@@ -75,10 +68,8 @@ docs:
- inspect_doc_structure
- create_table_with_data
- debug_table_structure
- read_document_comments
- create_document_comment
- reply_to_document_comment
- resolve_document_comment
- list_document_comments
- manage_document_comment
sheets:
core:
@@ -91,10 +82,9 @@ sheets:
- format_sheet_range
complete:
- create_sheet
- read_spreadsheet_comments
- create_spreadsheet_comment
- reply_to_spreadsheet_comment
- resolve_spreadsheet_comment
- list_spreadsheet_comments
- manage_spreadsheet_comment
- manage_conditional_formatting
chat:
core:
@@ -127,53 +117,37 @@ slides:
- get_page
- get_page_thumbnail
complete:
- read_presentation_comments
- create_presentation_comment
- reply_to_presentation_comment
- resolve_presentation_comment
- list_presentation_comments
- manage_presentation_comment
tasks:
core:
- get_task
- list_tasks
- create_task
- update_task
extended:
- delete_task
- manage_task
extended: []
complete:
- list_task_lists
- get_task_list
- create_task_list
- update_task_list
- delete_task_list
- move_task
- clear_completed_tasks
- manage_task_list
contacts:
core:
- search_contacts
- get_contact
- list_contacts
- create_contact
- manage_contact
extended:
- update_contact
- delete_contact
- list_contact_groups
- get_contact_group
complete:
- batch_create_contacts
- batch_update_contacts
- batch_delete_contacts
- create_contact_group
- update_contact_group
- delete_contact_group
- modify_contact_group_members
- manage_contacts_batch
- manage_contact_group
search:
core:
- search_custom
extended:
- search_custom_siterestrict
extended: []
complete:
- get_search_engine_info
@@ -187,10 +161,8 @@ appscript:
- run_script_function
- generate_trigger_code
extended:
- create_deployment
- manage_deployment
- list_deployments
- update_deployment
- delete_deployment
- delete_script_project
- list_versions
- create_version

View File

@@ -2,7 +2,6 @@ import io
import logging
import os
import zipfile
import xml.etree.ElementTree as ET
import ssl
import asyncio
import functools
@@ -10,6 +9,8 @@ import functools
from pathlib import Path
from typing import List, Optional
from defusedxml import ElementTree as ET
from googleapiclient.errors import HttpError
from .api_enablement import get_api_enablement_message
from auth.google_auth import GoogleAuthenticationError
@@ -226,7 +227,7 @@ def extract_office_xml_text(file_bytes: bytes, mime_type: str) -> Optional[str]:
"""
Very light-weight XML scraper for Word, Excel, PowerPoint files.
Returns plain-text if something readable is found, else None.
No external deps just std-lib zipfile + ElementTree.
Uses zipfile + defusedxml.ElementTree.
"""
shared_strings: List[str] = []
ns_excel_main = "http://schemas.openxmlformats.org/spreadsheetml/2006/main"