Skip to main content
Glama

Google Calendar MCP Server

by bezael
gcalClient.ts4.87 kB
/** * Cliente de Google Calendar con autenticación OAuth2 o Service Account */ import * as fs from 'fs'; import { OAuth2Client } from 'google-auth-library'; import { calendar_v3, google } from 'googleapis'; import { createAuthError } from './utils/errors.js'; import { logger } from './utils/logger.js'; let calendarClient: calendar_v3.Calendar | null = null; let defaultCalendarId = 'primary'; /** * Detecta el modo de autenticación basado en las variables de entorno */ function getAuthMode(): 'oauth2' | 'service_account' { if (process.env.GOOGLE_SERVICE_ACCOUNT_KEY_FILE || process.env.GOOGLE_SERVICE_ACCOUNT_KEY) { return 'service_account'; } return 'oauth2'; } /** * Crea cliente con Service Account */ async function createServiceAccountClient(): Promise<calendar_v3.Calendar> { let credentials: Record<string, unknown>; // Opción 1: Archivo JSON de credenciales if (process.env.GOOGLE_SERVICE_ACCOUNT_KEY_FILE) { const keyFilePath = process.env.GOOGLE_SERVICE_ACCOUNT_KEY_FILE; if (!fs.existsSync(keyFilePath)) { throw createAuthError( `No se encontró el archivo de Service Account: ${keyFilePath}`, { keyFilePath } ); } const keyFileContent = fs.readFileSync(keyFilePath, 'utf-8'); credentials = JSON.parse(keyFileContent); logger.info('Usando Service Account desde archivo', { keyFilePath }); } // Opción 2: JSON en variable de entorno else if (process.env.GOOGLE_SERVICE_ACCOUNT_KEY) { credentials = JSON.parse(process.env.GOOGLE_SERVICE_ACCOUNT_KEY); logger.info('Usando Service Account desde variable de entorno'); } else { throw createAuthError('No se encontraron credenciales de Service Account'); } const auth = new google.auth.GoogleAuth({ credentials, scopes: ['https://www.googleapis.com/auth/calendar'], }); const authClient = await auth.getClient(); return google.calendar({ version: 'v3', auth: authClient as OAuth2Client }); } /** * Crea cliente con OAuth2 */ async function createOAuth2Client(): Promise<calendar_v3.Calendar> { const clientId = process.env.GOOGLE_CLIENT_ID; const clientSecret = process.env.GOOGLE_CLIENT_SECRET; const refreshToken = process.env.GOOGLE_REFRESH_TOKEN; const redirectUri = process.env.GOOGLE_REDIRECT_URI || 'http://localhost:3000/oauth2callback'; // Validar variables requeridas const missingVars: string[] = []; if (!clientId) missingVars.push('GOOGLE_CLIENT_ID'); if (!clientSecret) missingVars.push('GOOGLE_CLIENT_SECRET'); if (!refreshToken) missingVars.push('GOOGLE_REFRESH_TOKEN'); if (missingVars.length > 0) { throw createAuthError( `Faltan variables de entorno requeridas: ${missingVars.join(', ')}`, { missingVars } ); } const oauth2Client = new google.auth.OAuth2( clientId, clientSecret, redirectUri ); oauth2Client.setCredentials({ refresh_token: refreshToken, }); // Verificar que podemos obtener un access token try { const tokenResponse = await oauth2Client.getAccessToken(); if (!tokenResponse.token) { throw createAuthError('No se pudo obtener el access token'); } logger.info('Access token obtenido correctamente (OAuth2)'); } catch (error) { if (error instanceof Error && error.message.includes('invalid_grant')) { throw createAuthError( 'El refresh token es inválido o ha expirado. Genera uno nuevo.', { originalError: error.message } ); } throw error; } return google.calendar({ version: 'v3', auth: oauth2Client }); } /** * Inicializa y devuelve el cliente de Google Calendar * Detecta automáticamente si usar OAuth2 o Service Account */ export async function getCalendarClient(): Promise<calendar_v3.Calendar> { if (calendarClient) { return calendarClient; } defaultCalendarId = process.env.GOOGLE_CALENDAR_ID || 'primary'; const authMode = getAuthMode(); logger.info(`Modo de autenticación: ${authMode}`); if (authMode === 'service_account') { calendarClient = await createServiceAccountClient(); } else { calendarClient = await createOAuth2Client(); } return calendarClient; } /** * Obtiene el ID del calendario por defecto */ export function getDefaultCalendarId(): string { return defaultCalendarId; } /** * Obtiene el calendarId a usar, priorizando el parámetro sobre el default */ export function resolveCalendarId(calendarId?: string): string { return calendarId || defaultCalendarId || 'primary'; } /** * Reinicia el cliente (útil para tests o cambio de credenciales) */ export function resetClient(): void { calendarClient = null; defaultCalendarId = 'primary'; }

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

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