Skip to main content
Glama
MisterSandFR

Supabase MCP Server - Self-Hosted Edition

by MisterSandFR
check_health.ts7.93 kB
import { Tool } from "@modelcontextprotocol/sdk/types.js"; import { z } from "zod"; import { ToolContext } from "./types.js"; import { executeSqlWithFallback } from "./utils.js"; const CheckHealthInputSchema = z.object({ includeMetrics: z.boolean().optional().describe("Include detailed performance metrics"), checkExternal: z.boolean().optional().describe("Check external services (Storage, Auth endpoints)") }); const CheckHealthOutputSchema = z.object({ content: z.array(z.object({ type: z.literal("text"), text: z.string() })) }); type CheckHealthInput = z.infer<typeof CheckHealthInputSchema>; interface HealthStatus { component: string; status: 'healthy' | 'degraded' | 'unhealthy'; responseTime?: number; details?: Record<string, any>; error?: string; } export const checkHealthTool: Tool = { name: "check_health", description: "Check health status of all Supabase components (PostgreSQL, Auth, Storage, Realtime)", inputSchema: CheckHealthInputSchema, mcpInputSchema: { type: "object", properties: { includeMetrics: { type: "boolean", description: "Include detailed performance metrics" }, checkExternal: { type: "boolean", description: "Check external services (Storage, Auth endpoints)" } } }, outputSchema: CheckHealthOutputSchema, execute: async (input: unknown, context: ToolContext) => { const validatedInput = CheckHealthInputSchema.parse(input || {}); const results: HealthStatus[] = []; // Check PostgreSQL const pgStart = Date.now(); try { const pgResult = await executeSqlWithFallback( "SELECT version(), current_database(), pg_size_pretty(pg_database_size(current_database())) as db_size, now() as server_time", context ); results.push({ component: 'PostgreSQL', status: 'healthy', responseTime: Date.now() - pgStart, details: pgResult.data?.[0] }); } catch (error) { results.push({ component: 'PostgreSQL', status: 'unhealthy', responseTime: Date.now() - pgStart, error: error instanceof Error ? error.message : String(error) }); } // Check connection pool try { const poolResult = await executeSqlWithFallback(` SELECT count(*) as total_connections, count(*) FILTER (WHERE state = 'active') as active, count(*) FILTER (WHERE state = 'idle') as idle, count(*) FILTER (WHERE state = 'idle in transaction') as idle_in_transaction, max(EXTRACT(EPOCH FROM (now() - state_change))) as longest_connection_seconds FROM pg_stat_activity WHERE datname = current_database() `, context); const poolData = poolResult.data?.[0]; const poolStatus = poolData?.total_connections > 90 ? 'degraded' : 'healthy'; results.push({ component: 'Connection Pool', status: poolStatus, details: poolData }); } catch (error) { results.push({ component: 'Connection Pool', status: 'unhealthy', error: error instanceof Error ? error.message : String(error) }); } // Check Replication (if configured) try { const replicationResult = await executeSqlWithFallback(` SELECT client_addr, state, sync_state, EXTRACT(EPOCH FROM (now() - backend_start)) as connection_seconds, EXTRACT(EPOCH FROM replay_lag) as replay_lag_seconds FROM pg_stat_replication `, context); if (replicationResult.data && replicationResult.data.length > 0) { const maxLag = Math.max(...replicationResult.data.map((r: any) => r.replay_lag_seconds || 0)); const replicationStatus = maxLag > 10 ? 'degraded' : 'healthy'; results.push({ component: 'Replication', status: replicationStatus, details: { replicas: replicationResult.data.length, max_lag_seconds: maxLag, replicas_detail: replicationResult.data } }); } } catch { // Replication might not be configured } // Check performance metrics if requested if (validatedInput.includeMetrics) { try { const metricsResult = await executeSqlWithFallback(` SELECT (SELECT count(*) FROM pg_stat_activity WHERE state = 'active') as active_queries, (SELECT count(*) FROM pg_stat_activity WHERE wait_event_type IS NOT NULL) as waiting_queries, (SELECT ROUND(100.0 * SUM(blks_hit) / NULLIF(SUM(blks_hit + blks_read), 0), 2) FROM pg_stat_database) as cache_hit_ratio, (SELECT count(*) FROM pg_stat_activity WHERE state = 'idle in transaction' AND EXTRACT(EPOCH FROM (now() - state_change)) > 300) as long_idle_transactions, (SELECT count(*) FROM pg_locks WHERE NOT granted) as blocked_queries `, context); const metrics = metricsResult.data?.[0]; const metricsStatus = metrics?.blocked_queries > 0 || metrics?.long_idle_transactions > 5 ? 'degraded' : 'healthy'; results.push({ component: 'Performance Metrics', status: metricsStatus, details: metrics }); } catch (error) { results.push({ component: 'Performance Metrics', status: 'unhealthy', error: error instanceof Error ? error.message : String(error) }); } } // Check external endpoints if requested and available if (validatedInput.checkExternal && context.supabase) { // Check Auth service const authStart = Date.now(); try { const authHealthUrl = `${context.config.url}/auth/v1/health`; const authResponse = await fetch(authHealthUrl); results.push({ component: 'Auth Service', status: authResponse.ok ? 'healthy' : 'degraded', responseTime: Date.now() - authStart, details: { statusCode: authResponse.status } }); } catch (error) { results.push({ component: 'Auth Service', status: 'unhealthy', responseTime: Date.now() - authStart, error: error instanceof Error ? error.message : String(error) }); } // Check Storage service const storageStart = Date.now(); try { const storageHealthUrl = `${context.config.url}/storage/v1/version`; const storageResponse = await fetch(storageHealthUrl); results.push({ component: 'Storage Service', status: storageResponse.ok ? 'healthy' : 'degraded', responseTime: Date.now() - storageStart, details: { statusCode: storageResponse.status } }); } catch (error) { results.push({ component: 'Storage Service', status: 'unhealthy', responseTime: Date.now() - storageStart, error: error instanceof Error ? error.message : String(error) }); } } // Calculate overall health const unhealthyCount = results.filter(r => r.status === 'unhealthy').length; const degradedCount = results.filter(r => r.status === 'degraded').length; const overallStatus = unhealthyCount > 0 ? 'unhealthy' : degradedCount > 0 ? 'degraded' : 'healthy'; return { content: [{ type: "text", text: JSON.stringify({ overall_status: overallStatus, timestamp: new Date().toISOString(), components: results, summary: { healthy: results.filter(r => r.status === 'healthy').length, degraded: degradedCount, unhealthy: unhealthyCount } }, null, 2) }] }; } };

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/MisterSandFR/Supabase-MCP-SelfHosted'

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