MCP Personal Assistant Agent
/**
* Google Calendar Provider
* Handles interaction with Google Calendar API
*/
import { google } from 'googleapis';
import logger from '../../../utils/logger.js';
import config from '../../../config/index.js';
let googleCalendarClient = null;
/**
* Get an authenticated Google Calendar client
* @returns {Promise<any>} Google Calendar client
*/
export async function getGoogleCalendarClient() {
if (googleCalendarClient) {
return googleCalendarClient;
}
try {
logger.info('Initializing Google Calendar client');
// Check if Google Calendar is enabled in config
if (!config.modules.calendar.providers.google.enabled) {
throw new Error('Google Calendar provider is disabled in configuration');
}
// Get auth credentials from environment variables
const credentials = getGoogleCredentials();
// Set up OAuth2 client
const oAuth2Client = new google.auth.OAuth2(
credentials.clientId,
credentials.clientSecret,
credentials.redirectUri
);
// Set credentials
oAuth2Client.setCredentials({
refresh_token: credentials.refreshToken,
access_token: credentials.accessToken,
});
// Create Calendar client
googleCalendarClient = google.calendar({ version: 'v3', auth: oAuth2Client });
logger.info('Google Calendar client initialized successfully');
return googleCalendarClient;
} catch (error) {
logger.error(`Failed to initialize Google Calendar client: ${error.message}`);
throw new Error(`Failed to initialize Google Calendar client: ${error.message}`);
}
}
/**
* Get Google API credentials from environment variables
* @returns {Object} Google API credentials
*/
function getGoogleCredentials() {
const clientId = process.env.GOOGLE_CLIENT_ID;
const clientSecret = process.env.GOOGLE_CLIENT_SECRET;
const redirectUri = process.env.GOOGLE_REDIRECT_URI;
const refreshToken = process.env.GOOGLE_REFRESH_TOKEN;
const accessToken = process.env.GOOGLE_ACCESS_TOKEN;
if (!clientId || !clientSecret || !redirectUri || !refreshToken) {
throw new Error('Missing required Google API credentials in environment variables');
}
return {
clientId,
clientSecret,
redirectUri,
refreshToken,
accessToken,
};
}
/**
* Fetch events from Google Calendar
* @param {Object} client - Google Calendar client
* @param {Object} options - Options for fetching events
* @returns {Promise<Array>} List of events
*/
export async function getEvents(client, options) {
try {
const { calendarId, startDate, endDate, maxResults } = options;
logger.info(`Fetching events from Google Calendar ${calendarId}`);
// Format start and end times as RFC3339 timestamps
let timeMin = new Date(startDate).toISOString();
let timeMax = endDate ? new Date(endDate).toISOString() : undefined;
// Fetch events
const response = await client.events.list({
calendarId,
timeMin,
timeMax,
maxResults: maxResults || 10,
singleEvents: true,
orderBy: 'startTime',
});
// Transform and return events
return response.data.items.map(event => ({
id: event.id,
summary: event.summary,
description: event.description,
location: event.location,
start: event.start.dateTime || event.start.date,
end: event.end.dateTime || event.end.date,
attendees: event.attendees ? event.attendees.map(a => a.email) : [],
link: event.htmlLink,
created: event.created,
updated: event.updated,
}));
} catch (error) {
logger.error(`Failed to fetch Google Calendar events: ${error.message}`);
throw new Error(`Failed to fetch calendar events: ${error.message}`);
}
}
/**
* Create a new event in Google Calendar
* @param {Object} client - Google Calendar client
* @param {Object} eventData - Event data
* @returns {Promise<Object>} Created event
*/
export async function createEvent(client, eventData) {
try {
const { calendarId, summary, startDateTime, endDateTime, description, location, attendees } = eventData;
logger.info(`Creating event "${summary}" in Google Calendar ${calendarId}`);
// Prepare event resource
const event = {
summary,
description,
location,
start: {
dateTime: new Date(startDateTime).toISOString(),
timeZone: config.user.timezone,
},
end: {
dateTime: endDateTime ? new Date(endDateTime).toISOString() : new Date(new Date(startDateTime).getTime() + 3600000).toISOString(),
timeZone: config.user.timezone,
},
};
// Add attendees if provided
if (attendees && attendees.length > 0) {
event.attendees = attendees.map(email => ({ email }));
}
// Insert event
const response = await client.events.insert({
calendarId,
resource: event,
sendUpdates: 'all',
});
// Return created event
return {
id: response.data.id,
summary: response.data.summary,
description: response.data.description,
location: response.data.location,
start: response.data.start.dateTime,
end: response.data.end.dateTime,
attendees: response.data.attendees ? response.data.attendees.map(a => a.email) : [],
link: response.data.htmlLink,
};
} catch (error) {
logger.error(`Failed to create Google Calendar event: ${error.message}`);
throw new Error(`Failed to create calendar event: ${error.message}`);
}
}
/**
* Update an existing event in Google Calendar
* @param {Object} client - Google Calendar client
* @param {Object} eventData - Event data
* @returns {Promise<Object>} Updated event
*/
export async function updateEvent(client, eventData) {
try {
const { calendarId, eventId, summary, startDateTime, endDateTime, description, location, attendees } = eventData;
logger.info(`Updating event ${eventId} in Google Calendar ${calendarId}`);
// First get the existing event
const existingEvent = await client.events.get({
calendarId,
eventId,
});
// Prepare update with only the provided fields
const updatedEvent = { ...existingEvent.data };
if (summary) updatedEvent.summary = summary;
if (description) updatedEvent.description = description;
if (location) updatedEvent.location = location;
if (startDateTime) {
updatedEvent.start = {
dateTime: new Date(startDateTime).toISOString(),
timeZone: config.user.timezone,
};
}
if (endDateTime) {
updatedEvent.end = {
dateTime: new Date(endDateTime).toISOString(),
timeZone: config.user.timezone,
};
}
if (attendees) {
updatedEvent.attendees = attendees.map(email => ({ email }));
}
// Update event
const response = await client.events.update({
calendarId,
eventId,
resource: updatedEvent,
sendUpdates: 'all',
});
// Return updated event
return {
id: response.data.id,
summary: response.data.summary,
description: response.data.description,
location: response.data.location,
start: response.data.start.dateTime,
end: response.data.end.dateTime,
attendees: response.data.attendees ? response.data.attendees.map(a => a.email) : [],
link: response.data.htmlLink,
};
} catch (error) {
logger.error(`Failed to update Google Calendar event: ${error.message}`);
throw new Error(`Failed to update calendar event: ${error.message}`);
}
}
/**
* Delete an event from Google Calendar
* @param {Object} client - Google Calendar client
* @param {Object} options - Delete options
* @returns {Promise<Object>} Result of deletion
*/
export async function deleteEvent(client, options) {
try {
const { calendarId, eventId } = options;
logger.info(`Deleting event ${eventId} from Google Calendar ${calendarId}`);
// Delete event
await client.events.delete({
calendarId,
eventId,
sendUpdates: 'all',
});
// Return success
return {
success: true,
message: `Event ${eventId} deleted successfully`,
};
} catch (error) {
logger.error(`Failed to delete Google Calendar event: ${error.message}`);
throw new Error(`Failed to delete calendar event: ${error.message}`);
}
}
/**
* List available calendars from Google Calendar
* @param {Object} client - Google Calendar client
* @returns {Promise<Array>} List of calendars
*/
export async function listCalendars(client) {
try {
logger.info('Listing Google Calendars');
// List calendars
const response = await client.calendarList.list();
// Transform and return calendars
return response.data.items.map(calendar => ({
id: calendar.id,
summary: calendar.summary,
description: calendar.description,
primary: calendar.primary || false,
accessRole: calendar.accessRole,
}));
} catch (error) {
logger.error(`Failed to list Google Calendars: ${error.message}`);
throw new Error(`Failed to list calendars: ${error.message}`);
}
}