Skip to main content
Glama

Google Calendar MCP

CreateEventHandler.ts7.01 kB
import { CallToolResult } from "@modelcontextprotocol/sdk/types.js"; import { OAuth2Client } from "google-auth-library"; import { CreateEventInput } from "../../tools/registry.js"; import { BaseToolHandler } from "./BaseToolHandler.js"; import { calendar_v3 } from 'googleapis'; import { createTimeObject } from "../utils/datetime.js"; import { validateEventId } from "../../utils/event-id-validator.js"; import { ConflictDetectionService } from "../../services/conflict-detection/index.js"; import { CONFLICT_DETECTION_CONFIG } from "../../services/conflict-detection/config.js"; import { createStructuredResponse, convertConflictsToStructured, createWarningsArray } from "../../utils/response-builder.js"; import { CreateEventResponse, convertGoogleEventToStructured, DuplicateInfo } from "../../types/structured-responses.js"; export class CreateEventHandler extends BaseToolHandler { private conflictDetectionService: ConflictDetectionService; constructor() { super(); this.conflictDetectionService = new ConflictDetectionService(); } async runTool(args: any, oauth2Client: OAuth2Client): Promise<CallToolResult> { const validArgs = args as CreateEventInput; // Create the event object for conflict checking const timezone = args.timeZone || await this.getCalendarTimezone(oauth2Client, validArgs.calendarId); const eventToCheck: calendar_v3.Schema$Event = { summary: args.summary, description: args.description, start: createTimeObject(args.start, timezone), end: createTimeObject(args.end, timezone), attendees: args.attendees, location: args.location, }; // Check for conflicts and duplicates const conflicts = await this.conflictDetectionService.checkConflicts( oauth2Client, eventToCheck, validArgs.calendarId, { checkDuplicates: true, checkConflicts: true, calendarsToCheck: validArgs.calendarsToCheck || [validArgs.calendarId], duplicateSimilarityThreshold: validArgs.duplicateSimilarityThreshold || CONFLICT_DETECTION_CONFIG.DEFAULT_DUPLICATE_THRESHOLD } ); // Block creation if exact or near-exact duplicate found const exactDuplicate = conflicts.duplicates.find( dup => dup.event.similarity >= CONFLICT_DETECTION_CONFIG.DUPLICATE_THRESHOLDS.BLOCKING ); if (exactDuplicate && validArgs.allowDuplicates !== true) { // Create a duplicate error response const duplicateInfo: DuplicateInfo = { event: { id: exactDuplicate.event.id || '', title: exactDuplicate.event.title, start: exactDuplicate.event.start || '', end: exactDuplicate.event.end || '', url: exactDuplicate.event.url, similarity: exactDuplicate.event.similarity }, calendarId: exactDuplicate.calendarId || '', suggestion: exactDuplicate.suggestion }; // Throw an error that will be handled by MCP SDK throw new Error( `Duplicate event detected (${Math.round(exactDuplicate.event.similarity * 100)}% similar). ` + `Event "${exactDuplicate.event.title}" already exists. ` + `To create anyway, set allowDuplicates to true.` ); } // Create the event const event = await this.createEvent(oauth2Client, validArgs); // Generate structured response with conflict warnings const structuredConflicts = convertConflictsToStructured(conflicts); const response: CreateEventResponse = { event: convertGoogleEventToStructured(event, validArgs.calendarId), conflicts: structuredConflicts.conflicts, duplicates: structuredConflicts.duplicates, warnings: createWarningsArray(conflicts) }; return createStructuredResponse(response); } private async createEvent( client: OAuth2Client, args: CreateEventInput ): Promise<calendar_v3.Schema$Event> { try { const calendar = this.getCalendar(client); // Validate custom event ID if provided if (args.eventId) { validateEventId(args.eventId); } // Use provided timezone or calendar's default timezone const timezone = args.timeZone || await this.getCalendarTimezone(client, args.calendarId); const requestBody: calendar_v3.Schema$Event = { summary: args.summary, description: args.description, start: createTimeObject(args.start, timezone), end: createTimeObject(args.end, timezone), attendees: args.attendees, location: args.location, colorId: args.colorId, reminders: args.reminders, recurrence: args.recurrence, transparency: args.transparency, visibility: args.visibility, guestsCanInviteOthers: args.guestsCanInviteOthers, guestsCanModify: args.guestsCanModify, guestsCanSeeOtherGuests: args.guestsCanSeeOtherGuests, anyoneCanAddSelf: args.anyoneCanAddSelf, conferenceData: args.conferenceData, extendedProperties: args.extendedProperties, attachments: args.attachments, source: args.source, ...(args.eventId && { id: args.eventId }) // Include custom ID if provided }; // Determine if we need to enable conference data or attachments const conferenceDataVersion = args.conferenceData ? 1 : undefined; const supportsAttachments = args.attachments ? true : undefined; const response = await calendar.events.insert({ calendarId: args.calendarId, requestBody: requestBody, sendUpdates: args.sendUpdates, ...(conferenceDataVersion && { conferenceDataVersion }), ...(supportsAttachments && { supportsAttachments }) }); if (!response.data) throw new Error('Failed to create event, no data returned'); return response.data; } catch (error: any) { // Handle ID conflict errors specifically if (error?.code === 409 || error?.response?.status === 409) { throw new Error(`Event ID '${args.eventId}' already exists. Please use a different ID.`); } throw this.handleGoogleApiError(error); } } }

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