Skip to main content
Glama
itsalfredakku

Postgres MCP Server

tables

Manage PostgreSQL database tables by listing, creating, altering, dropping, or retrieving detailed information about them within specified schemas.

Instructions

Table management: list, create, alter, drop tables and get detailed table information

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
actionYesAction: list (all tables), info (table details), create (new table), drop (remove table), add_column (add column), drop_column (remove column), rename (rename table)
schemaNameNoSchema name (default: public)public
tableNameNoTable name (required for info, create, drop, add_column, drop_column, rename)
columnsNoColumn definitions for create action
columnNameNoColumn name (required for add_column, drop_column)
dataTypeNoData type (required for add_column)
newNameNoNew name (required for rename action)
optionsNoAction-specific options

Implementation Reference

  • Input schema and description for the 'tables' MCP tool, defining supported actions (list, info, create, drop, etc.) and parameters.
    {
      name: 'tables',
      description: 'Table management: list, create, alter, drop tables and get detailed table information',
      inputSchema: {
        type: 'object',
        properties: {
          action: {
            type: 'string',
            enum: ['list', 'info', 'create', 'drop', 'add_column', 'drop_column', 'rename'],
            description: 'Action: list (all tables), info (table details), create (new table), drop (remove table), add_column (add column), drop_column (remove column), rename (rename table)'
          },
          schemaName: {
            type: 'string',
            description: 'Schema name (default: public)',
            default: 'public'
          },
          tableName: {
            type: 'string',
            description: 'Table name (required for info, create, drop, add_column, drop_column, rename)'
          },
          columns: {
            type: 'array',
            items: {
              type: 'object',
              properties: {
                name: { type: 'string' },
                type: { type: 'string' },
                nullable: { type: 'boolean', default: true },
                defaultValue: { type: 'string' },
                primaryKey: { type: 'boolean', default: false }
              },
              required: ['name', 'type']
            },
            description: 'Column definitions for create action'
          },
          columnName: {
            type: 'string',
            description: 'Column name (required for add_column, drop_column)'
          },
          dataType: {
            type: 'string',
            description: 'Data type (required for add_column)'
          },
          newName: {
            type: 'string',
            description: 'New name (required for rename action)'
          },
          options: {
            type: 'object',
            properties: {
              includeViews: { type: 'boolean', default: false },
              includeSystemTables: { type: 'boolean', default: false },
              ifNotExists: { type: 'boolean', default: false },
              ifExists: { type: 'boolean', default: true },
              cascade: { type: 'boolean', default: false },
              temporary: { type: 'boolean', default: false }
            },
            description: 'Action-specific options'
          }
        },
        required: ['action']
      }
    },
  • Primary handler for 'tables' tool execution. Parses arguments, validates inputs, dispatches to TablesAPIClient methods based on action, and formats MCP response.
    private async handleTables(args: any) {
      const { action, tableName, schemaName = 'public', columns, columnName, dataType, newName, options = {} } = args;
    
      switch (action) {
        case 'list':
          return {
            content: [{
              type: 'text',
              text: JSON.stringify(await this.tablesClient.listTables(schemaName, options.includeViews, options.includeSystemTables), null, 2)
            }]
          };
    
        case 'info':
          ParameterValidator.validateRequired(tableName, 'tableName');
          return {
            content: [{
              type: 'text',
              text: JSON.stringify(await this.tablesClient.getTableInfo(tableName, schemaName), null, 2)
            }]
          };
    
        case 'create':
          ParameterValidator.validateRequired(tableName, 'tableName');
          ParameterValidator.validateRequired(columns, 'columns');
          return {
            content: [{
              type: 'text',
              text: JSON.stringify(await this.tablesClient.createTable(tableName, columns, { schema: schemaName, ...options }), null, 2)
            }]
          };
    
        case 'drop':
          ParameterValidator.validateRequired(tableName, 'tableName');
          return {
            content: [{
              type: 'text',
              text: JSON.stringify(await this.tablesClient.dropTable(tableName, schemaName, options.cascade, options.ifExists), null, 2)
            }]
          };
    
        case 'add_column':
          ParameterValidator.validateRequired(tableName, 'tableName');
          ParameterValidator.validateRequired(columnName, 'columnName');
          ParameterValidator.validateRequired(dataType, 'dataType');
          return {
            content: [{
              type: 'text',
              text: JSON.stringify(await this.tablesClient.addColumn(tableName, columnName, dataType, schemaName, options), null, 2)
            }]
          };
    
        default:
          throw new Error(`Unknown tables action: ${action}`);
      }
    }
  • src/index.ts:634-636 (registration)
    Registers all tool definitions (including 'tables') by returning toolDefinitions array in response to ListToolsRequestSchema.
    this.server.setRequestHandler(ListToolsRequestSchema, async () => ({
      tools: toolDefinitions,
    }));
  • TablesAPIClient class implements core table operations (listTables, getTableInfo, createTable, dropTable, addColumn) using direct PostgreSQL queries via DatabaseConnectionManager.
    export class TablesAPIClient {
      constructor(private dbManager: DatabaseConnectionManager) {}
    
      /**
       * List all tables in the database
       */
      async listTables(
        schemaName?: string,
        includeViews: boolean = false,
        includeSystemTables: boolean = false
      ): Promise<TableInfo[]> {
        let sql = `
          SELECT 
            t.table_schema as schema_name,
            t.table_name,
            t.table_type,
            pg_catalog.pg_get_userbyid(c.relowner) as owner,
            c.relhasindex as has_indexes,
            c.relhasrules as has_rules,
            c.relhastriggers as has_triggers,
            pg_catalog.pg_relation_size(c.oid) as size_bytes
          FROM information_schema.tables t
          JOIN pg_catalog.pg_class c ON c.relname = t.table_name
          JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace AND n.nspname = t.table_schema
          WHERE 1=1
        `;
    
        const params: any[] = [];
        let paramIndex = 1;
    
        if (schemaName) {
          const validatedSchema = ParameterValidator.validateSchemaName(schemaName);
          sql += ` AND t.table_schema = $${paramIndex}`;
          params.push(validatedSchema);
          paramIndex++;
        } else if (!includeSystemTables) {
          sql += ` AND t.table_schema NOT IN ('information_schema', 'pg_catalog', 'pg_toast')`;
        }
    
        if (!includeViews) {
          sql += ` AND t.table_type = 'BASE TABLE'`;
        }
    
        sql += ` ORDER BY t.table_schema, t.table_name`;
    
        const result = await this.dbManager.query(sql, params, { readOnly: true });
    
        return result.rows.map((row: any) => ({
          schemaName: row.schema_name,
          tableName: row.table_name,
          tableType: row.table_type,
          owner: row.owner,
          hasIndexes: row.has_indexes,
          hasRules: row.has_rules,
          hasTriggers: row.has_triggers,
          sizeBytes: parseInt(row.size_bytes) || 0
        }));
      }
    
      /**
       * Get detailed information about a specific table
       */
      async getTableInfo(tableName: string, schemaName: string = 'public'): Promise<{
        table: TableInfo;
        columns: ColumnInfo[];
        indexes: any[];
        constraints: any[];
        statistics: any;
      }> {
        const validatedTable = ParameterValidator.validateTableName(tableName);
        const validatedSchema = ParameterValidator.validateSchemaName(schemaName);
    
        // Get table info
        const tableInfo = await this.getTableBasicInfo(validatedTable, validatedSchema);
        
        // Get columns
        const columns = await this.getTableColumns(validatedTable, validatedSchema);
        
        // Get indexes
        const indexes = await this.getTableIndexes(validatedTable, validatedSchema);
        
        // Get constraints
        const constraints = await this.getTableConstraints(validatedTable, validatedSchema);
        
        // Get statistics
        const statistics = await this.getTableStatistics(validatedTable, validatedSchema);
    
        return {
          table: tableInfo,
          columns,
          indexes,
          constraints,
          statistics
        };
      }
    
      /**
       * Create a new table
       */
      async createTable(
        tableName: string,
        columns: Array<{
          name: string;
          type: string;
          nullable?: boolean;
          defaultValue?: string;
          primaryKey?: boolean;
        }>,
        options: CreateTableOptions = {}
      ): Promise<{ success: boolean; message: string }> {
        const validatedTable = ParameterValidator.validateTableName(tableName);
        
        // Validate columns
        if (!columns || columns.length === 0) {
          throw new Error('At least one column is required');
        }
    
        for (const col of columns) {
          ParameterValidator.validateColumnName(col.name);
          ParameterValidator.validateDataType(col.type);
        }
    
        const schema = options.schema ? ParameterValidator.validateSchemaName(options.schema) : 'public';
        const fullTableName = `${schema}.${validatedTable}`;
    
        // Build CREATE TABLE statement
        let sql = 'CREATE';
        
        if (options.temporary) sql += ' TEMPORARY';
        if (options.unlogged) sql += ' UNLOGGED';
        
        sql += ' TABLE';
        
        if (options.ifNotExists) sql += ' IF NOT EXISTS';
        
        sql += ` ${fullTableName} (`;
    
        const columnDefinitions = columns.map(col => {
          let def = `${col.name} ${col.type}`;
          
          if (col.nullable === false) def += ' NOT NULL';
          if (col.defaultValue) def += ` DEFAULT ${col.defaultValue}`;
          if (col.primaryKey) def += ' PRIMARY KEY';
          
          return def;
        }).join(', ');
    
        sql += columnDefinitions + ')';
    
        try {
          await this.dbManager.query(sql);
          
          logger.info('Table created successfully', { 
            tableName: fullTableName,
            columns: columns.length 
          });
          
          return {
            success: true,
            message: `Table ${fullTableName} created successfully with ${columns.length} columns`
          };
        } catch (error) {
          logger.error('Failed to create table', { 
            tableName: fullTableName,
            error: error instanceof Error ? error.message : error 
          });
          throw error;
        }
      }
    
      /**
       * Drop a table
       */
      async dropTable(
        tableName: string,
        schemaName: string = 'public',
        cascade: boolean = false,
        ifExists: boolean = true
      ): Promise<{ success: boolean; message: string }> {
        const validatedTable = ParameterValidator.validateTableName(tableName);
        const validatedSchema = ParameterValidator.validateSchemaName(schemaName);
        const fullTableName = `${validatedSchema}.${validatedTable}`;
    
        let sql = 'DROP TABLE';
        if (ifExists) sql += ' IF EXISTS';
        sql += ` ${fullTableName}`;
        if (cascade) sql += ' CASCADE';
    
        try {
          await this.dbManager.query(sql);
          
          logger.info('Table dropped successfully', { tableName: fullTableName });
          
          return {
            success: true,
            message: `Table ${fullTableName} dropped successfully`
          };
        } catch (error) {
          logger.error('Failed to drop table', { 
            tableName: fullTableName,
            error: error instanceof Error ? error.message : error 
          });
          throw error;
        }
      }
    
      /**
       * Add a column to an existing table
       */
      async addColumn(
        tableName: string,
        columnName: string,
        dataType: string,
        schemaName: string = 'public',
        options: {
          nullable?: boolean;
          defaultValue?: string;
          ifNotExists?: boolean;
        } = {}
      ): Promise<{ success: boolean; message: string }> {
        const validatedTable = ParameterValidator.validateTableName(tableName);
        const validatedColumn = ParameterValidator.validateColumnName(columnName);
        const validatedType = ParameterValidator.validateDataType(dataType);
        const validatedSchema = ParameterValidator.validateSchemaName(schemaName);
        
        const fullTableName = `${validatedSchema}.${validatedTable}`;
    
        let sql = `ALTER TABLE ${fullTableName} ADD`;
        if (options.ifNotExists) sql += ' IF NOT EXISTS';
        sql += ` COLUMN ${validatedColumn} ${validatedType}`;
        
        if (options.nullable === false) sql += ' NOT NULL';
        if (options.defaultValue) sql += ` DEFAULT ${options.defaultValue}`;
    
        try {
          await this.dbManager.query(sql);
          
          logger.info('Column added successfully', { 
            tableName: fullTableName,
            columnName: validatedColumn 
          });
          
          return {
            success: true,
            message: `Column ${validatedColumn} added to ${fullTableName} successfully`
          };
        } catch (error) {
          logger.error('Failed to add column', { 
            tableName: fullTableName,
            columnName: validatedColumn,
            error: error instanceof Error ? error.message : error 
          });
          throw error;
        }
      }
    
      /**
       * Get table columns
       */
      private async getTableColumns(tableName: string, schemaName: string): Promise<ColumnInfo[]> {
        const sql = `
          SELECT 
            c.column_name,
            c.data_type,
            c.is_nullable = 'YES' as is_nullable,
            c.column_default as default_value,
            c.character_maximum_length as max_length,
            c.numeric_precision as precision,
            c.numeric_scale as scale,
            CASE WHEN pk.column_name IS NOT NULL THEN true ELSE false END as is_primary_key,
            CASE WHEN fk.column_name IS NOT NULL THEN true ELSE false END as is_foreign_key
          FROM information_schema.columns c
          LEFT JOIN (
            SELECT ku.column_name
            FROM information_schema.table_constraints tc
            JOIN information_schema.key_column_usage ku 
              ON tc.constraint_name = ku.constraint_name
              AND tc.table_schema = ku.table_schema
            WHERE tc.constraint_type = 'PRIMARY KEY'
              AND tc.table_name = $1
              AND tc.table_schema = $2
          ) pk ON pk.column_name = c.column_name
          LEFT JOIN (
            SELECT ku.column_name
            FROM information_schema.table_constraints tc
            JOIN information_schema.key_column_usage ku 
              ON tc.constraint_name = ku.constraint_name
              AND tc.table_schema = ku.table_schema
            WHERE tc.constraint_type = 'FOREIGN KEY'
              AND tc.table_name = $1
              AND tc.table_schema = $2
          ) fk ON fk.column_name = c.column_name
          WHERE c.table_name = $1 AND c.table_schema = $2
          ORDER BY c.ordinal_position
        `;
    
        const result = await this.dbManager.query(sql, [tableName, schemaName], { readOnly: true });
        
        return result.rows.map((row: any) => ({
          columnName: row.column_name,
          dataType: row.data_type,
          isNullable: row.is_nullable,
          defaultValue: row.default_value,
          isPrimaryKey: row.is_primary_key,
          isForeignKey: row.is_foreign_key,
          maxLength: row.max_length,
          precision: row.precision,
          scale: row.scale
        }));
      }
    
      /**
       * Get table basic info
       */
      private async getTableBasicInfo(tableName: string, schemaName: string): Promise<TableInfo> {
        const sql = `
          SELECT 
            t.table_schema as schema_name,
            t.table_name,
            t.table_type,
            pg_catalog.pg_get_userbyid(c.relowner) as owner,
            c.relhasindex as has_indexes,
            c.relhasrules as has_rules,
            c.relhastriggers as has_triggers,
            pg_catalog.pg_relation_size(c.oid) as size_bytes,
            c.reltuples::bigint as row_count
          FROM information_schema.tables t
          JOIN pg_catalog.pg_class c ON c.relname = t.table_name
          JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace AND n.nspname = t.table_schema
          WHERE t.table_name = $1 AND t.table_schema = $2
        `;
    
        const result = await this.dbManager.query(sql, [tableName, schemaName], { readOnly: true });
        
        if (result.rows.length === 0) {
          throw new Error(`Table ${schemaName}.${tableName} not found`);
        }
    
        const row = result.rows[0];
        return {
          schemaName: row.schema_name,
          tableName: row.table_name,
          tableType: row.table_type,
          owner: row.owner,
          hasIndexes: row.has_indexes,
          hasRules: row.has_rules,
          hasTriggers: row.has_triggers,
          sizeBytes: parseInt(row.size_bytes) || 0,
          rowCount: parseInt(row.row_count) || 0
        };
      }
    
      /**
       * Get table indexes
       */
      private async getTableIndexes(tableName: string, schemaName: string): Promise<any[]> {
        const sql = `
          SELECT 
            indexname,
            indexdef,
            schemaname
          FROM pg_indexes 
          WHERE tablename = $1 AND schemaname = $2
          ORDER BY indexname
        `;
    
        const result = await this.dbManager.query(sql, [tableName, schemaName], { readOnly: true });
        return result.rows;
      }
    
      /**
       * Get table constraints
       */
      private async getTableConstraints(tableName: string, schemaName: string): Promise<any[]> {
        const sql = `
          SELECT 
            tc.constraint_name,
            tc.constraint_type,
            kcu.column_name,
            ccu.table_name AS foreign_table_name,
            ccu.column_name AS foreign_column_name
          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 ccu.constraint_name = tc.constraint_name
            AND ccu.table_schema = tc.table_schema
          WHERE tc.table_name = $1 AND tc.table_schema = $2
          ORDER BY tc.constraint_type, tc.constraint_name
        `;
    
        const result = await this.dbManager.query(sql, [tableName, schemaName], { readOnly: true });
        return result.rows;
      }
    
      /**
       * Get table statistics
       */
      private async getTableStatistics(tableName: string, schemaName: string): Promise<any> {
        const sql = `
          SELECT 
            schemaname,
            tablename,
            seq_scan,
            seq_tup_read,
            idx_scan,
            idx_tup_fetch,
            n_tup_ins,
            n_tup_upd,
            n_tup_del,
            n_tup_hot_upd,
            n_live_tup,
            n_dead_tup,
            last_vacuum,
            last_autovacuum,
            last_analyze,
            last_autoanalyze
          FROM pg_stat_user_tables 
          WHERE tablename = $1 AND schemaname = $2
        `;
    
        const result = await this.dbManager.query(sql, [tableName, schemaName], { readOnly: true });
        return result.rows[0] || {};
      }
    }
  • Instantiates TablesAPIClient for use in the 'tables' tool handler.
    this.tablesClient = new TablesAPIClient(this.dbManager);
    this.schemaClient = new SchemaAPIClient(this.dbManager, this.cache);

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

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