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);
Behavior2/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations provided, the description carries the full burden of behavioral disclosure but offers minimal information. It lists actions but doesn't describe their effects (e.g., that 'drop' is destructive, 'create' requires permissions, or how errors are handled). For a complex tool with 8 parameters and no annotations, this leaves critical behavioral traits undocumented.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is extremely concise—a single, well-structured sentence that efficiently lists all key actions without redundancy. It's front-loaded with the core purpose ('Table management') and uses a colon to enumerate operations, making every word earn its place with zero waste.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness2/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given the tool's complexity (8 parameters, no annotations, no output schema), the description is incomplete. It lacks information on behavioral traits, error handling, permissions, or output expectations. While the schema covers parameters well, the description fails to compensate for the absence of annotations and output schema, leaving gaps in contextual understanding.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The schema description coverage is 100%, providing detailed parameter documentation. The description adds no parameter-specific information beyond the high-level action list, which the schema already covers comprehensively. This meets the baseline of 3, as the schema does the heavy lifting, but the description doesn't enhance parameter understanding.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the tool's purpose as 'Table management: list, create, alter, drop tables and get detailed table information', which specifies the verb (manage) and resource (tables) with concrete actions. However, it doesn't differentiate this tool from potential siblings like 'schemas' or 'data' that might also handle table-related operations, preventing a perfect score.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines2/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description provides no guidance on when to use this tool versus alternatives. It doesn't mention prerequisites, context for choosing specific actions, or how it relates to sibling tools like 'schemas' or 'data'. The agent must infer usage solely from the action parameter, which is insufficient for optimal tool selection.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Install Server

Other Tools

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