Skip to main content
Glama

Google Calendar and Meet MCP Server

by INSIDE-HAIR
healthCheck.ts•13.4 kB
/** * Health Check System for Google Meet MCP Server * Provides comprehensive system health monitoring */ export interface HealthStatus { status: 'healthy' | 'degraded' | 'unhealthy'; timestamp: string; version: string; uptime: number; memory: NodeJS.MemoryUsage; auth: AuthHealthStatus; apis: ApiHealthStatus; dependencies: DependencyHealth[]; } export interface AuthHealthStatus { status: 'healthy' | 'degraded' | 'unhealthy'; token_valid: boolean; expires_in?: number; scopes_granted?: string[]; error?: string; } export interface ApiHealthStatus { calendar_api: ApiEndpointHealth; meet_api: ApiEndpointHealth; overall_status: 'healthy' | 'degraded' | 'unhealthy'; } export interface ApiEndpointHealth { status: 'healthy' | 'degraded' | 'unhealthy'; response_time_ms?: number; last_success?: string; last_error?: string; error_count: number; } export interface DependencyHealth { name: string; status: 'healthy' | 'degraded' | 'unhealthy'; type: 'service' | 'database' | 'api' | 'file'; details?: Record<string, any>; } export interface HealthCheckConfig { timeout: number; checkIntervalMs: number; unhealthyThreshold: number; degradedThreshold: number; } export class HealthChecker { private config: HealthCheckConfig; private oauth2Client: any; private lastHealthCheck?: HealthStatus; private errorCounts: Map<string, number> = new Map(); private lastSuccessTime: Map<string, Date> = new Map(); constructor( oauth2Client: any, config: Partial<HealthCheckConfig> = {} ) { this.oauth2Client = oauth2Client; this.config = { timeout: config.timeout || 5000, checkIntervalMs: config.checkIntervalMs || 30000, unhealthyThreshold: config.unhealthyThreshold || 5, degradedThreshold: config.degradedThreshold || 2, ...config }; } /** * Get comprehensive health status */ async getHealthStatus(): Promise<HealthStatus> { const startTime = Date.now(); try { const [authHealth, apiHealth, dependencies] = await Promise.allSettled([ this.checkAuthHealth(), this.checkApiHealth(), this.checkDependencies() ]); const healthStatus: HealthStatus = { status: this.calculateOverallStatus([ this.extractResult(authHealth)?.status || 'unhealthy', this.extractResult(apiHealth)?.overall_status || 'unhealthy' ]), timestamp: new Date().toISOString(), version: process.env.npm_package_version || '3.0.0', uptime: process.uptime(), memory: process.memoryUsage(), auth: this.extractResult(authHealth) || this.getUnhealthyAuthStatus(), apis: this.extractResult(apiHealth) || this.getUnhealthyApiStatus(), dependencies: this.extractResult(dependencies) || [] }; this.lastHealthCheck = healthStatus; return healthStatus; } catch (error) { return this.getEmergencyHealthStatus(error as Error); } } /** * Check OAuth authentication health */ private async checkAuthHealth(): Promise<AuthHealthStatus> { try { if (!this.oauth2Client) { return { status: 'unhealthy', token_valid: false, error: 'OAuth2 client not initialized' }; } // Try to get access token (this will refresh if needed) const tokenResponse = await Promise.race([ this.oauth2Client.getAccessToken(), this.createTimeoutPromise(this.config.timeout, 'Token check timeout') ]); if (!tokenResponse.token) { return { status: 'unhealthy', token_valid: false, error: 'No access token available' }; } // Calculate token expiry const credentials = this.oauth2Client.credentials; const expiresIn = credentials.expiry_date ? Math.max(0, Math.floor((credentials.expiry_date - Date.now()) / 1000)) : undefined; // Check if token is about to expire (less than 5 minutes) const status = expiresIn && expiresIn < 300 ? 'degraded' : 'healthy'; return { status, token_valid: true, expires_in: expiresIn, scopes_granted: this.extractScopesFromCredentials(credentials) }; } catch (error) { return { status: 'unhealthy', token_valid: false, error: error instanceof Error ? error.message : 'Unknown auth error' }; } } /** * Check Google APIs health */ private async checkApiHealth(): Promise<ApiHealthStatus> { const [calendarResult, meetResult] = await Promise.allSettled([ this.checkCalendarApi(), this.checkMeetApi() ]); const calendarHealth = this.extractResult(calendarResult) || this.getUnhealthyEndpointStatus(); const meetHealth = this.extractResult(meetResult) || this.getUnhealthyEndpointStatus(); const overallStatus = this.calculateOverallStatus([ calendarHealth.status, meetHealth.status ]); return { calendar_api: calendarHealth, meet_api: meetHealth, overall_status: overallStatus }; } /** * Check Calendar API health */ private async checkCalendarApi(): Promise<ApiEndpointHealth> { const apiName = 'calendar_api'; const startTime = Date.now(); try { // Simple API call to check health const response = await Promise.race([ fetch('https://www.googleapis.com/calendar/v3/users/me/calendarList?maxResults=1', { headers: { 'Authorization': `Bearer ${await this.getAccessToken()}` } }), this.createTimeoutPromise<Response>(this.config.timeout, 'Calendar API timeout') ]); const responseTime = Date.now() - startTime; if (response.ok) { this.resetErrorCount(apiName); this.lastSuccessTime.set(apiName, new Date()); return { status: 'healthy', response_time_ms: responseTime, last_success: new Date().toISOString(), error_count: 0 }; } else { this.incrementErrorCount(apiName); const status = this.getStatusFromErrorCount(apiName); return { status, response_time_ms: responseTime, last_error: `HTTP ${response.status}: ${response.statusText}`, error_count: this.errorCounts.get(apiName) || 0 }; } } catch (error) { this.incrementErrorCount(apiName); const status = this.getStatusFromErrorCount(apiName); return { status, response_time_ms: Date.now() - startTime, last_error: error instanceof Error ? error.message : 'Unknown error', error_count: this.errorCounts.get(apiName) || 0 }; } } /** * Check Meet API health */ private async checkMeetApi(): Promise<ApiEndpointHealth> { const apiName = 'meet_api'; const startTime = Date.now(); try { // Simple API call to check health - try to list conference records with limit 1 const response = await Promise.race([ fetch('https://meet.googleapis.com/v2/conferenceRecords?pageSize=1', { headers: { 'Authorization': `Bearer ${await this.getAccessToken()}` } }), this.createTimeoutPromise<Response>(this.config.timeout, 'Meet API timeout') ]); const responseTime = Date.now() - startTime; if (response.ok) { this.resetErrorCount(apiName); this.lastSuccessTime.set(apiName, new Date()); return { status: 'healthy', response_time_ms: responseTime, last_success: new Date().toISOString(), error_count: 0 }; } else { this.incrementErrorCount(apiName); const status = this.getStatusFromErrorCount(apiName); return { status, response_time_ms: responseTime, last_error: `HTTP ${response.status}: ${response.statusText}`, error_count: this.errorCounts.get(apiName) || 0 }; } } catch (error) { this.incrementErrorCount(apiName); const status = this.getStatusFromErrorCount(apiName); return { status, response_time_ms: Date.now() - startTime, last_error: error instanceof Error ? error.message : 'Unknown error', error_count: this.errorCounts.get(apiName) || 0 }; } } /** * Check system dependencies */ private async checkDependencies(): Promise<DependencyHealth[]> { const dependencies: DependencyHealth[] = []; // Check Node.js version dependencies.push({ name: 'nodejs', status: 'healthy', type: 'service', details: { version: process.version, platform: process.platform, arch: process.arch } }); // Check memory usage const memory = process.memoryUsage(); const memoryStatus = memory.heapUsed / memory.heapTotal > 0.9 ? 'degraded' : 'healthy'; dependencies.push({ name: 'memory', status: memoryStatus, type: 'service', details: { heap_used_mb: Math.round(memory.heapUsed / 1024 / 1024), heap_total_mb: Math.round(memory.heapTotal / 1024 / 1024), heap_usage_percent: Math.round((memory.heapUsed / memory.heapTotal) * 100) } }); // Check file system access try { await import('fs/promises').then(fs => fs.access('.', fs.constants.R_OK)); dependencies.push({ name: 'filesystem', status: 'healthy', type: 'file' }); } catch (error) { dependencies.push({ name: 'filesystem', status: 'unhealthy', type: 'file', details: { error: error instanceof Error ? error.message : 'Unknown error' } }); } return dependencies; } /** * Get the last cached health status (useful for quick checks) */ getLastHealthStatus(): HealthStatus | undefined { return this.lastHealthCheck; } /** * Simple health check that returns just the status */ async isHealthy(): Promise<boolean> { try { const health = await this.getHealthStatus(); return health.status === 'healthy'; } catch { return false; } } // Private helper methods private async getAccessToken(): Promise<string> { const tokenResponse = await this.oauth2Client.getAccessToken(); return tokenResponse.token; } private createTimeoutPromise<T>(timeoutMs: number, message: string): Promise<T> { return new Promise((_, reject) => { setTimeout(() => reject(new Error(message)), timeoutMs); }); } private extractResult<T>(settledResult: PromiseSettledResult<T>): T | null { return settledResult.status === 'fulfilled' ? settledResult.value : null; } private calculateOverallStatus(statuses: string[]): 'healthy' | 'degraded' | 'unhealthy' { if (statuses.some(s => s === 'unhealthy')) return 'unhealthy'; if (statuses.some(s => s === 'degraded')) return 'degraded'; return 'healthy'; } private extractScopesFromCredentials(credentials: any): string[] { // Extract scopes from token if available if (credentials.scope) { return credentials.scope.split(' '); } // Return expected scopes as fallback return [ 'https://www.googleapis.com/auth/calendar', 'https://www.googleapis.com/auth/meetings.space.created', 'https://www.googleapis.com/auth/meetings.space.readonly' ]; } private incrementErrorCount(apiName: string): void { const current = this.errorCounts.get(apiName) || 0; this.errorCounts.set(apiName, current + 1); } private resetErrorCount(apiName: string): void { this.errorCounts.set(apiName, 0); } private getStatusFromErrorCount(apiName: string): 'healthy' | 'degraded' | 'unhealthy' { const count = this.errorCounts.get(apiName) || 0; if (count >= this.config.unhealthyThreshold) return 'unhealthy'; if (count >= this.config.degradedThreshold) return 'degraded'; return 'healthy'; } private getUnhealthyAuthStatus(): AuthHealthStatus { return { status: 'unhealthy', token_valid: false, error: 'Failed to check auth status' }; } private getUnhealthyApiStatus(): ApiHealthStatus { return { calendar_api: this.getUnhealthyEndpointStatus(), meet_api: this.getUnhealthyEndpointStatus(), overall_status: 'unhealthy' }; } private getUnhealthyEndpointStatus(): ApiEndpointHealth { return { status: 'unhealthy', error_count: 999, last_error: 'Failed to check API status' }; } private getEmergencyHealthStatus(error: Error): HealthStatus { return { status: 'unhealthy', timestamp: new Date().toISOString(), version: process.env.npm_package_version || '3.0.0', uptime: process.uptime(), memory: process.memoryUsage(), auth: { status: 'unhealthy', token_valid: false, error: 'System error during health check' }, apis: { calendar_api: { status: 'unhealthy', error_count: 999 }, meet_api: { status: 'unhealthy', error_count: 999 }, overall_status: 'unhealthy' }, dependencies: [{ name: 'system', status: 'unhealthy', type: 'service', details: { error: error.message } }] }; } }

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/INSIDE-HAIR/mcp-google-calendar-and-meet'

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