hey it works great now

This commit is contained in:
Taylor Wilsdon
2025-05-06 09:36:48 -04:00
parent a7659d087c
commit face6bcbce
6 changed files with 230 additions and 321 deletions

View File

@@ -1,7 +1,12 @@
# calendar/calendar_tools.py
"""
Google Calendar MCP Tools
This module provides MCP tools for interacting with Google Calendar API.
"""
import datetime
import logging
import asyncio
import sys
from typing import List, Optional
from google.oauth2.credentials import Credentials
@@ -10,7 +15,12 @@ from googleapiclient.errors import HttpError
from auth.google_auth import get_credentials, start_auth_flow, handle_auth_callback
from auth.oauth_manager import start_oauth_flow, check_auth_status, stop_oauth_flow
# REMOVED: from core.server import server
# Configure module logger
logger = logging.getLogger(__name__)
# Import the server directly (will be initialized before this module is imported)
from core.server import server
# Define Google Calendar API Scopes
CALENDAR_READONLY_SCOPE = "https://www.googleapis.com/auth/calendar.readonly"
@@ -22,25 +32,26 @@ logger = logging.getLogger(__name__)
# --- Tool Implementations ---
# REMOVED: @server.tool()
@server.tool() # Added decorator
async def start_auth(user_id: str) -> str:
"""
Start the Google OAuth authentication process with automatic callback handling.
This tool provides a smoother authentication experience by automatically
opening a browser window and handling the callback process.
Args:
user_id: The unique identifier (e.g., email address) for the user.
Returns:
Instructions for completing the authentication.
"""
logger.info(f"Starting OAuth authentication flow for user: {user_id}")
# Use the Calendar readonly scope by default
scopes = [CALENDAR_READONLY_SCOPE]
# Request calendar scope, user email scope, AND openid scope
scopes = [CALENDAR_READONLY_SCOPE, "https://www.googleapis.com/auth/userinfo.email", "openid"]
try:
# Start the OAuth flow with automatic callback handling
# Run synchronous function in a thread
@@ -50,19 +61,19 @@ async def start_auth(user_id: str) -> str:
logger.error(f"Error starting authentication flow: {e}")
return f"Failed to start authentication: {e}"
# REMOVED: @server.tool()
@server.tool() # Added decorator
async def auth_status(user_id: str) -> str:
"""
Check the status of an ongoing authentication process.
Args:
user_id: The unique identifier (e.g., email address) for the user.
Returns:
A status message about the authentication process.
"""
logger.info(f"Checking authentication status for user: {user_id}")
try:
# Check current status
# Run synchronous function in a thread
@@ -72,28 +83,28 @@ async def auth_status(user_id: str) -> str:
logger.error(f"Error checking authentication status: {e}")
return f"Failed to check authentication status: {e}"
# REMOVED: @server.tool()
@server.tool() # Added decorator
async def complete_auth(user_id: str, authorization_code: str) -> str:
"""
Completes the OAuth flow by exchanging the authorization code for credentials.
Args:
user_id: The unique identifier (e.g., email address) for the user.
authorization_code: The authorization code received from Google OAuth.
Returns:
A string indicating success or failure.
"""
logger.info(f"Attempting to complete authentication for user: {user_id}")
try:
# Get the scopes used during the initial auth request
scopes = [CALENDAR_READONLY_SCOPE] # Default to readonly scope
# Construct the full callback URL
redirect_uri = "http://localhost:8080/callback"
full_callback_url = f"{redirect_uri}?code={authorization_code}"
# Use handle_auth_callback to exchange the code for credentials
# Run synchronous function in a thread
user_email, credentials = await asyncio.to_thread(
@@ -109,14 +120,15 @@ async def complete_auth(user_id: str, authorization_code: str) -> str:
logger.warning(f"User ID mismatch: provided {user_id}, authenticated as {user_email}")
return (f"Warning: You authenticated as {user_email}, but requested credentials for {user_id}. "
f"Using authenticated email {user_email} for credentials.")
logger.info(f"Successfully completed authentication for user: {user_email}")
return f"Authentication successful! You can now use the Google Calendar tools with user: {user_email}"
except Exception as e:
logger.error(f"Error completing authentication: {e}", exc_info=True)
return f"Failed to complete authentication: {e}"
# REMOVED: @server.tool()
@server.tool() # Added decorator
async def list_calendars(user_id: str) -> str:
"""
Lists the Google Calendars the user has access to.
@@ -174,7 +186,8 @@ async def list_calendars(user_id: str) -> str:
except Exception as e:
logger.exception(f"An unexpected error occurred while listing calendars for {user_id}: {e}")
return f"An unexpected error occurred: {e}"
# REMOVED: @server.tool()
@server.tool() # Added decorator
async def get_events(
user_id: str,
calendar_id: str = 'primary',
@@ -264,7 +277,7 @@ async def get_events(
return f"An unexpected error occurred: {e}"
# REMOVED: @server.tool()
@server.tool() # Added decorator
async def create_event(
user_id: str,
summary: str,
@@ -303,12 +316,11 @@ async def create_event(
logger.debug(f"get_credentials returned: {credentials}")
if not credentials or not credentials.valid:
logger.warning(f"Missing or invalid credentials for user: {user_id} (write access needed)")
logger.warning(f"Missing or invalid credentials for user: {user_id}")
try:
# Use the automatic flow for better user experience
# For event creation, we need write permissions
# Run synchronous function in a thread
result = await asyncio.to_thread(start_oauth_flow, user_id, scopes) # scopes already includes CALENDAR_EVENTS_SCOPE
result = await asyncio.to_thread(start_oauth_flow, user_id, scopes)
return result
except Exception as e:
logger.error(f"Failed to start auth flow: {e}")
@@ -319,24 +331,20 @@ async def create_event(
try:
service = build('calendar', 'v3', credentials=credentials)
logger.info(f"Successfully built calendar service for user: {user_id} (with write access)")
logger.info(f"Successfully built calendar service for user: {user_id}")
event_body = {
'summary': summary,
'start': {'dateTime': start_time},
'end': {'dateTime': end_time},
'location': location,
'description': description,
'start': {'dateTime': start_time, 'timeZone': timezone},
'end': {'dateTime': end_time, 'timeZone': timezone},
'attendees': [{'email': email} for email in attendees] if attendees else [],
}
# Add optional fields if provided
if description:
event_body['description'] = description
if location:
event_body['location'] = location
if attendees:
event_body['attendees'] = [{'email': email} for email in attendees]
if timezone:
# Apply timezone to start and end times if provided
event_body['start']['timeZone'] = timezone
event_body['end']['timeZone'] = timezone
# Remove None values from the event body
event_body = {k: v for k, v in event_body.items() if v is not None}
if 'attendees' in event_body and not event_body['attendees']:
del event_body['attendees'] # Don't send empty attendees list
logger.debug(f"Creating event with body: {event_body}")
@@ -345,21 +353,14 @@ async def create_event(
body=event_body
).execute()
event_summary = created_event.get('summary', 'N/A')
event_link = created_event.get('htmlLink', 'N/A')
logger.info(f"Successfully created event '{event_summary}' for user: {user_id}")
return (
f"Successfully created event: '{event_summary}' in calendar '{calendar_id}'.\n"
f"Link: {event_link}"
)
event_link = created_event.get('htmlLink')
logger.info(f"Successfully created event for user: {user_id}, event ID: {created_event['id']}")
return f"Event created successfully! View it here: {event_link}"
except HttpError as error:
logger.error(f"An API error occurred for user {user_id} creating event: {error}")
# TODO: Check error details for specific auth issues (e.g., insufficient permissions)
return f"An API error occurred while creating the event: {error}. Ensure you have write permissions for this calendar."
# TODO: Check error details for specific auth issues
return f"An API error occurred while creating the event: {error}. You might need to re-authenticate."
except Exception as e:
logger.exception(f"An unexpected error occurred while creating event for {user_id}: {e}")
return f"An unexpected error occurred: {e}"
logger.exception(f"An unexpected error occurred while getting events for {user_id}: {e}")
return f"An unexpected error occurred: {e}"