UNPKG

@henkey/postgres-mcp-server

Version:

A Model Context Protocol (MCP) server that provides comprehensive PostgreSQL database management capabilities for AI assistants

232 lines 10.9 kB
import { DatabaseConnection } from '../utils/connection.js'; import { z } from 'zod'; import { McpError, ErrorCode } from '@modelcontextprotocol/sdk/types.js'; const DebugDatabaseInputSchema = z.object({ connectionString: z.string().optional(), issue: z.enum(['connection', 'performance', 'locks', 'replication']), logLevel: z.enum(['info', 'debug', 'trace']).optional().default('info'), }); async function executeDebugDatabase(input, getConnectionString) { const resolvedConnectionString = getConnectionString(input.connectionString); const db = DatabaseConnection.getInstance(); try { await db.connect(resolvedConnectionString); switch (input.issue) { case 'connection': return await debugConnection(db); case 'performance': return await debugPerformance(db); case 'locks': return await debugLocks(db); case 'replication': return await debugReplication(db); default: // This case should be unreachable due to Zod validation throw new McpError(ErrorCode.InvalidParams, `Unsupported issue type: ${input.issue}`); } } finally { // Ensure disconnect is called even if connect fails or other errors occur await db.disconnect(); } } export const debugDatabaseTool = { name: 'pg_debug_database', description: 'Debug common PostgreSQL issues', inputSchema: DebugDatabaseInputSchema, async execute(params, getConnectionString) { const validationResult = DebugDatabaseInputSchema.safeParse(params); if (!validationResult.success) { const errorDetails = validationResult.error.errors.map(e => `${e.path.join('.')}: ${e.message}`).join(', '); return { content: [{ type: 'text', text: `Invalid input: ${errorDetails}` }], isError: true, }; } try { const result = await executeDebugDatabase(validationResult.data, getConnectionString); // Convert DebugResult to ToolOutput format return { content: [ { type: 'text', text: `Debug Result for Issue: ${result.issue}` }, { type: 'text', text: `Status: ${result.status}` }, { type: 'text', text: `Details:\n${result.details.join('\n')}` }, { type: 'text', text: `Recommendations:\n${result.recommendations.join('\n')}` }, ], }; } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); return { content: [{ type: 'text', text: `Error debugging database: ${errorMessage}` }], isError: true, }; } } }; async function debugConnection(db) { const result = { issue: 'connection', status: 'ok', details: [], recommendations: [] }; try { // Check max connections const maxConns = await db.query("SELECT setting FROM pg_settings WHERE name = 'max_connections'"); const currentConns = await db.query('SELECT count(*) FROM pg_stat_activity'); const max = Number.parseInt(maxConns[0].setting); const current = Number.parseInt(currentConns[0].count); const percentage = (current / max) * 100; result.details.push(`Current connections: ${current}/${max} (${percentage.toFixed(1)}%)`); if (percentage > 80) { result.status = 'warning'; result.recommendations.push('High connection usage. Consider implementing connection pooling', 'Review application connection handling', 'Monitor for connection leaks'); } // Check for idle connections const idleConns = await db.query("SELECT count(*) FROM pg_stat_activity WHERE state = 'idle'"); const idleCount = Number.parseInt(idleConns[0].count); if (idleCount > 5) { result.details.push(`High number of idle connections: ${idleCount}`); result.recommendations.push('Consider implementing connection timeouts', 'Review connection pool settings'); } } catch (error) { result.status = 'error'; result.details.push(`Connection error: ${error instanceof Error ? error.message : String(error)}`); } return result; } async function debugPerformance(db) { const result = { issue: 'performance', status: 'ok', details: [], recommendations: [] }; try { // Check slow queries const slowQueries = await db.query(`SELECT query, extract(epoch from now() - query_start) as duration FROM pg_stat_activity WHERE state = 'active' AND query NOT LIKE '%pg_stat_activity%' AND query_start < now() - interval '30 second'`); if (slowQueries.length > 0) { result.status = 'warning'; result.details.push('Long-running queries detected:'); for (const q of slowQueries) { result.details.push(`Duration: ${q.duration}s - Query: ${q.query}`); } result.recommendations.push('Review and optimize slow queries', 'Consider adding appropriate indexes', 'Check for missing VACUUM operations'); } // Check index usage const unusedIndexes = await db.query(`SELECT s.schemaname, s.relname AS tablename, s.indexrelname AS indexname, s.idx_scan FROM pg_stat_user_indexes s WHERE s.idx_scan = 0 AND s.schemaname NOT IN ('pg_catalog', 'information_schema')`); if (unusedIndexes.length > 0) { result.details.push('Unused indexes found:'); for (const idx of unusedIndexes) { result.details.push(`${idx.schemaname}.${idx.tablename} - ${idx.indexname}`); } result.recommendations.push('Consider removing unused indexes', 'Review index strategy'); } } catch (error) { result.status = 'error'; result.details.push(`Performance analysis error: ${error instanceof Error ? error.message : String(error)}`); } return result; } async function debugLocks(db) { const result = { issue: 'locks', status: 'ok', details: [], recommendations: [] }; try { const locks = await db.query(`SELECT blocked_locks.pid AS blocked_pid, blocked_activity.usename AS blocked_user, blocking_locks.pid AS blocking_pid, blocking_activity.usename AS blocking_user, blocked_activity.query AS blocked_statement FROM pg_catalog.pg_locks blocked_locks JOIN pg_catalog.pg_stat_activity blocked_activity ON blocked_activity.pid = blocked_locks.pid JOIN pg_catalog.pg_locks blocking_locks ON blocking_locks.locktype = blocked_locks.locktype AND blocking_locks.DATABASE IS NOT DISTINCT FROM blocked_locks.DATABASE AND blocking_locks.relation IS NOT DISTINCT FROM blocked_locks.relation AND blocking_locks.page IS NOT DISTINCT FROM blocked_locks.page AND blocking_locks.tuple IS NOT DISTINCT FROM blocked_locks.tuple AND blocking_locks.virtualxid IS NOT DISTINCT FROM blocked_locks.virtualxid AND blocking_locks.transactionid IS NOT DISTINCT FROM blocked_locks.transactionid AND blocking_locks.classid IS NOT DISTINCT FROM blocked_locks.classid AND blocking_locks.objid IS NOT DISTINCT FROM blocked_locks.objid AND blocking_locks.objsubid IS NOT DISTINCT FROM blocked_locks.objsubid AND blocking_locks.pid != blocked_locks.pid JOIN pg_catalog.pg_stat_activity blocking_activity ON blocking_activity.pid = blocking_locks.pid WHERE NOT blocked_locks.GRANTED`); if (locks.length > 0) { result.status = 'warning'; result.details.push('Lock conflicts detected:'); for (const lock of locks) { result.details.push(`Process ${lock.blocked_pid} (${lock.blocked_user}) blocked by process ${lock.blocking_pid} (${lock.blocking_user})`); result.details.push(`Blocked query: ${lock.blocked_statement}`); } result.recommendations.push('Consider killing blocking queries if appropriate', 'Review transaction management in application code', 'Check for long-running transactions'); } } catch (error) { result.status = 'error'; result.details.push(`Lock analysis error: ${error instanceof Error ? error.message : String(error)}`); } return result; } async function debugReplication(db) { const result = { issue: 'replication', status: 'ok', details: [], recommendations: [] }; try { // Check replication status const replicationStatus = await db.query(`SELECT client_addr, state, sent_lsn, write_lsn, flush_lsn, replay_lsn, write_lag, flush_lag, replay_lag FROM pg_stat_replication`); if (replicationStatus.length === 0) { result.details.push('No active replication detected'); result.recommendations.push('If replication is expected, check configuration', 'Verify replication slots are created', 'Check network connectivity between nodes'); return result; } result.status = 'ok'; // Default to ok, specific checks might change it result.details.push('Replication status:'); for (const status of replicationStatus) { result.details.push(`Replica: ${status.client_addr}, State: ${status.state}, Sent LSN: ${status.sent_lsn}, Replay LSN: ${status.replay_lsn}`); const writeLagSeconds = status.write_lag ? Number.parseFloat(status.write_lag.split(' ')[0]) : 0; const flushLagSeconds = status.flush_lag ? Number.parseFloat(status.flush_lag.split(' ')[0]) : 0; const replayLagSeconds = status.replay_lag ? Number.parseFloat(status.replay_lag.split(' ')[0]) : 0; if (writeLagSeconds > 60 || flushLagSeconds > 60 || replayLagSeconds > 60) { result.status = 'warning'; result.recommendations.push(`High replication lag (${status.replay_lag}) for ${status.client_addr}`, 'Check network bandwidth between nodes', 'Review WAL settings', 'Monitor system resources on replica'); } } } catch (error) { result.status = 'error'; result.details.push(`Replication analysis error: ${error instanceof Error ? error.message : String(error)}`); } return result; } //# sourceMappingURL=debug.js.map