Skip to main content
Glama

Google Calendar MCP

import { CallToolResult } from "@modelcontextprotocol/sdk/types.js"; import { OAuth2Client } from "google-auth-library"; import { UpdateEventInput } from "../../tools/registry.js"; import { BaseToolHandler } from "./BaseToolHandler.js"; import { calendar_v3 } from 'googleapis'; import { RecurringEventHelpers, RecurringEventError, RECURRING_EVENT_ERRORS } from './RecurringEventHelpers.js'; import { formatEventWithDetails } from "../utils.js"; export class UpdateEventHandler extends BaseToolHandler { async runTool(args: any, oauth2Client: OAuth2Client): Promise<CallToolResult> { const validArgs = args as UpdateEventInput; const event = await this.updateEventWithScope(oauth2Client, validArgs); const eventDetails = formatEventWithDetails(event, validArgs.calendarId); const text = `Event updated successfully!\n\n${eventDetails}`; return { content: [{ type: "text", text: text }] }; } private async updateEventWithScope( client: OAuth2Client, args: UpdateEventInput ): Promise<calendar_v3.Schema$Event> { try { const calendar = this.getCalendar(client); const helpers = new RecurringEventHelpers(calendar); // Get calendar's default timezone if not provided const defaultTimeZone = await this.getCalendarTimezone(client, args.calendarId); // Detect event type and validate scope usage const eventType = await helpers.detectEventType(args.eventId, args.calendarId); if (args.modificationScope && args.modificationScope !== 'all' && eventType !== 'recurring') { throw new RecurringEventError( 'Scope other than "all" only applies to recurring events', RECURRING_EVENT_ERRORS.NON_RECURRING_SCOPE ); } switch (args.modificationScope) { case 'thisEventOnly': return this.updateSingleInstance(helpers, args, defaultTimeZone); case 'all': case undefined: return this.updateAllInstances(helpers, args, defaultTimeZone); case 'thisAndFollowing': return this.updateFutureInstances(helpers, args, defaultTimeZone); default: throw new RecurringEventError( `Invalid modification scope: ${args.modificationScope}`, RECURRING_EVENT_ERRORS.INVALID_SCOPE ); } } catch (error) { if (error instanceof RecurringEventError) { throw error; } throw this.handleGoogleApiError(error); } } private async updateSingleInstance( helpers: RecurringEventHelpers, args: UpdateEventInput, defaultTimeZone: string ): Promise<calendar_v3.Schema$Event> { if (!args.originalStartTime) { throw new RecurringEventError( 'originalStartTime is required for single instance updates', RECURRING_EVENT_ERRORS.MISSING_ORIGINAL_TIME ); } const calendar = helpers.getCalendar(); const instanceId = helpers.formatInstanceId(args.eventId, args.originalStartTime); const response = await calendar.events.patch({ calendarId: args.calendarId, eventId: instanceId, requestBody: helpers.buildUpdateRequestBody(args, defaultTimeZone) }); if (!response.data) throw new Error('Failed to update event instance'); return response.data; } private async updateAllInstances( helpers: RecurringEventHelpers, args: UpdateEventInput, defaultTimeZone: string ): Promise<calendar_v3.Schema$Event> { const calendar = helpers.getCalendar(); const response = await calendar.events.patch({ calendarId: args.calendarId, eventId: args.eventId, requestBody: helpers.buildUpdateRequestBody(args, defaultTimeZone) }); if (!response.data) throw new Error('Failed to update event'); return response.data; } private async updateFutureInstances( helpers: RecurringEventHelpers, args: UpdateEventInput, defaultTimeZone: string ): Promise<calendar_v3.Schema$Event> { if (!args.futureStartDate) { throw new RecurringEventError( 'futureStartDate is required for future instance updates', RECURRING_EVENT_ERRORS.MISSING_FUTURE_DATE ); } const calendar = helpers.getCalendar(); const effectiveTimeZone = args.timeZone || defaultTimeZone; // 1. Get original event const originalResponse = await calendar.events.get({ calendarId: args.calendarId, eventId: args.eventId }); const originalEvent = originalResponse.data; if (!originalEvent.recurrence) { throw new Error('Event does not have recurrence rules'); } // 2. Calculate UNTIL date and update original event const untilDate = helpers.calculateUntilDate(args.futureStartDate); const updatedRecurrence = helpers.updateRecurrenceWithUntil(originalEvent.recurrence, untilDate); await calendar.events.patch({ calendarId: args.calendarId, eventId: args.eventId, requestBody: { recurrence: updatedRecurrence } }); // 3. Create new recurring event starting from future date const requestBody = helpers.buildUpdateRequestBody(args, defaultTimeZone); // Calculate end time if start time is changing let endTime = args.end; if (args.start || args.futureStartDate) { const newStartTime = args.start || args.futureStartDate; endTime = endTime || helpers.calculateEndTime(newStartTime, originalEvent); } const newEvent = { ...helpers.cleanEventForDuplication(originalEvent), ...requestBody, start: { dateTime: args.start || args.futureStartDate, timeZone: effectiveTimeZone }, end: { dateTime: endTime, timeZone: effectiveTimeZone } }; const response = await calendar.events.insert({ calendarId: args.calendarId, requestBody: newEvent }); if (!response.data) throw new Error('Failed to create new recurring event'); return response.data; } }

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