Skip to main content
Glama
itsalfredakku

Postgres MCP Server

query

Execute SQL queries with transaction support, analyze performance, validate syntax, and manage active queries in PostgreSQL databases.

Instructions

Execute SQL queries with transaction support, query analysis, and performance monitoring

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
actionYesAction: execute (single query), transaction (multiple queries), explain (execution plan), analyze (performance), validate (syntax), cancel (query by PID), active (list active queries)
sqlNoSQL query to execute (required for execute, explain, analyze, validate actions)
parametersNoQuery parameters for parameterized queries
queriesNoArray of queries for transaction action
optionsNoQuery execution options
pidNoProcess ID of query to cancel (required for cancel action)

Implementation Reference

  • Main handler function for the 'query' tool. Dispatches to QueryAPIClient methods based on the 'action' parameter (execute, transaction, explain, analyze, validate, active, cancel).
    private async handleQuery(args: any) {
      const { action, sql, parameters, queries, options, pid } = args;
    
      switch (action) {
        case 'execute':
          ParameterValidator.validateRequired(sql, 'sql');
          return {
            content: [{
              type: 'text',
              text: JSON.stringify(await this.queryClient.executeQuery(sql, parameters, options), null, 2)
            }]
          };
    
        case 'transaction':
          ParameterValidator.validateRequired(queries, 'queries');
          return {
            content: [{
              type: 'text',
              text: JSON.stringify(await this.queryClient.executeTransaction(queries, options?.readOnly), null, 2)
            }]
          };
    
        case 'explain':
          ParameterValidator.validateRequired(sql, 'sql');
          return {
            content: [{
              type: 'text',
              text: JSON.stringify(await this.queryClient.getExecutionPlan(sql, parameters), null, 2)
            }]
          };
    
        case 'analyze':
          ParameterValidator.validateRequired(sql, 'sql');
          return {
            content: [{
              type: 'text',
              text: JSON.stringify(await this.queryClient.analyzeQuery(sql, parameters), null, 2)
            }]
          };
    
        case 'validate':
          ParameterValidator.validateRequired(sql, 'sql');
          return {
            content: [{
              type: 'text',
              text: JSON.stringify(await this.queryClient.validateSyntax(sql), null, 2)
            }]
          };
    
        case 'active':
          return {
            content: [{
              type: 'text',
              text: JSON.stringify(await this.queryClient.getActiveQueries(), null, 2)
            }]
          };
    
        case 'cancel':
          ParameterValidator.validateRequired(pid, 'pid');
          return {
            content: [{
              type: 'text',
              text: JSON.stringify({ cancelled: await this.queryClient.cancelQuery(pid) }, null, 2)
            }]
          };
    
        default:
          throw new Error(`Unknown query action: ${action}`);
      }
    }
  • JSON Schema defining the input parameters and structure for the 'query' tool.
    {
      name: 'query',
      description: 'Execute SQL queries with transaction support, query analysis, and performance monitoring',
      inputSchema: {
        type: 'object',
        properties: {
          action: {
            type: 'string',
            enum: ['execute', 'transaction', 'explain', 'analyze', 'validate', 'cancel', 'active'],
            description: 'Action: execute (single query), transaction (multiple queries), explain (execution plan), analyze (performance), validate (syntax), cancel (query by PID), active (list active queries)'
          },
          sql: {
            type: 'string',
            description: 'SQL query to execute (required for execute, explain, analyze, validate actions)'
          },
          parameters: {
            type: 'array',
            items: { type: 'string' },
            description: 'Query parameters for parameterized queries'
          },
          queries: {
            type: 'array',
            items: {
              type: 'object',
              properties: {
                sql: { type: 'string' },
                parameters: { type: 'array', items: { type: 'string' } }
              },
              required: ['sql']
            },
            description: 'Array of queries for transaction action'
          },
          options: {
            type: 'object',
            properties: {
              timeout: { type: 'integer', description: 'Query timeout in milliseconds' },
              limit: { type: 'integer', description: 'Maximum number of rows to return' },
              offset: { type: 'integer', description: 'Number of rows to skip' },
              readOnly: { type: 'boolean', description: 'Execute as read-only transaction' }
            },
            description: 'Query execution options'
          },
          pid: {
            type: 'integer',
            description: 'Process ID of query to cancel (required for cancel action)'
          }
        },
        required: ['action']
      }
    },
  • src/index.ts:634-637 (registration)
    Registration of tool definitions via ListToolsRequestSchema handler, which returns the list including 'query' tool schema.
    this.server.setRequestHandler(ListToolsRequestSchema, async () => ({
      tools: toolDefinitions,
    }));
  • src/index.ts:644-645 (registration)
    Dispatch registration in CallToolRequestSchema handler switch statement, routing 'query' calls to handleQuery method.
    return await this.handleQuery(args);
  • QueryAPIClient class providing the core query execution, transaction, analysis, and monitoring functionality used by the 'query' tool handler.
    export class QueryAPIClient {
      constructor(private dbManager: DatabaseConnectionManager) {}
    
      /**
       * Execute a SQL query
       */
      async executeQuery(
        sql: string, 
        parameters?: any[], 
        options: QueryOptions = {}
      ): Promise<QueryExecutionResult> {
        const startTime = Date.now();
        
        // Validate SQL
        const validatedSql = ParameterValidator.validateSql(sql);
        
        // Add LIMIT if specified and not already present
        let finalSql = validatedSql;
        if (options.limit && !finalSql.toUpperCase().includes('LIMIT')) {
          finalSql += ` LIMIT ${ParameterValidator.validateLimit(options.limit)}`;
        }
        
        // Add OFFSET if specified
        if (options.offset && !finalSql.toUpperCase().includes('OFFSET')) {
          finalSql += ` OFFSET ${ParameterValidator.validateOffset(options.offset)}`;
        }
        
        // Add EXPLAIN if requested
        if (options.explain) {
          const explainPrefix = options.analyze ? 'EXPLAIN (ANALYZE, BUFFERS)' : 'EXPLAIN';
          finalSql = `${explainPrefix} ${finalSql}`;
        }
    
        try {
          const result = await this.dbManager.query(finalSql, parameters, {
            timeout: options.timeout,
            readOnly: this.isReadOnlyQuery(validatedSql)
          });
    
          const duration = Date.now() - startTime;
    
          return {
            rows: result.rows,
            rowCount: result.rowCount || 0,
            fields: result.fields.map((field: any) => ({
              name: field.name,
              dataTypeID: field.dataTypeID,
              dataTypeSize: field.dataTypeSize,
              dataTypeModifier: field.dataTypeModifier,
              format: field.format
            })),
            command: result.command,
            duration
          };
        } catch (error) {
          logger.error('Query execution failed', { 
            sql: sql.substring(0, 100),
            parameters,
            error: error instanceof Error ? error.message : error 
          });
          throw error;
        }
      }
    
      /**
       * Execute multiple queries in a transaction
       */
      async executeTransaction(
        queries: Array<{ sql: string; parameters?: any[] }>,
        readOnly: boolean = false
      ): Promise<QueryExecutionResult[]> {
        const transactionId = await this.dbManager.beginTransaction(readOnly);
        
        try {
          const results: QueryExecutionResult[] = [];
          
          for (const query of queries) {
            const startTime = Date.now();
            const validatedSql = ParameterValidator.validateSql(query.sql);
            
            const result = await this.dbManager.queryInTransaction(
              transactionId,
              validatedSql,
              query.parameters
            );
            
            const duration = Date.now() - startTime;
            
            results.push({
              rows: result.rows,
              rowCount: result.rowCount || 0,
              fields: result.fields.map((field: any) => ({
                name: field.name,
                dataTypeID: field.dataTypeID,
                dataTypeSize: field.dataTypeSize,
                dataTypeModifier: field.dataTypeModifier,
                format: field.format
              })),
              command: result.command,
              duration
            });
          }
          
          await this.dbManager.commitTransaction(transactionId);
          return results;
          
        } catch (error) {
          await this.dbManager.rollbackTransaction(transactionId);
          throw error;
        }
      }
    
      /**
       * Get query execution plan
       */
      async getExecutionPlan(
        sql: string, 
        parameters?: any[],
        analyze: boolean = false
      ): Promise<any[]> {
        const validatedSql = ParameterValidator.validateSql(sql);
        const explainSql = analyze 
          ? `EXPLAIN (ANALYZE, BUFFERS, FORMAT JSON) ${validatedSql}`
          : `EXPLAIN (FORMAT JSON) ${validatedSql}`;
        
        const result = await this.dbManager.query(explainSql, parameters, { readOnly: true });
        return result.rows[0]['QUERY PLAN'];
      }
    
      /**
       * Analyze query performance
       */
      async analyzeQuery(sql: string, parameters?: any[]): Promise<{
        executionPlan: any[];
        statistics: {
          planningTime: number;
          executionTime: number;
          totalCost: number;
          rows: number;
        };
      }> {
        const plan = await this.getExecutionPlan(sql, parameters, true);
        
        return {
          executionPlan: plan,
          statistics: {
            planningTime: plan[0]['Planning Time'] || 0,
            executionTime: plan[0]['Execution Time'] || 0,
            totalCost: plan[0]['Total Cost'] || 0,
            rows: plan[0]['Actual Rows'] || 0
          }
        };
      }
    
      /**
       * Validate SQL syntax without execution
       */
      async validateSyntax(sql: string): Promise<{ valid: boolean; error?: string }> {
        try {
          // Use EXPLAIN to validate syntax without execution
          const validatedSql = ParameterValidator.validateSql(sql);
          await this.dbManager.query(`EXPLAIN ${validatedSql}`, [], { readOnly: true });
          return { valid: true };
        } catch (error) {
          return { 
            valid: false, 
            error: error instanceof Error ? error.message : 'Unknown syntax error' 
          };
        }
      }
    
      /**
       * Get active queries
       */
      async getActiveQueries(): Promise<any[]> {
        const sql = `
          SELECT 
            pid,
            now() - pg_stat_activity.query_start AS duration,
            query,
            state,
            client_addr,
            application_name
          FROM pg_stat_activity 
          WHERE state = 'active'
            AND query NOT ILIKE '%pg_stat_activity%'
          ORDER BY duration DESC
        `;
        
        const result = await this.dbManager.query(sql, [], { readOnly: true });
        return result.rows;
      }
    
      /**
       * Cancel a query by PID
       */
      async cancelQuery(pid: number): Promise<boolean> {
        const validatedPid = ParameterValidator.validateNumber(pid, 'pid', 1);
        
        const result = await this.dbManager.query(
          'SELECT pg_cancel_backend($1) as cancelled',
          [validatedPid]
        );
        
        return result.rows[0]?.cancelled || false;
      }
    
      /**
       * Get query statistics
       */
      async getQueryStatistics(
        schemaName?: string,
        limit: number = 50
      ): Promise<any[]> {
        const validatedLimit = ParameterValidator.validateLimit(limit);
        
        let sql = `
          SELECT 
            schemaname,
            tablename,
            seq_scan,
            seq_tup_read,
            idx_scan,
            idx_tup_fetch,
            n_tup_ins,
            n_tup_upd,
            n_tup_del
          FROM pg_stat_user_tables
        `;
        
        const params: any[] = [];
        
        if (schemaName) {
          const validatedSchema = ParameterValidator.validateSchemaName(schemaName);
          sql += ` WHERE schemaname = $1`;
          params.push(validatedSchema);
        }
        
        sql += ` ORDER BY seq_scan + idx_scan DESC LIMIT $${params.length + 1}`;
        params.push(validatedLimit);
        
        const result = await this.dbManager.query(sql, params, { readOnly: true });
        return result.rows;
      }
    
      /**
       * Check if query is read-only
       */
      private isReadOnlyQuery(sql: string): boolean {
        const writeOperations = [
          'INSERT', 'UPDATE', 'DELETE', 'CREATE', 'DROP', 'ALTER',
          'TRUNCATE', 'GRANT', 'REVOKE', 'COPY'
        ];
        
        const normalizedSql = sql.trim().toUpperCase();
        return !writeOperations.some(op => normalizedSql.startsWith(op));
      }
    }
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. While it mentions 'transaction support, query analysis, and performance monitoring', it doesn't address critical behavioral aspects: whether queries can modify data, authentication requirements, rate limits, error handling, or what happens with long-running queries. For a tool with multiple actions including potentially destructive operations, this is insufficient.

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 sentence that efficiently communicates the core functionality and key features. Every word earns its place: 'Execute SQL queries' establishes the primary purpose, while 'with transaction support, query analysis, and performance monitoring' efficiently lists additional capabilities without redundancy or unnecessary elaboration.

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?

For a complex tool with 6 parameters, multiple actions (including potentially destructive ones like 'execute'), no annotations, and no output schema, the description is inadequate. It doesn't explain what the tool returns, error conditions, security implications, or how the different actions relate to each other. The single sentence description fails to provide sufficient context for safe and effective use of this multi-faceted tool.

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?

Schema description coverage is 100%, so the schema already documents all 6 parameters thoroughly. The description doesn't add any parameter-specific information beyond what's in the schema. It mentions general capabilities but provides no additional context about parameter usage, relationships between parameters, or practical examples. Baseline 3 is appropriate when the schema does the heavy lifting.

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: 'Execute SQL queries' with additional capabilities like 'transaction support, query analysis, and performance monitoring'. It specifies the verb ('Execute') and resource ('SQL queries'), but doesn't explicitly differentiate from sibling tools like 'transactions' or 'monitoring' which might have overlapping functionality.

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. With sibling tools like 'transactions', 'monitoring', 'admin', and 'data', there's no indication of which scenarios call for this multi-action query tool versus those specialized tools. The description only lists capabilities without contextual usage advice.

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