diff --git a/gcalendar/calendar_tools.py b/gcalendar/calendar_tools.py index 68959346..57067331 100644 --- a/gcalendar/calendar_tools.py +++ b/gcalendar/calendar_tools.py @@ -18,6 +18,8 @@ from auth.service_decorator import require_google_service from core.utils import handle_http_errors +from mcp.types import ToolAnnotations + from core.server import server @@ -301,7 +303,12 @@ def _correct_time_format_for_api( return time_str -@server.tool() +@server.tool( + annotations=ToolAnnotations( + title="List Calendars", + readOnlyHint=True, + ), +) @handle_http_errors("list_calendars", is_read_only=True, service_type="calendar") @require_google_service("calendar", "calendar_read") async def list_calendars(service, user_google_email: str) -> str: diff --git a/gchat/chat_tools.py b/gchat/chat_tools.py index 36f25433..86bc050a 100644 --- a/gchat/chat_tools.py +++ b/gchat/chat_tools.py @@ -12,6 +12,8 @@ # Auth & server utilities from auth.service_decorator import require_google_service +from mcp.types import ToolAnnotations + from core.server import server from core.utils import handle_http_errors diff --git a/gdocs/docs_tools.py b/gdocs/docs_tools.py index 62b3853a..75f6ee48 100644 --- a/gdocs/docs_tools.py +++ b/gdocs/docs_tools.py @@ -11,6 +11,8 @@ from googleapiclient.http import MediaIoBaseDownload, MediaIoBaseUpload +from mcp.types import ToolAnnotations + # Auth & server utilities from auth.service_decorator import require_google_service, require_multiple_services from core.utils import extract_office_xml_text, handle_http_errors @@ -49,7 +51,12 @@ logger = logging.getLogger(__name__) -@server.tool() +@server.tool( + annotations=ToolAnnotations( + title="Search Docs", + readOnlyHint=True, + ), +) @handle_http_errors("search_docs", is_read_only=True, service_type="docs") @require_google_service("drive", "drive_read") async def search_docs( @@ -91,7 +98,12 @@ async def search_docs( return "\n".join(output) -@server.tool() +@server.tool( + annotations=ToolAnnotations( + title="Get Doc Content", + readOnlyHint=True, + ), +) @handle_http_errors("get_doc_content", is_read_only=True, service_type="docs") @require_multiple_services( [ @@ -276,7 +288,12 @@ def process_tab_hierarchy(tab, level=0): return header + body_text -@server.tool() +@server.tool( + annotations=ToolAnnotations( + title="List Docs in Folder", + readOnlyHint=True, + ), +) @handle_http_errors("list_docs_in_folder", is_read_only=True, service_type="docs") @require_google_service("drive", "drive_read") async def list_docs_in_folder( @@ -314,7 +331,12 @@ async def list_docs_in_folder( return "\n".join(out) -@server.tool() +@server.tool( + annotations=ToolAnnotations( + title="Create Doc", + destructiveHint=False, + ), +) @handle_http_errors("create_doc", service_type="docs") @require_google_service("docs", "docs_write") async def create_doc( @@ -350,7 +372,12 @@ async def create_doc( return msg -@server.tool() +@server.tool( + annotations=ToolAnnotations( + title="Modify Doc Text", + destructiveHint=True, + ), +) @handle_http_errors("modify_doc_text", service_type="docs") @require_google_service("docs", "docs_write") async def modify_doc_text( @@ -559,7 +586,12 @@ async def modify_doc_text( return f"{operation_summary} in document {document_id}.{text_info} Link: {link}" -@server.tool() +@server.tool( + annotations=ToolAnnotations( + title="Find and Replace in Doc", + destructiveHint=True, + ), +) @handle_http_errors("find_and_replace_doc", service_type="docs") @require_google_service("docs", "docs_write") async def find_and_replace_doc( @@ -606,7 +638,12 @@ async def find_and_replace_doc( return f"Replaced {replacements} occurrence(s) of '{find_text}' with '{replace_text}' in document {document_id}. Link: {link}" -@server.tool() +@server.tool( + annotations=ToolAnnotations( + title="Insert Doc Elements", + destructiveHint=True, + ), +) @handle_http_errors("insert_doc_elements", service_type="docs") @require_google_service("docs", "docs_write") async def insert_doc_elements( @@ -688,7 +725,12 @@ async def insert_doc_elements( return f"Inserted {description} at index {index} in document {document_id}. Link: {link}" -@server.tool() +@server.tool( + annotations=ToolAnnotations( + title="Insert Doc Image", + destructiveHint=True, + ), +) @handle_http_errors("insert_doc_image", service_type="docs") @require_multiple_services( [ @@ -780,7 +822,12 @@ async def insert_doc_image( return f"Inserted {source_description}{size_info} at index {index} in document {document_id}. Link: {link}" -@server.tool() +@server.tool( + annotations=ToolAnnotations( + title="Update Doc Headers/Footers", + destructiveHint=True, + ), +) @handle_http_errors("update_doc_headers_footers", service_type="docs") @require_google_service("docs", "docs_write") async def update_doc_headers_footers( @@ -837,7 +884,12 @@ async def update_doc_headers_footers( return f"Error: {message}" -@server.tool() +@server.tool( + annotations=ToolAnnotations( + title="Batch Update Doc", + destructiveHint=True, + ), +) @handle_http_errors("batch_update_doc", service_type="docs") @require_google_service("docs", "docs_write") async def batch_update_doc( @@ -894,7 +946,12 @@ async def batch_update_doc( return f"Error: {message}" -@server.tool() +@server.tool( + annotations=ToolAnnotations( + title="Inspect Doc Structure", + readOnlyHint=True, + ), +) @handle_http_errors("inspect_doc_structure", is_read_only=True, service_type="docs") @require_google_service("docs", "docs_read") async def inspect_doc_structure( @@ -1022,7 +1079,12 @@ async def inspect_doc_structure( return f"Document structure analysis for {document_id}:\n\n{json.dumps(result, indent=2)}\n\nLink: {link}" -@server.tool() +@server.tool( + annotations=ToolAnnotations( + title="Create Table with Data", + destructiveHint=False, + ), +) @handle_http_errors("create_table_with_data", service_type="docs") @require_google_service("docs", "docs_write") async def create_table_with_data( @@ -1120,7 +1182,12 @@ async def create_table_with_data( return f"ERROR: {message}" -@server.tool() +@server.tool( + annotations=ToolAnnotations( + title="Debug Table Structure", + readOnlyHint=True, + ), +) @handle_http_errors("debug_table_structure", is_read_only=True, service_type="docs") @require_google_service("docs", "docs_read") async def debug_table_structure( @@ -1207,7 +1274,12 @@ async def debug_table_structure( return f"Table structure debug for table {table_index}:\n\n{json.dumps(debug_info, indent=2)}\n\nLink: {link}" -@server.tool() +@server.tool( + annotations=ToolAnnotations( + title="Export Doc to PDF", + destructiveHint=False, + ), +) @handle_http_errors("export_doc_to_pdf", service_type="drive") @require_google_service("drive", "drive_file") async def export_doc_to_pdf( diff --git a/gdrive/drive_tools.py b/gdrive/drive_tools.py index 1e7980b3..1f41bbf5 100644 --- a/gdrive/drive_tools.py +++ b/gdrive/drive_tools.py @@ -19,6 +19,8 @@ from googleapiclient.errors import HttpError from googleapiclient.http import MediaIoBaseDownload, MediaIoBaseUpload +from mcp.types import ToolAnnotations + from auth.service_decorator import require_google_service from auth.oauth_config import is_stateless_mode from core.attachment_storage import get_attachment_storage, get_attachment_url @@ -44,7 +46,12 @@ UPLOAD_CHUNK_SIZE_BYTES = 5 * 1024 * 1024 # 5 MB (Google recommended minimum) -@server.tool() +@server.tool( + annotations=ToolAnnotations( + title="Search Drive Files", + readOnlyHint=True, + ), +) @handle_http_errors("search_drive_files", is_read_only=True, service_type="drive") @require_google_service("drive", "drive_read") async def search_drive_files( @@ -118,7 +125,12 @@ async def search_drive_files( return text_output -@server.tool() +@server.tool( + annotations=ToolAnnotations( + title="Get Drive File Content", + readOnlyHint=True, + ), +) @handle_http_errors("get_drive_file_content", is_read_only=True, service_type="drive") @require_google_service("drive", "drive_read") async def get_drive_file_content( @@ -209,7 +221,12 @@ async def get_drive_file_content( return header + body_text -@server.tool() +@server.tool( + annotations=ToolAnnotations( + title="Get Drive File Download URL", + readOnlyHint=True, + ), +) @handle_http_errors( "get_drive_file_download_url", is_read_only=True, service_type="drive" ) @@ -385,7 +402,12 @@ async def get_drive_file_download_url( ) -@server.tool() +@server.tool( + annotations=ToolAnnotations( + title="List Drive Items", + readOnlyHint=True, + ), +) @handle_http_errors("list_drive_items", is_read_only=True, service_type="drive") @require_google_service("drive", "drive_read") async def list_drive_items( @@ -445,7 +467,12 @@ async def list_drive_items( return text_output -@server.tool() +@server.tool( + annotations=ToolAnnotations( + title="Create Drive File", + destructiveHint=False, + ), +) @handle_http_errors("create_drive_file", service_type="drive") @require_google_service("drive", "drive_file") async def create_drive_file( @@ -680,7 +707,12 @@ async def create_drive_file( return confirmation_message -@server.tool() +@server.tool( + annotations=ToolAnnotations( + title="Get Drive File Permissions", + readOnlyHint=True, + ), +) @handle_http_errors( "get_drive_file_permissions", is_read_only=True, service_type="drive" ) @@ -789,7 +821,12 @@ async def get_drive_file_permissions( return f"Error getting file permissions: {e}" -@server.tool() +@server.tool( + annotations=ToolAnnotations( + title="Check Drive File Public Access", + readOnlyHint=True, + ), +) @handle_http_errors( "check_drive_file_public_access", is_read_only=True, service_type="drive" ) @@ -886,7 +923,12 @@ async def check_drive_file_public_access( return "\n".join(output_parts) -@server.tool() +@server.tool( + annotations=ToolAnnotations( + title="Update Drive File", + destructiveHint=True, + ), +) @handle_http_errors("update_drive_file", is_read_only=False, service_type="drive") @require_google_service("drive", "drive_file") async def update_drive_file( @@ -1063,7 +1105,12 @@ async def _resolve_parent_arguments(parent_arg: Optional[str]) -> Optional[str]: return "\n".join(output_parts) -@server.tool() +@server.tool( + annotations=ToolAnnotations( + title="Get Drive Shareable Link", + readOnlyHint=True, + ), +) @handle_http_errors("get_drive_shareable_link", is_read_only=True, service_type="drive") @require_google_service("drive", "drive_read") async def get_drive_shareable_link( @@ -1123,7 +1170,12 @@ async def get_drive_shareable_link( return "\n".join(output_parts) -@server.tool() +@server.tool( + annotations=ToolAnnotations( + title="Share Drive File", + destructiveHint=False, + ), +) @handle_http_errors("share_drive_file", is_read_only=False, service_type="drive") @require_google_service("drive", "drive_file") async def share_drive_file( @@ -1219,7 +1271,12 @@ async def share_drive_file( return "\n".join(output_parts) -@server.tool() +@server.tool( + annotations=ToolAnnotations( + title="Batch Share Drive File", + destructiveHint=False, + ), +) @handle_http_errors("batch_share_drive_file", is_read_only=False, service_type="drive") @require_google_service("drive", "drive_file") async def batch_share_drive_file( @@ -1362,7 +1419,12 @@ async def batch_share_drive_file( return "\n".join(output_parts) -@server.tool() +@server.tool( + annotations=ToolAnnotations( + title="Update Drive Permission", + destructiveHint=True, + ), +) @handle_http_errors("update_drive_permission", is_read_only=False, service_type="drive") @require_google_service("drive", "drive_file") async def update_drive_permission( @@ -1443,7 +1505,12 @@ async def update_drive_permission( return "\n".join(output_parts) -@server.tool() +@server.tool( + annotations=ToolAnnotations( + title="Remove Drive Permission", + destructiveHint=True, + ), +) @handle_http_errors("remove_drive_permission", is_read_only=False, service_type="drive") @require_google_service("drive", "drive_file") async def remove_drive_permission( @@ -1487,7 +1554,12 @@ async def remove_drive_permission( return "\n".join(output_parts) -@server.tool() +@server.tool( + annotations=ToolAnnotations( + title="Transfer Drive Ownership", + destructiveHint=True, + ), +) @handle_http_errors( "transfer_drive_ownership", is_read_only=False, service_type="drive" ) diff --git a/gforms/forms_tools.py b/gforms/forms_tools.py index a2a52254..3945b0c8 100644 --- a/gforms/forms_tools.py +++ b/gforms/forms_tools.py @@ -10,6 +10,8 @@ from auth.service_decorator import require_google_service +from mcp.types import ToolAnnotations + from core.server import server from core.utils import handle_http_errors diff --git a/gmail/gmail_tools.py b/gmail/gmail_tools.py index 5b4e2b23..568bf549 100644 --- a/gmail/gmail_tools.py +++ b/gmail/gmail_tools.py @@ -16,6 +16,8 @@ from fastapi import Body from pydantic import Field +from mcp.types import ToolAnnotations + from auth.service_decorator import require_google_service from core.utils import handle_http_errors from core.server import server @@ -381,7 +383,12 @@ def _format_gmail_results_plain( return "\n".join(lines) -@server.tool() +@server.tool( + annotations=ToolAnnotations( + title="Search Gmail Messages", + readOnlyHint=True, + ), +) @handle_http_errors("search_gmail_messages", is_read_only=True, service_type="gmail") @require_google_service("gmail", "gmail_read") async def search_gmail_messages( @@ -445,7 +452,12 @@ async def search_gmail_messages( return formatted_output -@server.tool() +@server.tool( + annotations=ToolAnnotations( + title="Get Gmail Message Content", + readOnlyHint=True, + ), +) @handle_http_errors( "get_gmail_message_content", is_read_only=True, service_type="gmail" ) @@ -544,7 +556,12 @@ async def get_gmail_message_content( return "\n".join(content_lines) -@server.tool() +@server.tool( + annotations=ToolAnnotations( + title="Get Gmail Messages Batch", + readOnlyHint=True, + ), +) @handle_http_errors( "get_gmail_messages_content_batch", is_read_only=True, service_type="gmail" ) @@ -741,7 +758,12 @@ async def fetch_message_with_retry(mid: str, max_retries: int = 3): return final_output -@server.tool() +@server.tool( + annotations=ToolAnnotations( + title="Get Gmail Attachment", + readOnlyHint=True, + ), +) @handle_http_errors( "get_gmail_attachment_content", is_read_only=True, service_type="gmail" ) @@ -888,7 +910,12 @@ async def get_gmail_attachment_content( return "\n".join(result_lines) -@server.tool() +@server.tool( + annotations=ToolAnnotations( + title="Send Gmail Message", + destructiveHint=False, + ), +) @handle_http_errors("send_gmail_message", service_type="gmail") @require_google_service("gmail", GMAIL_SEND_SCOPE) async def send_gmail_message( @@ -994,7 +1021,12 @@ async def send_gmail_message( return f"Email sent! Message ID: {message_id}" -@server.tool() +@server.tool( + annotations=ToolAnnotations( + title="Draft Gmail Message", + destructiveHint=False, + ), +) @handle_http_errors("draft_gmail_message", service_type="gmail") @require_google_service("gmail", GMAIL_COMPOSE_SCOPE) async def draft_gmail_message( @@ -1189,7 +1221,12 @@ def _format_thread_content(thread_data: dict, thread_id: str) -> str: return "\n".join(content_lines) -@server.tool() +@server.tool( + annotations=ToolAnnotations( + title="Get Gmail Thread Content", + readOnlyHint=True, + ), +) @require_google_service("gmail", "gmail_read") @handle_http_errors("get_gmail_thread_content", is_read_only=True, service_type="gmail") async def get_gmail_thread_content( @@ -1217,7 +1254,12 @@ async def get_gmail_thread_content( return _format_thread_content(thread_response, thread_id) -@server.tool() +@server.tool( + annotations=ToolAnnotations( + title="Get Gmail Threads Batch", + readOnlyHint=True, + ), +) @require_google_service("gmail", "gmail_read") @handle_http_errors( "get_gmail_threads_content_batch", is_read_only=True, service_type="gmail" @@ -1326,7 +1368,12 @@ async def fetch_thread_with_retry(tid: str, max_retries: int = 3): return header + "\n\n" + "\n---\n\n".join(output_threads) -@server.tool() +@server.tool( + annotations=ToolAnnotations( + title="List Gmail Labels", + readOnlyHint=True, + ), +) @handle_http_errors("list_gmail_labels", is_read_only=True, service_type="gmail") @require_google_service("gmail", "gmail_read") async def list_gmail_labels(service, user_google_email: str) -> str: @@ -1374,7 +1421,12 @@ async def list_gmail_labels(service, user_google_email: str) -> str: return "\n".join(lines) -@server.tool() +@server.tool( + annotations=ToolAnnotations( + title="Manage Gmail Label", + destructiveHint=True, + ), +) @handle_http_errors("manage_gmail_label", service_type="gmail") @require_google_service("gmail", GMAIL_LABELS_SCOPE) async def manage_gmail_label( @@ -1453,7 +1505,12 @@ async def manage_gmail_label( return f"Label '{label_name}' (ID: {label_id}) deleted successfully!" -@server.tool() +@server.tool( + annotations=ToolAnnotations( + title="List Gmail Filters", + readOnlyHint=True, + ), +) @handle_http_errors("list_gmail_filters", is_read_only=True, service_type="gmail") @require_google_service("gmail", "gmail_settings_basic") async def list_gmail_filters(service, user_google_email: str) -> str: @@ -1531,7 +1588,12 @@ async def list_gmail_filters(service, user_google_email: str) -> str: return "\n".join(lines).rstrip() -@server.tool() +@server.tool( + annotations=ToolAnnotations( + title="Create Gmail Filter", + destructiveHint=False, + ), +) @handle_http_errors("create_gmail_filter", service_type="gmail") @require_google_service("gmail", "gmail_settings_basic") async def create_gmail_filter( @@ -1571,7 +1633,12 @@ async def create_gmail_filter( return f"Filter created successfully!\nFilter ID: {filter_id}" -@server.tool() +@server.tool( + annotations=ToolAnnotations( + title="Delete Gmail Filter", + destructiveHint=True, + ), +) @handle_http_errors("delete_gmail_filter", service_type="gmail") @require_google_service("gmail", "gmail_settings_basic") async def delete_gmail_filter( @@ -1610,7 +1677,12 @@ async def delete_gmail_filter( ) -@server.tool() +@server.tool( + annotations=ToolAnnotations( + title="Modify Gmail Message Labels", + destructiveHint=True, + ), +) @handle_http_errors("modify_gmail_message_labels", service_type="gmail") @require_google_service("gmail", GMAIL_MODIFY_SCOPE) async def modify_gmail_message_labels( @@ -1666,7 +1738,12 @@ async def modify_gmail_message_labels( return f"Message labels updated successfully!\nMessage ID: {message_id}\n{'; '.join(actions)}" -@server.tool() +@server.tool( + annotations=ToolAnnotations( + title="Batch Modify Gmail Labels", + destructiveHint=True, + ), +) @handle_http_errors("batch_modify_gmail_message_labels", service_type="gmail") @require_google_service("gmail", GMAIL_MODIFY_SCOPE) async def batch_modify_gmail_message_labels( diff --git a/gsearch/search_tools.py b/gsearch/search_tools.py index 6afb0fd6..77da95cf 100644 --- a/gsearch/search_tools.py +++ b/gsearch/search_tools.py @@ -10,6 +10,8 @@ from typing import Optional, List, Literal from auth.service_decorator import require_google_service +from mcp.types import ToolAnnotations + from core.server import server from core.utils import handle_http_errors diff --git a/gsheets/sheets_tools.py b/gsheets/sheets_tools.py index db6a9cd0..3804d4ad 100644 --- a/gsheets/sheets_tools.py +++ b/gsheets/sheets_tools.py @@ -11,6 +11,8 @@ from typing import List, Optional, Union from auth.service_decorator import require_google_service +from mcp.types import ToolAnnotations + from core.server import server from core.utils import handle_http_errors, UserInputError from core.comments import create_comment_tools @@ -957,7 +959,12 @@ async def create_spreadsheet( return text_output -@server.tool() +@server.tool( + annotations=ToolAnnotations( + title="Create Sheet", + destructiveHint=False, + ), +) @handle_http_errors("create_sheet", service_type="sheets") @require_google_service("sheets", "sheets_write") async def create_sheet( diff --git a/gslides/slides_tools.py b/gslides/slides_tools.py index 2c2c9be0..5c0866fb 100644 --- a/gslides/slides_tools.py +++ b/gslides/slides_tools.py @@ -10,6 +10,8 @@ from auth.service_decorator import require_google_service +from mcp.types import ToolAnnotations + from core.server import server from core.utils import handle_http_errors from core.comments import create_comment_tools @@ -17,7 +19,12 @@ logger = logging.getLogger(__name__) -@server.tool() +@server.tool( + annotations=ToolAnnotations( + title="Create Presentation", + destructiveHint=False, + ), +) @handle_http_errors("create_presentation", service_type="slides") @require_google_service("slides", "slides") async def create_presentation( diff --git a/gtasks/tasks_tools.py b/gtasks/tasks_tools.py index 70489cdf..9f18510d 100644 --- a/gtasks/tasks_tools.py +++ b/gtasks/tasks_tools.py @@ -13,6 +13,8 @@ from mcp import Resource from auth.service_decorator import require_google_service +from mcp.types import ToolAnnotations + from core.server import server from core.utils import handle_http_errors @@ -67,9 +69,14 @@ def _adjust_due_max_for_tasks_api(due_max: str) -> str: return adjusted.isoformat() -@server.tool() # type: ignore -@require_google_service("tasks", "tasks_read") # type: ignore -@handle_http_errors("list_task_lists", service_type="tasks") # type: ignore +@server.tool( + annotations=ToolAnnotations( + title="List Task Lists", + readOnlyHint=True, + ), +) +@require_google_service("tasks", "tasks_read") +@handle_http_errors("list_task_lists", service_type="tasks") async def list_task_lists( service: Resource, user_google_email: str,