From e9c9c288449ce3067ca82e85dc9b2becfd084006 Mon Sep 17 00:00:00 2001 From: Taylor Wilsdon Date: Sun, 1 Mar 2026 13:23:40 -0500 Subject: [PATCH] cleanup --- README.md | 63 -------------------------------- gappsscript/apps_script_tools.py | 2 + gcontacts/contacts_tools.py | 47 +++++++++++++----------- gsheets/sheets_tools.py | 12 +++++- gtasks/tasks_tools.py | 25 ++++++------- 5 files changed, 50 insertions(+), 99 deletions(-) diff --git a/README.md b/README.md index 951cb41..7740390 100644 --- a/README.md +++ b/README.md @@ -1105,69 +1105,6 @@ Saved files expire after 1 hour and are cleaned up automatically. --- -### 🔄 Tool Consolidation & Migration - -**Tool Count Reduction**: This release consolidates 139 tools down to 111 tools (20% reduction) by combining CRUD operations into action-based `manage_*` tools. - -
-Migration Mapping ← Old tool → New tool mapping - -| Old Tool | New Tool | Action Parameter | -|----------|----------|------------------| -| `create_event` | `manage_event` | `action="create"` | -| `modify_event` | `manage_event` | `action="update"` | -| `delete_event` | `manage_event` | `action="delete"` | -| `share_drive_file` | `manage_drive_access` | `action="grant"` | -| `batch_share_drive_file` | `manage_drive_access` | `action="grant_batch"` | -| `update_drive_permission` | `manage_drive_access` | `action="update"` | -| `remove_drive_permission` | `manage_drive_access` | `action="revoke"` | -| `transfer_drive_ownership` | `manage_drive_access` | `action="transfer_owner"` | -| `create_gmail_filter` | `manage_gmail_filter` | `action="create"` | -| `delete_gmail_filter` | `manage_gmail_filter` | `action="delete"` | -| `create_task` | `manage_task` | `action="create"` | -| `update_task` | `manage_task` | `action="update"` | -| `delete_task` | `manage_task` | `action="delete"` | -| `move_task` | `manage_task` | `action="move"` | -| `create_task_list` | `manage_task_list` | `action="create"` | -| `update_task_list` | `manage_task_list` | `action="update"` | -| `delete_task_list` | `manage_task_list` | `action="delete"` | -| `clear_completed_tasks` | `manage_task_list` | `action="clear_completed"` | -| `create_contact` | `manage_contact` | `action="create"` | -| `update_contact` | `manage_contact` | `action="update"` | -| `delete_contact` | `manage_contact` | `action="delete"` | -| `batch_create_contacts` | `manage_contacts_batch` | `action="create"` | -| `batch_update_contacts` | `manage_contacts_batch` | `action="update"` | -| `batch_delete_contacts` | `manage_contacts_batch` | `action="delete"` | -| `create_contact_group` | `manage_contact_group` | `action="create"` | -| `update_contact_group` | `manage_contact_group` | `action="update"` | -| `delete_contact_group` | `manage_contact_group` | `action="delete"` | -| `modify_contact_group_members` | `manage_contact_group` | `action="modify_members"` | -| `create_deployment` | `manage_deployment` | `action="create"` | -| `update_deployment` | `manage_deployment` | `action="update"` | -| `delete_deployment` | `manage_deployment` | `action="delete"` | -| `add_conditional_formatting` | `manage_conditional_formatting` | `action="add"` | -| `update_conditional_formatting` | `manage_conditional_formatting` | `action="update"` | -| `delete_conditional_formatting` | `manage_conditional_formatting` | `action="delete"` | -| `read_document_comments` | `list_document_comments` | N/A (renamed) | -| `create_document_comment` | `manage_document_comment` | `action="create"` | -| `reply_to_document_comment` | `manage_document_comment` | `action="reply"` | -| `resolve_document_comment` | `manage_document_comment` | `action="resolve"` | -| `read_spreadsheet_comments` | `list_spreadsheet_comments` | N/A (renamed) | -| `create_spreadsheet_comment` | `manage_spreadsheet_comment` | `action="create"` | -| `reply_to_spreadsheet_comment` | `manage_spreadsheet_comment` | `action="reply"` | -| `resolve_spreadsheet_comment` | `manage_spreadsheet_comment` | `action="resolve"` | -| `read_presentation_comments` | `list_presentation_comments` | N/A (renamed) | -| `create_presentation_comment` | `manage_presentation_comment` | `action="create"` | -| `reply_to_presentation_comment` | `manage_presentation_comment` | `action="reply"` | -| `resolve_presentation_comment` | `manage_presentation_comment` | `action="resolve"` | -| `search_custom_siterestrict` | `search_custom` | Use `sites` parameter | - -**Breaking Change**: Legacy tools have been removed. Use the new consolidated tools with appropriate action parameters. - -
- ---- - ### Connect to Claude Desktop The server supports two transport modes: diff --git a/gappsscript/apps_script_tools.py b/gappsscript/apps_script_tools.py index a99f549..39917ca 100644 --- a/gappsscript/apps_script_tools.py +++ b/gappsscript/apps_script_tools.py @@ -500,6 +500,8 @@ async def manage_deployment( elif action == "update": if not deployment_id: raise ValueError("deployment_id is required for update action") + if description is None or description == "": + raise ValueError("description is required for update action") return await _update_deployment_impl( service, user_google_email, script_id, deployment_id, description ) diff --git a/gcontacts/contacts_tools.py b/gcontacts/contacts_tools.py index 012a4a0..8eb5315 100644 --- a/gcontacts/contacts_tools.py +++ b/gcontacts/contacts_tools.py @@ -13,7 +13,7 @@ from mcp import Resource from auth.service_decorator import require_google_service from core.server import server -from core.utils import handle_http_errors +from core.utils import UserInputError, handle_http_errors logger = logging.getLogger(__name__) @@ -449,7 +449,7 @@ async def manage_contact( """ action = action.lower().strip() if action not in ("create", "update", "delete"): - raise Exception( + raise UserInputError( f"Invalid action '{action}'. Must be 'create', 'update', or 'delete'." ) @@ -470,7 +470,7 @@ async def manage_contact( ) if not body: - raise Exception( + raise UserInputError( "At least one field (name, email, phone, etc.) must be provided." ) @@ -489,7 +489,7 @@ async def manage_contact( # update and delete both require contact_id if not contact_id: - raise Exception(f"contact_id is required for '{action}' action.") + raise UserInputError(f"contact_id is required for '{action}' action.") # Normalize resource name if not contact_id.startswith("people/"): @@ -520,7 +520,7 @@ async def manage_contact( ) if not body: - raise Exception( + raise UserInputError( "At least one field (name, email, phone, etc.) must be provided." ) @@ -566,6 +566,8 @@ async def manage_contact( logger.info(f"Deleted contact {resource_name} for {user_google_email}") return response + except UserInputError: + raise except HttpError as error: if error.resp.status == 404: message = f"Contact not found: {contact_id}" @@ -764,7 +766,7 @@ async def manage_contacts_batch( """ action = action.lower().strip() if action not in ("create", "update", "delete"): - raise Exception( + raise UserInputError( f"Invalid action '{action}'. Must be 'create', 'update', or 'delete'." ) @@ -775,12 +777,12 @@ async def manage_contacts_batch( try: if action == "create": if not contacts: - raise Exception( + raise UserInputError( "contacts parameter is required for 'create' action." ) if len(contacts) > 200: - raise Exception("Maximum 200 contacts can be created in a batch.") + raise UserInputError("Maximum 200 contacts can be created in a batch.") contact_bodies = [] for contact in contacts: @@ -796,7 +798,7 @@ async def manage_contacts_batch( contact_bodies.append({"contactPerson": body}) if not contact_bodies: - raise Exception("No valid contact data provided.") + raise UserInputError("No valid contact data provided.") batch_body = { "contacts": contact_bodies, @@ -823,19 +825,19 @@ async def manage_contacts_batch( if action == "update": if not updates: - raise Exception( + raise UserInputError( "updates parameter is required for 'update' action." ) if len(updates) > 200: - raise Exception("Maximum 200 contacts can be updated in a batch.") + raise UserInputError("Maximum 200 contacts can be updated in a batch.") # Fetch all contacts to get their etags resource_names = [] for update in updates: cid = update.get("contact_id") if not cid: - raise Exception("Each update must include a contact_id.") + raise UserInputError("Each update must include a contact_id.") if not cid.startswith("people/"): cid = f"people/{cid}" resource_names.append(cid) @@ -894,7 +896,7 @@ async def manage_contacts_batch( update_fields_set.add("organizations") if not update_bodies: - raise Exception("No valid update data provided.") + raise UserInputError("No valid update data provided.") batch_body = { "contacts": update_bodies, @@ -922,12 +924,12 @@ async def manage_contacts_batch( # action == "delete" if not contact_ids: - raise Exception( + raise UserInputError( "contact_ids parameter is required for 'delete' action." ) if len(contact_ids) > 500: - raise Exception("Maximum 500 contacts can be deleted in a batch.") + raise UserInputError("Maximum 500 contacts can be deleted in a batch.") resource_names = [] for cid in contact_ids: @@ -948,6 +950,8 @@ async def manage_contacts_batch( ) return response + except UserInputError: + raise except HttpError as error: message = f"API error: {error}. You might need to re-authenticate. LLM: Try 'start_google_auth' with the user's email ({user_google_email}) and service_name='Google Contacts'." logger.error(message, exc_info=True) @@ -992,7 +996,7 @@ async def manage_contact_group( """ action = action.lower().strip() if action not in ("create", "update", "delete", "modify_members"): - raise Exception( + raise UserInputError( f"Invalid action '{action}'. Must be 'create', 'update', 'delete', or 'modify_members'." ) @@ -1003,7 +1007,7 @@ async def manage_contact_group( try: if action == "create": if not name: - raise Exception("name is required for 'create' action.") + raise UserInputError("name is required for 'create' action.") body = {"contactGroup": {"name": name}} @@ -1025,7 +1029,7 @@ async def manage_contact_group( # All other actions require group_id if not group_id: - raise Exception(f"group_id is required for '{action}' action.") + raise UserInputError(f"group_id is required for '{action}' action.") # Normalize resource name if not group_id.startswith("contactGroups/"): @@ -1035,7 +1039,7 @@ async def manage_contact_group( if action == "update": if not name: - raise Exception("name is required for 'update' action.") + raise UserInputError("name is required for 'update' action.") body = {"contactGroup": {"name": name}} @@ -1076,7 +1080,7 @@ async def manage_contact_group( # action == "modify_members" if not add_contact_ids and not remove_contact_ids: - raise Exception( + raise UserInputError( "At least one of add_contact_ids or remove_contact_ids must be provided." ) @@ -1128,6 +1132,8 @@ async def manage_contact_group( ) return response + except UserInputError: + raise except HttpError as error: if error.resp.status == 404: message = f"Contact group not found: {group_id}" @@ -1141,4 +1147,3 @@ async def manage_contact_group( logger.exception(message) raise Exception(message) - diff --git a/gsheets/sheets_tools.py b/gsheets/sheets_tools.py index e28f4c4..69b8dd4 100644 --- a/gsheets/sheets_tools.py +++ b/gsheets/sheets_tools.py @@ -807,8 +807,12 @@ async def manage_conditional_formatting( "rule_index must be a non-negative integer when provided." ) - condition_values_list = _parse_condition_values(condition_values) gradient_points_list = _parse_gradient_points(gradient_points) + condition_values_list = ( + None + if gradient_points_list + else _parse_condition_values(condition_values) + ) sheets, sheet_titles = await _fetch_sheets_with_rules( service, spreadsheet_id @@ -903,8 +907,12 @@ async def manage_conditional_formatting( if not isinstance(rule_index, int) or rule_index < 0: raise UserInputError("rule_index must be a non-negative integer.") - condition_values_list = _parse_condition_values(condition_values) gradient_points_list = _parse_gradient_points(gradient_points) + condition_values_list = ( + None + if gradient_points_list is not None + else _parse_condition_values(condition_values) + ) sheets, sheet_titles = await _fetch_sheets_with_rules( service, spreadsheet_id diff --git a/gtasks/tasks_tools.py b/gtasks/tasks_tools.py index ea353ce..3bbdb65 100644 --- a/gtasks/tasks_tools.py +++ b/gtasks/tasks_tools.py @@ -15,7 +15,7 @@ from mcp import Resource from auth.oauth_config import is_oauth21_enabled, is_external_oauth21_provider from auth.service_decorator import require_google_service from core.server import server -from core.utils import handle_http_errors +from core.utils import UserInputError, handle_http_errors logger = logging.getLogger(__name__) @@ -352,32 +352,32 @@ async def manage_task_list( valid_actions = ("create", "update", "delete", "clear_completed") if action not in valid_actions: - raise ValueError( + raise UserInputError( f"Invalid action '{action}'. Must be one of: {', '.join(valid_actions)}" ) if action == "create": if not title: - raise ValueError("'title' is required for the 'create' action.") + raise UserInputError("'title' is required for the 'create' action.") return await _create_task_list_impl(service, user_google_email, title) if action == "update": if not task_list_id: - raise ValueError("'task_list_id' is required for the 'update' action.") + raise UserInputError("'task_list_id' is required for the 'update' action.") if not title: - raise ValueError("'title' is required for the 'update' action.") + raise UserInputError("'title' is required for the 'update' action.") return await _update_task_list_impl( service, user_google_email, task_list_id, title ) if action == "delete": if not task_list_id: - raise ValueError("'task_list_id' is required for the 'delete' action.") + raise UserInputError("'task_list_id' is required for the 'delete' action.") return await _delete_task_list_impl(service, user_google_email, task_list_id) # action == "clear_completed" if not task_list_id: - raise ValueError( + raise UserInputError( "'task_list_id' is required for the 'clear_completed' action." ) return await _clear_completed_tasks_impl(service, user_google_email, task_list_id) @@ -963,13 +963,13 @@ async def manage_task( valid_actions = ("create", "update", "delete", "move") if action not in valid_actions: - raise ValueError( + raise UserInputError( f"Invalid action '{action}'. Must be one of: {', '.join(valid_actions)}" ) if action == "create": if not title: - raise ValueError("'title' is required for the 'create' action.") + raise UserInputError("'title' is required for the 'create' action.") return await _create_task_impl( service, user_google_email, @@ -983,7 +983,7 @@ async def manage_task( if action == "update": if not task_id: - raise ValueError("'task_id' is required for the 'update' action.") + raise UserInputError("'task_id' is required for the 'update' action.") return await _update_task_impl( service, user_google_email, @@ -997,14 +997,14 @@ async def manage_task( if action == "delete": if not task_id: - raise ValueError("'task_id' is required for the 'delete' action.") + raise UserInputError("'task_id' is required for the 'delete' action.") return await _delete_task_impl( service, user_google_email, task_list_id, task_id ) # action == "move" if not task_id: - raise ValueError("'task_id' is required for the 'move' action.") + raise UserInputError("'task_id' is required for the 'move' action.") return await _move_task_impl( service, user_google_email, @@ -1026,4 +1026,3 @@ async def manage_task( -