Skip to main content
Glama
martymarkenson

PostgreSQL MCP Server

Execute SQL Query

execute-sql-query

Execute SELECT queries on PostgreSQL databases to retrieve data securely through the MCP server, supporting read-only operations with built-in security validation.

Instructions

Execute a SQL query on the database. Only SELECT statements are allowed for security.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
sqlYesThe SQL query to execute

Implementation Reference

  • The core handler function for the 'execute-sql-query' tool. It validates the SQL query using sqlValidation, executes it safely with initDb().unsafe(sql), and returns the JSON-formatted results or an error message.
    async ({ sql }) => {
      const validation = sqlValidation(sql);
      if (!validation.success) {
        return {
          content: [{
            type: "text",
            text: `❌ Query validation failed: ${validation.message}`
          }],
          isError: true
        };
      }
      try {
        const results = await initDb().unsafe(sql);
        return {
          content: [{
            type: "text",
            text: JSON.stringify(results, null, 2)
          }]
        };
      } catch (err: unknown) {
        const error = err as Error;
        return {
          content: [{
            type: "text",
            text: `❌ Query execution failed: ${error.message}`
          }],
          isError: true
        };
      }
    }
  • src/server.ts:165-204 (registration)
    The registration call for the 'execute-sql-query' tool, specifying the name, metadata (title, description), input schema using Zod, and the handler function.
    server.registerTool(
      "execute-sql-query",
      {
        title: "Execute SQL Query",
        description: "Execute a SQL query on the database. Only SELECT statements are allowed for security.",
        inputSchema: {
          sql: z.string().describe("The SQL query to execute")
        },
      },
      async ({ sql }) => {
        const validation = sqlValidation(sql);
        if (!validation.success) {
          return {
            content: [{
              type: "text",
              text: `❌ Query validation failed: ${validation.message}`
            }],
            isError: true
          };
        }
        try {
          const results = await initDb().unsafe(sql);
          return {
            content: [{
              type: "text",
              text: JSON.stringify(results, null, 2)
            }]
          };
        } catch (err: unknown) {
          const error = err as Error;
          return {
            content: [{
              type: "text",
              text: `❌ Query execution failed: ${error.message}`
            }],
            isError: true
          };
        }
      }
    );
  • The input schema definition for the tool, using Zod to validate a single 'sql' string parameter.
    inputSchema: {
      sql: z.string().describe("The SQL query to execute")
    },
  • SQL validation helper function used by the handler to ensure only safe (SELECT) queries are executed by blocking dangerous SQL keywords.
    function sqlValidation(sql: string) {
      const dangerousKeywords = ["DROP", "DELETE", "TRUNCATE", "ALTER", "CREATE", "INSERT", "UPDATE"];
      
      // check if these keywords appear at the start of statements allows them in column names, comments, etc.
      const statements = sql.split(';').map(s => s.trim()).filter(s => s.length > 0);
      
      for (const statement of statements) {
        // Skip empty statements and comments
        if (!statement || statement.startsWith('--') || statement.startsWith('/*')) {
          continue;
        }
        
        // Check if statement starts with a dangerous keyword
        const firstWord = statement.split(/\s+/)[0].toUpperCase();
        if (dangerousKeywords.includes(firstWord)) {
          return {
            success: false,
            message: `Dangerous operation detected: ${firstWord}. Only SELECT statements are allowed.`
          };
        }
      }
      
      return {
        success: true,
        message: "SQL is safe"
      };
    }
  • Database initialization helper that lazily creates and caches the database connection using getDb() for reuse across tool calls.
    const initDb = () => {
      if(!dbConnection) {
        dbConnection = getDb();
      }
      return dbConnection;
    };
Behavior4/5

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

With no annotations provided, the description carries full burden. It discloses important behavioral traits: the tool executes SQL queries (implies read/write potential) but restricts to SELECT statements for security. However, it doesn't mention other behavioral aspects like error handling, timeout behavior, or result format.

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?

Two sentences with zero waste. The first sentence states the core purpose, the second adds critical security constraint. Every word earns its place, and the most important information (SELECT-only restriction) is appropriately positioned.

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

Completeness4/5

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

For a single-parameter tool with no annotations and no output schema, the description provides good coverage of purpose and security constraints. However, it doesn't describe what the tool returns (query results format, error responses), which would be helpful given the lack of output schema.

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% (the 'sql' parameter is fully documented in the schema), so the baseline is 3. The description adds no additional parameter information beyond what the schema already provides about the SQL query parameter.

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

Purpose5/5

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

The description clearly states the specific action ('Execute a SQL query') and resource ('on the database'), distinguishing it from sibling tools like 'get-all-tables' (which lists tables) and 'test-postgres-connection' (which tests connectivity). It provides a complete verb+resource+scope combination.

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

Usage Guidelines5/5

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

The description explicitly states 'Only SELECT statements are allowed for security', providing clear when-to-use guidance (for SELECT queries) and when-not-to-use (for non-SELECT operations like INSERT/UPDATE/DELETE). This creates a strong usage boundary without naming alternatives.

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/martymarkenson/Postgres-Connector-MCP'

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