Skip to main content
Glama
cesarvarela

PostgreSQL MCP Server

by cesarvarela

execute-query

Execute parameterized SQL queries on PostgreSQL databases with built-in safety checks to prevent SQL injection. Supports SELECT, INSERT, UPDATE, and DELETE operations.

Instructions

Execute a parameterized SQL query with safety checks. Supports SELECT, INSERT, UPDATE, DELETE operations with parameter binding to prevent SQL injection.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
queryYes
paramsNo
explainNo

Implementation Reference

  • Core handler function for 'execute-query' tool: validates input, checks connection and SQL safety (blocks DROP/TRUNCATE/CREATE/INSERT, requires WHERE for UPDATE/DELETE), optionally runs EXPLAIN, executes query via executePostgresQuery, processes numeric results, formats response with query type, timing, row count.
    export async function executeQuery(
      rawParams: any
    ): McpToolResponse {
      try {
        // Validate and parse parameters
        const params = executeQuerySchema.parse(rawParams);
        
        // Check database connection status
        const connectionStatus = getConnectionStatus();
        if (connectionStatus.status !== 'connected') {
          return createDatabaseUnavailableResponse("execute SQL query");
        }
        
        const { query, params: queryParams, explain } = params;
    
        // Basic security checks
        const trimmedQuery = query.trim().toLowerCase();
        
        // Prevent dangerous operations
        const dangerousPatterns = [
          /drop\s+table/i,
          /drop\s+database/i,
          /drop\s+schema/i,
          /truncate\s+table/i,
          /alter\s+table.*drop/i,
          /alter\s+table.*add/i, // Prevent adding columns
          /create\s+table/i, // Prevent creating tables
          /insert\s+into/i, // Prevent data insertion for security
        ];
    
        // Check for DELETE/UPDATE without WHERE clause
        if (trimmedQuery.startsWith('delete from') && !trimmedQuery.includes(' where ')) {
          throw new Error(`DELETE without WHERE clause is not allowed for safety.`);
        }
        if (trimmedQuery.startsWith('update ') && trimmedQuery.includes(' set ') && !trimmedQuery.includes(' where ')) {
          throw new Error(`UPDATE without WHERE clause is not allowed for safety.`);
        }
    
        for (const pattern of dangerousPatterns) {
          if (pattern.test(query)) {
            throw new Error(`Potentially dangerous SQL operation detected. Query rejected for safety.`);
          }
        }
    
    
        const startTime = Date.now();
        let results: any[];
        let executionPlan: any[] | undefined;
    
        // Execute EXPLAIN if requested
        if (explain) {
          const explainQuery = `EXPLAIN (FORMAT JSON, ANALYZE, BUFFERS) ${query}`;
          try {
            executionPlan = await executePostgresQuery(explainQuery, queryParams);
          } catch (explainError) {
            debug("Failed to get execution plan: %o", explainError);
            // Continue with normal execution even if EXPLAIN fails
          }
        }
    
        // Execute the main query
        results = await executePostgresQuery(query, queryParams);
        const executionTime = Date.now() - startTime;
    
        // Convert numeric strings to numbers for better usability
        results = results.map(row => {
          const convertedRow: any = {};
          for (const [key, value] of Object.entries(row)) {
            if (typeof value === 'string' && value !== '' && !isNaN(Number(value))) {
              // Only convert if it's a proper numeric string
              const numValue = Number(value);
              if (Number.isInteger(numValue) || !Number.isNaN(numValue)) {
                convertedRow[key] = numValue;
              } else {
                convertedRow[key] = value;
              }
            } else {
              convertedRow[key] = value;
            }
          }
          return convertedRow;
        });
    
        // Determine query type
        let queryType = "SELECT";
        if (trimmedQuery.startsWith("insert")) {
          queryType = "INSERT";
        } else if (trimmedQuery.startsWith("update")) {
          queryType = "UPDATE";
        } else if (trimmedQuery.startsWith("delete")) {
          queryType = "DELETE";
        } else if (trimmedQuery.startsWith("create")) {
          queryType = "CREATE";
        } else if (trimmedQuery.startsWith("alter")) {
          queryType = "ALTER";
        }
    
        const response = {
          success: true,
          query_type: queryType,
          execution_time_ms: executionTime,
          row_count: results.length,
          data: results,
          results: results, // Add for backward compatibility with tests
          ...(executionPlan && { execution_plan: executionPlan }),
          executed_at: new Date().toISOString(),
        };
    
        debug("Query executed successfully: %s rows in %dms", results.length, executionTime);
        return createMcpSuccessResponse(response);
    
      } catch (error) {
        return createMcpErrorResponse("execute query", error);
      }
    }
  • Zod schema defining inputs for execute-query: 'query' (required SQL string), 'params' (optional array of parameters), 'explain' (optional boolean for execution plan).
    // Zod schema for input validation
    export const executeQueryShape: ZodRawShape = {
      query: z.string().min(1, "SQL query is required"),
      params: z.array(z.any()).optional().default([]),
      explain: z.boolean().optional().default(false),
    };
    
    export const executeQuerySchema = z.object(executeQueryShape);
  • index.ts:41-46 (registration)
    Registration of 'execute-query' tool in McpServer with description, input schema (executeQueryShape), and handler (executeQuery).
    server.tool(
      "execute-query",
      "Execute a parameterized SQL query with safety checks. Supports SELECT, INSERT, UPDATE, DELETE operations with parameter binding to prevent SQL injection.",
      executeQueryShape,
      executeQuery
    );
Behavior3/5

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

With no annotations provided, the description carries the full burden. It discloses safety checks and parameter binding to prevent SQL injection, which are useful behavioral traits. However, it lacks details on permissions, rate limits, error handling, or what 'safety checks' entail, leaving gaps for a mutation-capable tool.

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

Conciseness4/5

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

The description is appropriately concise with two sentences that are front-loaded and avoid redundancy. Each sentence adds value: the first states the core functionality, and the second expands on supported operations and safety features.

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 (supports mutations, 3 parameters with 0% schema coverage, no output schema, and no annotations), the description is incomplete. It lacks details on return values, error conditions, transactional behavior, and when to use versus siblings, making it inadequate for safe agent operation.

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

Parameters2/5

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

Schema description coverage is 0%, so the description must compensate but adds minimal parameter semantics. It mentions 'parameterized SQL query' and 'parameter binding' hinting at 'query' and 'params', but doesn't explain 'explain' or provide syntax/format details. With 3 parameters undocumented, this is insufficient.

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 a parameterized SQL query with safety checks' specifies the verb (execute) and resource (SQL query), and mentions support for SELECT, INSERT, UPDATE, DELETE operations. It distinguishes from siblings like 'query-table' by being more general, though not explicitly contrasting them.

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 like 'query-table', 'insert-data', 'update-data', or 'delete-data'. It mentions support for various SQL operations but doesn't specify contexts, prerequisites, or exclusions for choosing among siblings.

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

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