Skip to main content
Glama
cesarvarela

PostgreSQL MCP Server

by cesarvarela
getTableInfo.ts6.86 kB
import { z, ZodRawShape } from "zod"; import { McpToolResponse, createMcpSuccessResponse, createMcpErrorResponse, createDatabaseUnavailableResponse, executePostgresQuery, sanitizeIdentifier, getConnectionStatus, debug, } from "./utils.js"; // Zod schema for input validation export const getTableInfoShape: ZodRawShape = { table: z.string().min(1, "Table name is required"), schema_name: z.string().optional().default("public"), include_statistics: z.boolean().optional().default(true), }; export const getTableInfoSchema = z.object(getTableInfoShape); interface TableStatistics { estimated_row_count: number; table_size_bytes: number; table_size_pretty: string; index_size_bytes: number; index_size_pretty: string; total_size_bytes: number; total_size_pretty: string; } // Tool implementation export async function getTableInfo( rawParams: any ): McpToolResponse { try { // Validate and parse parameters const params = getTableInfoSchema.parse(rawParams); // Check database connection status const connectionStatus = getConnectionStatus(); if (connectionStatus.status !== 'connected') { return createDatabaseUnavailableResponse("get table information"); } const { table, schema_name, include_statistics } = params; // Validate identifiers const sanitizedTable = sanitizeIdentifier(table); const sanitizedSchema = sanitizeIdentifier(schema_name); // Get basic table information const tableInfoQuery = ` SELECT t.table_name, t.table_schema, t.table_type, obj_description(c.oid) as table_comment FROM information_schema.tables t LEFT JOIN pg_class c ON c.relname = t.table_name LEFT JOIN pg_namespace n ON n.oid = c.relnamespace AND n.nspname = t.table_schema WHERE t.table_schema = $1 AND t.table_name = $2 `; const tableInfo = await executePostgresQuery(tableInfoQuery, [sanitizedSchema, sanitizedTable]); if (tableInfo.length === 0) { throw new Error(`Table ${sanitizedSchema}.${sanitizedTable} not found`); } // Get detailed column information const columnsQuery = ` SELECT c.column_name, c.data_type, c.is_nullable, c.column_default, c.character_maximum_length, c.numeric_precision, c.numeric_scale, c.ordinal_position, col_description(pgc.oid, c.ordinal_position) as column_comment FROM information_schema.columns c LEFT JOIN pg_class pgc ON pgc.relname = c.table_name LEFT JOIN pg_namespace pgn ON pgn.oid = pgc.relnamespace AND pgn.nspname = c.table_schema WHERE c.table_schema = $1 AND c.table_name = $2 ORDER BY c.ordinal_position `; const columns = await executePostgresQuery(columnsQuery, [sanitizedSchema, sanitizedTable]); // Get constraints const constraintsQuery = ` SELECT tc.constraint_name, tc.constraint_type, kcu.column_name, ccu.table_name AS foreign_table_name, ccu.column_name AS foreign_column_name, rc.match_option, rc.update_rule, rc.delete_rule FROM information_schema.table_constraints tc LEFT JOIN information_schema.key_column_usage kcu ON tc.constraint_name = kcu.constraint_name AND tc.table_schema = kcu.table_schema LEFT JOIN information_schema.constraint_column_usage ccu ON tc.constraint_name = ccu.constraint_name AND tc.table_schema = ccu.table_schema LEFT JOIN information_schema.referential_constraints rc ON tc.constraint_name = rc.constraint_name AND tc.table_schema = rc.constraint_schema WHERE tc.table_schema = $1 AND tc.table_name = $2 ORDER BY tc.constraint_type, tc.constraint_name `; const constraints = await executePostgresQuery(constraintsQuery, [sanitizedSchema, sanitizedTable]); // Get indexes const indexesQuery = ` SELECT i.relname as index_name, a.attname as column_name, ix.indisunique as is_unique, ix.indisprimary as is_primary, am.amname as index_type FROM pg_class i JOIN pg_index ix ON i.oid = ix.indexrelid JOIN pg_class t ON t.oid = ix.indrelid JOIN pg_namespace n ON n.oid = t.relnamespace JOIN pg_attribute a ON a.attrelid = t.oid AND a.attnum = ANY(ix.indkey) JOIN pg_am am ON i.relam = am.oid WHERE n.nspname = $1 AND t.relname = $2 ORDER BY i.relname, a.attnum `; const indexes = await executePostgresQuery(indexesQuery, [sanitizedSchema, sanitizedTable]); let statistics: TableStatistics | undefined; // Get table statistics if requested if (include_statistics) { const statsQuery = ` SELECT schemaname, tablename, attname, n_distinct, correlation FROM pg_stats WHERE schemaname = $1 AND tablename = $2 `; const sizeQuery = ` SELECT pg_stat_get_live_tuples(c.oid) as estimated_row_count, pg_total_relation_size(c.oid) as total_size_bytes, pg_size_pretty(pg_total_relation_size(c.oid)) as total_size_pretty, pg_relation_size(c.oid) as table_size_bytes, pg_size_pretty(pg_relation_size(c.oid)) as table_size_pretty, pg_total_relation_size(c.oid) - pg_relation_size(c.oid) as index_size_bytes, pg_size_pretty(pg_total_relation_size(c.oid) - pg_relation_size(c.oid)) as index_size_pretty FROM pg_class c JOIN pg_namespace n ON n.oid = c.relnamespace WHERE n.nspname = $1 AND c.relname = $2 `; try { const sizeResult = await executePostgresQuery(sizeQuery, [sanitizedSchema, sanitizedTable]); if (sizeResult.length > 0) { const rawStats = sizeResult[0]; statistics = { estimated_row_count: parseInt(rawStats.estimated_row_count) || 0, table_size_bytes: parseInt(rawStats.table_size_bytes) || 0, table_size_pretty: rawStats.table_size_pretty, index_size_bytes: parseInt(rawStats.index_size_bytes) || 0, index_size_pretty: rawStats.index_size_pretty, total_size_bytes: parseInt(rawStats.total_size_bytes) || 0, total_size_pretty: rawStats.total_size_pretty, }; } } catch (error) { debug("Failed to get table statistics: %o", error); } } const response = { table: tableInfo[0], columns: columns, constraints: constraints, indexes: indexes, ...(statistics && { statistics }), generated_at: new Date().toISOString(), }; return createMcpSuccessResponse(response); } catch (error) { return createMcpErrorResponse("get table info", error); } }

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/cesarvarela/postgres-mcp'

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