Skip to main content
Glama

Google Calendar MCP

import { calendar_v3 } from 'googleapis'; export class RecurringEventHelpers { private calendar: calendar_v3.Calendar; constructor(calendar: calendar_v3.Calendar) { this.calendar = calendar; } /** * Get the calendar instance */ getCalendar(): calendar_v3.Calendar { return this.calendar; } /** * Detects if an event is recurring or single */ async detectEventType(eventId: string, calendarId: string): Promise<'recurring' | 'single'> { const response = await this.calendar.events.get({ calendarId, eventId }); const event = response.data; return event.recurrence && event.recurrence.length > 0 ? 'recurring' : 'single'; } /** * Formats an instance ID for single instance updates */ formatInstanceId(eventId: string, originalStartTime: string): string { // Convert to UTC first, then format to basic format: YYYYMMDDTHHMMSSZ const utcDate = new Date(originalStartTime); const basicTimeFormat = utcDate.toISOString().replace(/[-:]/g, '').split('.')[0] + 'Z'; return `${eventId}_${basicTimeFormat}`; } /** * Calculates the UNTIL date for future instance updates */ calculateUntilDate(futureStartDate: string): string { const futureDate = new Date(futureStartDate); const untilDate = new Date(futureDate.getTime() - 86400000); // -1 day return untilDate.toISOString().replace(/[-:]/g, '').split('.')[0] + 'Z'; } /** * Calculates end time based on original duration */ calculateEndTime(newStartTime: string, originalEvent: calendar_v3.Schema$Event): string { const newStart = new Date(newStartTime); const originalStart = new Date(originalEvent.start!.dateTime!); const originalEnd = new Date(originalEvent.end!.dateTime!); const duration = originalEnd.getTime() - originalStart.getTime(); return new Date(newStart.getTime() + duration).toISOString(); } /** * Updates recurrence rule with UNTIL clause */ updateRecurrenceWithUntil(recurrence: string[], untilDate: string): string[] { if (!recurrence || recurrence.length === 0) { throw new Error('No recurrence rule found'); } const updatedRecurrence: string[] = []; let foundRRule = false; for (const rule of recurrence) { if (rule.startsWith('RRULE:')) { foundRRule = true; const updatedRule = rule .replace(/;UNTIL=\d{8}T\d{6}Z/g, '') // Remove existing UNTIL .replace(/;COUNT=\d+/g, '') // Remove COUNT if present + `;UNTIL=${untilDate}`; updatedRecurrence.push(updatedRule); } else { // Preserve EXDATE, RDATE, and other rules as-is updatedRecurrence.push(rule); } } if (!foundRRule) { throw new Error('No RRULE found in recurrence rules'); } return updatedRecurrence; } /** * Cleans event fields for new event creation */ cleanEventForDuplication(event: calendar_v3.Schema$Event): calendar_v3.Schema$Event { const cleanedEvent = { ...event }; // Remove fields that shouldn't be duplicated delete cleanedEvent.id; delete cleanedEvent.etag; delete cleanedEvent.iCalUID; delete cleanedEvent.created; delete cleanedEvent.updated; delete cleanedEvent.htmlLink; delete cleanedEvent.hangoutLink; return cleanedEvent; } /** * Builds request body for event updates */ buildUpdateRequestBody(args: any, defaultTimeZone?: string): calendar_v3.Schema$Event { const requestBody: calendar_v3.Schema$Event = {}; if (args.summary !== undefined && args.summary !== null) requestBody.summary = args.summary; if (args.description !== undefined && args.description !== null) requestBody.description = args.description; if (args.location !== undefined && args.location !== null) requestBody.location = args.location; if (args.colorId !== undefined && args.colorId !== null) requestBody.colorId = args.colorId; if (args.attendees !== undefined && args.attendees !== null) requestBody.attendees = args.attendees; if (args.reminders !== undefined && args.reminders !== null) requestBody.reminders = args.reminders; if (args.recurrence !== undefined && args.recurrence !== null) requestBody.recurrence = args.recurrence; // Handle time changes let timeChanged = false; const effectiveTimeZone = args.timeZone || defaultTimeZone; if (args.start !== undefined && args.start !== null) { requestBody.start = { dateTime: args.start, timeZone: effectiveTimeZone }; timeChanged = true; } if (args.end !== undefined && args.end !== null) { requestBody.end = { dateTime: args.end, timeZone: effectiveTimeZone }; timeChanged = true; } // Only add timezone objects if there were actual time changes, OR if neither start/end provided but timezone is given if (timeChanged || (!args.start && !args.end && effectiveTimeZone)) { if (!requestBody.start) requestBody.start = {}; if (!requestBody.end) requestBody.end = {}; if (!requestBody.start.timeZone) requestBody.start.timeZone = effectiveTimeZone; if (!requestBody.end.timeZone) requestBody.end.timeZone = effectiveTimeZone; } return requestBody; } } /** * Custom error class for recurring event errors */ export class RecurringEventError extends Error { public code: string; constructor(message: string, code: string) { super(message); this.name = 'RecurringEventError'; this.code = code; } } export const RECURRING_EVENT_ERRORS = { INVALID_SCOPE: 'INVALID_MODIFICATION_SCOPE', MISSING_ORIGINAL_TIME: 'MISSING_ORIGINAL_START_TIME', MISSING_FUTURE_DATE: 'MISSING_FUTURE_START_DATE', PAST_FUTURE_DATE: 'FUTURE_DATE_IN_PAST', NON_RECURRING_SCOPE: 'SCOPE_NOT_APPLICABLE_TO_SINGLE_EVENT' };

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/nspady/google-calendar-mcp'

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