Skip to main content
Glama

Google Calendar MCP Server

by peadams21
index.ts15.6 kB
#!/usr/bin/env node /** * Google Calendar MCP Server * * This MCP server provides tools for integrating with Google Calendar API. * It handles authentication, event management, and calendar operations * through the Model Context Protocol. */ import { Server } from "@modelcontextprotocol/sdk/server/index.js"; import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import { CallToolRequestSchema, ListToolsRequestSchema, } from "@modelcontextprotocol/sdk/types.js"; import type { Tool } from "@modelcontextprotocol/sdk/types.js"; import { z } from "zod"; import { GoogleAuth } from "google-auth-library"; import { google } from "googleapis"; import * as dotenv from "dotenv"; // Load environment variables dotenv.config(); // Google Calendar API setup const auth = new GoogleAuth({ keyFile: process.env.GOOGLE_SERVICE_ACCOUNT_KEY_FILE, scopes: [ "https://www.googleapis.com/auth/calendar", "https://www.googleapis.com/auth/calendar.events", ], }); const calendar = google.calendar({ version: "v3", auth }); // Tool schemas const ListEventsArgsSchema = z.object({ calendarId: z.string().optional().default("primary"), timeMin: z.string().optional(), timeMax: z.string().optional(), maxResults: z.number().optional().default(250), singleEvents: z.boolean().optional().default(true), orderBy: z.enum(["startTime", "updated"]).optional().default("startTime"), }); const CreateEventArgsSchema = z.object({ calendarId: z.string().optional().default("primary"), summary: z.string(), description: z.string().optional(), start: z.object({ dateTime: z.string(), timeZone: z.string().optional(), }), end: z.object({ dateTime: z.string(), timeZone: z.string().optional(), }), location: z.string().optional(), attendees: z.array(z.object({ email: z.string(), displayName: z.string().optional(), })).optional(), recurrence: z.array(z.string()).optional(), }); const UpdateEventArgsSchema = z.object({ calendarId: z.string().optional().default("primary"), eventId: z.string(), summary: z.string().optional(), description: z.string().optional(), start: z.object({ dateTime: z.string(), timeZone: z.string().optional(), }).optional(), end: z.object({ dateTime: z.string(), timeZone: z.string().optional(), }).optional(), location: z.string().optional(), attendees: z.array(z.object({ email: z.string(), displayName: z.string().optional(), })).optional(), }); const DeleteEventArgsSchema = z.object({ calendarId: z.string().optional().default("primary"), eventId: z.string(), }); const ListCalendarsArgsSchema = z.object({ minAccessRole: z.enum(["freeBusyReader", "owner", "reader", "writer"]).optional(), showDeleted: z.boolean().optional().default(false), showHidden: z.boolean().optional().default(false), }); // MCP Server setup const server = new Server( { name: "google-calendar-mcp-server", version: "1.0.0", }, { capabilities: { tools: {}, }, } ); // Tool definitions const tools: Tool[] = [ { name: "list_events", description: "List events from a Google Calendar", inputSchema: { type: "object", properties: { calendarId: { type: "string", description: "Calendar ID (default: 'primary')", default: "primary", }, timeMin: { type: "string", description: "Lower bound for event start time (RFC3339 timestamp)", }, timeMax: { type: "string", description: "Upper bound for event start time (RFC3339 timestamp)", }, maxResults: { type: "number", description: "Maximum number of events to return (default: 250)", default: 250, }, singleEvents: { type: "boolean", description: "Whether to expand recurring events (default: true)", default: true, }, orderBy: { type: "string", enum: ["startTime", "updated"], description: "Order of events (default: 'startTime')", default: "startTime", }, }, }, }, { name: "create_event", description: "Create a new event in Google Calendar", inputSchema: { type: "object", properties: { calendarId: { type: "string", description: "Calendar ID (default: 'primary')", default: "primary", }, summary: { type: "string", description: "Event title/summary", }, description: { type: "string", description: "Event description", }, start: { type: "object", properties: { dateTime: { type: "string", description: "Start date and time (RFC3339 timestamp)", }, timeZone: { type: "string", description: "Time zone (e.g., 'America/New_York')", }, }, required: ["dateTime"], }, end: { type: "object", properties: { dateTime: { type: "string", description: "End date and time (RFC3339 timestamp)", }, timeZone: { type: "string", description: "Time zone (e.g., 'America/New_York')", }, }, required: ["dateTime"], }, location: { type: "string", description: "Event location", }, attendees: { type: "array", items: { type: "object", properties: { email: { type: "string" }, displayName: { type: "string" }, }, required: ["email"], }, description: "List of attendees", }, recurrence: { type: "array", items: { type: "string" }, description: "Recurrence rules (RRULE format)", }, }, required: ["summary", "start", "end"], }, }, { name: "update_event", description: "Update an existing event in Google Calendar", inputSchema: { type: "object", properties: { calendarId: { type: "string", description: "Calendar ID (default: 'primary')", default: "primary", }, eventId: { type: "string", description: "Event ID to update", }, summary: { type: "string", description: "Event title/summary", }, description: { type: "string", description: "Event description", }, start: { type: "object", properties: { dateTime: { type: "string", description: "Start date and time (RFC3339 timestamp)", }, timeZone: { type: "string", description: "Time zone (e.g., 'America/New_York')", }, }, }, end: { type: "object", properties: { dateTime: { type: "string", description: "End date and time (RFC3339 timestamp)", }, timeZone: { type: "string", description: "Time zone (e.g., 'America/New_York')", }, }, }, location: { type: "string", description: "Event location", }, attendees: { type: "array", items: { type: "object", properties: { email: { type: "string" }, displayName: { type: "string" }, }, required: ["email"], }, description: "List of attendees", }, }, required: ["eventId"], }, }, { name: "delete_event", description: "Delete an event from Google Calendar", inputSchema: { type: "object", properties: { calendarId: { type: "string", description: "Calendar ID (default: 'primary')", default: "primary", }, eventId: { type: "string", description: "Event ID to delete", }, }, required: ["eventId"], }, }, { name: "list_calendars", description: "List available Google Calendars", inputSchema: { type: "object", properties: { minAccessRole: { type: "string", enum: ["freeBusyReader", "owner", "reader", "writer"], description: "Minimum access role filter", }, showDeleted: { type: "boolean", description: "Include deleted calendars (default: false)", default: false, }, showHidden: { type: "boolean", description: "Include hidden calendars (default: false)", default: false, }, }, }, }, ]; // Tool handlers async function handleListEvents(args: z.infer<typeof ListEventsArgsSchema>) { try { const response = await calendar.events.list({ calendarId: args.calendarId, timeMin: args.timeMin, timeMax: args.timeMax, maxResults: args.maxResults, singleEvents: args.singleEvents, orderBy: args.orderBy, }); return { content: [ { type: "text", text: JSON.stringify({ success: true, events: response.data.items || [], nextPageToken: response.data.nextPageToken, summary: `Found ${(response.data.items || []).length} events`, }, null, 2), }, ], }; } catch (error) { return { content: [ { type: "text", text: JSON.stringify({ success: false, error: error instanceof Error ? error.message : "Unknown error", }, null, 2), }, ], isError: true, }; } } async function handleCreateEvent(args: z.infer<typeof CreateEventArgsSchema>) { try { const event = { summary: args.summary, description: args.description, start: args.start, end: args.end, location: args.location, attendees: args.attendees, recurrence: args.recurrence, }; const response = await calendar.events.insert({ calendarId: args.calendarId, requestBody: event, }); return { content: [ { type: "text", text: JSON.stringify({ success: true, event: response.data, message: `Event "${args.summary}" created successfully`, }, null, 2), }, ], }; } catch (error) { return { content: [ { type: "text", text: JSON.stringify({ success: false, error: error instanceof Error ? error.message : "Unknown error", }, null, 2), }, ], isError: true, }; } } async function handleUpdateEvent(args: z.infer<typeof UpdateEventArgsSchema>) { try { const updates: any = {}; if (args.summary !== undefined) updates.summary = args.summary; if (args.description !== undefined) updates.description = args.description; if (args.start !== undefined) updates.start = args.start; if (args.end !== undefined) updates.end = args.end; if (args.location !== undefined) updates.location = args.location; if (args.attendees !== undefined) updates.attendees = args.attendees; const response = await calendar.events.patch({ calendarId: args.calendarId, eventId: args.eventId, requestBody: updates, }); return { content: [ { type: "text", text: JSON.stringify({ success: true, event: response.data, message: `Event "${args.eventId}" updated successfully`, }, null, 2), }, ], }; } catch (error) { return { content: [ { type: "text", text: JSON.stringify({ success: false, error: error instanceof Error ? error.message : "Unknown error", }, null, 2), }, ], isError: true, }; } } async function handleDeleteEvent(args: z.infer<typeof DeleteEventArgsSchema>) { try { await calendar.events.delete({ calendarId: args.calendarId, eventId: args.eventId, }); return { content: [ { type: "text", text: JSON.stringify({ success: true, message: `Event "${args.eventId}" deleted successfully`, }, null, 2), }, ], }; } catch (error) { return { content: [ { type: "text", text: JSON.stringify({ success: false, error: error instanceof Error ? error.message : "Unknown error", }, null, 2), }, ], isError: true, }; } } async function handleListCalendars(args: z.infer<typeof ListCalendarsArgsSchema>) { try { const response = await calendar.calendarList.list({ minAccessRole: args.minAccessRole, showDeleted: args.showDeleted, showHidden: args.showHidden, }); return { content: [ { type: "text", text: JSON.stringify({ success: true, calendars: response.data.items || [], summary: `Found ${(response.data.items || []).length} calendars`, }, null, 2), }, ], }; } catch (error) { return { content: [ { type: "text", text: JSON.stringify({ success: false, error: error instanceof Error ? error.message : "Unknown error", }, null, 2), }, ], isError: true, }; } } // Register handlers server.setRequestHandler(ListToolsRequestSchema, async () => { return { tools }; }); server.setRequestHandler(CallToolRequestSchema, async (request) => { const { name, arguments: args } = request.params; try { switch (name) { case "list_events": { const validatedArgs = ListEventsArgsSchema.parse(args); return await handleListEvents(validatedArgs); } case "create_event": { const validatedArgs = CreateEventArgsSchema.parse(args); return await handleCreateEvent(validatedArgs); } case "update_event": { const validatedArgs = UpdateEventArgsSchema.parse(args); return await handleUpdateEvent(validatedArgs); } case "delete_event": { const validatedArgs = DeleteEventArgsSchema.parse(args); return await handleDeleteEvent(validatedArgs); } case "list_calendars": { const validatedArgs = ListCalendarsArgsSchema.parse(args); return await handleListCalendars(validatedArgs); } default: throw new Error(`Unknown tool: ${name}`); } } catch (error) { return { content: [ { type: "text", text: `Error: ${error instanceof Error ? error.message : "Unknown error"}`, }, ], isError: true, }; } }); // Start server async function main() { const transport = new StdioServerTransport(); await server.connect(transport); console.error("Google Calendar MCP Server running on stdio"); } if (require.main === module) { main().catch((error) => { console.error("Server error:", error); process.exit(1); }); }

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/peadams21/Google-Calendar-MCP-Server'

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