Skip to main content
Glama

Google Calendar MCP

EventSimilarityChecker.ts5.75 kB
import { calendar_v3 } from "googleapis"; export class EventSimilarityChecker { private readonly DEFAULT_SIMILARITY_THRESHOLD = 0.7; /** * Check if two events are potentially duplicates based on similarity * Uses simplified rules-based approach instead of complex weighted calculations */ checkSimilarity(event1: calendar_v3.Schema$Event, event2: calendar_v3.Schema$Event): number { // Check if one is all-day and the other is timed const event1IsAllDay = this.isAllDayEvent(event1); const event2IsAllDay = this.isAllDayEvent(event2); if (event1IsAllDay !== event2IsAllDay) { // Different event types - not duplicates return 0.2; // Low similarity } const titleMatch = this.titlesMatch(event1.summary, event2.summary); const timeOverlap = this.eventsOverlap(event1, event2); const sameDay = this.eventsOnSameDay(event1, event2); // Simple rules-based scoring if (titleMatch.exact && timeOverlap) { return 0.95; // Almost certainly a duplicate } if (titleMatch.similar && timeOverlap) { return 0.7; // Potential duplicate } if (titleMatch.exact && sameDay) { return 0.6; // Same title on same day but different times } if (titleMatch.exact && !sameDay) { return 0.4; // Same title but different day - likely recurring event } if (titleMatch.similar) { return 0.3; // Similar titles only } return 0.1; // No significant similarity } /** * Check if an event is an all-day event */ private isAllDayEvent(event: calendar_v3.Schema$Event): boolean { return !event.start?.dateTime && !!event.start?.date; } /** * Check if two titles match (exact or similar) * Simplified string matching without Levenshtein distance */ private titlesMatch(title1?: string | null, title2?: string | null): { exact: boolean; similar: boolean } { if (!title1 || !title2) { return { exact: false, similar: false }; } const t1 = title1.toLowerCase().trim(); const t2 = title2.toLowerCase().trim(); // Exact match if (t1 === t2) { return { exact: true, similar: true }; } // Check if one contains the other (for variations like "Meeting" vs "Team Meeting") if (t1.includes(t2) || t2.includes(t1)) { return { exact: false, similar: true }; } // Check for common significant words (more than 3 characters) const words1 = t1.split(/\s+/).filter(w => w.length > 3); const words2 = t2.split(/\s+/).filter(w => w.length > 3); if (words1.length > 0 && words2.length > 0) { const commonWords = words1.filter(w => words2.includes(w)); const similarity = commonWords.length / Math.min(words1.length, words2.length); return { exact: false, similar: similarity >= 0.5 }; } return { exact: false, similar: false }; } /** * Check if two events are on the same day */ private eventsOnSameDay(event1: calendar_v3.Schema$Event, event2: calendar_v3.Schema$Event): boolean { const time1 = this.getEventTime(event1); const time2 = this.getEventTime(event2); if (!time1 || !time2) return false; // Compare dates only (ignore time) const date1 = new Date(time1.start); const date2 = new Date(time2.start); return date1.getFullYear() === date2.getFullYear() && date1.getMonth() === date2.getMonth() && date1.getDate() === date2.getDate(); } /** * Extract event time information * * Note: This method handles both: * - Events being created (may have timezone-naive datetimes with separate timeZone field) * - Events from Google Calendar (have timezone-aware datetimes) * * The MCP trusts Google Calendar to return only relevant events in the queried time range. * Any timezone conversions are handled by the Google Calendar API, not by this service. */ private getEventTime(event: calendar_v3.Schema$Event): { start: Date; end: Date } | null { const startTime = event.start?.dateTime || event.start?.date; const endTime = event.end?.dateTime || event.end?.date; if (!startTime || !endTime) return null; // Parse the datetime strings as-is // Google Calendar API ensures we only get events in the requested time range return { start: new Date(startTime), end: new Date(endTime) }; } /** * Check if two events overlap in time * Consolidated overlap logic used throughout the service */ eventsOverlap(event1: calendar_v3.Schema$Event, event2: calendar_v3.Schema$Event): boolean { const time1 = this.getEventTime(event1); const time2 = this.getEventTime(event2); if (!time1 || !time2) return false; return time1.start < time2.end && time2.start < time1.end; } /** * Calculate overlap duration in milliseconds * Used by ConflictAnalyzer for detailed overlap analysis */ calculateOverlapDuration(event1: calendar_v3.Schema$Event, event2: calendar_v3.Schema$Event): number { const time1 = this.getEventTime(event1); const time2 = this.getEventTime(event2); if (!time1 || !time2) return 0; const overlapStart = Math.max(time1.start.getTime(), time2.start.getTime()); const overlapEnd = Math.min(time1.end.getTime(), time2.end.getTime()); return Math.max(0, overlapEnd - overlapStart); } /** * Determine if events are likely duplicates */ isDuplicate(event1: calendar_v3.Schema$Event, event2: calendar_v3.Schema$Event, threshold?: number): boolean { const similarity = this.checkSimilarity(event1, event2); return similarity >= (threshold || this.DEFAULT_SIMILARITY_THRESHOLD); } }

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