Skip to main content
Glama

Google Calendar MCP

import { CallToolResult } from "@modelcontextprotocol/sdk/types.js"; import { McpError, ErrorCode } from "@modelcontextprotocol/sdk/types.js"; import { OAuth2Client } from "google-auth-library"; import { GaxiosError } from 'gaxios'; import { calendar_v3, google } from "googleapis"; export abstract class BaseToolHandler { abstract runTool(args: any, oauth2Client: OAuth2Client): Promise<CallToolResult>; protected handleGoogleApiError(error: unknown): never { if (error instanceof GaxiosError) { const status = error.response?.status; const errorData = error.response?.data; // Handle specific Google API errors with appropriate MCP error codes if (errorData?.error === 'invalid_grant') { throw new McpError( ErrorCode.InvalidRequest, 'Authentication token is invalid or expired. Please re-run the authentication process (e.g., `npm run auth`).' ); } if (status === 403) { throw new McpError( ErrorCode.InvalidRequest, `Access denied: ${errorData?.error?.message || 'Insufficient permissions'}` ); } if (status === 404) { throw new McpError( ErrorCode.InvalidRequest, `Resource not found: ${errorData?.error?.message || 'The requested calendar or event does not exist'}` ); } if (status === 429) { throw new McpError( ErrorCode.InternalError, 'Rate limit exceeded. Please try again later.' ); } if (status && status >= 500) { throw new McpError( ErrorCode.InternalError, `Google API server error: ${errorData?.error?.message || error.message}` ); } // Generic Google API error throw new McpError( ErrorCode.InvalidRequest, `Google API error: ${errorData?.error?.message || error.message}` ); } // Handle non-Google API errors if (error instanceof Error) { throw new McpError( ErrorCode.InternalError, `Internal error: ${error.message}` ); } throw new McpError( ErrorCode.InternalError, 'An unknown error occurred' ); } protected getCalendar(auth: OAuth2Client): calendar_v3.Calendar { return google.calendar({ version: 'v3', auth, timeout: 3000 // 3 second timeout for API calls }); } protected async withTimeout<T>(promise: Promise<T>, timeoutMs: number = 30000): Promise<T> { const timeoutPromise = new Promise<never>((_, reject) => { setTimeout(() => reject(new Error(`Operation timed out after ${timeoutMs}ms`)), timeoutMs); }); return Promise.race([promise, timeoutPromise]); } /** * Gets calendar details including default timezone * @param client OAuth2Client * @param calendarId Calendar ID to fetch details for * @returns Calendar details with timezone */ protected async getCalendarDetails(client: OAuth2Client, calendarId: string): Promise<calendar_v3.Schema$CalendarListEntry> { try { const calendar = this.getCalendar(client); const response = await calendar.calendarList.get({ calendarId }); if (!response.data) { throw new Error(`Calendar ${calendarId} not found`); } return response.data; } catch (error) { throw this.handleGoogleApiError(error); } } /** * Gets the default timezone for a calendar, falling back to UTC if not available * @param client OAuth2Client * @param calendarId Calendar ID * @returns Timezone string (IANA format) */ protected async getCalendarTimezone(client: OAuth2Client, calendarId: string): Promise<string> { try { const calendarDetails = await this.getCalendarDetails(client, calendarId); return calendarDetails.timeZone || 'UTC'; } catch (error) { // If we can't get calendar details, fall back to UTC return 'UTC'; } } }

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