Skip to main content
Glama
HenkDz

PostgreSQL MCP Server

pg_execute_query

Execute PostgreSQL SELECT queries to retrieve data, count rows, or check record existence using parameterized queries with optional safety limits.

Instructions

Execute SELECT queries and data retrieval operations - operation="select/count/exists" with query and optional parameters. Examples: operation="select", query="SELECT * FROM users WHERE created_at > $1", parameters=["2024-01-01"]

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
connectionStringNoPostgreSQL connection string (optional)
operationYesQuery operation: select (fetch rows), count (count rows), exists (check existence)
queryYesSQL SELECT query to execute
parametersNoParameter values for prepared statement placeholders ($1, $2, etc.)
limitNoMaximum number of rows to return (safety limit)
timeoutNoQuery timeout in milliseconds

Implementation Reference

  • The complete definition of the `executeQueryTool` object for `pg_execute_query`, including the `execute` handler function that processes tool arguments, validates input, calls the internal query executor, and formats the response.
    export const executeQueryTool: PostgresTool = {
      name: 'pg_execute_query',
      description: 'Execute SELECT queries and data retrieval operations - operation="select/count/exists" with query and optional parameters. Examples: operation="select", query="SELECT * FROM users WHERE created_at > $1", parameters=["2024-01-01"]',
      inputSchema: ExecuteQueryInputSchema,
      execute: async (args: unknown, getConnectionStringVal: GetConnectionStringFn): Promise<ToolOutput> => {
        const { 
          connectionString: connStringArg,
          operation,
          query,
          parameters,
          limit,
          timeout
        } = args as {
          connectionString?: string;
          operation: 'select' | 'count' | 'exists';
          query: string;
          parameters?: unknown[];
          limit?: number;
          timeout?: number;
        };
    
        const resolvedConnString = getConnectionStringVal(connStringArg);
    
        try {
          // Input validation
          if (!query?.trim()) {
            return { 
              content: [{ type: 'text', text: 'Error: query is required' }], 
              isError: true 
            };
          }
    
          const result = await executeQuery({
            connectionString: resolvedConnString,
            operation,
            query,
            parameters: parameters ?? [],
            limit,
            timeout
          }, getConnectionStringVal);
    
          let responseText = '';
          switch (operation) {
            case 'select':
              responseText = `Query executed successfully. Retrieved ${result.rowCount} rows.\n\nResults:\n${JSON.stringify(result.rows, null, 2)}`;
              break;
            case 'count':
              responseText = `Count query executed successfully. Total rows: ${result.result}`;
              break;
            case 'exists':
              responseText = `Exists query executed successfully. Result: ${result.result ? 'EXISTS' : 'NOT EXISTS'}`;
              break;
          }
    
          return { content: [{ type: 'text', text: responseText }] };
    
        } catch (error) {
          return { 
            content: [{ type: 'text', text: `Error executing ${operation} query: ${error instanceof Error ? error.message : String(error)}` }], 
            isError: true 
          };
        }
      }
    };
  • Zod schema definition (`ExecuteQueryInputSchema`) for input validation of the `pg_execute_query` tool, defining parameters like connectionString, operation, query, parameters, limit, and timeout.
    const ExecuteQueryInputSchema = z.object({
      connectionString: z.string().optional().describe('PostgreSQL connection string (optional)'),
      operation: z.enum(['select', 'count', 'exists']).describe('Query operation: select (fetch rows), count (count rows), exists (check existence)'),
      query: z.string().describe('SQL SELECT query to execute'),
      parameters: z.array(z.unknown()).optional().default([]).describe('Parameter values for prepared statement placeholders ($1, $2, etc.)'),
      limit: z.number().optional().describe('Maximum number of rows to return (safety limit)'),
      timeout: z.number().optional().describe('Query timeout in milliseconds')
    });
  • src/index.ts:225-257 (registration)
    The `allTools` array where `executeQueryTool` (pg_execute_query) is registered alongside other tools. This array is passed to the `PostgreSQLServer` constructor, making the tool available to the MCP server.
    const allTools: PostgresTool[] = [
      // Core Analysis & Debugging
      analyzeDatabaseTool,
      debugDatabaseTool,
      
      // Schema & Structure Management (Meta-Tools)
      manageSchemaTools,
      manageFunctionsTool,
      manageTriggersTools,
      manageIndexesTool,
      manageConstraintsTool,
      manageRLSTool,
      
      // User & Security Management
      manageUsersTool,
      
      // Query & Performance Management
      manageQueryTool,
      
      // Data Operations (Enhancement Tools)
      executeQueryTool,
      executeMutationTool,
      executeSqlTool,
      
      // Documentation & Metadata
      manageCommentsTool,
      
      // Data Migration & Monitoring
      exportTableDataTool,
      importTableDataTool,
      copyBetweenDatabasesTool,
      monitorDatabaseTool
    ];
  • Internal `executeQuery` helper function that handles the core logic: connects to the database, validates SELECT queries, executes based on operation (select/count/exists), applies limits/timeouts, and returns results.
    async function executeQuery(
      input: ExecuteQueryInput,
      getConnectionString: GetConnectionStringFn
    ): Promise<{ operation: string; rowCount: number; rows?: unknown[]; result?: unknown }> {
      const resolvedConnectionString = getConnectionString(input.connectionString);
      const db = DatabaseConnection.getInstance();
      const { operation, query, parameters, limit, timeout } = input;
    
      try {
        await db.connect(resolvedConnectionString);
        
        // Validate query is a SELECT-like operation
        const trimmedQuery = query.trim().toLowerCase();
        if (!trimmedQuery.startsWith('select') && !trimmedQuery.startsWith('with')) {
          throw new McpError(ErrorCode.InvalidParams, 'Query must be a SELECT statement or CTE (WITH clause)');
        }
    
        let finalQuery = query;
        const queryParams = parameters || [];
    
        // Apply limit if specified and not already in query
        if (limit && !trimmedQuery.includes('limit')) {
          finalQuery += ` LIMIT ${limit}`;
        }
    
        const queryOptions = timeout ? { timeout } : {};
    
        switch (operation) {
          case 'select': {
            const rows = await db.query(finalQuery, queryParams, queryOptions);
            return {
              operation: 'select',
              rowCount: rows.length,
              rows: rows
            };
          }
    
          case 'count': {
            // Wrap the query in a COUNT to get total rows
            const countQuery = `SELECT COUNT(*) as total FROM (${query}) as subquery`;
            const result = await db.queryOne<{ total: number }>(countQuery, queryParams, queryOptions);
            return {
              operation: 'count',
              rowCount: 1,
              result: result?.total || 0
            };
          }
    
          case 'exists': {
            // Wrap the query in an EXISTS check
            const existsQuery = `SELECT EXISTS (${query}) as exists`;
            const result = await db.queryOne<{ exists: boolean }>(existsQuery, queryParams, queryOptions);
            return {
              operation: 'exists',
              rowCount: 1,
              result: result?.exists || false
            };
          }
    
          default:
            throw new McpError(ErrorCode.InvalidParams, `Unknown operation: ${operation}`);
        }
      } catch (error) {
        throw new McpError(ErrorCode.InternalError, `Failed to execute query: ${error instanceof Error ? error.message : String(error)}`);
      } finally {
        await db.disconnect();
      }
    }
  • src/index.ts:30-30 (registration)
    Import statement that brings `executeQueryTool` into the main index file for registration in the MCP server.
    import { executeQueryTool, executeMutationTool, executeSqlTool } from './tools/data.js';

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/HenkDz/postgresql-mcp-server'

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