Skip to main content
Glama
YawLabs

@yawlabs/postgres-mcp

by YawLabs

Explain query plan

pg_explain
Destructive

Retrieve PostgreSQL query plans for SQL statements, optionally running EXPLAIN ANALYZE, and inspect buffers, planner settings, memory, or hypothetical indexes to diagnose performance issues.

Instructions

Get the query plan for a SQL statement. By default, this uses plain EXPLAIN (no execution). Set analyze: true to run the query with EXPLAIN ANALYZE - for non-SELECT statements, ALLOW_WRITES=1 is required (since ANALYZE actually executes the statement). Writes executed during EXPLAIN ANALYZE are rolled back, so you can inspect a plan for an INSERT/UPDATE/DELETE without persisting the rows -- but only what Postgres rolls back is undone: a sequence the statement advanced (nextval, a serial or identity column) stays advanced, and any side effect of a function the statement called that lands outside table data (pg_terminate_backend, an advisory lock) persists. Format is text (default) or json. Pass the raw SQL (not an EXPLAIN-prefixed statement). Planner options (all optional): buffers reports shared/local/temp block hits and is the fastest way to tell a bad plan from a cold cache - it defaults to TRUE whenever analyze is true (matching PostgreSQL 18, which turns it on for you), pass buffers: false to suppress it; requesting it WITHOUT analyze needs PostgreSQL 13+. verbose adds output columns and schema-qualified names. settings (PostgreSQL 12+) lists planner GUCs set away from their defaults - the usual explanation for a plan that looks impossible. wal (PostgreSQL 13+) reports WAL generated and serialize (none|text|binary, PostgreSQL 17+) charges the cost of building the result rows; both require analyze. memory (PostgreSQL 17+) reports memory used by the PLANNER, so it works with or without analyze - use it alone to ask why planning a statement is expensive. generic_plan (PostgreSQL 16+) plans a parameterized statement WITHOUT values for its $1/$2 placeholders and cannot be combined with analyze or params. costs and timing default to true (as in postgres); set either to false to drop those columns, and note timing only applies with analyze. Options that need a newer server than the one connected are rejected with an explicit error naming the required version instead of a confusing parse failure. Set hypothetical_indexes to a list of {table, columns, using?} to ask the planner 'what would the plan be if these indexes existed?' -- requires the HypoPG extension (CREATE EXTENSION hypopg). The hypothetical indexes are torn down at the end of the call, never touching real disk.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
sqlYesThe SQL statement to explain. Do NOT prefix with EXPLAIN.
walNoReport WAL generated by the statement. Requires `analyze` (PostgreSQL 13+).
costsNoInclude estimated cost/rows/width. Set false for a terser plan.
formatNoOutput format.text
memoryNoReport memory used by the planner (PostgreSQL 17+). Works with or without `analyze`, since planning happens either way.
paramsNoPositional parameters referenced as $1, $2, ... in the SQL.
timingNoInclude per-node actual timing. Setting it to false REQUIRES `analyze: true` (it is rejected otherwise, not silently ignored); false lowers measurement overhead.
analyzeNoRun EXPLAIN ANALYZE (actually executes the query).
buffersNoReport buffer hits/reads/dirtied. Defaults to TRUE when `analyze` is true (PostgreSQL 18 does the same); pass false to suppress. Requesting it without `analyze` requires PostgreSQL 13+.
verboseNoInclude output columns, schema-qualified names, and triggers.
settingsNoReport planner GUCs set away from their defaults - explains a weird plan (PostgreSQL 12+).
serializeNoCharge the cost of serializing result rows (network-bound queries hide it otherwise). Requires `analyze` (PostgreSQL 17+).
generic_planNoPlan the statement with UNKNOWN values for its $1/$2 placeholders - the plan a prepared statement would get. Cannot be combined with `analyze` or `params` (PostgreSQL 16+).
hypothetical_indexesNoList of indexes the planner should pretend exist for this EXPLAIN. Requires the HypoPG extension. Indexes are session-scoped and reset at the end of the call.

Output Schema

TableJSON Schema
NameRequiredDescriptionDefault
planYesNewline-joined plan text for `format: "text"` (with a trailing truncation marker when POSTGRES_MAX_ROWS chopped it), or the parsed plan array for `format: "json"`.

Schema Changelog

Changes observed during successful MCP inspections.

  1. Changed14 schema fields changedv0.12.0
    • addedInput schema / $defs
      Added value: +{
      +  "__schema0": {
      +    "anyOf": [
      +      {
      +        "type": "string"
      +      },
      +      {
      +        "type": "number"
      +      },
      +      {
      +        "type": "boolean"
      +      },
      +      {
      +        "type": "null"
      +      },
      +      {
      +        "items": {
      +          "$ref": "#/$defs/__schema0"
      +        },
      +        "type": "array"
      +      },
      +      {
      +        "additionalProperties": {
      +          "$ref": "#/$defs/__schema0"
      +        },
      +        "propertyNames": {
      +          "type": "string"
      +        },
      +        "type": "object"
      +      }
      +    ]
      +  }
      +}
    • changedInput schema / $schema
      Previous value: -"http://json-schema.org/draft-07/schema#"New value: +"https://json-schema.org/draft/2020-12/schema"
    • removedInput schema / definitions
      Removed value: -{
      -  "__schema0": {
      -    "anyOf": [
      -      {
      -        "type": "string"
      -      },
      -      {
      -        "type": "number"
      -      },
      -      {
      -        "type": "boolean"
      -      },
      -      {
      -        "type": "null"
      -      },
      -      {
      -        "items": {
      -          "$ref": "#/definitions/__schema0"
      -        },
      -        "type": "array"
      -      },
      -      {
      -        "additionalProperties": {
      -          "$ref": "#/definitions/__schema0"
      -        },
      -        "propertyNames": {
      -          "type": "string"
      -        },
      -        "type": "object"
      -      }
      -    ]
      -  }
      -}
    • addedInput schema / properties / buffers
      Added value: +{
      +  "description": "Report buffer hits/reads/dirtied. Defaults to TRUE when `analyze` is true (PostgreSQL 18 does the same); pass false to suppress. Requesting it without `analyze` requires PostgreSQL 13+.",
      +  "type": "boolean"
      +}
    • addedInput schema / properties / costs
      Added value: +{
      +  "default": true,
      +  "description": "Include estimated cost/rows/width. Set false for a terser plan.",
      +  "type": "boolean"
      +}
    • addedInput schema / properties / generic_plan
      Added value: +{
      +  "default": false,
      +  "description": "Plan the statement with UNKNOWN values for its $1/$2 placeholders - the plan a prepared statement would get. Cannot be combined with `analyze` or `params` (PostgreSQL 16+).",
      +  "type": "boolean"
      +}
    • addedInput schema / properties / memory
      Added value: +{
      +  "default": false,
      +  "description": "Report memory used by the planner (PostgreSQL 17+). Works with or without `analyze`, since planning happens either way.",
      +  "type": "boolean"
      +}
    • changedInput schema / properties / params / items / $ref
      Previous value: -"#/definitions/__schema0"New value: +"#/$defs/__schema0"
    • addedInput schema / properties / serialize
      Added value: +{
      +  "description": "Charge the cost of serializing result rows (network-bound queries hide it otherwise). Requires `analyze` (PostgreSQL 17+).",
      +  "enum": [
      +    "none",
      +    "text",
      +    "binary"
      +  ],
      +  "type": "string"
      +}
    • addedInput schema / properties / settings
      Added value: +{
      +  "default": false,
      +  "description": "Report planner GUCs set away from their defaults - explains a weird plan (PostgreSQL 12+).",
      +  "type": "boolean"
      +}
    • addedInput schema / properties / timing
      Added value: +{
      +  "default": true,
      +  "description": "Include per-node actual timing. Setting it to false REQUIRES `analyze: true` (it is rejected otherwise, not silently ignored); false lowers measurement overhead.",
      +  "type": "boolean"
      +}
    • addedInput schema / properties / verbose
      Added value: +{
      +  "default": false,
      +  "description": "Include output columns, schema-qualified names, and triggers.",
      +  "type": "boolean"
      +}
    • addedInput schema / properties / wal
      Added value: +{
      +  "default": false,
      +  "description": "Report WAL generated by the statement. Requires `analyze` (PostgreSQL 13+).",
      +  "type": "boolean"
      +}
    • changedOutput schema / (root)
      Previous value: -nullNew value: +{
      +  "$schema": "https://json-schema.org/draft/2020-12/schema",
      +  "additionalProperties": false,
      +  "properties": {
      +    "plan": {
      +      "anyOf": [
      +        {
      +          "type": "string"
      +        },
      +        {
      +          "items": {},
      +          "type": "array"
      +        }
      +      ],
      +      "description": "Newline-joined plan text for `format: \"text\"` (with a trailing truncation marker when POSTGRES_MAX_ROWS chopped it), or the parsed plan array for `format: \"json\"`."
      +    }
      +  },
      +  "required": [
      +    "plan"
      +  ],
      +  "type": "object"
      +}
  2. First observedv0.7.0

TDQS

A4.3/5.0
Behavior5/5

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

The description goes far beyond the annotations, disclosing that EXPLAIN ANALYZE actually executes the statement, that writes are rolled back but sequences and side effects outside table data persist, and that hypothetical indexes are torn down. It also clarifies error behavior with version mismatches tolerant. This is a rich, honest picture of the tool's side effects and non-deterministic aspects, fully consistent with the destructiveHint and openWorldHint annotations.

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 long, but appropriately so for a tool with 14 parameters and many cross-options constraints. It is front-loaded with the core behavior (plain EXPLAIN vs ANALYZE) and then systematically walks through each option. It is a single dense paragraph rather than bulleted, which slightly harms scannability, but every sentence adds needed operational detail (version gates, defaults, side effects) and none feels redundant.

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

Completeness5/5

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

For a tool of this complexity, the description is remarkably complete. It covers default behavior, execution vs planning, side effects of ANALYZE, rollback boundaries, version requirements for every optional feature, error handling, and the hypothetical-index workflow. The presence of an output schema means return-value documentation is handled elsewhere, so nothing critical is missing for an agent to call this correctly.

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

Parameters5/5

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

The input schema already describes every parameter (100% coverage), but the description adds substantial value: it explains why one might use buffers ('fastest way to tell a bad plan from a cold cache'), what settings reveals about impossible-looking plans, the interaction between timing and analyze, and that generic_plan cannot be combined with analyze or params. These are actionable insights an agent needs to choose parameters intelligently, well beyond the schema's terse descriptions.

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 opens with 'Get the query plan for a SQL statement' – a specific verb and resource that clearly tells an agent what the tool does. It does not explicitly distinguish from sibling tools, but the resource ('query plan') is unique among the sibling list, and the rest of the description reinforces the scope. Without an explicit sibling contrast, it falls short of a 5.

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

Usage Guidelines3/5

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

The description provides extensive conditions for parameters (e.g., 'set analyze: true to run with EXPLAIN ANALYZE', version requirements, when buffers defaults to true), so there is clear context for invoking options. However, it never explicitly says when to prefer this tool over alternatives like pg_query – it only implies that you should use this for query plans. It lacks a direct when-to-use or when-not-to-use statement regarding sibling tools.

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