Skip to main content
Glama

MCP Google Workspace Server

by j3k0
calendar.ts12.6 kB
import { Tool, TextContent, ImageContent, EmbeddedResource } from '@modelcontextprotocol/sdk/types.js'; import { GAuthService } from '../services/gauth.js'; import { google } from 'googleapis'; import { USER_ID_ARG } from '../types/tool-handler.js'; const CALENDAR_ID_ARG = 'calendar_id'; export class CalendarTools { private calendar: ReturnType<typeof google.calendar>; constructor(private gauth: GAuthService) { this.calendar = google.calendar({ version: 'v3', auth: this.gauth.getClient() }); } getTools(): Tool[] { return [ { name: 'calendar_list_accounts', description: 'Lists all configured Google accounts that can be used with the calendar tools. This tool does not require a user_id as it lists available accounts before selection.', inputSchema: { type: 'object', properties: {}, additionalProperties: false, required: [] } }, { name: 'calendar_list', description: `Lists all calendars accessible by the user. Call it before any other tool whenever the user specifies a particular agenda (Family, Holidays, etc.). Returns detailed calendar metadata including access roles and timezone information.`, inputSchema: { type: 'object', properties: { [USER_ID_ARG]: { type: 'string', description: 'Email address of the user' } }, required: [USER_ID_ARG] } }, { name: 'calendar_get_events', description: 'Retrieves calendar events from the user\'s Google Calendar within a specified time range.', inputSchema: { type: 'object', properties: { [USER_ID_ARG]: { type: 'string', description: 'Email address of the user' }, [CALENDAR_ID_ARG]: { type: 'string', description: 'Calendar ID to fetch events from. Use "primary" for the primary calendar.', default: 'primary' }, time_min: { type: 'string', description: 'Start time in RFC3339 format (e.g. 2024-12-01T00:00:00Z). Defaults to current time if not specified.' }, time_max: { type: 'string', description: 'End time in RFC3339 format (e.g. 2024-12-31T23:59:59Z). Optional.' }, max_results: { type: 'integer', description: 'Maximum number of events to return (1-2500)', minimum: 1, maximum: 2500, default: 250 }, show_deleted: { type: 'boolean', description: 'Whether to include deleted events', default: false }, timezone: { type: 'string', description: 'Timezone for the events (e.g. \'America/New_York\'). Defaults to UTC.', default: 'UTC' } }, required: [USER_ID_ARG] } }, { name: 'calendar_create_event', description: 'Creates a new event in the specified Google Calendar.', inputSchema: { type: 'object', properties: { [USER_ID_ARG]: { type: 'string', description: 'Email address of the user' }, [CALENDAR_ID_ARG]: { type: 'string', description: 'Calendar ID to create the event in. Use "primary" for the primary calendar.', default: 'primary' }, summary: { type: 'string', description: 'Title of the event' }, start_time: { type: 'string', description: 'Start time in RFC3339 format (e.g. 2024-12-01T10:00:00Z)' }, end_time: { type: 'string', description: 'End time in RFC3339 format (e.g. 2024-12-01T11:00:00Z)' }, location: { type: 'string', description: 'Location of the event (optional)' }, description: { type: 'string', description: 'Description or notes for the event (optional)' }, attendees: { type: 'array', items: { type: 'string' }, description: 'List of attendee email addresses (optional)' }, send_notifications: { type: 'boolean', description: 'Whether to send notifications to attendees', default: true }, timezone: { type: 'string', description: 'Timezone for the event (e.g. \'America/New_York\'). Defaults to UTC.', default: 'UTC' } }, required: [USER_ID_ARG, 'summary', 'start_time', 'end_time'] } }, { name: 'calendar_delete_event', description: 'Deletes an event from the specified Google Calendar.', inputSchema: { type: 'object', properties: { [USER_ID_ARG]: { type: 'string', description: 'Email address of the user' }, [CALENDAR_ID_ARG]: { type: 'string', description: 'Calendar ID containing the event. Use "primary" for the primary calendar.', default: 'primary' }, event_id: { type: 'string', description: 'The ID of the calendar event to delete' }, send_notifications: { type: 'boolean', description: 'Whether to send cancellation notifications to attendees', default: true } }, required: [USER_ID_ARG, 'event_id'] } } ]; } async handleTool(name: string, args: Record<string, any>): Promise<Array<TextContent | ImageContent | EmbeddedResource>> { switch (name) { case 'calendar_list_accounts': return this.listAccounts(); case 'calendar_list': return this.listCalendars(args); case 'calendar_get_events': return this.getCalendarEvents(args); case 'calendar_create_event': return this.createCalendarEvent(args); case 'calendar_delete_event': return this.deleteCalendarEvent(args); default: throw new Error(`Unknown tool: ${name}`); } } private async listAccounts(): Promise<Array<TextContent>> { try { const accounts = await this.gauth.getAccountInfo(); const accountList = accounts.map(account => ({ email: account.email, accountType: account.accountType, extraInfo: account.extraInfo, description: account.toDescription() })); if (accountList.length === 0) { return [{ type: 'text', text: JSON.stringify({ message: 'No accounts configured. Please check your .accounts.json file.', accounts: [] }, null, 2) }]; } return [{ type: 'text', text: JSON.stringify({ message: `Found ${accountList.length} configured account(s)`, accounts: accountList }, null, 2) }]; } catch (error) { console.error('Error listing accounts:', error); return [{ type: 'text', text: JSON.stringify({ error: `Failed to list accounts: ${(error as Error).message}`, accounts: [] }, null, 2) }]; } } private async listCalendars(args: Record<string, any>): Promise<Array<TextContent>> { const userId = args[USER_ID_ARG]; if (!userId) { throw new Error(`Missing required argument: ${USER_ID_ARG}`); } try { console.error('Attempting to list calendars...'); const response = await this.calendar.calendarList.list(); const calendars = response.data.items?.map(calendar => ({ id: calendar.id, summary: calendar.summary, primary: calendar.primary || false, timeZone: calendar.timeZone, etag: calendar.etag, accessRole: calendar.accessRole })) || []; console.error(`Successfully retrieved ${calendars.length} calendars`); return [{ type: 'text', text: JSON.stringify(calendars, null, 2) }]; } catch (error) { console.error('Error listing calendars:', error); throw error; } } private async getCalendarEvents(args: Record<string, any>): Promise<Array<TextContent>> { const userId = args[USER_ID_ARG]; if (!userId) { throw new Error(`Missing required argument: ${USER_ID_ARG}`); } try { const timeMin = args.time_min || new Date().toISOString(); const maxResults = Math.min(Math.max(1, args.max_results || 250), 2500); const calendarId = args[CALENDAR_ID_ARG] || 'primary'; const timezone = args.timezone || 'UTC'; const params = { calendarId, timeMin, maxResults, singleEvents: true, orderBy: 'startTime' as const, showDeleted: args.show_deleted || false }; if (args.time_max) { Object.assign(params, { timeMax: args.time_max }); } const response = await this.calendar.events.list(params); const events = response.data.items?.map(event => ({ id: event.id, summary: event.summary, description: event.description, start: event.start, end: event.end, status: event.status, creator: event.creator, organizer: event.organizer, attendees: event.attendees, location: event.location, hangoutLink: event.hangoutLink, conferenceData: event.conferenceData, recurringEventId: event.recurringEventId })) || []; return [{ type: 'text', text: JSON.stringify(events, null, 2) }]; } catch (error) { console.error('Error getting calendar events:', error); throw error; } } private async createCalendarEvent(args: Record<string, any>): Promise<Array<TextContent>> { const userId = args[USER_ID_ARG]; const required = ['summary', 'start_time', 'end_time']; if (!userId) { throw new Error(`Missing required argument: ${USER_ID_ARG}`); } if (!required.every(key => key in args)) { throw new Error(`Missing required arguments: ${required.filter(key => !(key in args)).join(', ')}`); } try { const timezone = args.timezone || 'UTC'; const event = { summary: args.summary, location: args.location, description: args.description, start: { dateTime: args.start_time, timeZone: timezone }, end: { dateTime: args.end_time, timeZone: timezone }, attendees: args.attendees?.map((email: string) => ({ email })) }; const response = await this.calendar.events.insert({ calendarId: args[CALENDAR_ID_ARG] || 'primary', requestBody: event, sendUpdates: args.send_notifications ? 'all' : 'none' }); return [{ type: 'text', text: JSON.stringify(response.data, null, 2) }]; } catch (error) { console.error('Error creating calendar event:', error); throw error; } } private async deleteCalendarEvent(args: Record<string, any>): Promise<Array<TextContent>> { const userId = args[USER_ID_ARG]; const eventId = args.event_id; if (!userId) { throw new Error(`Missing required argument: ${USER_ID_ARG}`); } if (!eventId) { throw new Error('Missing required argument: event_id'); } try { await this.calendar.events.delete({ calendarId: args[CALENDAR_ID_ARG] || 'primary', eventId: eventId, sendUpdates: args.send_notifications ? 'all' : 'none' }); return [{ type: 'text', text: JSON.stringify({ success: true, message: 'Event successfully deleted' }, null, 2) }]; } catch (error) { console.error('Error deleting calendar event:', error); return [{ type: 'text', text: JSON.stringify({ success: false, message: 'Failed to delete event' }, null, 2) }]; } } }

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/j3k0/mcp-google-workspace'

If you have feedback or need assistance with the MCP directory API, please join our Discord server