2025-05-06 09:36:48 -04:00
"""
Google Calendar MCP Tools
This module provides MCP tools for interacting with Google Calendar API.
"""
2025-05-22 13:33:06 -04:00
2025-04-27 14:37:08 -04:00
import datetime
import logging
2025-04-27 16:59:35 -04:00
import asyncio
2025-05-10 15:11:13 -04:00
import os
2025-05-06 09:36:48 -04:00
import sys
2025-05-11 17:39:15 -04:00
from typing import List , Optional , Dict , Any
2025-05-11 10:07:37 -04:00
from mcp import types
2025-04-27 14:37:08 -04:00
from googleapiclient.errors import HttpError
2025-05-24 10:43:55 -04:00
from auth.google_auth import get_authenticated_google_service
2025-05-22 13:33:06 -04:00
from core.server import (
server ,
CALENDAR_READONLY_SCOPE ,
CALENDAR_EVENTS_SCOPE ,
)
2025-05-06 09:36:48 -04:00
# Configure module logger
logger = logging . getLogger ( __name__ )
2025-05-22 13:33:06 -04:00
2025-05-13 12:45:24 -04:00
# Helper function to ensure time strings for API calls are correctly formatted
2025-05-22 13:33:06 -04:00
def _correct_time_format_for_api (
time_str : Optional [ str ], param_name : str
) -> Optional [ str ]:
2025-05-13 12:45:24 -04:00
if not time_str :
return None
2025-05-17 15:33:09 -04:00
# Log the incoming time string for debugging
2025-05-22 13:33:06 -04:00
logger . info (
f "_correct_time_format_for_api: Processing { param_name } with value ' { time_str } '"
)
2025-05-17 15:33:09 -04:00
# Handle date-only format (YYYY-MM-DD)
2025-05-22 13:33:06 -04:00
if len ( time_str ) == 10 and time_str . count ( "-" ) == 2 :
2025-05-17 15:33:09 -04:00
try :
# Validate it's a proper date
datetime . datetime . strptime ( time_str , "%Y-%m- %d " )
# For date-only, append T00:00:00Z to make it RFC3339 compliant
formatted = f " { time_str } T00:00:00Z"
2025-05-22 13:33:06 -04:00
logger . info (
f "Formatting date-only { param_name } ' { time_str } ' to RFC3339: ' { formatted } '"
)
2025-05-17 15:33:09 -04:00
return formatted
except ValueError :
2025-05-22 13:33:06 -04:00
logger . warning (
f " { param_name } ' { time_str } ' looks like a date but is not valid YYYY-MM-DD. Using as is."
)
2025-05-17 15:33:09 -04:00
return time_str
2025-05-13 12:45:24 -04:00
# Specifically address YYYY-MM-DDTHH:MM:SS by appending 'Z'
2025-05-22 13:33:06 -04:00
if (
len ( time_str ) == 19
and time_str [ 10 ] == "T"
and time_str . count ( ":" ) == 2
and not (
time_str . endswith ( "Z" ) or ( "+" in time_str [ 10 :]) or ( "-" in time_str [ 10 :])
)
):
2025-05-13 12:45:24 -04:00
try :
# Validate the format before appending 'Z'
datetime . datetime . strptime ( time_str , "%Y-%m- %d T%H:%M:%S" )
2025-05-22 13:33:06 -04:00
logger . info (
f "Formatting { param_name } ' { time_str } ' by appending 'Z' for UTC."
)
2025-05-13 12:45:24 -04:00
return time_str + "Z"
except ValueError :
2025-05-22 13:33:06 -04:00
logger . warning (
f " { param_name } ' { time_str } ' looks like it needs 'Z' but is not valid YYYY-MM-DDTHH:MM:SS. Using as is."
)
2025-05-13 12:45:24 -04:00
return time_str
2025-05-17 15:33:09 -04:00
# If it already has timezone info or doesn't match our patterns, return as is
logger . info ( f " { param_name } ' { time_str } ' doesn't need formatting, using as is." )
2025-05-13 12:45:24 -04:00
return time_str
2025-05-06 12:17:22 -04:00
2025-05-17 10:25:55 -04:00
# --- Helper for Authentication and Service Initialization ---
2025-05-22 13:33:06 -04:00
2025-05-06 12:17:22 -04:00
# --- Tool Implementations ---
2025-05-06 09:36:48 -04:00
2025-05-11 17:15:05 -04:00
@server.tool ()
2025-05-22 13:33:06 -04:00
async def list_calendars (
2025-05-24 10:43:55 -04:00
user_google_email : str ,
2025-05-22 13:33:06 -04:00
) -> types . CallToolResult :
2025-04-27 14:37:08 -04:00
"""
2025-05-12 14:32:44 -04:00
Retrieves a list of calendars accessible to the authenticated user.
2025-05-10 17:57:25 -04:00
Args:
2025-05-24 10:43:55 -04:00
user_google_email (str): The user's Google email address. Required.
2025-05-12 14:32:44 -04:00
2025-05-10 17:57:25 -04:00
Returns:
2025-05-12 14:32:44 -04:00
types.CallToolResult: Contains a list of the user's calendars (summary, ID, primary status),
2025-05-13 12:36:53 -04:00
an error message if the API call fails,
or an authentication guidance message if credentials are required.
2025-04-27 14:37:08 -04:00
"""
2025-05-24 10:43:55 -04:00
tool_name = "list_calendars"
2025-05-22 13:33:06 -04:00
logger . info (
2025-05-24 10:43:55 -04:00
f "[ { tool_name } ] Invoked. Email: ' { user_google_email } '"
2025-05-22 13:33:06 -04:00
)
2025-05-24 10:43:55 -04:00
auth_result = await get_authenticated_google_service (
service_name = "calendar" ,
version = "v3" ,
tool_name = tool_name ,
2025-05-11 17:39:15 -04:00
user_google_email = user_google_email ,
2025-05-22 13:33:06 -04:00
required_scopes = [ CALENDAR_READONLY_SCOPE ],
2025-05-11 17:39:15 -04:00
)
2025-05-17 10:25:55 -04:00
if isinstance ( auth_result , types . CallToolResult ):
2025-06-04 18:48:17 -04:00
return auth_result
2025-05-24 10:43:55 -04:00
service , user_email = auth_result
2025-04-27 14:37:08 -04:00
try :
2025-05-22 13:33:06 -04:00
calendar_list_response = await asyncio . to_thread (
service . calendarList () . list () . execute
)
items = calendar_list_response . get ( "items" , [])
2025-05-11 15:37:44 -04:00
if not items :
2025-05-22 13:33:06 -04:00
return types . CallToolResult (
content = [
types . TextContent (
2025-05-24 10:43:55 -04:00
type = "text" , text = f "No calendars found for { user_email } ."
2025-05-22 13:33:06 -04:00
)
]
)
2025-05-13 12:36:53 -04:00
2025-05-22 13:33:06 -04:00
calendars_summary_list = [
f "- \" { cal . get ( 'summary' , 'No Summary' ) } \" { ' (Primary)' if cal . get ( 'primary' ) else '' } (ID: { cal [ 'id' ] } )"
for cal in items
]
text_output = (
2025-05-24 10:43:55 -04:00
f "Successfully listed { len ( items ) } calendars for { user_email } : \n "
2025-05-22 13:33:06 -04:00
+ " \n " . join ( calendars_summary_list )
)
2025-05-24 10:43:55 -04:00
logger . info ( f "Successfully listed { len ( items ) } calendars for { user_email } ." )
2025-05-22 13:33:06 -04:00
return types . CallToolResult (
content = [ types . TextContent ( type = "text" , text = text_output )]
)
2025-04-27 14:37:08 -04:00
except HttpError as error :
2025-05-20 14:51:10 -04:00
message = f "API error listing calendars: { error } . You might need to re-authenticate. LLM: Try 'start_google_auth' with user's email and service_name='Google Calendar'."
2025-05-11 17:39:15 -04:00
logger . error ( message , exc_info = True )
2025-05-22 13:33:06 -04:00
return types . CallToolResult (
isError = True , content = [ types . TextContent ( type = "text" , text = message )]
)
2025-04-27 14:37:08 -04:00
except Exception as e :
2025-05-11 17:39:15 -04:00
message = f "Unexpected error listing calendars: { e } ."
logger . exception ( message )
2025-05-22 13:33:06 -04:00
return types . CallToolResult (
isError = True , content = [ types . TextContent ( type = "text" , text = message )]
)
2025-05-06 09:36:48 -04:00
2025-05-06 12:17:22 -04:00
@server.tool ()
2025-04-27 14:37:08 -04:00
async def get_events (
2025-05-24 10:43:55 -04:00
user_google_email : str ,
2025-05-22 13:33:06 -04:00
calendar_id : str = "primary" ,
2025-04-27 14:37:08 -04:00
time_min : Optional [ str ] = None ,
time_max : Optional [ str ] = None ,
max_results : int = 25 ,
2025-05-11 10:07:37 -04:00
) -> types . CallToolResult :
2025-04-27 14:37:08 -04:00
"""
2025-05-12 14:32:44 -04:00
Retrieves a list of events from a specified Google Calendar within a given time range.
2025-05-10 17:57:25 -04:00
Args:
2025-05-24 10:43:55 -04:00
user_google_email (str): The user's Google email address. Required.
2025-05-12 14:32:44 -04:00
calendar_id (str): The ID of the calendar to query. Use 'primary' for the user's primary calendar. Defaults to 'primary'. Calendar IDs can be obtained using `list_calendars`.
time_min (Optional[str]): The start of the time range (inclusive) in RFC3339 format (e.g., '2024-05-12T10:00:00Z' or '2024-05-12'). If omitted, defaults to the current time.
time_max (Optional[str]): The end of the time range (exclusive) in RFC3339 format. If omitted, events starting from `time_min` onwards are considered (up to `max_results`).
max_results (int): The maximum number of events to return. Defaults to 25.
2025-05-10 17:57:25 -04:00
Returns:
2025-05-12 14:32:44 -04:00
types.CallToolResult: Contains a list of events (summary, start time, link) within the specified range,
2025-05-13 12:36:53 -04:00
an error message if the API call fails,
or an authentication guidance message if credentials are required.
2025-04-27 14:37:08 -04:00
"""
2025-05-24 10:43:55 -04:00
tool_name = "get_events"
auth_result = await get_authenticated_google_service (
2025-05-24 11:54:31 -04:00
service_name = "calendar" ,
version = "v3" ,
tool_name = tool_name ,
user_google_email = user_google_email ,
2025-05-22 13:33:06 -04:00
required_scopes = [ CALENDAR_READONLY_SCOPE ],
2025-05-11 17:39:15 -04:00
)
2025-05-24 11:54:31 -04:00
2025-05-17 10:25:55 -04:00
if isinstance ( auth_result , types . CallToolResult ):
2025-06-04 18:48:17 -04:00
return auth_result
2025-05-24 10:43:55 -04:00
service , user_email = auth_result
2025-04-27 14:37:08 -04:00
try :
2025-05-22 13:33:06 -04:00
logger . info (
f "[get_events] Raw time parameters - time_min: ' { time_min } ', time_max: ' { time_max } '"
)
2025-05-13 12:45:24 -04:00
# Ensure time_min and time_max are correctly formatted for the API
formatted_time_min = _correct_time_format_for_api ( time_min , "time_min" )
2025-05-22 13:33:06 -04:00
effective_time_min = formatted_time_min or (
datetime . datetime . utcnow () . isoformat () + "Z"
)
2025-05-13 12:45:24 -04:00
if time_min is None :
2025-05-22 13:33:06 -04:00
logger . info (
f "time_min not provided, defaulting to current UTC time: { effective_time_min } "
)
2025-05-17 15:33:09 -04:00
else :
2025-05-22 13:33:06 -04:00
logger . info (
f "time_min processing: original=' { time_min } ', formatted=' { formatted_time_min } ', effective=' { effective_time_min } '"
)
2025-05-13 12:45:24 -04:00
effective_time_max = _correct_time_format_for_api ( time_max , "time_max" )
2025-05-17 15:33:09 -04:00
if time_max :
2025-05-22 13:33:06 -04:00
logger . info (
f "time_max processing: original=' { time_max } ', formatted=' { effective_time_max } '"
)
2025-05-13 12:36:53 -04:00
2025-05-17 15:33:09 -04:00
# Log the final API call parameters
2025-05-22 13:33:06 -04:00
logger . info (
f "[get_events] Final API parameters - calendarId: ' { calendar_id } ', timeMin: ' { effective_time_min } ', timeMax: ' { effective_time_max } ', maxResults: { max_results } "
)
2025-04-27 14:37:08 -04:00
2025-05-06 12:17:22 -04:00
events_result = await asyncio . to_thread (
2025-05-22 13:33:06 -04:00
service . events ()
. list (
calendarId = calendar_id ,
timeMin = effective_time_min ,
timeMax = effective_time_max ,
maxResults = max_results ,
singleEvents = True ,
orderBy = "startTime" ,
)
. execute
2025-05-06 12:17:22 -04:00
)
2025-05-22 13:33:06 -04:00
items = events_result . get ( "items" , [])
2025-05-11 15:37:44 -04:00
if not items :
2025-05-22 13:33:06 -04:00
return types . CallToolResult (
content = [
types . TextContent (
type = "text" ,
2025-05-24 10:43:55 -04:00
text = f "No events found in calendar ' { calendar_id } ' for { user_email } for the specified time range." ,
2025-05-22 13:33:06 -04:00
)
]
)
2025-05-11 15:37:44 -04:00
2025-05-11 17:15:05 -04:00
event_details_list = []
2025-05-11 17:39:15 -04:00
for item in items :
2025-05-22 13:33:06 -04:00
summary = item . get ( "summary" , "No Title" )
start = item [ "start" ] . get ( "dateTime" , item [ "start" ] . get ( "date" ))
link = item . get ( "htmlLink" , "No Link" )
event_id = item . get ( "id" , "No ID" )
2025-05-17 10:45:00 -04:00
# Include the event ID in the output so users can copy it for modify/delete operations
2025-05-22 13:33:06 -04:00
event_details_list . append (
f '- " { summary } " (Starts: { start } ) ID: { event_id } | Link: { link } '
)
2025-05-13 12:36:53 -04:00
2025-05-22 13:33:06 -04:00
text_output = (
2025-05-24 10:43:55 -04:00
f "Successfully retrieved { len ( items ) } events from calendar ' { calendar_id } ' for { user_email } : \n "
2025-05-22 13:33:06 -04:00
+ " \n " . join ( event_details_list )
)
2025-05-24 10:43:55 -04:00
logger . info ( f "Successfully retrieved { len ( items ) } events for { user_email } ." )
2025-05-22 13:33:06 -04:00
return types . CallToolResult (
content = [ types . TextContent ( type = "text" , text = text_output )]
)
2025-04-27 14:37:08 -04:00
except HttpError as error :
2025-05-20 14:51:10 -04:00
message = f "API error getting events: { error } . You might need to re-authenticate. LLM: Try 'start_google_auth' with user's email and service_name='Google Calendar'."
2025-05-11 17:39:15 -04:00
logger . error ( message , exc_info = True )
2025-05-22 13:33:06 -04:00
return types . CallToolResult (
isError = True , content = [ types . TextContent ( type = "text" , text = message )]
)
2025-04-27 14:37:08 -04:00
except Exception as e :
2025-05-11 17:39:15 -04:00
message = f "Unexpected error getting events: { e } ."
logger . exception ( message )
2025-05-22 13:33:06 -04:00
return types . CallToolResult (
isError = True , content = [ types . TextContent ( type = "text" , text = message )]
)
2025-04-27 14:37:08 -04:00
2025-05-06 12:17:22 -04:00
@server.tool ()
2025-04-27 14:37:08 -04:00
async def create_event (
2025-05-24 10:43:55 -04:00
user_google_email : str ,
2025-04-27 14:37:08 -04:00
summary : str ,
2025-05-13 12:36:53 -04:00
start_time : str ,
end_time : str ,
2025-05-22 13:33:06 -04:00
calendar_id : str = "primary" ,
2025-04-27 14:37:08 -04:00
description : Optional [ str ] = None ,
location : Optional [ str ] = None ,
attendees : Optional [ List [ str ]] = None ,
2025-05-06 12:17:22 -04:00
timezone : Optional [ str ] = None ,
2025-05-11 10:07:37 -04:00
) -> types . CallToolResult :
2025-04-27 14:37:08 -04:00
"""
2025-05-24 10:43:55 -04:00
Creates a new event.
2025-05-13 12:36:53 -04:00
2025-05-10 17:57:25 -04:00
Args:
2025-05-24 10:43:55 -04:00
user_google_email (str): The user's Google email address. Required.
2025-05-11 17:39:15 -04:00
summary (str): Event title.
start_time (str): Start time (RFC3339, e.g., "2023-10-27T10:00:00-07:00" or "2023-10-27" for all-day).
end_time (str): End time (RFC3339, e.g., "2023-10-27T11:00:00-07:00" or "2023-10-28" for all-day).
calendar_id (str): Calendar ID (default: 'primary').
2025-05-11 17:15:05 -04:00
description (Optional[str]): Event description.
location (Optional[str]): Event location.
2025-05-11 17:39:15 -04:00
attendees (Optional[List[str]]): Attendee email addresses.
timezone (Optional[str]): Timezone (e.g., "America/New_York").
2025-05-13 12:36:53 -04:00
2025-05-10 17:57:25 -04:00
Returns:
2025-05-11 17:39:15 -04:00
A CallToolResult confirming creation or an error/auth guidance message.
2025-04-27 14:37:08 -04:00
"""
2025-05-24 10:43:55 -04:00
tool_name = "create_event"
2025-05-22 13:33:06 -04:00
logger . info (
2025-05-24 10:43:55 -04:00
f "[ { tool_name } ] Invoked. Email: ' { user_google_email } ', Summary: { summary } "
2025-05-22 13:33:06 -04:00
)
2025-05-24 10:43:55 -04:00
auth_result = await get_authenticated_google_service (
service_name = "calendar" ,
version = "v3" ,
tool_name = tool_name ,
2025-05-11 17:39:15 -04:00
user_google_email = user_google_email ,
2025-05-22 13:33:06 -04:00
required_scopes = [ CALENDAR_EVENTS_SCOPE ],
2025-05-11 17:39:15 -04:00
)
2025-05-17 10:25:55 -04:00
if isinstance ( auth_result , types . CallToolResult ):
2025-06-04 18:48:17 -04:00
return auth_result
2025-05-24 10:43:55 -04:00
service , user_email = auth_result
2025-04-27 14:37:08 -04:00
try :
2025-05-11 15:37:44 -04:00
event_body : Dict [ str , Any ] = {
2025-05-22 13:33:06 -04:00
"summary" : summary ,
"start" : (
{ "date" : start_time }
if "T" not in start_time
else { "dateTime" : start_time }
),
"end" : (
{ "date" : end_time } if "T" not in end_time else { "dateTime" : end_time }
),
2025-04-27 14:37:08 -04:00
}
2025-05-22 13:33:06 -04:00
if location :
event_body [ "location" ] = location
if description :
event_body [ "description" ] = description
2025-05-11 17:39:15 -04:00
if timezone :
2025-05-22 13:33:06 -04:00
if "dateTime" in event_body [ "start" ]:
event_body [ "start" ][ "timeZone" ] = timezone
if "dateTime" in event_body [ "end" ]:
event_body [ "end" ][ "timeZone" ] = timezone
if attendees :
event_body [ "attendees" ] = [{ "email" : email } for email in attendees ]
2025-05-11 17:39:15 -04:00
created_event = await asyncio . to_thread (
service . events () . insert ( calendarId = calendar_id , body = event_body ) . execute
2025-05-06 12:17:22 -04:00
)
2025-05-13 12:36:53 -04:00
2025-05-22 13:33:06 -04:00
link = created_event . get ( "htmlLink" , "No link available" )
2025-05-24 10:43:55 -04:00
# Corrected confirmation_message to use user_email
confirmation_message = f "Successfully created event ' { created_event . get ( 'summary' , summary ) } ' for { user_email } . Link: { link } "
# Corrected logger to use user_email and include event ID
2025-05-22 13:33:06 -04:00
logger . info (
2025-05-24 10:43:55 -04:00
f "Event created successfully for { user_email } . ID: { created_event . get ( 'id' ) } , Link: { link } "
2025-05-22 13:33:06 -04:00
)
return types . CallToolResult (
content = [ types . TextContent ( type = "text" , text = confirmation_message )]
)
2025-04-27 14:37:08 -04:00
except HttpError as error :
2025-05-24 10:43:55 -04:00
# Corrected error message to use user_email and provide better guidance
# user_email_for_error is now user_email from the helper or the original user_google_email
message = f "API error creating event: { error } . You might need to re-authenticate. LLM: Try 'start_google_auth' with the user's email ( { user_email if user_email != 'Unknown' else 'target Google account' } ) and service_name='Google Calendar'."
2025-05-11 17:39:15 -04:00
logger . error ( message , exc_info = True )
2025-05-22 13:33:06 -04:00
return types . CallToolResult (
isError = True , content = [ types . TextContent ( type = "text" , text = message )]
)
2025-04-27 14:37:08 -04:00
except Exception as e :
2025-05-11 17:39:15 -04:00
message = f "Unexpected error creating event: { e } ."
logger . exception ( message )
2025-05-22 13:33:06 -04:00
return types . CallToolResult (
isError = True , content = [ types . TextContent ( type = "text" , text = message )]
)
2025-05-17 10:21:14 -04:00
@server.tool ()
async def modify_event (
2025-05-24 10:43:55 -04:00
user_google_email : str ,
2025-05-17 10:21:14 -04:00
event_id : str ,
2025-05-22 13:33:06 -04:00
calendar_id : str = "primary" ,
2025-05-17 10:21:14 -04:00
summary : Optional [ str ] = None ,
start_time : Optional [ str ] = None ,
end_time : Optional [ str ] = None ,
description : Optional [ str ] = None ,
location : Optional [ str ] = None ,
attendees : Optional [ List [ str ]] = None ,
timezone : Optional [ str ] = None ,
) -> types . CallToolResult :
"""
2025-05-24 10:43:55 -04:00
Modifies an existing event.
2025-05-17 10:21:14 -04:00
Args:
2025-05-24 10:43:55 -04:00
user_google_email (str): The user's Google email address. Required.
2025-05-17 10:21:14 -04:00
event_id (str): The ID of the event to modify.
calendar_id (str): Calendar ID (default: 'primary').
summary (Optional[str]): New event title.
start_time (Optional[str]): New start time (RFC3339, e.g., "2023-10-27T10:00:00-07:00" or "2023-10-27" for all-day).
end_time (Optional[str]): New end time (RFC3339, e.g., "2023-10-27T11:00:00-07:00" or "2023-10-28" for all-day).
description (Optional[str]): New event description.
location (Optional[str]): New event location.
attendees (Optional[List[str]]): New attendee email addresses.
timezone (Optional[str]): New timezone (e.g., "America/New_York").
Returns:
A CallToolResult confirming modification or an error/auth guidance message.
"""
2025-05-24 11:54:31 -04:00
tool_name = "modify_event"
2025-05-22 13:33:06 -04:00
logger . info (
2025-05-24 10:43:55 -04:00
f "[ { tool_name } ] Invoked. Email: ' { user_google_email } ', Event ID: { event_id } "
2025-05-22 13:33:06 -04:00
)
2025-05-24 10:43:55 -04:00
auth_result = await get_authenticated_google_service (
service_name = "calendar" ,
version = "v3" ,
tool_name = tool_name ,
2025-05-17 10:21:14 -04:00
user_google_email = user_google_email ,
2025-05-22 13:33:06 -04:00
required_scopes = [ CALENDAR_EVENTS_SCOPE ],
2025-05-17 10:21:14 -04:00
)
2025-05-17 10:25:55 -04:00
if isinstance ( auth_result , types . CallToolResult ):
2025-06-04 18:48:17 -04:00
return auth_result
2025-05-24 10:43:55 -04:00
service , user_email = auth_result
2025-05-17 10:21:14 -04:00
try :
# Build the event body with only the fields that are provided
event_body : Dict [ str , Any ] = {}
2025-05-22 13:33:06 -04:00
if summary is not None :
event_body [ "summary" ] = summary
2025-05-17 10:21:14 -04:00
if start_time is not None :
2025-05-22 13:33:06 -04:00
event_body [ "start" ] = (
{ "date" : start_time }
if "T" not in start_time
else { "dateTime" : start_time }
)
if timezone is not None and "dateTime" in event_body [ "start" ]:
event_body [ "start" ][ "timeZone" ] = timezone
2025-05-17 10:21:14 -04:00
if end_time is not None :
2025-05-22 13:33:06 -04:00
event_body [ "end" ] = (
{ "date" : end_time } if "T" not in end_time else { "dateTime" : end_time }
)
if timezone is not None and "dateTime" in event_body [ "end" ]:
event_body [ "end" ][ "timeZone" ] = timezone
if description is not None :
event_body [ "description" ] = description
if location is not None :
event_body [ "location" ] = location
if attendees is not None :
event_body [ "attendees" ] = [{ "email" : email } for email in attendees ]
if (
timezone is not None
and "start" not in event_body
and "end" not in event_body
):
# If timezone is provided but start/end times are not, we need to fetch the existing event
# to apply the timezone correctly. This is a simplification; a full implementation
# might handle this more robustly or require start/end with timezone.
# For now, we'll log a warning and skip applying timezone if start/end are missing.
logger . warning (
f "[modify_event] Timezone provided but start_time and end_time are missing. Timezone will not be applied unless start/end times are also provided."
)
2025-05-17 10:21:14 -04:00
if not event_body :
2025-05-22 13:33:06 -04:00
message = "No fields provided to modify the event."
logger . warning ( f "[modify_event] { message } " )
return types . CallToolResult (
isError = True , content = [ types . TextContent ( type = "text" , text = message )]
)
2025-05-17 10:21:14 -04:00
2025-05-17 10:45:00 -04:00
# Log the event ID for debugging
2025-05-22 13:33:06 -04:00
logger . info (
f "[modify_event] Attempting to update event with ID: ' { event_id } ' in calendar ' { calendar_id } '"
)
2025-05-17 10:45:00 -04:00
# Try to get the event first to verify it exists
try :
await asyncio . to_thread (
service . events () . get ( calendarId = calendar_id , eventId = event_id ) . execute
)
2025-05-22 13:33:06 -04:00
logger . info (
f "[modify_event] Successfully verified event exists before update"
)
2025-05-17 10:45:00 -04:00
except HttpError as get_error :
if get_error . resp . status == 404 :
2025-05-22 13:33:06 -04:00
logger . error (
f "[modify_event] Event not found during pre-update verification: { get_error } "
)
2025-05-17 10:45:00 -04:00
message = f "Event not found during verification. The event with ID ' { event_id } ' could not be found in calendar ' { calendar_id } '. This may be due to incorrect ID format or the event no longer exists."
2025-05-22 13:33:06 -04:00
return types . CallToolResult (
isError = True , content = [ types . TextContent ( type = "text" , text = message )]
)
2025-05-17 10:45:00 -04:00
else :
2025-05-22 13:33:06 -04:00
logger . warning (
f "[modify_event] Error during pre-update verification, but proceeding with update: { get_error } "
)
2025-05-17 10:45:00 -04:00
# Proceed with the update
2025-05-17 10:21:14 -04:00
updated_event = await asyncio . to_thread (
2025-05-22 13:33:06 -04:00
service . events ()
. update ( calendarId = calendar_id , eventId = event_id , body = event_body )
. execute
2025-05-17 10:21:14 -04:00
)
2025-05-22 13:33:06 -04:00
link = updated_event . get ( "htmlLink" , "No link available" )
2025-05-24 10:43:55 -04:00
confirmation_message = f "Successfully modified event ' { updated_event . get ( 'summary' , summary ) } ' (ID: { event_id } ) for { user_email } . Link: { link } "
2025-05-22 13:33:06 -04:00
logger . info (
2025-05-24 10:43:55 -04:00
f "Event modified successfully for { user_email } . ID: { updated_event . get ( 'id' ) } , Link: { link } "
2025-05-22 13:33:06 -04:00
)
return types . CallToolResult (
content = [ types . TextContent ( type = "text" , text = confirmation_message )]
)
2025-05-17 10:21:14 -04:00
except HttpError as error :
2025-05-17 10:45:00 -04:00
# Check for 404 Not Found error specifically
if error . resp . status == 404 :
message = f "Event not found. The event with ID ' { event_id } ' could not be found in calendar ' { calendar_id } '. LLM: The event may have been deleted, or the event ID might be incorrect. Verify the event exists using 'get_events' before attempting to modify it."
logger . error ( f "[modify_event] { message } " )
else :
2025-05-24 10:43:55 -04:00
message = f "API error modifying event (ID: { event_id } ): { error } . You might need to re-authenticate. LLM: Try 'start_google_auth' with the user's email ( { user_email if user_email != 'Unknown' else 'target Google account' } ) and service_name='Google Calendar'."
2025-05-17 10:45:00 -04:00
logger . error ( message , exc_info = True )
2025-05-22 13:33:06 -04:00
return types . CallToolResult (
isError = True , content = [ types . TextContent ( type = "text" , text = message )]
)
2025-05-17 10:21:14 -04:00
except Exception as e :
message = f "Unexpected error modifying event (ID: { event_id } ): { e } ."
logger . exception ( message )
2025-05-22 13:33:06 -04:00
return types . CallToolResult (
isError = True , content = [ types . TextContent ( type = "text" , text = message )]
)
2025-05-17 10:21:14 -04:00
@server.tool ()
async def delete_event (
2025-05-24 10:43:55 -04:00
user_google_email : str ,
2025-05-17 10:21:14 -04:00
event_id : str ,
2025-05-22 13:33:06 -04:00
calendar_id : str = "primary" ,
2025-05-17 10:21:14 -04:00
) -> types . CallToolResult :
"""
2025-05-24 10:43:55 -04:00
Deletes an existing event.
2025-05-17 10:21:14 -04:00
Args:
2025-05-24 10:43:55 -04:00
user_google_email (str): The user's Google email address. Required.
2025-05-17 10:21:14 -04:00
event_id (str): The ID of the event to delete.
calendar_id (str): Calendar ID (default: 'primary').
Returns:
A CallToolResult confirming deletion or an error/auth guidance message.
"""
2025-05-24 10:43:55 -04:00
tool_name = "delete_event"
2025-05-22 13:33:06 -04:00
logger . info (
2025-05-24 10:43:55 -04:00
f "[ { tool_name } ] Invoked. Email: ' { user_google_email } ', Event ID: { event_id } "
2025-05-22 13:33:06 -04:00
)
2025-05-24 10:43:55 -04:00
auth_result = await get_authenticated_google_service (
service_name = "calendar" ,
version = "v3" ,
tool_name = tool_name ,
2025-05-17 10:21:14 -04:00
user_google_email = user_google_email ,
2025-05-22 13:33:06 -04:00
required_scopes = [ CALENDAR_EVENTS_SCOPE ],
2025-05-17 10:21:14 -04:00
)
2025-05-17 10:25:55 -04:00
if isinstance ( auth_result , types . CallToolResult ):
2025-06-04 18:48:17 -04:00
return auth_result
2025-05-24 10:43:55 -04:00
service , user_email = auth_result
2025-05-17 10:21:14 -04:00
try :
2025-05-17 10:45:00 -04:00
# Log the event ID for debugging
2025-05-22 13:33:06 -04:00
logger . info (
f "[delete_event] Attempting to delete event with ID: ' { event_id } ' in calendar ' { calendar_id } '"
)
2025-05-17 10:45:00 -04:00
# Try to get the event first to verify it exists
try :
await asyncio . to_thread (
service . events () . get ( calendarId = calendar_id , eventId = event_id ) . execute
)
2025-05-22 13:33:06 -04:00
logger . info (
f "[delete_event] Successfully verified event exists before deletion"
)
2025-05-17 10:45:00 -04:00
except HttpError as get_error :
if get_error . resp . status == 404 :
2025-05-22 13:33:06 -04:00
logger . error (
f "[delete_event] Event not found during pre-delete verification: { get_error } "
)
2025-05-17 10:45:00 -04:00
message = f "Event not found during verification. The event with ID ' { event_id } ' could not be found in calendar ' { calendar_id } '. This may be due to incorrect ID format or the event no longer exists."
2025-05-22 13:33:06 -04:00
return types . CallToolResult (
isError = True , content = [ types . TextContent ( type = "text" , text = message )]
)
2025-05-17 10:45:00 -04:00
else :
2025-05-22 13:33:06 -04:00
logger . warning (
f "[delete_event] Error during pre-delete verification, but proceeding with deletion: { get_error } "
)
2025-05-17 10:45:00 -04:00
# Proceed with the deletion
2025-05-17 10:21:14 -04:00
await asyncio . to_thread (
service . events () . delete ( calendarId = calendar_id , eventId = event_id ) . execute
)
2025-05-24 10:43:55 -04:00
confirmation_message = f "Successfully deleted event (ID: { event_id } ) from calendar ' { calendar_id } ' for { user_email } ."
logger . info ( f "Event deleted successfully for { user_email } . ID: { event_id } " )
2025-05-22 13:33:06 -04:00
return types . CallToolResult (
content = [ types . TextContent ( type = "text" , text = confirmation_message )]
)
2025-05-17 10:21:14 -04:00
except HttpError as error :
2025-05-17 10:45:00 -04:00
# Check for 404 Not Found error specifically
if error . resp . status == 404 :
message = f "Event not found. The event with ID ' { event_id } ' could not be found in calendar ' { calendar_id } '. LLM: The event may have been deleted already, or the event ID might be incorrect."
logger . error ( f "[delete_event] { message } " )
else :
2025-05-24 10:43:55 -04:00
message = f "API error deleting event (ID: { event_id } ): { error } . You might need to re-authenticate. LLM: Try 'start_google_auth' with the user's email ( { user_email if user_email != 'Unknown' else 'target Google account' } ) and service_name='Google Calendar'."
2025-05-17 10:45:00 -04:00
logger . error ( message , exc_info = True )
2025-05-22 13:33:06 -04:00
return types . CallToolResult (
isError = True , content = [ types . TextContent ( type = "text" , text = message )]
)
2025-05-17 10:21:14 -04:00
except Exception as e :
message = f "Unexpected error deleting event (ID: { event_id } ): { e } ."
logger . exception ( message )
2025-05-22 13:33:06 -04:00
return types . CallToolResult (
isError = True , content = [ types . TextContent ( type = "text" , text = message )]
)