feat: add support for file attachments in event creation and enhance file creation with URL fetching
This commit is contained in:
@@ -9,6 +9,7 @@ import logging
|
||||
import asyncio
|
||||
import os
|
||||
import sys
|
||||
import re
|
||||
from typing import List, Optional, Dict, Any
|
||||
|
||||
from mcp import types
|
||||
@@ -231,6 +232,7 @@ async def create_event(
|
||||
location: Optional[str] = None,
|
||||
attendees: Optional[List[str]] = None,
|
||||
timezone: Optional[str] = None,
|
||||
attachments: Optional[List[str]] = None, # New parameter for Drive file links/IDs
|
||||
) -> str:
|
||||
"""
|
||||
Creates a new event.
|
||||
@@ -245,6 +247,7 @@ async def create_event(
|
||||
location (Optional[str]): Event location.
|
||||
attendees (Optional[List[str]]): Attendee email addresses.
|
||||
timezone (Optional[str]): Timezone (e.g., "America/New_York").
|
||||
attachments (Optional[List[str]]): List of Google Drive file URLs or IDs to attach to the event.
|
||||
|
||||
Returns:
|
||||
str: Confirmation message of the successful event creation with event link.
|
||||
@@ -252,7 +255,11 @@ async def create_event(
|
||||
logger.info(
|
||||
f"[create_event] Invoked. Email: '{user_google_email}', Summary: {summary}"
|
||||
)
|
||||
|
||||
logger.info(f"[create_event] Incoming attachments param: {attachments}")
|
||||
# If attachments is a string, split by comma and strip whitespace
|
||||
if attachments and isinstance(attachments, str):
|
||||
attachments = [a.strip() for a in attachments.split(',') if a.strip()]
|
||||
logger.info(f"[create_event] Parsed attachments list from string: {attachments}")
|
||||
try:
|
||||
event_body: Dict[str, Any] = {
|
||||
"summary": summary,
|
||||
@@ -276,14 +283,53 @@ async def create_event(
|
||||
event_body["end"]["timeZone"] = timezone
|
||||
if attendees:
|
||||
event_body["attendees"] = [{"email": email} for email in attendees]
|
||||
|
||||
created_event = await asyncio.to_thread(
|
||||
service.events().insert(calendarId=calendar_id, body=event_body).execute
|
||||
)
|
||||
|
||||
if attachments:
|
||||
# Accept both file URLs and file IDs. If a URL, extract the fileId.
|
||||
event_body["attachments"] = []
|
||||
from googleapiclient.discovery import build
|
||||
drive_service = None
|
||||
try:
|
||||
drive_service = service._http and build("drive", "v3", http=service._http)
|
||||
except Exception as e:
|
||||
logger.warning(f"Could not build Drive service for MIME type lookup: {e}")
|
||||
for att in attachments:
|
||||
file_id = None
|
||||
if att.startswith("https://"):
|
||||
# Match /d/<id>, /file/d/<id>, ?id=<id>
|
||||
match = re.search(r"(?:/d/|/file/d/|id=)([\w-]+)", att)
|
||||
file_id = match.group(1) if match else None
|
||||
logger.info(f"[create_event] Extracted file_id '{file_id}' from attachment URL '{att}'")
|
||||
else:
|
||||
file_id = att
|
||||
logger.info(f"[create_event] Using direct file_id '{file_id}' for attachment")
|
||||
if file_id:
|
||||
file_url = f"https://drive.google.com/open?id={file_id}"
|
||||
mime_type = "application/vnd.google-apps.drive-sdk"
|
||||
# Try to get the actual MIME type from Drive
|
||||
if drive_service:
|
||||
try:
|
||||
file_metadata = await asyncio.to_thread(
|
||||
drive_service.files().get(fileId=file_id, fields="mimeType").execute
|
||||
)
|
||||
mime_type = file_metadata.get("mimeType", mime_type)
|
||||
except Exception as e:
|
||||
logger.warning(f"Could not fetch MIME type for file {file_id}: {e}")
|
||||
event_body["attachments"].append({
|
||||
"fileUrl": file_url,
|
||||
"title": f"Drive Attachment ({file_id})",
|
||||
"mimeType": mime_type,
|
||||
})
|
||||
created_event = await asyncio.to_thread(
|
||||
lambda: service.events().insert(
|
||||
calendarId=calendar_id, body=event_body, supportsAttachments=True
|
||||
).execute()
|
||||
)
|
||||
else:
|
||||
created_event = await asyncio.to_thread(
|
||||
service.events().insert(calendarId=calendar_id, body=event_body).execute
|
||||
)
|
||||
link = created_event.get("htmlLink", "No link available")
|
||||
confirmation_message = f"Successfully created event '{created_event.get('summary', summary)}' for {user_google_email}. Link: {link}"
|
||||
logger.info(
|
||||
logger.info(
|
||||
f"Event created successfully for {user_google_email}. ID: {created_event.get('id')}, Link: {link}"
|
||||
)
|
||||
return confirmation_message
|
||||
@@ -495,3 +541,59 @@ async def delete_event(service, user_google_email: str, event_id: str, calendar_
|
||||
message = f"Unexpected error deleting event (ID: {event_id}): {e}."
|
||||
logger.exception(message)
|
||||
raise Exception(message)
|
||||
|
||||
|
||||
@server.tool()
|
||||
@require_google_service("calendar", "calendar_read")
|
||||
async def get_event(
|
||||
service,
|
||||
user_google_email: str,
|
||||
event_id: str,
|
||||
calendar_id: str = "primary"
|
||||
) -> str:
|
||||
"""
|
||||
Retrieves the details of a single event by its ID from a specified Google Calendar.
|
||||
|
||||
Args:
|
||||
service: Authenticated Google Calendar service (injected).
|
||||
user_google_email (str): The user's Google email address. Required.
|
||||
event_id (str): The ID of the event to retrieve. Required.
|
||||
calendar_id (str): The ID of the calendar to query. Defaults to 'primary'.
|
||||
|
||||
Returns:
|
||||
str: A formatted string with the event's details.
|
||||
"""
|
||||
logger.info(f"[get_event] Invoked. Email: '{user_google_email}', Event ID: {event_id}")
|
||||
try:
|
||||
event = await asyncio.to_thread(
|
||||
service.events().get(calendarId=calendar_id, eventId=event_id).execute
|
||||
)
|
||||
summary = event.get("summary", "No Title")
|
||||
start = event["start"].get("dateTime", event["start"].get("date"))
|
||||
end = event["end"].get("dateTime", event["end"].get("date"))
|
||||
link = event.get("htmlLink", "No Link")
|
||||
description = event.get("description", "No Description")
|
||||
location = event.get("location", "No Location")
|
||||
attendees = event.get("attendees", [])
|
||||
attendee_emails = ", ".join([a.get("email", "") for a in attendees]) if attendees else "None"
|
||||
event_details = (
|
||||
f'Event Details:\n'
|
||||
f'- Title: {summary}\n'
|
||||
f'- Starts: {start}\n'
|
||||
f'- Ends: {end}\n'
|
||||
f'- Description: {description}\n'
|
||||
f'- Location: {location}\n'
|
||||
f'- Attendees: {attendee_emails}\n'
|
||||
f'- Event ID: {event_id}\n'
|
||||
f'- Link: {link}'
|
||||
)
|
||||
logger.info(f"[get_event] Successfully retrieved event {event_id} for {user_google_email}.")
|
||||
return event_details
|
||||
except HttpError as error:
|
||||
message = f"API error getting event: {error}. You might need to re-authenticate. LLM: Try 'start_google_auth' with user's email and service_name='Google Calendar'."
|
||||
logger.error(message, exc_info=True)
|
||||
raise Exception(message)
|
||||
except Exception as e:
|
||||
message = f"Unexpected error getting event: {e}"
|
||||
logger.exception(message)
|
||||
raise Exception(message)
|
||||
|
||||
Reference in New Issue
Block a user