hey it works great now
This commit is contained in:
@@ -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}"
|
||||
Reference in New Issue
Block a user