Skip to main content
Glama
events.ts15.6 kB
import { z } from "zod" import type { calendar_v3 } from "googleapis" import type { Auth } from "googleapis" import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js" function checkAuthentication(oauth2Client: Auth.OAuth2Client) { if (!oauth2Client.credentials.access_token && !oauth2Client.credentials.refresh_token) { throw new Error("Authentication required. Please authenticate first using `generate_oauth_url` and `exchange_auth_code` tools.") } } export function registerEventTools( server: McpServer, calendar: calendar_v3.Calendar, oauth2Client: Auth.OAuth2Client, ) { // Tool: List Events server.tool( "list_events", "List events from a calendar within a specified time range", { calendar_id: z .string() .default("primary") .describe("Calendar ID (use 'primary' for user's primary calendar)"), time_min: z .string() .optional() .describe("Start time (ISO 8601 format, e.g., '2024-01-01T00:00:00Z')"), time_max: z .string() .optional() .describe("End time (ISO 8601 format, e.g., '2024-01-31T23:59:59Z')"), max_results: z .number() .optional() .default(10) .describe("Maximum number of events to return (default: 10)"), single_events: z .boolean() .optional() .default(true) .describe("Whether to expand recurring events into individual occurrences"), order_by: z .enum(["startTime", "updated"]) .optional() .default("startTime") .describe("How to order the events"), q: z .string() .optional() .describe("Free text search terms"), }, async ({ calendar_id, time_min, time_max, max_results, single_events, order_by, q }) => { try { // Check authentication only when tool is invoked checkAuthentication(oauth2Client) const response = await calendar.events.list({ calendarId: calendar_id, timeMin: time_min, timeMax: time_max, maxResults: max_results, singleEvents: single_events, orderBy: order_by, q: q, }) const events = response.data.items || [] if (events.length === 0) { return { content: [ { type: "text", text: `📅 **No events found** in the specified time range${q ? ` matching "${q}"` : ""}.`, }, ], } } let markdown = `# 📅 Calendar Events (${events.length} found)\n\n` if (q) { markdown += `**Search:** "${q}"\n` } if (time_min || time_max) { markdown += `**Time Range:** ${time_min || 'Start'} → ${time_max || 'End'}\n` } markdown += `**Calendar:** ${calendar_id}\n\n---\n\n` events.forEach((event, index) => { const start = event.start?.dateTime || event.start?.date const end = event.end?.dateTime || event.end?.date const isAllDay = !event.start?.dateTime markdown += `## ${index + 1}. ${event.summary || "Untitled Event"}${isAllDay ? " 📅" : " ⏰"}\n\n` markdown += `- **Event ID:** \`${event.id}\`\n` markdown += `- **Status:** ${event.status || "Unknown"}\n` if (start) { if (isAllDay) { markdown += `- **Date:** ${new Date(start).toLocaleDateString()}\n` } else { markdown += `- **Start:** ${new Date(start).toLocaleString()}\n` if (end) { markdown += `- **End:** ${new Date(end).toLocaleString()}\n` } } } if (event.location) { markdown += `- **Location:** ${event.location}\n` } if (event.description) { const truncatedDesc = event.description.length > 100 ? event.description.substring(0, 100) + "..." : event.description markdown += `- **Description:** ${truncatedDesc}\n` } if (event.attendees && event.attendees.length > 0) { markdown += `- **Attendees:** ${event.attendees.length} person${event.attendees.length > 1 ? 's' : ''}\n` } if (event.recurringEventId) { markdown += `- **Recurring Event ID:** \`${event.recurringEventId}\`\n` } if (event.htmlLink) { markdown += `- **[View in Google Calendar](${event.htmlLink})**\n` } markdown += `\n` }) markdown += `---\n\n**💡 Tip:** Use event IDs with other event tools to modify or delete specific events.` return { content: [{ type: "text", text: markdown }], } } catch (e: any) { return { content: [{ type: "text", text: `Error listing events: ${e.message}` }], } } }, ) // Tool: Get Event Details server.tool( "get_event", "Get detailed information about a specific event", { calendar_id: z .string() .default("primary") .describe("Calendar ID containing the event"), event_id: z .string() .describe("Event ID to retrieve"), }, async ({ calendar_id, event_id }) => { try { // Check authentication only when tool is invoked checkAuthentication(oauth2Client) const response = await calendar.events.get({ calendarId: calendar_id, eventId: event_id, }) const event = response.data const start = event.start?.dateTime || event.start?.date const end = event.end?.dateTime || event.end?.date const isAllDay = !event.start?.dateTime let markdown = `# 📅 Event Details ## ${event.summary || "Untitled Event"} ### Basic Information - **Event ID:** \`${event.id}\` - **Status:** ${event.status || "Unknown"} - **Created:** ${event.created ? new Date(event.created).toLocaleString() : "Unknown"} - **Updated:** ${event.updated ? new Date(event.updated).toLocaleString() : "Unknown"} ### Time & Location ` if (start) { if (isAllDay) { markdown += `- **All-day event on:** ${new Date(start).toLocaleDateString()}\n` } else { markdown += `- **Start:** ${new Date(start).toLocaleString()}\n` if (end) { markdown += `- **End:** ${new Date(end).toLocaleString()}\n` } if (event.start?.timeZone) { markdown += `- **Timezone:** ${event.start.timeZone}\n` } } } if (event.location) { markdown += `- **Location:** ${event.location}\n` } if (event.description) { markdown += `\n### Description\n${event.description}\n` } if (event.attendees && event.attendees.length > 0) { markdown += `\n### Attendees (${event.attendees.length})\n` event.attendees.forEach(attendee => { const status = attendee.responseStatus const statusEmoji = status === "accepted" ? "✅" : status === "declined" ? "❌" : status === "tentative" ? "❓" : "⏳" markdown += `- ${statusEmoji} ${attendee.displayName || attendee.email || "Unknown"}` if (attendee.organizer) markdown += " (Organizer)" if (attendee.optional) markdown += " (Optional)" markdown += "\n" }) } if (event.reminders?.useDefault === false && event.reminders?.overrides) { markdown += `\n### Reminders\n` event.reminders.overrides.forEach(reminder => { markdown += `- ${reminder.method}: ${reminder.minutes} minutes before\n` }) } else if (event.reminders?.useDefault) { markdown += `\n### Reminders\nUsing default calendar reminders\n` } if (event.recurrence) { markdown += `\n### Recurrence\n` event.recurrence.forEach(rule => { markdown += `- ${rule}\n` }) } if (event.conferenceData?.entryPoints) { markdown += `\n### Conference Details\n` event.conferenceData.entryPoints.forEach(entry => { if (entry.entryPointType === "video") { markdown += `- **Video Call:** [Join Meeting](${entry.uri})\n` } else if (entry.entryPointType === "phone") { markdown += `- **Phone:** ${entry.label || entry.uri}\n` } }) } if (event.htmlLink) { markdown += `\n### Links\n- **[View in Google Calendar](${event.htmlLink})**\n` } markdown += `\n---\n\n**ETag:** \`${event.etag}\`\n**Kind:** ${event.kind}` return { content: [{ type: "text", text: markdown }], } } catch (e: any) { return { content: [{ type: "text", text: `Error getting event details: ${e.message}` }], } } }, ) // Tool: Create Event server.tool( "create_event", "Create a new calendar event", { calendar_id: z .string() .default("primary") .describe("Calendar ID where the event will be created"), summary: z .string() .describe("Event title/summary"), description: z .string() .optional() .describe("Event description"), start_time: z .string() .describe("Start time (ISO 8601 format, e.g., '2024-01-15T10:00:00Z' or '2024-01-15' for all-day)"), end_time: z .string() .describe("End time (ISO 8601 format, e.g., '2024-01-15T11:00:00Z' or '2024-01-15' for all-day)"), location: z .string() .optional() .describe("Event location"), attendees: z .array(z.string()) .optional() .describe("List of attendee email addresses"), timezone: z .string() .optional() .describe("Timezone for the event (e.g., 'America/New_York')"), all_day: z .boolean() .optional() .default(false) .describe("Whether this is an all-day event"), }, async ({ calendar_id, summary, description, start_time, end_time, location, attendees, timezone, all_day }) => { try { // Check authentication only when tool is invoked checkAuthentication(oauth2Client) const eventBody: calendar_v3.Schema$Event = { summary, description, location, } // Handle time formatting if (all_day) { eventBody.start = { date: start_time.split('T')[0] } eventBody.end = { date: end_time.split('T')[0] } } else { eventBody.start = { dateTime: start_time, timeZone: timezone } eventBody.end = { dateTime: end_time, timeZone: timezone } } // Handle attendees if (attendees && attendees.length > 0) { eventBody.attendees = attendees.map(email => ({ email })) } const response = await calendar.events.insert({ calendarId: calendar_id, requestBody: eventBody, }) const newEvent = response.data const markdown = `# ✅ Event Created Successfully ## ${newEvent.summary} ### Event Details - **Event ID:** \`${newEvent.id}\` - **Status:** ${newEvent.status} - **Created:** ${newEvent.created ? new Date(newEvent.created).toLocaleString() : "Just now"} ### Time & Location ${all_day ? `- **All-day event:** ${new Date(start_time).toLocaleDateString()}` : `- **Start:** ${new Date(start_time).toLocaleString()} - **End:** ${new Date(end_time).toLocaleString()}` } ${location ? `- **Location:** ${location}` : ""} ${attendees && attendees.length > 0 ? `### Attendees ${attendees.map(email => `- ${email}`).join('\n')}` : ""} ### Links - **[View in Google Calendar](${newEvent.htmlLink})** --- **💡 Tip:** Save the event ID \`${newEvent.id}\` to modify or delete this event later.` return { content: [{ type: "text", text: markdown }], } } catch (e: any) { return { content: [{ type: "text", text: `Error creating event: ${e.message}` }], } } }, ) // Tool: Update Event server.tool( "update_event", "Update an existing calendar event", { calendar_id: z .string() .default("primary") .describe("Calendar ID containing the event"), event_id: z .string() .describe("Event ID to update"), summary: z .string() .optional() .describe("New event title/summary"), description: z .string() .optional() .describe("New event description"), start_time: z .string() .optional() .describe("New start time (ISO 8601 format)"), end_time: z .string() .optional() .describe("New end time (ISO 8601 format)"), location: z .string() .optional() .describe("New event location"), status: z .enum(["confirmed", "cancelled", "tentative"]) .optional() .describe("Event status"), }, async ({ calendar_id, event_id, summary, description, start_time, end_time, location, status }) => { try { // Check authentication only when tool is invoked checkAuthentication(oauth2Client) // First get the existing event const existingEvent = await calendar.events.get({ calendarId: calendar_id, eventId: event_id, }) const updateBody: calendar_v3.Schema$Event = { ...existingEvent.data } // Update only provided fields if (summary !== undefined) updateBody.summary = summary if (description !== undefined) updateBody.description = description if (location !== undefined) updateBody.location = location if (status !== undefined) updateBody.status = status if (start_time !== undefined) { updateBody.start = { dateTime: start_time } } if (end_time !== undefined) { updateBody.end = { dateTime: end_time } } const response = await calendar.events.update({ calendarId: calendar_id, eventId: event_id, requestBody: updateBody, }) const updatedEvent = response.data const markdown = `# ✅ Event Updated Successfully ## ${updatedEvent.summary} ### Updated Event Details - **Event ID:** \`${updatedEvent.id}\` - **Status:** ${updatedEvent.status} - **Last Updated:** ${updatedEvent.updated ? new Date(updatedEvent.updated).toLocaleString() : "Just now"} ### Current Information - **Start:** ${updatedEvent.start?.dateTime ? new Date(updatedEvent.start.dateTime).toLocaleString() : updatedEvent.start?.date} - **End:** ${updatedEvent.end?.dateTime ? new Date(updatedEvent.end.dateTime).toLocaleString() : updatedEvent.end?.date} ${updatedEvent.location ? `- **Location:** ${updatedEvent.location}` : ""} ${updatedEvent.description ? `- **Description:** ${updatedEvent.description}` : ""} ### Links - **[View in Google Calendar](${updatedEvent.htmlLink})** --- **💡 Changes have been saved and will appear in Google Calendar immediately.` return { content: [{ type: "text", text: markdown }], } } catch (e: any) { return { content: [{ type: "text", text: `Error updating event: ${e.message}` }], } } }, ) // Tool: Delete Event server.tool( "delete_event", "Delete a calendar event", { calendar_id: z .string() .default("primary") .describe("Calendar ID containing the event"), event_id: z .string() .describe("Event ID to delete"), }, async ({ calendar_id, event_id }) => { try { // Check authentication only when tool is invoked checkAuthentication(oauth2Client) // Get event details before deletion for confirmation const eventDetails = await calendar.events.get({ calendarId: calendar_id, eventId: event_id, }) await calendar.events.delete({ calendarId: calendar_id, eventId: event_id, }) const deletedEvent = eventDetails.data const markdown = `# ✅ Event Deleted Successfully ## ${deletedEvent.summary || "Untitled Event"} ### Deleted Event Information - **Event ID:** \`${event_id}\` - **Calendar:** ${calendar_id} - **Date/Time:** ${deletedEvent.start?.dateTime ? new Date(deletedEvent.start.dateTime).toLocaleString() : deletedEvent.start?.date || "Unknown" } ### ⚠️ Important Notes - This event has been permanently deleted - All attendees will be notified of the cancellation - This action cannot be undone --- **💡 The event will no longer appear in Google Calendar for you or any attendees.` return { content: [{ type: "text", text: markdown }], } } catch (e: any) { return { content: [{ type: "text", text: `Error deleting event: ${e.message}` }], } } }, ) }

Latest Blog Posts

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

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