Skip to main content
Glama
YawLabs

@yawlabs/postgres-mcp

by YawLabs

@yawlabs/postgres-mcp

npm version License: MIT

Query a PostgreSQL database from Claude Code, Cursor, and any MCP client. Read-only by default - writes opt in via a single env var - so an agent can't silently drop your tables.

Built and maintained by Yaw Labs.

Add to Yaw MCP

One click adds this to your local Yaw MCP config so it's available in every Yaw Terminal session. Or install manually below.

What's new in 0.11.0

PostgreSQL 18 support, a new I/O observability tool, and version-gated catalog queries. Full detail in the CHANGELOG.

Three breaking changes if you are upgrading from 0.10.x:

  1. pg_seq_scan_tables, pg_unused_indexes and pg_top_queries return an envelope, not a bare row array. Read data.rows where you used to read data. The envelope carries stats_reset, because a cumulative scan count means nothing without knowing when the counters were last reset -- if that happened an hour ago, every index looks unused, which is how a load-bearing index gets dropped.

  2. pg_explain with analyze: true now emits BUFFERS, matching what PostgreSQL 18 does server-side. Plans get longer; pass buffers: false for the old output.

  3. Node 22 is the floor. Node 20 reached end of life.

Worth knowing even if you are not upgrading yet:

  • pg_describe_table now flags generated and identity columns. Previously a generated column's expression surfaced as default_value with nothing marking it, so an agent read the column as optional-with-a-default and wrote an INSERT that PostgreSQL rejects.

  • New pg_io_stats exposes pg_stat_io (PG16+) plus in-flight async I/O from pg_aios and the active io_method (PG18+).

  • pg_advisor checks multixact wraparound alongside transaction-ID wraparound. A lock-heavy workload can exhaust multixacts while relfrozenxid still looks healthy.

  • Every version-dependent column is gated on server_version_num, so older servers get a thinner answer rather than an error.

Related MCP server: pg-mcp

Backstory

Anthropic's reference Postgres MCP server, @modelcontextprotocol/server-postgres, was archived in May 2025 and marked deprecated on npm in July 2025. Anthropic has not shipped a replacement. Despite the deprecation, the last published version (v0.6.2) is still pulled ~20,000 times per week - a lot of agents are pointed at an unmaintained package.

That unmaintained package also has a known, publicly documented stacked-query SQL injection (Datadog Security Labs) that bypasses its BEGIN READ ONLY wrapper with input like COMMIT; DROP SCHEMA public CASCADE;. It has never been patched at npm.

A handful of community forks have appeared, but each fills a narrow slice:

  • @zeddotdev/postgres-context-server - Zed's fork, primarily a security patch on the original shape.

  • Postgres MCP Pro (Crystal DBA) - focused on index tuning and hypothetical-index / buffer-cache diagnostics.

  • AWS Labs Postgres MCP - tied to Aurora / RDS Data API + Secrets Manager.

None of them position themselves as a general-purpose daily driver you'd hand to Claude Code or Cursor against an arbitrary Postgres: modern introspection, perf helpers, role/privilege awareness, and a write-safety posture out of the box. That's the gap @yawlabs/postgres-mcp fills.

Why this one?

  • Read-only by default, with an unconditional read-only tool too - pg_query runs user SQL in a BEGIN READ ONLY transaction, so postgres itself (not string parsing) blocks writes; opt in to writes with ALLOW_WRITES=1. pg_readonly is a separate tool that stays read-only regardless of ALLOW_WRITES, so hosts that gate tools individually (Claude Code permissions, mcp.hosting) can auto-allow it -- paired with a least-privileged role, since READ ONLY bounds writes to the database rather than every side effect (details).

  • Role-based access as the primary control - the recommended posture is to use a least-privileged postgres role in DATABASE_URL (e.g. one with GRANT pg_read_all_data); postgres itself then enforces the boundary, no env var needed. See Configuring access.

  • Extended query protocol for all user SQL - pg_query sends user input with queryMode: 'extended', which restricts each request to a single statement. This closes the stacked-query injection class (COMMIT; DROP SCHEMA x CASCADE;) that defeated the reference server's BEGIN READ ONLY wrapper. Integration test asserts the rejection.

  • Parameterized queries - pg_query takes a params array for $1, $2, etc. No string-interpolated SQL in our code path.

  • Written from scratch, actively maintained - not a fork of the deprecated code. Unit + integration tests (npm test, npm run test:integration) run against a real Postgres; releases cut via release.sh.

  • Schema introspection built in - pg_list_schemas, pg_list_tables, pg_describe_table return columns, primary keys, foreign keys, and indexes without the agent having to remember pg_catalog joins.

  • EXPLAIN as a first-class tool - text or JSON format, with optional ANALYZE. ANALYZE for non-SELECT statements requires ALLOW_WRITES=1 and always rolls back, so the plan is real but the write doesn't persist.

  • Perf diagnostics the deprecated server never had - pg_top_queries (from pg_stat_statements), pg_seq_scan_tables, pg_unused_indexes, pg_table_bloat, pg_inspect_locks, pg_replication_status. Answer "why is this slow?" in one tool call.

  • Health snapshot - pg_health returns version, db size, connection counts, and the 10 longest-running active queries in one call.

  • Role and privilege awareness - pg_list_roles and pg_table_privileges for the common "who can touch what?" questions.

  • Instant startup - ships as a single bundled file with zero runtime dependencies. No multi-minute node_modules install on every npx cold start.

  • Result truncation - large result sets are capped at POSTGRES_MAX_ROWS (default 1000) with a truncated: true flag, so a stray SELECT * FROM events doesn't blow out the model context.

Quick start

1. Create .mcp.json in your project root

macOS / Linux / WSL:

{
  "mcpServers": {
    "postgres": {
      "command": "npx",
      "args": ["-y", "@yawlabs/postgres-mcp@latest"],
      "env": {
        "DATABASE_URL": "postgres://user:password@host:5432/dbname"
      }
    }
  }
}

Windows:

{
  "mcpServers": {
    "postgres": {
      "command": "cmd",
      "args": ["/c", "npx", "-y", "@yawlabs/postgres-mcp@latest"],
      "env": {
        "DATABASE_URL": "postgres://user:password@host:5432/dbname"
      }
    }
  }
}

Why the extra step on Windows? Since Node 20, child_process.spawn cannot directly execute .cmd files (that's what npx is on Windows). Wrapping with cmd /c is the standard workaround.

2. Restart and approve

Restart Claude Code (or your MCP client) and approve the postgres MCP server when prompted.

3. (Optional) Enable writes

Read-only is the default. If you want the agent to be able to INSERT, UPDATE, DELETE, or run DDL, add ALLOW_WRITES=1 to the env block:

"env": {
  "DATABASE_URL": "postgres://...",
  "ALLOW_WRITES": "1"
}

Prefer scoping this to dev/test databases - for production, leave writes off and use migration tools out-of-band.

Configuring access

The role in DATABASE_URL is the primary access control. Postgres has had a battle-tested permission system for 30 years; lean on it instead of relying on ALLOW_WRITES alone. A least-privileged role makes writes server-rejected no matter what tools or env vars are configured.

Read-only agent (recommended default):

CREATE ROLE mcp_reader LOGIN PASSWORD 'change-me';
GRANT CONNECT ON DATABASE your_db TO mcp_reader;
GRANT USAGE ON SCHEMA public TO mcp_reader;
GRANT pg_read_all_data TO mcp_reader;

Point DATABASE_URL at mcp_reader. Postgres rejects every write, every DDL, every privilege change - regardless of ALLOW_WRITES. No app-level guard to bypass; the database is the boundary.

Scoped write agent (dev/test or narrow production use):

CREATE ROLE mcp_writer LOGIN PASSWORD 'change-me';
GRANT CONNECT ON DATABASE your_db TO mcp_writer;
GRANT USAGE ON SCHEMA public TO mcp_writer;
GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA public TO mcp_writer;
GRANT USAGE ON ALL SEQUENCES IN SCHEMA public TO mcp_writer;
-- DDL not granted -- the agent can change data but not schema.

Set ALLOW_WRITES=1 so pg_query will issue writes, and rely on the role to keep the agent away from DDL and other schemas.

Per-tool gating in the host:

Tools split cleanly across two authority classes:

  • Auto-allow: pg_readonly (server-side BEGIN READ ONLY, unconditional), plus the introspection tools (pg_list_*, pg_describe_table, pg_search_columns, pg_explain without ANALYZE-of-write, pg_health, pg_inspect_locks, pg_table_bloat, pg_unused_indexes, pg_top_queries, pg_replication_status, pg_advisor, pg_table_privileges, pg_list_roles).

  • Always prompt: pg_query (can write when the role allows it), pg_kill (changes session state).

Claude Code's permissions block and mcp.hosting's per-tool toggle both honor this split.

What READ ONLY does and does not cover. A BEGIN READ ONLY transaction blocks writes to the database -- INSERT/UPDATE/DELETE, DDL, nextval/setval. It does not block functions whose effect lands outside the table data. SELECT pg_terminate_backend(...), pg_cancel_backend, pg_read_file, lo_export, and COPY ... TO PROGRAM all run to completion inside pg_readonly, which means auto-allowing pg_readonly reaches the same capability that pg_kill puts behind ALLOW_WRITES=1. Every one of them still requires a privilege the DATABASE_URL role must actually hold (pg_signal_backend, pg_read_server_files, superuser), so the role is the control that bounds this tool, not the transaction mode. If you auto-allow pg_readonly, use a least-privileged role -- see Configuring access.

ALLOW_WRITES as defense-in-depth:

ALLOW_WRITES is a secondary belt-and-braces gate. Useful when:

  • You're on a managed database where creating a second role is awkward (some Supabase/Neon plans).

  • You want a single role that can write, but want the MCP server to refuse writes anyway during normal operation.

Otherwise, configure the role and stop relying on ALLOW_WRITES.

What can an agent do with this?

Once connected, the agent picks tools automatically based on what you ask. A few single-tool examples:

  • "Describe the users table" -> pg_describe_table -> returns kind, columns, PK, FKs, indexes.

  • "Which tables have a user_id column?" -> pg_search_columns with pattern user_id -> one call instead of iterating every table.

  • "This query is slow, why?" -> pg_explain with analyze: true -> returns the plan with actual row counts and timing.

  • "What's the slowest query we run?" -> pg_top_queries -> returns the top N from pg_stat_statements with mean/total/min/max times.

  • "Do we have any unused indexes?" -> pg_unused_indexes -> returns non-unique, non-primary indexes with zero or low scan counts + their size.

  • "Is pgvector installed?" -> pg_list_extensions -> yes/no with version.

The bigger leverage is multi-tool reasoning. A few real workflows:

  • Unstick a hung app. pg_inspect_locks returns blocked PID + blocking PID + the offending query, then pg_kill (ALLOW_WRITES=1 required) cancels the blocker. The agent can run both in one turn - it's the fastest path from "the app is frozen" to "back up."

  • Chase a slow page. pg_top_queries ranks the worst queries, pg_explain with analyze: true shows the plan for the top hit, pg_seq_scan_tables and pg_unused_indexes say whether the answer is "add an index here" or "drop a dead one there."

  • Oncall triage. pg_health checks connectivity + active-query count + database size; pg_inspect_locks and pg_replication_status confirm whether contention or replication lag is in play before paging the on-call DBA.

Tools

Tool

Description

pg_readonly

Run a SQL statement with no persistent data changes - always inside BEGIN READ ONLY, regardless of ALLOW_WRITES. The recommended tool for read access, and the one to auto-allow; pair it with a least-privileged role (why).

pg_query

Run a SQL query. Writes gated by the role in DATABASE_URL first, ALLOW_WRITES second. Supports parameterized queries via params. Result fields include dataTypeName (e.g. int4, jsonb) alongside dataTypeID.

pg_list_schemas

List non-system schemas.

pg_list_tables

List tables (and optionally views) in a schema with estimated row counts. Paginated via limit/offset.

pg_describe_table

Kind, columns, PK, outgoing FKs, incoming FKs (referenced_by), CHECK / UNIQUE / EXCLUDE constraints, indexes, and partition parent/children for a relation. Generated and identity columns are flagged (generated, identity, generation_expression) so an agent doesn't try to write to them. Constraints carry validated, plus enforced / has_period on PG18+.

pg_list_views

List views and materialized views in a schema, including their SQL definitions.

pg_list_functions

List functions, procedures, and aggregates in a schema with signatures and return types.

pg_list_extensions

List installed extensions (pgvector, postgis, pg_stat_statements, etc.) with versions.

pg_search_columns

Find columns by name pattern across all user schemas. Case-insensitive, supports SQL LIKE wildcards.

pg_explain

EXPLAIN or EXPLAIN ANALYZE for a SQL statement. Text or JSON output. Planner options: buffers (on by default with analyze), settings, verbose, wal, costs, timing, plus generic_plan (PG16+, plan a parameterized query with no values) and memory / serialize (PG17+). Optional hypothetical_indexes (requires the HypoPG extension) lets you ask "what would the plan be with these indexes?" without creating them on disk.

pg_index_advisor

Recommend indexes for a workload and prove each one pays for itself first. Takes statements you pass or the top N from pg_stat_statements, harvests candidate columns from what the planner reports as filters / join keys / sort keys (no SQL parser - every token is intersected with the real pg_attribute column list), then costs each candidate with HypoPG hypothetical indexes and keeps only what measurably lowers estimated cost. Greedy and bounded via max_candidates / max_explains, so a big workload cannot run away; budget_exhausted flags a truncated search. Returns the CREATE INDEX (plus a CONCURRENTLY form), cost before/after, which statements each index helps, and the estimated size. PG18-aware: PG18 added B-tree skip scan, so a multi-column index whose leading column is never filtered is no longer useless - that classic prune is gated on the server version rather than applied blindly. Requires HypoPG; indexes are session-scoped and reset on every exit path.

pg_health

Server version, database size, connections against max_connections, active queries with wait events and transaction age, pg_stat_database rollup (deadlocks, temp files, cache hit ratio), table count.

pg_top_queries

Top N queries by total/mean execution time. Requires the pg_stat_statements extension. Returns stats_reset (from pg_stat_statements_info, a different clock from the other stats tools) and dealloc on extension 1.9+ - a non-zero dealloc means entries were evicted past pg_stat_statements.max, so the ranking is drawn from an incomplete population.

pg_seq_scan_tables

Tables with heavy sequential scans - missing-index candidates. Returns the stats_reset window alongside the rows, since the counters mean nothing without it. last_seq_scan / last_idx_scan on PG16+.

pg_unused_indexes

Non-unique, non-primary indexes with low scan counts - drop candidates. Also returns stats_reset: a recently reset counter makes every index look unused, which is how a load-bearing index gets dropped. last_idx_scan on PG16+.

pg_io_stats

I/O observability: pg_stat_io read/write/extend/fsync counts, bytes and times per backend type and context (PG16+), plus in-flight async I/O handles from pg_aios and the active io_method (PG18+).

pg_inspect_locks

Who is blocking whom right now (blocked PID, blocker PID, lock type, queries).

pg_list_roles

Database roles with login/superuser/createdb flags and group memberships.

pg_table_privileges

Who has SELECT/INSERT/UPDATE/DELETE/etc. on a table or whole schema.

pg_table_bloat

Tables with high dead-tuple ratios - VACUUM candidates.

pg_replication_status

Replication slots, connected replicas, and current WAL position.

pg_advisor

Rolled-up DBA lints in one call: sequence-exhaustion candidates, wraparound risk for both counters (per-database and per-table age(relfrozenxid) against autovacuum_freeze_max_age, and mxid_age(relminmxid) against autovacuum_multixact_freeze_max_age -- a lock-heavy workload can exhaust multixacts while xids look healthy; triggered_by says which), tables without a primary key, and (configurable) public tables with RLS disabled. The "what should I be looking at?" starting point.

pg_kill

Cancel a running query or terminate a backend connection. Requires ALLOW_WRITES=1.

Configuration

All env vars are read from the MCP server's environment:

Variable

Default

Purpose

DATABASE_URL

(required)

PostgreSQL connection string.

ALLOW_WRITES

unset

Secondary write gate for pg_query and pg_explain ANALYZE-of-writes. Set to 1 or true to lift the BEGIN READ ONLY wrapper. The role in DATABASE_URL is the primary control - see Configuring access. Does not affect pg_readonly, which is unconditional.

POSTGRES_STATEMENT_TIMEOUT_MS

30000

Per-statement timeout.

POSTGRES_CONNECTION_TIMEOUT_MS

10000

TCP connect timeout. Without this, a dead host hangs until the OS gives up (~2 minutes).

POSTGRES_MAX_ROWS

1000

Cap on rows returned by pg_query.

POSTGRES_POOL_MAX

5

Max pool connections. Set to 1 for single-threaded backends (pglite-socket, PgBouncer transaction mode).

POSTGRES_SSL_REJECT_UNAUTHORIZED

unset

Set to false to skip TLS cert verification (for managed DBs using private-CA certs). Connection is still encrypted.

POSTGRES_APPLICATION_NAME

postgres-mcp

Value reported in pg_stat_activity.application_name, so agent traffic is identifiable to whoever is watching the database. An application_name in DATABASE_URL takes precedence over this.

POSTGRES_MCP_RUNTIME

auto

Which JS runtime executes the server: auto (prefer oam, fall back to Node), oam (require oam, fail if absent), node (never use oam). See Runtime.

OAM_BIN

unset

Explicit path to an oam binary, checked before PATH and the default install locations.

Supported Postgres versions

Tested on PostgreSQL 15, 17 and 18 in the integration matrix.

Works on PG13+, but note where upstream support actually sits: PG13 reached end of life on 2025-11-13 and PG14 does so on 2026-11-12. PG13/14 are not exercised here and are not a compatibility target going forward. PG12 and below are further out of support and some tools rely on columns that landed in PG13 (pg_replication_status reading wal_status, pg_top_queries reading *_exec_time).

Newer server versions unlock extra fields rather than being required. Every version-dependent column is gated on server_version_num and simply omitted on servers that predate it, so nothing errors -- you get a slightly thinner answer. The cut points that matter:

Server

What it adds

PG16+

last_idx_scan / last_seq_scan in the stats tools (index/table staleness rather than a bare counter), pg_explain generic_plan

PG17+

pg_explain memory and serialize

PG18+

Generated-column form (stored vs virtual), NOT NULL constraint validity, conenforced / conperiod constraint metadata in pg_describe_table, relallfrozen freeze coverage in pg_advisor. BUFFERS is on by default with EXPLAIN ANALYZE server-side

If the version probe fails, the server assumes the oldest supported shape rather than emitting SQL a server might reject.

Runtime

The published postgres-mcp command is a small launcher that prefers the oam runtime and falls back to Node.

If you do not have oam, nothing changes. The fallback is not a re-exec: npm already started Node to run the launcher, so falling back is a plain import() of the server into that same process. It costs a few existsSync calls and no subprocess, and behaves identically to running dist/index.js under Node directly.

If you do have oam, the server runs under it. Verified equivalent on both runtimes: all 22 tools register, queries return identical rows and dataTypeName values, and the error paths match. oam supplies every node: builtin the driver needs, including net, tls, crypto, and dns (SCRAM auth and the extended query protocol both work).

Startup cost, measured. windows-arm64, 1.4 MB bundle, postgres-mcp version (full module init), every binary warmed first, mean of 12 runs:

path

startup

standalone binary (oam compile)

298ms

oam run dist/index.js

306ms

node dist/index.js

358ms

launcher -> Node (in-process)

370ms

launcher -> oam (spawn)

409ms

oam starts faster than Node here. What the launcher costs is the spawn: reaching oam means Node has already booted, and that hop (~100ms) is larger than oam's ~52ms advantage. So through the npm bin, the two land within ~40ms of each other, and POSTGRES_MCP_RUNTIME=node is a marginal win rather than a meaningful one.

Either way it is a one-time cost per MCP session, not per tool call -- hosts spawn the server once and hold it open. If startup genuinely matters, the standalone binary avoids the launcher entirely and is the fastest option.

Earlier releases of this README reported ~650-900ms for Node and ~980-1290ms for oam, and advised opting out of oam on that basis. Those figures were measured against cold, freshly-built binaries and reflected the Windows on-access virus scanner rather than either runtime. They were wrong in both magnitude and direction. Corrected in 0.9.1.

{
  "mcpServers": {
    "postgres": {
      "command": "npx",
      "args": ["-y", "@yawlabs/postgres-mcp"],
      "env": {
        "DATABASE_URL": "postgres://...",
        "POSTGRES_MCP_RUNTIME": "node"  // opt out of oam
      }
    }
  }
}

Connecting to managed Postgres (Supabase, Neon, RDS, etc.)

Most managed databases require TLS but serve certs signed by a private CA that Node's default trust store doesn't recognize. The symptom is one of:

  • self signed certificate in certificate chain

  • unable to get local issuer certificate

  • unable to verify the first certificate

To allow the connection while keeping traffic encrypted, add POSTGRES_SSL_REJECT_UNAUTHORIZED=false to the env block:

"env": {
  "DATABASE_URL": "postgres://user:pass@host:5432/db?sslmode=require",
  "POSTGRES_SSL_REJECT_UNAUTHORIZED": "false"
}

This disables certificate chain verification only -- the TCP connection is still TLS-encrypted end-to-end. For production setups where you can install the CA, prefer putting the cert in the Node trust store (NODE_EXTRA_CA_CERTS) over disabling verification globally.

Shaving a round trip on PG17+. Postgres 17 added direct TLS negotiation, which skips the plaintext SSLRequest handshake before the TLS one. The bundled driver supports it, so append sslnegotiation=direct to your DATABASE_URL:

postgres://user:pass@host:5432/db?sslmode=require&sslnegotiation=direct

It is opt-in rather than a default because a PG16-or-older server will reject the connection outright, and the saving is one round trip per pooled connection -- worth it on a distant managed database, invisible on a local one.

Troubleshooting

DATABASE_URL is not set - Your MCP client is launching the server without the env var. On Windows especially, env vars set in bash / PowerShell profiles are not inherited by MCP servers launched via cmd. Put DATABASE_URL directly in the env block of .mcp.json.

password authentication failed - Check the username, password, and that the user has CONNECT privilege on the database. URL-encode special characters in the password (@ → %40, # → %23, / → %2F).

SASL: SCRAM-SERVER-FIRST-MESSAGE: client password must be a string - The password in your connection string is empty or became null after URL decoding. Re-check your connection string.

canceling statement due to statement timeout - A single query exceeded POSTGRES_STATEMENT_TIMEOUT_MS (default 30s). Increase it, narrow the query with WHERE, or add an index. This is working as designed -- the timeout exists so a runaway query cannot hang the agent.

Write blocked: this server is in read-only mode - You asked the agent to write via pg_query but ALLOW_WRITES is not set. Either add ALLOW_WRITES=1 to the env block of .mcp.json and restart your MCP client (dev/test DBs), or - cleaner for production - use a role with INSERT/UPDATE/DELETE grants in DATABASE_URL and keep ALLOW_WRITES unset. See Configuring access. Note that pg_readonly always rejects writes; if you want writes, the call has to go through pg_query.

Connection pool exhaustion with PgBouncer transaction mode or pglite-socket - These backends don't support concurrent queries on a single connection. Set POSTGRES_POOL_MAX=1 in the env block.

First query is slow, subsequent queries are fast - Expected. The pg driver lazily establishes the first connection; subsequent queries reuse the pool.

Development

Run the full suite (unit + integration) against a real Postgres:

DATABASE_URL='postgres://user:pass@host:5432/db' POSTGRES_MCP_INTEGRATION=1 npm run test:integration

The integration suite assumes a disposable database -- it creates and drops a test_fixture schema. Don't point it at anything you care about.

To also run the destructive tests (REVOKE / restricted-role path), add POSTGRES_MCP_DESTRUCTIVE_TESTS=1. Only safe on a disposable cluster:

DATABASE_URL='postgres://user:pass@host:5432/db' POSTGRES_MCP_INTEGRATION=1 POSTGRES_MCP_DESTRUCTIVE_TESTS=1 npm run test:integration

Windows: integration tests via WSL2

Native Postgres on Windows ARM64 is fragile (UCRT runtime gaps, missing ARM64 builds). The reliable path is a disposable Ubuntu under WSL2 with the integration suite running inside WSL (WSL2's NAT blocks the Windows host from reaching :5432, so don't try to run the tests from PowerShell):

wsl --install -d Ubuntu --no-launch
# reboot, then:
wsl -d Ubuntu -u root bash -c "apt-get update && apt-get install -y nodejs npm rsync"
wsl -d Ubuntu -u root bash /mnt/c/path/to/postgres-mcp/scripts/wsl-pg-setup.sh
wsl -d Ubuntu -u root bash /mnt/c/path/to/postgres-mcp/scripts/wsl-test-matrix.sh

wsl-pg-setup.sh installs PG15, PG17 and PG18 from the PGDG apt repo (ports are auto-assigned by pg_createcluster -- typically 17 on 5432, 18 on 5433, 15 on 5434), sets the postgres password to postgres, and creates postgres_mcp_test in each. wsl-test-matrix.sh rsyncs the working tree into /root/postgres-mcp, runs npm ci once, and runs the integration suite against every cluster found via pg_lsclusters.

Running these from Git Bash instead of PowerShell? Prefix both script invocations with MSYS_NO_PATHCONV=1. Git Bash rewrites the /mnt/c/... argument before wsl.exe sees it, so the script arrives as C:/Users/<you>/scoop/apps/git/<ver>/mnt/c/... and bash exits with "No such file or directory" having run nothing. Also avoid piping either script into tail/head -- the pipeline's exit status is the last command's, so a failing matrix reports success.

Tear down when finished: wsl --unregister Ubuntu.

License

MIT © 2026 YawLabs

Available Tools

23 tools
pg_advisorDatabase advisor (DBA lints)A
Read-onlyIdempotent

Rolled-up DBA lint pass. One call returns four categories of findings:

  • sequence_exhaustion: SERIAL / BIGSERIAL / IDENTITY sequences whose last_value is above seqExhaustionThreshold of max_value. The classic incident class.

  • wraparound_risk: transaction-ID AND multixact wraparound pressure, the classic pageable incident. {autovacuum_freeze_max_age, autovacuum_multixact_freeze_max_age, databases[], tables[]}. Those two cluster GUCs are the divisors both lists are measured against (null if unreadable). Multixact IDs are a SEPARATE 32-bit counter, consumed by row-level locking (SELECT ... FOR SHARE/UPDATE, FK checks), so a lock-heavy workload can exhaust them while relfrozenxid stays perfectly healthy -- both counters are checked here. databases rows: {database, xid_age (age(datfrozenxid)), mxid_age (mxid_age(datminmxid)), pct_of_freeze_max_age, pct_of_multixact_freeze_max_age, triggered_by} -- template databases included, since template0 ages like any other and the cluster horizon is the minimum across all of them. tables rows: {schema, table, relkind, xid_age (age(relfrozenxid)), freeze_max_age, pct_of_freeze_max_age, mxid_age (mxid_age(relminmxid)), multixact_freeze_max_age, pct_of_multixact_freeze_max_age, triggered_by}, where freeze_max_age / multixact_freeze_max_age are the EFFECTIVE limits -- a per-table autovacuum_freeze_max_age / autovacuum_multixact_freeze_max_age storage parameter wins over the GUC. A row is returned when EITHER ratio is at or above wraparoundThreshold, and triggered_by ('xid' | 'multixact' | 'both') says which one did it: 'xid' means chase freezing/autovacuum, 'multixact' means chase the lock-heavy workload burning members. mxid_age and pct_of_multixact_freeze_max_age are null on rows whose minmxid is InvalidMultiXactId (no multixact ever recorded); such rows can only be xid-triggered. At pct_of_freeze_max_age 1.0 autovacuum forces an anti-wraparound VACUUM, and near 2.1 billion xids (or 4.2 billion multixacts) the server stops accepting writes. tables deliberately includes pg_catalog and pg_toast relations -- the culprit is more often a TOAST table or a system catalog than a user table. On PG18+ table rows also carry pages / all_frozen_pages / frozen_page_fraction from pg_class.relallfrozen (visibility-map freeze coverage); those three keys are ABSENT on older servers rather than null.

  • tables_without_primary_key: user tables (plain and partitioned) with no PK defined. Bloat candidates and a sign of design drift; some replication setups also need PKs. Foreign tables are excluded -- PostgreSQL forbids declaring PKs on foreign tables.

  • public_tables_without_rls: tables in public (or any schema in rlsSchemas) with row-level security disabled. Useful as a security baseline check. Any category whose query fails (permission-gated catalogs on managed providers) appends to _warnings and returns empty; the other categories still return. Use this as the 'what should I be looking at?' starting point, then drill into pg_unused_indexes, pg_table_bloat, pg_seq_scan_tables for the perf side.

ParametersJSON Schema
NameRequiredDescriptionDefault
limitNoMax rows per category (default 50).
rlsSchemasNoSchemas where RLS-missing should be flagged. Defaults to ['public'].
wraparoundThresholdNoMinimum used-fraction to flag a database or table for wraparound risk (default 0.5 = 50%). Applied to BOTH ratios -- age(frozenxid) / autovacuum_freeze_max_age and mxid_age(minmxid) / autovacuum_multixact_freeze_max_age -- and a row is flagged if either one clears it. 1.0 is where autovacuum starts forcing anti-wraparound VACUUMs.
seqExhaustionThresholdNoMinimum used-fraction (last_value / max_value) to flag a sequence (default 0.5 = 50%).

Output Schema

ParametersJSON Schema
NameRequiredDescription
_warningsNo
wraparound_riskYes
sequence_exhaustionYes
public_tables_without_rlsYes
tables_without_primary_keyYesPlain and partitioned tables only; foreign tables cannot have a PK and are excluded.

TDQS

A4.4/5.0
Behavior5/5

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

Beyond the readOnly/idempotent/destructive annotations, the description discloses rich behavioral nuance: permission-gated failures append to _warnings and return empty while other categories still return, rows are emitted when EITHER ratio crosses threshold, triggered_by distinguishes xid vs multixact causes, nulls mean InvalidMultiXactId, and PG18+ keys are absent rather than null. This far exceeds the annotation baseline.

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 the complexity of a four-category lint tool justifies much of it. It is well structured with a front-loaded summary and per-category bullets, and the behavioral edge cases included are operational rather than filler. It loses a point for some rhetorical padding and for re-explaining return-row details that an output schema could carry.

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?

The description is essentially complete for an agent deciding whether and how to invoke this tool. It covers intended usage, per-category row logic, threshold behavior, null semantics, version differences, system-catalog inclusion, failure modes, and clear drill-down routing to sibling tools. Combined with the output schema and annotations, nothing critical is missing.

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%, and the schema already documents limit, rlsSchemas, wraparoundThreshold, and seqExhaustionThreshold with detailed semantics, including 'Applied to BOTH ratios' and '1.0 is where autovacuum starts forcing anti-wraparound VACUUMs.' The tool description reinforces these meanings but does not add significant new parameter-level meaning beyond what the schema already provides.

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 opens with 'Rolled-up DBA lint pass' and enumerates the four distinct categories of findings, giving a specific verb, resource, and scope. It also differentiates itself from sibling tools by framing itself as the starting point before drilling into pg_unused_indexes, pg_table_bloat, and pg_seq_scan_tables.

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

Usage Guidelines4/5

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

The description explicitly says to use this as the 'what should I be looking at?' starting point and names the perf-oriented sibling tools to drill into afterward. It provides clear context but does not spell out explicit when-not cases or exclusions for other sibling tools like pg_health or pg_describe_table.

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

pg_describe_tableDescribe tableA
Read-onlyIdempotent

Describe a relation: kind (table / view / materialized_view / partitioned_table / foreign_table), columns (name, type, nullable, default, generated, identity), primary key, foreign keys (outgoing), referenced_by (other tables whose FKs point at this one), constraints (CHECK / UNIQUE non-PK / EXCLUDE), indexes, and partition info (partition_of parent, partitions children). Works on views and materialized views too -- PK/FK/constraint/index lists will simply be empty for a plain view. Use kind to disambiguate before assuming you can write to the relation. Generated columns (generated: 'stored' / 'virtual') and identity: 'always' columns are NOT writable -- omit them from INSERT/UPDATE column lists; a generated column's expression is reported as generation_expression, never as default_value. On PostgreSQL 18+ constraints also report validated / enforced / has_period, and columns report not_null_validated -- a NOT VALID not-null constraint means nullable: false can still hide NULLs.

ParametersJSON Schema
NameRequiredDescriptionDefault
tableYesTable name.
schemaNoSchema name (defaults to 'public').public

Output Schema

ParametersJSON Schema
NameRequiredDescription
kindYestable | partitioned_table | view | materialized_view | foreign_table, or the raw relkind. Defaults to 'table' with a `_warnings` entry when the kind fetch failed.
tableYes
schemaYes
columnsYes
indexesYes
_warningsNo
partitionsNoPresent only when this relation is a partitioned parent WITH children.
constraintsYesCHECK / non-PK UNIQUE / EXCLUDE only; PK and FK have their own lists.
primary_keyYesKey columns in declared order; INCLUDE columns are excluded.
foreign_keysYes
partition_ofNoPresent only when this relation is itself a partition.
referenced_byYesOther tables whose foreign keys point AT this one.

TDQS

A4.5/5.0
Behavior5/5

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

Annotations already declare readOnlyHint, openWorldHint, idempotentHint, and destructiveHint, but the description adds substantial behavioral detail beyond that: generated and identity columns are NOT writable, generation expressions are reported separately, and PostgreSQL 18+ introduces additional fields like `validated` and `not_null_validated`. It also warns about the subtle case where `nullable: false` can still hide NULLs under a NOT VALID constraint, which is exactly the kind of behavioral nuance an agent needs.

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?

Although the description is long, every sentence carries essential information: the full output inventory, edge cases for views and materialized views, write-safety caveats for generated and identity columns, and version-specific behavior. It is front-loaded with the core output structure and then layers on important nuances without any filler or repetition.

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?

The tool is complex, but the description covers all critical aspects needed for correct use: what the output contains, special relation kinds, write restrictions, version-specific fields, and the NULL caveat. Since an output schema exists, the exact return structure does not need to be spelled out in prose, and nothing important is missing.

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 input schema already fully documents both parameters (`table` and `schema`). The description adds no new parameter-specific meaning beyond what the schema provides, so the baseline score of 3 is appropriate.

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 uses a specific verb ('Describe') with a clear resource ('a relation') and enumerates the exact information returned: kind, columns, primary key, foreign keys, constraints, indexes, and partition info. It also explicitly distinguishes itself from sibling listing tools by covering views and materialized views, making it immediately clear what this tool does and what it is not.

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

Usage Guidelines4/5

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

The description gives practical guidance: use `kind` to disambiguate before assuming you can write, and it states that the tool works on views and materialized views. It does not explicitly name sibling alternatives or state when not to use this tool, but the context is clear enough for an agent to know when this detailed inspection tool is appropriate.

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

pg_explainExplain query planA
Destructive

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 always rolled back, so you can inspect a plan for an INSERT/UPDATE/DELETE without persisting the mutation. 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.

ParametersJSON 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

ParametersJSON Schema
NameRequiredDescription
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"`.

TDQS

A4.7/5.0
Behavior5/5

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

The description discloses far more than the annotations alone: plain EXPLAIN does not execute, ANALYZE does execute, non-SELECT requires ALLOW_WRITES=1, and writes during EXPLAIN ANALYZE are rolled back. It also explains version-gated behavior, explicit version errors instead of parse failures, HypoPG dependency, and teardown of hypothetical indexes. None of this contradicts the annotations, and the destructiveHint is consistent with the fact that ANALYZE actually runs the statement.

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 proportionately so: it must explain 14 parameters plus cross-cutting version requirements and side effects. It front-loads the core behavior first, then works through options in a logical order. It loses a point because it is a dense wall of prose in places, and some default information is repeated from the schema rather than relying on the structured field.

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 with 14 parameters, execution side effects, version constraints, and an output schema, the description is remarkably complete. It covers behavior, security/authorization implications, rollback semantics, extension requirements, and parameter combinations. The presence of an output schema means the description does not need to document return-value structure, and nothing critical is missing for correct invocation.

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 schema already covers 100% of parameters, but the description adds significant meaning on top: default-on behavior for buffers when analyze is true, PostgreSQL version requirements per option, incompatibilities such as generic_plan vs analyze/params, and the semantics of serialize levels. This goes well beyond the schema's standalone property descriptions.

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 opens with a precise verb and resource: 'Get the query plan for a SQL statement.' It immediately clarifies the key distinction between plain EXPLAIN and EXPLAIN ANALYZE, which separates this tool from siblings like pg_query or pg_advisor. The raw-SQL-not-prefixed instruction further disambiguates the input contract.

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

Usage Guidelines4/5

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

The description gives strong option-level usage guidance: when to use buffers ('fastest way to tell a bad plan from a cold cache'), settings ('usual explanation for a plan that looks impossible'), memory ('ask why planning a statement is expensive'), and generic_plan. It also records important constraints such as 'cannot be combined with analyze or params.' However, it never explicitly routes the agent to an alternative sibling for cases where EXPLAIN is not the right tool, so it misses the 'when not to use this tool' part.

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

pg_healthDatabase health snapshotA
Read-onlyIdempotent

Quick health snapshot: server version, database size, connection counts measured against max_connections, active queries with their wait events, a pg_stat_database rollup, and table count. Useful as a connection sanity check and to spot runaway queries, connection-cap pressure, and lock/IO waits.

  • connections: total for the CURRENT database, broken down into active / idle / idle_in_transaction / idle_in_transaction_aborted / other (starting, fastpath function call, disabled) / state_unavailable -- those six sum to total. idle_in_transaction_aborted is called out separately because it holds locks and blocks vacuum while doing no work and will never commit. state_unavailable counts sessions whose state reads NULL because the role lacks pg_read_all_stats / pg_monitor membership; a non-zero value means every other bucket is under-counted by at least that much, so do NOT read active: 0 next to it as an idle database. Plus cluster_client_backends (client backends across ALL databases -- those are what actually consume connection slots), max_connections, superuser_reserved_connections, and used_fraction (cluster_client_backends / max_connections). A raw connection count means nothing without the cap; read used_fraction first.

  • active_queries: pid, state, query, application_name, backend_type, wait_event_type / wait_event (both NULL when the backend is running rather than waiting -- the single most diagnostic pair in pg_stat_activity). Both are reported verbatim as the server spells them, and that spelling changes between majors: a backend waiting on a buffer pin reports wait_event_type 'BufferPin' through PostgreSQL 18 and 'Buffer' from 19 on, with the wait_event names beneath it changing to match. Read them against the reported version rather than hard-coding a literal. duration_seconds (since query_start) and transaction_age_seconds (since xact_start). A large transaction_age_seconds next to a small duration_seconds is a long-open transaction, the usual root cause behind lock waits, bloat, and stalled autovacuum.

  • database_stats: pg_stat_database for the current database -- deadlocks, temp_files / temp_bytes (work_mem spills), conflicts (recovery conflicts, only ever non-zero on a replica), blks_hit / blks_read / cache_hit_ratio, and stats_reset. Every counter is CUMULATIVE since stats_reset, not a rate -- interpret them against that timestamp. Sub-queries that fail (several of these are permission-gated on managed providers) append to _warnings and leave their field null; the rest of the snapshot still returns.

ParametersJSON Schema
NameRequiredDescriptionDefault
activeQueryLimitNoMax active queries to return (default 10, max 100).

Output Schema

ParametersJSON Schema
NameRequiredDescription
versionNoFull `version()` banner. Absent (with a `_warnings` entry) if the row came back without it.
databaseNo
_warningsNo
connectedYesAlways true on a success response -- the version probe answered.
connectionsNo
table_countNoUser tables and partitioned tables, as a decimal string.
active_queriesYesEmpty array both when nothing is running and when the fetch failed -- check `_warnings`.
database_statsYesNull when pg_stat_database is unreadable OR has no row for this database.

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 readOnly/idempotent annotations. It explains partial failure via _warnings, cumulative counters, permission-related under-counting, wait_event naming differences across PostgreSQL versions, and the significance of idle_in_transaction_aborted. These are exactly the behavioral quirks an agent needs to correctly interpret results.

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 long but dense and well-structured, with a front-loaded summary followed by organized bullets. Every subsection carries diagnostic value, and the caveats about permissions, cumulative counters, and version-dependent wait events earn their place.

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 complex read-only diagnostic tool, the description is remarkably complete: it covers the three main output sections, key fields, interpretation guidance, failure behavior, and permission caveats. Since an output schema is present, the description does not need to restate return shapes, and it supplies the contextual semantics that schemas cannot convey.

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?

The input schema already fully documents activeQueryLimit with default, minimum, maximum, and a clear description. The tool description does not add further parameter-level meaning, so the baseline of 3 applies because schema coverage is 100%.

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 identifies the tool as a health snapshot covering server version, database size, connection counts, active queries, wait events, database stats, and table count. This distinguishes it from query-focused siblings like pg_query or pg_top_queries, but it does not explicitly name alternative tools or draw sharp boundaries with siblings that also surface wait events or connection-related metrics.

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

Usage Guidelines4/5

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

The description gives explicit use cases: 'a connection sanity check' and to 'spot runaway queries, connection-cap pressure, and lock/IO waits.' It does not, however, mention when not to use this tool or point to specific sibling tools for deeper diagnostics, so it stops short of full alternative routing.

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

pg_index_advisorRecommend indexes for a workloadA
Read-onlyIdempotent

Recommend indexes for a workload, and prove each one pays for itself before recommending it. Give it statements (the SQL you care about) or let it take the top N from pg_stat_statements; it plans each statement, generates candidate indexes, costs them with HypoPG hypothetical indexes, and returns only the ones that measurably cut estimated cost. How candidates are generated, and the honest limit: this tool has NO SQL parser and does not read your SQL text. It EXPLAINs each statement and harvests the columns the PLANNER reports as filters, join keys, and sort keys, then intersects those tokens with the real column list from pg_attribute -- so a candidate can never name a column that does not exist. The extraction is deliberately loose (a token matching a real column name on a different table can slip through); HypoPG is the arbiter, and anything that does not lower cost is discarded. Column ORDER within each candidate is equality columns first (most selective first, from pg_stats), then at most one range column, then sort columns. The search is greedy and BOUNDED. Each accepted index stays in place while the rest are re-costed on top of it, so later picks account for what earlier ones already fixed. max_candidates caps how many candidates are considered and max_explains caps total EXPLAIN round trips; when a cap stops the search early, budget_exhausted is true and the result is a truncated search, not a converged one. PostgreSQL 18 note, and it reverses a rule you have probably internalized: PG18 added B-tree SKIP SCAN, so a multi-column index whose LEADING column the query never constrains CAN now be used. The classic 'leading column never filtered means the index is useless' heuristic is wrong on PG18+. This tool gates that prune on the server version -- on PG18+ such candidates are kept and costed (skip_scan_available: true, and an accepted one carries requires_skip_scan), below PG18 they are pruned as unusable and counted in candidates_pruned_leading_column. Requires the HypoPG extension (CREATE EXTENSION hypopg;). Hypothetical indexes are session-scoped and are reset before the call returns, on the success and the failure path alike, so they never touch disk and never leak into a later query plan. Statements are only ever EXPLAINed, never executed, inside a BEGIN READ ONLY transaction. Costs are PLANNER ESTIMATES, not measurements: they are the right way to compare two plans for the same statement and the wrong way to predict wall-clock time. They are weighted by calls when the workload came from pg_stat_statements, so a query run a million times outranks an identical one run twice. Validate a recommendation with pg_explain before creating it, and create it with CONCURRENTLY in production (create_statement_concurrently).

ParametersJSON Schema
NameRequiredDescriptionDefault
limitNoHow many statements to pull from pg_stat_statements. Ignored when `statements` is given.
schemaNoOnly recommend indexes on tables in this schema. Candidates elsewhere are dropped.
statementsNoThe workload to optimize. When omitted, the top `limit` statements from pg_stat_statements are used instead (and weighted by their call counts).
max_explainsNoCap on EXPLAIN round trips spent searching (baseline plans are not counted). The search stops when the next candidate would exceed it and reports `budget_exhausted: true`.
max_candidatesNoCap on candidate indexes considered. Candidates are ranked by table sequential-scan count first.
min_improvementNoFraction of total weighted workload cost an index must remove to be accepted (0.1 = 10%). Relative rather than absolute so it means the same thing on a small and a large database.
max_index_columnsNoWidest candidate index to consider. Every narrower prefix is considered too.
max_recommendationsNoStop after this many accepted indexes, even if more would still help.

Output Schema

ParametersJSON Schema
NameRequiredDescription
_warningsNo
statementsYesThe workload as analyzed, in the order `helps_statements.statement` indexes into.
explains_usedYesEXPLAIN round trips spent searching, excluding baseline plans.
explain_budgetYes
recommendationsYes
budget_exhaustedYesTrue when `max_explains` stopped the search before it converged.
final_workload_costYesWeighted total after applying every recommendation.
skip_scan_availableYesTrue on PostgreSQL 18+, where B-tree skip scan exists.
candidates_consideredYes
baseline_workload_costYesWeighted total estimated cost before any recommendation.
candidates_pruned_leading_columnYesMulti-column candidates dropped by the pre-PG18 leading-column rule. Always 0 on PG18+.

TDQS

A4.7/5.0
Behavior5/5

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

The description goes far beyond the readOnlyHint/destructiveHint annotations: no SQL parser, EXPLAIN-only execution, BEGIN READ ONLY transaction, HypoPG reset on both success and failure paths, bounded greedy search, PG18 skip-scan version dependence, and planner-estimate semantics. This is exceptionally thorough behavioral disclosure.

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 long but every paragraph earns its place: input modes, candidate generation limitations, bounded search behavior, PG18 caveat, safety guarantees, and cost semantics are all relevant. The bolded paragraph lead-ins and code-formatted parameter names make it easy to scan.

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 with 8 optional parameters, an output schema, and subtle server-version behavior, the description is complete. It covers prerequisites, side effects, algorithm limits, version-specific pruning, and what the returned recommendations mean. Since the output schema exists, not enumerating return values in prose is acceptable.

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

Parameters4/5

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

The input schema already provides 100% parameter coverage, so the baseline is 3. The description adds meaningful context beyond the schema by explaining that `max_candidates` and `max_explains` cap the search and produce `budget_exhausted`, and by clarifying that `statements` weighting by `calls` applies when pulling from pg_stat_statements.

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 leads with a specific verb and object: 'Recommend indexes for a workload', then substantiates it with a clear mechanism: plan statements, generate candidates, cost them with HypoPG, and return only cost-reducing indexes. This sharply distinguishes it from generic siblings like pg_advisor or pg_unused_indexes.

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

Usage Guidelines4/5

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

The description clearly tells the agent when to supply explicit `statements` versus using `pg_stat_statements`, and it names `pg_explain` as the follow-up validation tool. It does not explicitly enumerate every sibling alternative or state when *not* to use it, but the usage context is strong enough for correct selection.

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

pg_inspect_locksInspect blocking locksA
Read-onlyIdempotent

Show current lock contention: which sessions are blocked and who is blocking them. Returns blocked PID, blocking PID, lock types, relation being contested, and the queries involved. Use this first when a tool call hangs or the app feels stuck - it's the fastest way to identify a long-held transaction holding a lock. Row shape: one row per (blocked_pid, blocking_pid) pair. A session waiting on multiple blockers appears on multiple rows -- group/deduplicate by blocked_pid if you want a per-blocked-session count. Scope: CLUSTER-WIDE, unlike pg_health active_queries, which is filtered to the database in DATABASE_URL. Lock waits cross databases (a shared catalog, a long transaction in a sibling database), so the blocker is not always somewhere this connection could see -- but it does mean a row here may name a pid in another database entirely. relation is only resolved for locks in the CURRENT database or on a cluster-shared catalog (pg_authid, pg_database, ...); it is NULL for a lock held in another database, because a pg_class OID is only meaningful within its own database. A NULL relation has TWO unrelated causes, and lock_type is what tells them apart. When the wait is ON a relation (lock_type relation / extend / page / tuple), NULL means the lock is held in another database and cannot be named from here. When the wait is on something else (transactionid / virtualxid / advisory), relation was only ever a best-effort hint -- an alphabetical guess among the blocker's held write-intent locks, not authoritative -- so NULL there means the guess found no candidate, and the blocker is very likely LOCAL. Do not read that second case as 'somewhere else': use blocking_pid and blocking_query, which are populated either way. Note also that pg_cancel_backend / pg_terminate_backend signal by PID across the whole cluster, so even a genuinely cross-database blocker is actionable via pg_kill. Use the blocked/blocking query text to disambiguate which table is actually contested.

ParametersJSON Schema
NameRequiredDescriptionDefault
limitNoMax blocked/blocker pairs (default 50).

Output Schema

ParametersJSON Schema
NameRequiredDescription
rowsYes

TDQS

A4.6/5.0
Behavior5/5

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

Beyond the readOnly/idempotent annotations, the description discloses cluster-wide scope, the two distinct causes of NULL relation values, how lock_type disambiguates them, and that relation is only a best-effort hint for non-relation lock types. It also explains row shape and deduplication behavior, substantially exceeding annotation coverage.

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 front-loaded with purpose and usage, then structured into scope, row-shape, and NULL-semantics sections. It is longer than typical and has some redundancy (e.g., repeating cross-database caveats), but every sentence contributes meaningful information for correct use and interpretation.

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?

The description is thoroughly complete for a lock-inspection tool: it covers output row shape, deduplication, cluster-wide scope, NULL relation semantics, lock_type disambiguation, and cross-database actionability via pg_kill. The output schema handles return-value structure, so nothing needed for correct selection and invocation is missing.

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?

The only parameter, limit, is fully described in the input schema with default, minimum, and maximum values. The description adds no parameter-specific meaning; with 100% schema coverage, the baseline score of 3 is appropriate.

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 tool's purpose: 'Show current lock contention: which sessions are blocked and who is blocking them.' It lists specific returned columns and explicitly distinguishes itself from pg_health via the cluster-wide versus database-scoped contrast, making its role among siblings unambiguous.

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?

It provides explicit when-to-use guidance: 'Use this first when a tool call hangs or the app feels stuck.' It also names the alternative pg_health and its scope limitation, explains cross-database lock waits, and notes that blockers remain actionable via pg_kill, giving an agent clear decision context.

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

pg_io_statsI/O statistics and in-flight async I/OA
Read-onlyIdempotent

I/O observability: cumulative per-backend-type I/O from pg_stat_io (PostgreSQL 16+), plus in-flight asynchronous I/O handles from pg_aios (PostgreSQL 18+). This is the layer underneath pg_top_queries and pg_health -- it says WHICH subsystem is doing the I/O (client backends vs autovacuum vs checkpointer vs walwriter) and through which path, which a per-query or per-table view cannot.

  • io: one row per (backend_type, io_object, io_context) combination. Counters reads / writes / extends / writebacks / hits / evictions / reuses / fsyncs are bigints returned as decimal strings; read_time_ms / write_time_ms / writeback_time_ms / extend_time_ms / fsync_time_ms are float8 milliseconds. A timing of 0 next to a non-zero op count means track_io_timing is off, NOT that the I/O was free -- turn it on to get real numbers. A NULL counter means the operation is not possible for that combination, which is different from 0.

  • io[].read_bytes / write_bytes / extend_bytes: a normalized byte figure that means the same thing on every supported server. On PG16-17 it is computed as op_bytes * <op count>; on PG18 op_bytes was removed and the server reports bytes directly. The top-level byte_accounting field says which source produced the numbers.

  • io[].stats_reset: these are CUMULATIVE counters, so a row is only interpretable next to its reset point. Reported per row because that is how the view reports it; pg_stat_reset_shared('io') resets them together in practice, but this tool does not assert that.

  • Rows whose counters are all zero are omitted by default (pg_stat_io is mostly zeros on a quiet system, and the noise buries the handful of rows that matter). Pass includeZeroRows: true for the full matrix.

  • in_flight + io_method: PostgreSQL 18+ ONLY, and both keys are ABSENT on older servers rather than empty/null -- an empty in_flight array would read as 'nothing is stalled' when the truth is 'this server cannot tell you'. in_flight is live, currently-outstanding async I/O (pid, io_id, op, state, off, length, target_desc), which is what you want while a stall is happening rather than after it. io_method (worker / io_uring / sync) explains what in_flight can contain: with io_method = sync there is no asynchronous submission, so the array is legitimately empty no matter how much I/O is running. Requires PostgreSQL 16+. Sub-queries that fail (pg_stat_io and pg_aios are permission-gated on some managed providers) append to _warnings and set their field to null; the rest of the response still returns.

ParametersJSON Schema
NameRequiredDescriptionDefault
limitNoMax rows per section (default 200). pg_stat_io has well under 200 combinations, so this effectively bounds the in-flight list on a busy PG18 server.
includeZeroRowsNoIf true, return every (backend_type, io_object, io_context) row including the ones with no recorded activity. Default false -- the view is mostly zeros on a quiet system.

Output Schema

ParametersJSON Schema
NameRequiredDescription
ioYesNull (not []) when the fetch was refused -- [] is a real answer on a freshly reset cluster.
_warningsNo
in_flightNoPostgreSQL 18+ only. Currently-outstanding async I/O. Null (not []) when the fetch was refused -- reading a denial as 'nothing outstanding' would point the investigation the wrong way.
io_methodNoPostgreSQL 18+ only. worker | io_uring | sync. With `sync` there is no async submission, so `in_flight` is legitimately empty however much I/O is running.
byte_accountingYesWhich source produced read_bytes / write_bytes / extend_bytes: native columns, or op_bytes * ops.
include_zero_rowsYesEchoed because it changes what an empty `io` means: no recorded I/O, vs the view returned nothing.
server_version_numYesEchoed so a caller can tell WHY the PG18-only keys are absent without a second round-trip.

TDQS

A4.7/5.0
Behavior5/5

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

The description goes far beyond the readOnlyHint/idempotent annotations, covering cumulative counter semantics, the difference between NULL and 0, the track_io_timing caveat, zero-row omission, version-gated absence of keys, and permission-failure fallback behavior. It also warns that stats_reset governs interpretability. This is exemplary transparency for a read-only observability tool.

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 long but densely informative, and every paragraph earns its place by explaining behavior that is not inferable from schema or annotations. It is logically organized: primary purpose, counter semantics, byte normalization, zero-row handling, version differences, and failure behavior. No filler is present.

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?

Given the tool's complexity, the description covers version requirements, permission failures, counter reset caveats, timing limitations, zero-row filtering, and PG18-specific behavior. The output schema exists and parameters are fully documented, so nothing an agent needs to correctly invoke and interpret this tool is missing.

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

Parameters4/5

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

Schema coverage is 100% and the input schema already describes both parameters well. The description adds extra value by explaining why the limit effectively bounds the in-flight list and why zero rows are omitted by default. It enriches, rather than merely repeats, the schema definitions.

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 opens with a precise statement of what the tool does: cumulative per-backend-type I/O from pg_stat_io plus in-flight async I/O from pg_aios. It explicitly distinguishes itself from pg_top_queries and pg_health by describing it as the subsystem-level layer underneath them, so an agent can tell it apart from sibling observability tools.

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

Usage Guidelines4/5

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

The description gives clear context for when this tool is appropriate: it identifies the subsystem doing I/O, which per-query or per-table views cannot, and notes that in-flight async I/O is useful during a stall. It names sibling tools but does not explicitly state 'use pg_top_queries instead if you need per-query I/O', so the guidance is strong but slightly implicit.

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

pg_killCancel or terminate a backendA
Destructive

Cancel a running query (SIGINT-equivalent) or terminate a backend connection (SIGTERM-equivalent) by PID. Find the PID via pg_health active_queries or pg_inspect_locks. Requires ALLOW_WRITES=1 since this changes database session state. The role in DATABASE_URL must have permission - cancelling another user's query needs the pg_signal_backend role or superuser. Note: pg_signal_backend does NOT cover superuser-owned backends - only a superuser can signal another superuser's session. Cancel is graceful; terminate is forceful. When signaled=false, the note field surfaces postgres's NOTICE explaining why (e.g. 'not a PostgreSQL backend process' for a non-pg PID, 'must be a member of...' for permission denial) so an agent can act on the specific cause rather than guess from a three-way list.

ParametersJSON Schema
NameRequiredDescriptionDefault
pidYesBackend PID to signal.
modeNo`cancel` aborts the current query; `terminate` closes the connection entirely.cancel

Output Schema

ParametersJSON Schema
NameRequiredDescription
pidYesEchoed back from the request.
modeYesEchoed back, after the safer 'cancel' default is applied.
noteYesOn `signaled: false`, postgres's own NOTICE explaining why -- act on this, not on the boolean.
signaledYesWhat pg_cancel_backend / pg_terminate_backend returned.

TDQS

A4.9/5.0
Behavior5/5

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

Goes well beyond the annotations: explains that cancel is graceful and terminate is forceful, that it changes database session state, and describes the signaled=false note behavior including concrete examples of cause-specific messages. Annotations already mark it destructive and non-readonly, and the description enriches rather than contradicts them.

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 information-dense without padding. Core operation is front-loaded, followed by prerequisites, permission nuances, and failure interpretation. Each sentence contributes necessary operational knowledge.

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?

Covers prerequisites, permissions, mode differences, failure causes, and how to find inputs. An output schema exists, so return value details are not required. Nothing material is missing for an agent to select and invoke this tool correctly.

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

Parameters4/5

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

Schema coverage is 100%, so baseline is 3. The description adds value by explaining the practical meaning of mode ('Cancel is graceful; terminate is forceful') and how to obtain the PID, plus the behavior of signaled=false for interpreting failures. This exceeds baseline but PID semantics remain largely defined by the schema.

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?

Description states a specific verb/resource pair: cancel a running query or terminate a backend by PID. It names the signal equivalents (SIGINT/SIGTERM), making the behavior unambiguous. It is clearly distinguished from sibling read-only tools like pg_health and pg_inspect_locks.

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?

Provides explicit guidance on when to use the tool and prerequisites: find PID via pg_health or pg_inspect_locks, require ALLOW_WRITES=1, and need pg_signal_backend role or superuser. It also explains the permission limitation around superuser-owned backends, so an agent knows when this tool will fail.

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

pg_list_extensionsList installed extensionsA
Read-onlyIdempotent

List installed PostgreSQL extensions. Returns name, version, schema, and description. Useful to check for pgvector, postgis, pg_stat_statements, uuid-ossp, etc. before writing queries that rely on them.

ParametersJSON Schema
NameRequiredDescriptionDefault

No parameters

Output Schema

ParametersJSON Schema
NameRequiredDescription
rowsYes

TDQS

A4.5/5.0
Behavior4/5

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

Annotations already declare readOnlyHint=true, idempotentHint=true, and destructiveHint=false, so the safety profile is fully covered. The description adds useful behavioral context by specifying what fields the tool returns and that it is intended as a pre-query check, without contradicting any annotations.

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 focused sentences with no filler. The main action is front-loaded, followed by return fields and a concrete use case, making it easy to scan and act on.

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 simple, read-only, zero-parameter tool, the description fully covers what it does, what it returns, and when to use it. Output schema exists, so return-value documentation is available elsewhere, and nothing important is missing.

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

Parameters4/5

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

The tool takes zero parameters, so parameter semantics are trivially satisfied. The description does not need to add parameter-specific details, and the baseline for zero parameters is 4.

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 states a specific action and resource: 'List installed PostgreSQL extensions.' It also names the returned fields (name, version, schema, description), which distinguishes it clearly from sibling list tools like pg_list_tables and pg_list_schemas.

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

Usage Guidelines4/5

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

The description gives clear context for when to use the tool: 'Useful to check for pgvector, postgis, pg_stat_statements, uuid-ossp, etc. before writing queries that rely on them.' It does not explicitly list exclusions or alternatives, but the use case is clear enough.

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

pg_list_functionsList functions and proceduresA
Read-onlyIdempotent

List functions, procedures, and aggregates in a schema. Returns name, arguments, return type, kind (function/procedure/aggregate/window), and implementation language.

ParametersJSON Schema
NameRequiredDescriptionDefault
schemaNoSchema name (defaults to 'public').public

Output Schema

ParametersJSON Schema
NameRequiredDescription
rowsYes

TDQS

A4.1/5.0
Behavior3/5

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

Annotations already declare readOnlyHint=true, idempotentHint=true, and destructiveHint=false, so the safety profile is covered. The description adds useful context about the kind of information returned (e.g., kind and implementation language), which is helpful but does not disclose extra behavioral traits such as permissions, performance, or system catalog filtering.

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 two sentences with no filler. The core action and target are front-loaded, and the return fields are listed compactly without redundancy. Every sentence contributes useful information.

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?

Given the single optional parameter fully described in the schema, the presence of an output schema, and annotations covering read-only/idempotent behavior, the description is complete. An agent has everything needed to invoke the tool correctly and understand its result shape.

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?

The schema fully documents the only parameter ('schema') with its default and meaning, so schema coverage is 100%. The description adds no new parameter-level details beyond referring to 'in a schema', matching the baseline for already-documented parameters.

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 uses a specific verb ('List') with a precise resource ('functions, procedures, and aggregates in a schema') and enumerates the returned attributes. This clearly distinguishes it from sibling list tools like pg_list_tables or pg_list_schemas based on resource type.

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

Usage Guidelines4/5

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

The description clearly implies when to use it: when information about schema-level functions, procedures, aggregates, or window functions is needed. It does not explicitly name alternatives or exclusions, but the resource-specific wording is enough to guide selection among the many sibling list tools.

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

pg_list_rolesList rolesA
Read-onlyIdempotent

List database roles (users and groups) with their login/superuser/createdb/createrole attributes and inherited role memberships. Use this to answer 'who has access to this database?' without needing to read pg_authid directly.

ParametersJSON Schema
NameRequiredDescriptionDefault
includeSystemNoIf true, include built-in `pg_*` roles (pg_read_all_data, pg_monitor, etc.).

Output Schema

ParametersJSON Schema
NameRequiredDescription
rowsYes

TDQS

A4/5.0
Behavior3/5

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

Annotations already declare readOnlyHint, openWorldHint, idempotentHint, and destructiveHint=false, so the safety profile is covered. The description adds useful content detail (attributes and inherited memberships) and notes it reads pg_authid indirectly, but does not disclose auth requirements, error behavior, or other operational traits.

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 focused sentences: the first states exactly what is listed, the second gives the motivating use case. No filler or restatement of the title.

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 one-optional-parameter listing tool with annotations and an output schema, the description is nearly complete. It could marginally strengthen the 'access' framing by noting that this returns role-level principals rather than object-level privileges, but the core information is sufficient.

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 coverage is 100%: the includeSystem parameter already has a clear description in the schema. The main description does not discuss parameters, so it adds no meaning beyond the schema, matching the baseline.

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?

Opens with a specific verb and direct object: 'List database roles (users and groups)' and enumerates the exact attributes returned. The mention of answering 'who has access to this database?' and avoiding pg_authid clearly frames what this tool is for and separates it from table/view/function inspection siblings.

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

Usage Guidelines4/5

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

The description gives a concrete use case ('Use this to answer "who has access to this database?"') but does not explicitly state when not to use it or name an alternative sibling. It is clear context without exclusionary guidance.

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

pg_list_schemasList schemasA
Read-onlyIdempotent

List non-system schemas in the database. Excludes pg_catalog, information_schema, and other pg_* internals.

ParametersJSON Schema
NameRequiredDescriptionDefault

No parameters

Output Schema

ParametersJSON Schema
NameRequiredDescription
rowsYes

TDQS

A4.5/5.0
Behavior4/5

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

Annotations already declare the operation is read-only, idempotent, open-world, and non-destructive. The description adds useful behavioral detail about which schemas are omitted, including `pg_catalog`, `information_schema`, and other `pg_*` internals, which is not conveyed by the annotations alone.

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 two short sentences with no redundancy. The primary action and scope are stated first, followed by a precise exclusion list, making the description efficient and easy to parse.

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 zero-parameter, read-only listing tool with an output schema and clear annotations, the description is fully adequate. Nothing an agent needs to invoke this tool correctly is missing.

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

Parameters4/5

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

The tool takes zero parameters, so the baseline is 4. There are no parameter semantics to clarify, and the description accurately reflects that the operation is unconditional.

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 uses a specific verb ('List') with a clear resource ('non-system schemas') and explicitly differentiates from siblings by stating what is excluded. An agent can immediately understand this tool is for enumerating user-defined schemas, not tables, views, or functions.

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

Usage Guidelines4/5

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

The description clearly establishes the tool's scope by excluding system schemas, providing sufficient context for when to use it. It does not explicitly name an alternative tool, but the sibling list and the self-contained nature of the operation make the intended use obvious.

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

pg_list_tablesList tables in a schemaA
Read-onlyIdempotent

List tables (and optionally views) in a schema. Returns name, type (table/view/materialized view/foreign), and estimated row count (from reltuples; null = no ANALYZE yet on PG 14+; 0 may mean empty or unanalyzed on PG <= 13). Paginate via limit/offset on very large schemas.

ParametersJSON Schema
NameRequiredDescriptionDefault
limitNoMax rows to return (default 500, max 10000).
offsetNoRows to skip for pagination (default 0).
schemaNoSchema name (defaults to 'public').public
includeViewsNoIf true, include views and materialized views.

Output Schema

ParametersJSON Schema
NameRequiredDescription
rowsYes

TDQS

A4.5/5.0
Behavior5/5

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

Annotations already establish read-only/idempotent/non-destructive behavior. The description adds valuable semantic caveats about row-count estimation: null vs 0 and PG version differences, which the annotations cannot express.

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?

Three sentences, all information-dense; the core action and optional flag lead, and caveats are packed without redundancy. Every sentence earns its place.

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 read-only list operation, annotations plus a complete schema plus an output schema already cover safety and return structure. The description adds the version-specific row-count interpretation and pagination, so nothing an agent needs to call it correctly is missing.

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 descriptions cover 100% of parameters, each with defaults and constraints, so the baseline is 3. The description's pagination mention mostly mirrors limit/offset schema docs and adds no new parameter-specific meaning.

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 opening clause 'List tables (and optionally views) in a schema' names a specific verb, a concrete resource, and an optional extension. This clearly distinguishes it from sibling tools like pg_list_views and pg_describe_table.

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

Usage Guidelines4/5

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

It gives clear context for when to use: listing tables in a schema, with includeViews for views, and explicit pagination advice for very large schemas. It does not name sibling alternatives or state when not to use this tool, but the context is strong enough for an agent to decide.

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

pg_list_viewsList views with definitionsA
Read-onlyIdempotent

List views and materialized views in a schema with their SQL definitions. Use this over pg_list_tables with includeViews: true when you want the view body, not just names.

ParametersJSON Schema
NameRequiredDescriptionDefault
schemaNoSchema name (defaults to 'public').public
includeMaterializedNoIf true, include materialized views.

Output Schema

ParametersJSON Schema
NameRequiredDescription
rowsYes

TDQS

A4.3/5.0
Behavior3/5

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

Annotations already declare readOnlyHint, openWorldHint, idempotentHint, and destructiveHint=false, covering the safety profile. The description adds useful context about returning SQL definitions and covering materialized views, but does not deeply describe output behavior; the output schema exists to fill that gap.

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 no filler. The core functionality is stated first, followed by a precise differentiation from a sibling tool.

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?

The tool is simple, has only two optional parameters fully described in the schema, carries read-only annotations, and has an output schema. The description supplies the missing usage context, so nothing an agent needs to call it correctly is absent.

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 both parameters. The description adds no new meaning beyond associating views with a schema and mentioning materialized views, which aligns with the existing parameter descriptions.

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 states a specific verb ('List') and resource ('views and materialized views in a schema'), and clarifies that SQL definitions are included. It also distinguishes itself from the sibling `pg_list_tables` with `includeViews: true`, making its purpose unambiguous.

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 tells the agent when to prefer this tool over `pg_list_tables` with `includeViews: true`: when the view body is needed, not just names. This provides clear selection logic among sibling tools.

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

pg_queryRun SQL queryA
Destructive

Run a SQL query against the configured PostgreSQL database. Postgres itself is the primary safety gate: the role in DATABASE_URL enforces what queries can succeed. The recommended posture is a least-privileged role (e.g. one granted pg_read_all_data), which makes writes server-rejected regardless of any env var. ALLOW_WRITES=1 is a secondary belt-and-braces gate - it lifts the in-server BEGIN READ ONLY wrapper, but it cannot grant privileges the role lacks. Useful for managed databases where creating a second role is awkward. For read-only access where you want the guarantee in the tool name, prefer pg_readonly. Use params for parameterized queries to avoid SQL injection. Params can be strings, numbers, booleans, null, arrays (for postgres arrays / ANY), or objects (for json/jsonb columns). Dates and UUIDs can be passed as ISO strings. Large result sets are truncated to POSTGRES_MAX_ROWS (default 1000) with a truncated: true flag.

ParametersJSON Schema
NameRequiredDescriptionDefault
sqlYesThe SQL statement to execute. Hard cap of 1 MB.
paramsNoPositional parameters referenced as $1, $2, ... in the SQL.

Output Schema

ParametersJSON Schema
NameRequiredDescription
rowsYesResult rows, capped at POSTGRES_MAX_ROWS. Values are whatever JSON type pg parsed the column into.
fieldsYesResult column descriptors, in select-list order.
commandNoPostgres command tag (`INSERT`, `CREATE TABLE`, ...). Absent on the cursor path -- read absence as 'row-returning statement, command unknown'.
rowCountYesRows AFFECTED for DML -- not necessarily rows.length -- and rows returned on the cursor path. Null when pg reported no count.
truncatedNoPresent and true only when the result hit POSTGRES_MAX_ROWS and rows were dropped.

TDQS

A5/5.0
Behavior5/5

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

Annotations already indicate destructive potential and non-read-only behavior; the description goes further by explaining the role-based safety gate, the BEGIN READ ONLY wrapper, ALLOW_WRITES as a secondary gate, and result truncation to POSTGRES_MAX_ROWS with a truncated flag. This gives the agent a clear model of side effects and limits.

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 dense but well-organized: it leads with the core operation, then safety posture, sibling routing, parameter usage, and truncation. Every sentence carries actionable information for an arbitrary-SQL tool with destructive capability; nothing feels like filler.

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 powerful SQL execution tool, the description covers the essential operational context: write-safety mechanisms, when to prefer the read-only sibling, parameter typing semantics, and result limits. The output schema exists to describe return values, so no critical context appears missing.

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?

Schema coverage is 100%, so baseline is 3, but the description adds substantial value beyond the schema: params may be strings, numbers, booleans, null, arrays (for Postgres arrays / ANY), or objects (for json/jsonb), and dates/UUIDs can be ISO strings. It also clarifies the SQL parameterization intent for injection safety.

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?

States a specific verb and resource ('Run a SQL query against the configured PostgreSQL database') and explicitly differentiates itself from pg_readonly, so an agent can tell them apart. The intended scope — arbitrary SQL against Postgres — is unmistakable.

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?

Gives explicit routing guidance: prefer pg_readonly when guaranteed read-only access is desired, and use pg_query for general SQL or managed databases where a second role is awkward. It also tells the agent to use params for parameterized queries to avoid SQL injection.

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

pg_readonlyRun read-only SQLA
Read-onlyIdempotent

Run a SQL statement with no persistent data changes. Always executes inside a BEGIN READ ONLY transaction regardless of ALLOW_WRITES, so postgres itself rejects any INSERT/UPDATE/DELETE/DDL and the transaction is always rolled back. Use this whenever the goal is to read - SELECT, EXPLAIN, SHOW, VALUES, WITH ... SELECT, etc. Scope caveat for hosts that auto-allow this tool: READ ONLY constrains writes to the DATABASE, not every side effect. Functions whose effect is outside the table data - pg_cancel_backend / pg_terminate_backend, pg_read_file, lo_export, COPY ... TO PROGRAM - are NOT blocked here and are NOT behind the ALLOW_WRITES gate that pg_kill sits behind. They still require the privileges the DATABASE_URL role holds, so a least-privileged role (e.g. pg_read_all_data) is what actually bounds this tool. Use params for parameterized queries to avoid SQL injection. Params can be strings, numbers, booleans, null, arrays (for postgres arrays / ANY), or objects (for json/jsonb columns). Large result sets are truncated to POSTGRES_MAX_ROWS (default 1000) with a truncated: true flag.

ParametersJSON Schema
NameRequiredDescriptionDefault
sqlYesThe SQL statement to execute. Hard cap of 1 MB.
paramsNoPositional parameters referenced as $1, $2, ... in the SQL.

Output Schema

ParametersJSON Schema
NameRequiredDescription
rowsYesResult rows, capped at POSTGRES_MAX_ROWS. Values are whatever JSON type pg parsed the column into.
fieldsYesResult column descriptors, in select-list order.
commandNoPostgres command tag (`INSERT`, `CREATE TABLE`, ...). Absent on the cursor path -- read absence as 'row-returning statement, command unknown'.
rowCountYesRows AFFECTED for DML -- not necessarily rows.length -- and rows returned on the cursor path. Null when pg reported no count.
truncatedNoPresent and true only when the result hit POSTGRES_MAX_ROWS and rows were dropped.

TDQS

A5/5.0
Behavior5/5

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

Annotations already declare readOnlyHint, idempotentHint, and destructiveHint=false, but the description adds substantial behavioral detail: transaction rollback semantics, independence from ALLOW_WRITES, unblocked external side-effect functions (pg_cancel_backend, pg_terminate_backend, pg_read_file, lo_export, COPY ... TO PROGRAM), privilege bounding by DATABASE_URL role, and truncation behavior with a truncated flag. This far exceeds what annotations alone provide, and there is no contradiction.

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 long but every section earns its place: core guarantee, supported statements, dangerous side-effect caveat, parameterization advice, and result truncation. It is front-loaded with the most important behavioral facts and contains no filler or repetition.

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?

Given the complexity of running arbitrary SQL safely, the description is remarkably complete. It covers transaction semantics, side-effect risks, authorization bounds, parameters, and truncation. Since an output schema exists, the description need not explain return values; nothing essential for correct invocation is missing.

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?

Schema coverage is 100%, so the baseline is 3, but the description adds real value beyond the schema: it advises using params to avoid SQL injection, explains positional $1/$2 referencing, and maps param types to their postgres semantics (arrays for ANY, objects for json/jsonb). This is genuinely helpful guidance that the schema alone does not convey.

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 opens with a precise, operational definition: run a SQL statement with no persistent data changes inside a BEGIN READ ONLY transaction, with postgres rejecting INSERT/UPDATE/DELETE/DDL. It enumerates supported statement types (SELECT, EXPLAIN, SHOW, VALUES, WITH ... SELECT), making the tool's scope unambiguous and clearly distinguishing it from pg_query.

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?

It explicitly says to use this tool whenever the goal is to read, and explains that write statements will be rejected by postgres, so the when-not is clear. The scope caveat also names side-effect functions that are NOT blocked and references pg_kill as the sibling tool behind ALLOW_WRITES, giving concrete routing guidance for edge cases.

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

pg_replication_statusReplication statusA
Read-onlyIdempotent

Replication overview: configured replication slots, connected replicas (from pg_stat_replication), and current WAL position. Use on primary to spot lagging or disconnected replicas, on replicas to see upstream status. Returns empty arrays on a standalone (non-replicated) database rather than erroring.

ParametersJSON Schema
NameRequiredDescriptionDefault

No parameters

Output Schema

ParametersJSON Schema
NameRequiredDescription
slotsYesEmpty both on a standalone database and when the fetch failed -- check `_warnings`.
replicasYes
_warningsNo
is_replicaYespg_is_in_recovery(). Null means the probe failed, NOT 'primary'.
wal_positionYesLast received LSN on a replica, current LSN on a primary. Null when the probe failed.

TDQS

A4.5/5.0
Behavior4/5

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

Annotations already declare readOnly/openWorld/idempotent/non-destructive traits. The description adds meaningful behavioral detail beyond annotations: it returns empty arrays on standalone databases instead of erroring, which is exactly the kind of edge-case disclosure agents need.

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?

Three sentences with no filler. The core output is front-loaded, usage guidance follows, and the standalone edge case is stated last. Every sentence earns its place.

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?

The tool is simple, parameterless, read-only, has an output schema, and its edge-case behavior is explicitly documented. The description fully covers what an agent needs to decide when to call it and what to expect.

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

Parameters4/5

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

The tool has zero parameters, and the schema confirms an empty properties object, so parameter documentation is not applicable. The baseline of 4 is appropriate because there is nothing missing for the agent to understand.

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 tool's resource (replication status) with specific components: configured slots, connected replicas from pg_stat_replication, and current WAL position. It distinguishes itself from sibling inspection tools, which cover different PostgreSQL domains.

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

Usage Guidelines4/5

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

The description gives explicit context for when to run the tool on a primary vs. on a replica, which is strong usage guidance. It does not name alternatives, but the tool's specialized scope makes alternatives implicit rather than necessary.

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

pg_search_columnsSearch columns by nameA
Read-onlyIdempotent

Search for columns by name across all user schemas. Supports SQL LIKE patterns (% matches any substring, _ matches one character). Case-insensitive. Use this instead of iterating pg_describe_table when the user asks 'which tables have X'.

ParametersJSON Schema
NameRequiredDescriptionDefault
limitNoMax rows to return (default 100).
schemaNoLimit to this schema. If omitted, searches all user schemas.
patternYesLIKE pattern. Use '%' for wildcard: 'user_id', '%email%', 'created_%'.

Output Schema

ParametersJSON Schema
NameRequiredDescription
rowsYes

TDQS

A4.7/5.0
Behavior4/5

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

Annotations already declare readOnly, idempotent, and non-destructive behavior. The description adds useful behavioral context beyond annotations: LIKE semantics, case-insensitivity, and cross-schema search behavior. No contradictions with annotations.

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?

Four short sentences, each earning its place: scope, LIKE syntax, case-insensitivity, and when-to-use guidance. Front-loaded with the primary action and scoping, no filler or repetition.

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?

Given the rich annotations, a complete input schema with 100% parameter coverage, and an output schema, the description covers all essential behavioral and routing information. There is no material gap for an agent to select and invoke this tool correctly.

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

Parameters4/5

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

Schema coverage is 100%, so the baseline is 3. The description still adds value by clarifying that `%` is a substring wildcard, `_` matches exactly one character, and matching is case-insensitive, which goes beyond the schema's brief 'LIKE pattern' descriptions.

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?

States a specific verb ('Search'), a precise resource ('columns by name'), and a clear scope ('across all user schemas'). It distinguishes itself from pg_describe_table by naming what it is not and why it would be preferred.

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?

Explicitly tells the agent when to use this tool: 'Use this instead of iterating pg_describe_table when the user asks which tables have X.' This gives a direct usage rule and names the alternative, so no inference is needed.

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

pg_seq_scan_tablesFind tables with heavy sequential scansA
Read-onlyIdempotent

Tables with high sequential-scan counts relative to index scans - the first place to look for missing-index candidates. Returns {rows, stats_reset, stats_reset_age_seconds}: each row has seq_scans, idx_scans, live tuples, and the ratio. A high ratio on a large table usually means a query is reading the whole table where an index would suffice. Pair with pg_top_queries to find which query is doing it. These counters are cumulative since the last statistics reset, so every ratio here is only meaningful relative to the top-level stats_reset (and stats_reset_age_seconds). A ratio measured over a window that was reset minutes ago describes that window, not the workload; stats_reset: null means the start of the window is unknown. On PostgreSQL 16+ each row also carries last_seq_scan and last_idx_scan timestamps (null = no such scan since the reset), which separate 'scanned hard months ago' from 'being scanned right now' in a way the raw counts cannot.

ParametersJSON Schema
NameRequiredDescriptionDefault
limitNoMax rows to return (default 20).
schemaNoLimit to one schema. If omitted, all user schemas are included.
minSizeNoMinimum live tuple count to include (default 1000, filters out tiny/empty tables).

Output Schema

ParametersJSON Schema
NameRequiredDescription
rowsYes
_warningsNo
stats_resetYesEvery counter in `rows` is cumulative SINCE this point. Null = start of the window unknown.
stats_reset_age_secondsYesSeconds since `stats_reset`; null whenever that is null.

TDQS

A4.5/5.0
Behavior5/5

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

Annotations already mark the tool read-only, idempotent, and non-destructive, but the description goes far beyond that by documenting that counters are cumulative since stats reset, ratios are only meaningful relative to stats_reset, and how null timestamps behave on PostgreSQL 16+. This is exactly the kind of non-obvious behavior an agent needs.

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 front-loaded with purpose and return shape, then layers necessary caveats about reset semantics and PG16-specific fields. Every sentence earns its place; the length is justified by the non-obvious cumulative-counter behavior that would otherwise trip up an agent.

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 read-only diagnostic tool with a full output schema and fully documented parameters, the description supplies the interpretive context that structured data cannot: what a high ratio means, how to use stats_reset, and how to connect results to pg_top_queries. Nothing essential is missing for correct invocation and result reasoning.

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 coverage is 100%, with limit, schema, and minSize each documented. The description adds no parameter-specific semantics, focusing instead on output interpretation, so the schema carries the load and the baseline 3 applies.

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 opens with a specific resource and scope: 'Tables with high sequential-scan counts relative to index scans' and frames it as 'the first place to look for missing-index candidates.' It states the return shape and makes the tool's diagnostic role unmistakable, distinguishing it from sibling tools like pg_unused_indexes and pg_top_queries.

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

Usage Guidelines4/5

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

The description gives clear usage context: use this as the first stop for missing-index candidates and pair with pg_top_queries to find the responsible query. It does not explicitly state when-not-to-use or name alternatives, so it stops short of a full routing guide.

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

pg_table_bloatEstimate table bloatA
Read-onlyIdempotent

Estimate table bloat (dead tuples + free space) for tables in a schema. Returns live tuples, dead tuples, dead-tuple ratio, last_vacuum / last_autovacuum timestamps, and total relation size. A high dead_ratio with a stale last_autovacuum is a sign a table needs VACUUM. On PostgreSQL 19+ every row also carries stats_reset: the last time THAT relation's counters were reset via pg_stat_reset_single_table_counters(). Read it before trusting anything else in the row -- a reset zeroes live_tuples and dead_tuples AND clears last_vacuum / last_autovacuum / last_analyze together, so a table reset a minute ago is indistinguishable from a pristine one without it. The key is ABSENT on older servers rather than null; null on PG19+ means this relation's counters have never been reset.

Three methods are available via the method parameter:

  • estimate (default): reads pg_stat_user_tables -- fast, no extensions, ANALYZE-driven approximations. Use this first.

  • approx: uses pgstattuple_approx() -- fast sampling pass, more accurate than estimates, requires the pgstattuple extension.

  • exact: uses pgstattuple() -- full table scan, exact counts, slow on large tables, requires the pgstattuple extension. Always pass schema with method='exact' -- scanning all user tables in one statement will hit statement_timeout on non-trivial databases. Install pgstattuple with CREATE EXTENSION pgstattuple (requires superuser).

ParametersJSON Schema
NameRequiredDescriptionDefault
limitNoMax rows to return (default 50).
methodNoBloat measurement method. 'estimate' (default) uses pg_stat_user_tables (fast, no extensions). 'approx' uses pgstattuple_approx() (fast sampling, more accurate). 'exact' uses pgstattuple() (full scan, exact but slow). Both 'approx' and 'exact' require the pgstattuple extension.estimate
schemaNoLimit to one schema. If omitted, all user schemas are included.
minDeadRatioNoMinimum dead-tuple fraction to include - dead / (live + dead). Default 0.1 = 10%.

Output Schema

ParametersJSON Schema
NameRequiredDescription
rowsYes

TDQS

A4.8/5.0
Behavior5/5

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

Annotations already declare readOnly, idempotent, non-destructive, yet the description goes far beyond them. It discloses the stats_reset caveat in detail, explaining how resets can masquerade as pristine tables and that the key is absent on older PG versions. It also reveals method tradeoffs, timeout risks, and superuser extension needs, providing substantial behavioral insight.

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?

Though long, every sentence carries operational weight: core purpose first, then a critical warning, then method guidance. The bulleted method breakdown makes scanning easy. No filler or redundancy; the length is justified by the complexity of the behavior.

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?

Given the tool's complexity, the description is remarkably complete. It covers return fields, interpretation of dead_ratio with last_autovacuum, the PG19+ stats_reset nuance, method selection, extension prerequisites, and a concrete timeout warning. The presence of an output schema means return-value details need no further elaboration.

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?

Schema coverage is 100%, so baseline is 3, but the description enriches method semantics substantially beyond the schema. It explains the practical consequences of each method, the timeout danger of exact without schema, and when to prefer estimate. This added meaning directly helps an agent choose parameter values correctly.

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 opens with a specific verb and resource: 'Estimate table bloat (dead tuples + free space) for tables in a schema.' It clarifies exactly what the tool returns and stands apart from sibling tools, which all target different PG operations. No ambiguity remains about the tool's core function.

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

Usage Guidelines4/5

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

The description gives clear method-selection guidance: 'Use this first' for estimate, warns that exact is slow, and mandates passing schema with exact to avoid timeouts. It also explains the extension requirement and install command. It does not explicitly contrast this tool with sibling tools, but the guidance within the tool is strong and context-rich.

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

pg_table_privilegesShow table privilegesA
Read-onlyIdempotent

Show which roles have which privileges (SELECT, INSERT, UPDATE, DELETE, TRUNCATE, REFERENCES, TRIGGER) on a table or on every table in a schema. If table is omitted, the result spans every table in schema, ordered by table then grantee. Use this to answer 'who can write to this table?' or to audit schema-wide access before a migration. Visibility caveat: backed by information_schema.table_privileges, which postgres filters by what the calling role can see. A least-privileged role may not see grants involving unrelated third-party roles. For a complete picture, run as a superuser or a member of pg_read_all_data.

ParametersJSON Schema
NameRequiredDescriptionDefault
tableNoTable name. Omit to list privileges for all tables in the schema.
schemaNoSchema name (defaults to 'public').public

Output Schema

ParametersJSON Schema
NameRequiredDescription
rowsYes

TDQS

A4.4/5.0
Behavior5/5

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

Annotations already provide readOnlyHint=true, idempotentHint=true, and destructiveHint=false, so the agent knows it is a safe read. The description adds crucial behavioral context beyond annotations: the tool is backed by information_schema.table_privileges, which postgres filters by the calling role's visibility, and a least-privileged role may miss grants. It also specifies ordering (by table then grantee) and the behavior when `table` is omitted. This is exactly the kind of behavioral disclosure that annotations alone cannot convey.

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 roughly four sentences, each carrying meaningful content: purpose, omission behavior, usage scenarios, and the visibility caveat with a recommendation. It is front-loaded with the core purpose and stays information-dense without wordiness. It could be slightly tighter (e.g., merging the usage and caveat sentences), but it is still well-structured and earns its length.

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 read-only tool with two optional parameters and an output schema present, the description covers all essential invocation context: what happens with and without `table`, the schema default, the visibility limitation, and how to get complete results. Since an output schema exists, the lack of a return-format explanation is not a gap. Nothing an agent needs in order to call this correctly or interpret the scope is missing.

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%, with both `table` and `schema` having descriptions. The description rephrases the omission behavior already present in the schema ('Omit to list privileges for all tables in the schema'), so it adds no new parameter-specific meaning. The only extra value is the ordering detail (by table then grantee), which is output behavior rather than parameter semantics. Thus baseline 3 is appropriate.

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 verb/resource (shows roles and their privileges) and enumerates the specific privilege types (SELECT, INSERT, etc.). It distinguishes itself from sibling tools (e.g., pg_list_tables, pg_describe_table) by focusing exclusively on privilege grants. It also provides a concrete use question ('who can write to this table?'), which removes any ambiguity about the tool's intent.

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

Usage Guidelines4/5

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

The description explicitly identifies when to use this tool: to answer 'who can write to this table?' or to audit schema-wide access before a migration. It also advises running as a superuser for a complete picture, which is a practical usage hint. However, it does not name alternatives or state when *not* to use it, so it lacks explicit exclusions. This fits the 'clear context, no exclusions' benchmark.

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

pg_top_queriesTop queries by execution timeA
Read-onlyIdempotent

Top N queries by total or mean execution time. Requires the pg_stat_statements extension to be installed and enabled (most managed Postgres providers have it on by default). Returns {rows, stats_reset, stats_reset_age_seconds, dealloc}: each row has normalized query text (constants replaced with ?), call count, total/mean/min/max time in ms, rows returned, and cache hit ratio. Use this to find slow queries worth optimizing. calls and total_time_ms are cumulative since the last pg_stat_statements_reset(), so this ranking only describes the window that started at the top-level stats_reset (with stats_reset_age_seconds beside it). This is pg_stat_statements' OWN reset clock, read from pg_stat_statements_info -- it is independent of the stats_reset reported by pg_seq_scan_tables / pg_unused_indexes, which comes from pg_stat_database, so do not compare the two timestamps or assume one implies the other. stats_reset: null means the start of the window is unknown, not that it covers all time. READ dealloc BEFORE TRUSTING THE RANKING: it counts how many times entries for the LEAST-EXECUTED statements were evicted because more distinct statements were seen than pg_stat_statements.max allows. A non-zero dealloc means this ranking is drawn from an INCOMPLETE population -- queries may be missing from these results entirely, and an evicted query's counters restart from zero if it runs again, understating it. The larger dealloc is, the more churn, so 'not in the top N' stops being evidence that a query is cheap. Raise pg_stat_statements.max to get a complete picture. On pg_stat_statements < 1.9 (before Postgres 14) pg_stat_statements_info does not exist, so stats_reset, stats_reset_age_seconds and dealloc are omitted entirely rather than returned as nulls, and a _warnings entry says so. On pg_stat_statements >= 1.10 (Postgres 15+), also returns io_read_time_ms and io_write_time_ms to separate IO-bound from CPU-bound queries (null when track_io_timing = off or the query did no measurable IO -- enable track_io_timing in postgresql.conf to get non-null values). Scoped to the database in DATABASE_URL: pg_stat_statements is cluster-wide, so results are filtered by dbid to match every other tool here rather than leaking query text from unrelated databases sharing the cluster.

ParametersJSON Schema
NameRequiredDescriptionDefault
limitNoNumber of rows to return (default 20).
orderByNoRanking: total_time (cumulative impact), mean_time (worst per-call), or calls (hottest).total_time

Output Schema

ParametersJSON Schema
NameRequiredDescription
rowsYes
deallocNoTimes least-executed entries were evicted for exceeding pg_stat_statements.max. Non-zero means this ranking is drawn from an INCOMPLETE population. Absent below extension 1.9.
_warningsNo
stats_resetNopg_stat_statements' OWN reset clock, independent of the pg_stat_database one the table/index tools report -- never compare the two. Absent below extension 1.9.
stats_reset_age_secondsNo

TDQS

A4.7/5.0
Behavior5/5

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

Exceptionally transparent: explains cumulative counters since stats_reset, the meaning of stats_reset:null, the dealloc eviction caveat that can invalidate rankings, version-specific behavior (<1.9 omissions, >=1.10 io timing), and DATABASE_URL scoping. This goes well beyond the readOnly/idempotent hints without contradicting them.

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 long, but every section earns its place: purpose, prerequisites, window semantics, the dealloc warning, version differences, and database scoping. The first sentence is a clear front-loaded summary, and there is no filler or repetition of schema content.

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?

Covers prerequisites, return value shape, caveats that affect interpretation, version differences, and cluster-wide scoping behavior. The output schema plus this description leaves no significant gap for an agent deciding whether and how to invoke this tool correctly.

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

Parameters4/5

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

Schema already covers both parameters at 100%, so the baseline is 3. The description adds semantic depth by explaining that total_time/mean_time/calls are cumulative since stats_reset and that the ranking window affects interpretation. It does not add much about `limit`, but that parameter is self-explanatory.

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?

Opens with a specific, unambiguous statement: 'Top N queries by total or mean execution time.' It clearly identifies the resource (pg_stat_statements query aggregates) and distinguishes itself from sibling performance tools like pg_explain or pg_io_stats by focusing on normalized query text and execution-time ranking.

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

Usage Guidelines4/5

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

Gives a direct use case ('Use this to find slow queries worth optimizing') and states the prerequisite that pg_stat_statements must be installed and enabled. It does not explicitly name alternative tools or when-not-to-use conditions, but the context is clear enough to guide selection.

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

pg_unused_indexesFind unused indexesA
Read-onlyIdempotent

Indexes that have never been scanned or have very low usage, largest first. Each unused index costs write amplification (every INSERT/UPDATE maintains it) and disk space, so before adding a new index, check whether the fix is to drop a dead one. Returns {rows, stats_reset, stats_reset_age_seconds}. READ THIS BEFORE RECOMMENDING A DROP: scans is a counter, not a verdict. It only counts since the last statistics reset, which is why the top-level stats_reset and stats_reset_age_seconds are part of the answer. If the counters were reset an hour ago, EVERY index looks unused; if stats_reset is null, the start of the window is unknown and the counts prove nothing. This list is only trustworthy once the reset age comfortably exceeds the slowest cycle that could use the index - a monthly report, a quarterly close, a yearly job, a failover-only query path. PRIMARY KEY and UNIQUE indexes are already excluded from these results: they enforce a constraint and stay load-bearing at zero scans, so they never appear here and their absence is not evidence of anything. On PostgreSQL 16+ each row also carries last_idx_scan, the timestamp of the most recent scan (null = never scanned since the reset). 'Not scanned since 2026-02-14' is a far better basis for a decision than a bare count. On PostgreSQL 18+, do not fall back on the old 'the leading column is never filtered, so this index is dead weight' reasoning. Skip scan lets the planner use a multi-column btree whose leading column is unconstrained, so such an index can now be doing real work.

ParametersJSON Schema
NameRequiredDescriptionDefault
limitNoMax rows to return (default 50).
schemaNoLimit to one schema. If omitted, all user schemas are included.
maxScansNoInclude indexes with scan count <= this (default 10). Use 0 for 'never scanned'.

Output Schema

ParametersJSON Schema
NameRequiredDescription
rowsYes
_warningsNo
stats_resetYesEvery counter in `rows` is cumulative SINCE this point. Null = start of the window unknown.
stats_reset_age_secondsYesSeconds since `stats_reset`; null whenever that is null.

TDQS

A4.6/5.0
Behavior5/5

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

Annotations already mark the tool read-only and idempotent, and the description adds substantial non-obvious behavioral detail: scan counts are window-dependent counters, stats_reset semantics can invalidate conclusions, PK/UNIQUE indexes are filtered out, and PG16+/PG18+ version differences change how results should be interpreted. This goes well beyond the structured 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 longer than typical, but the extra length is largely justified because misusing this tool can lead to harmful DROP INDEX recommendations. The core result and the critical stats_reset warning are front-loaded, and the version-specific notes earn their place. It could be slightly tightened but is not padded.

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 high-stakes diagnostic tool, the description covers the output shape, the main trust caveat, excluded index types, version-specific behavior, and how to reason about scan counts. With the output schema covering field-level details, nothing essential is missing for an agent to use the tool correctly.

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

Parameters4/5

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

The input schema already documents all three parameters, so the baseline is 3. The description adds real semantic value by explaining that maxScans must be interpreted relative to the stats_reset window and that a low count is not a verdict by itself. It also clarifies meaningfully what a value like 0 implies about the reset window.

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?

States exactly what is returned: indexes that have never been scanned or have very low usage, ordered largest first. It also names the return payload and gives concrete selection criteria, making the tool's resource and behavior unambiguous and distinct from siblings like pg_index_advisor or pg_seq_scan_tables.

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

Usage Guidelines4/5

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

Provides explicit guidance on when the output is trustworthy: stats_reset age must exceed the slowest relevant cycle, and null stats_reset means counts prove nothing. It also warns that PK/UNIQUE indexes are deliberately excluded. It does not explicitly name alternative sibling tools, but the contextual guidance is strong enough for correct selection.

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

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections. Dates show when Glama detected each change.

  1. 16 tool updatesv0.12.1
    • Changedpg_advisor18 fields changed
      • removedOutput schema / properties / wraparound_risk / properties / autovacuum_freeze_max_age / anyOf
        Removed value: -[
        -  {
        -    "type": "number"
        -  },
        -  {
        -    "type": "null"
        -  }
        -]
      • addedOutput schema / properties / wraparound_risk / properties / autovacuum_freeze_max_age / type
        Added value: +[
        +  "number",
        +  "null"
        +]
      • removedOutput schema / properties / wraparound_risk / properties / autovacuum_multixact_freeze_max_age / anyOf
        Removed value: -[
        -  {
        -    "type": "number"
        -  },
        -  {
        -    "type": "null"
        -  }
        -]
      • addedOutput schema / properties / wraparound_risk / properties / autovacuum_multixact_freeze_max_age / type
        Added value: +[
        +  "number",
        +  "null"
        +]
      • removedOutput schema / properties / wraparound_risk / properties / databases / items / properties / mxid_age / anyOf
        Removed value: -[
        -  {
        -    "type": "number"
        -  },
        -  {
        -    "type": "null"
        -  }
        -]
      • addedOutput schema / properties / wraparound_risk / properties / databases / items / properties / mxid_age / type
        Added value: +[
        +  "number",
        +  "null"
        +]
      • removedOutput schema / properties / wraparound_risk / properties / databases / items / properties / pct_of_freeze_max_age / anyOf
        Removed value: -[
        -  {
        -    "type": "number"
        -  },
        -  {
        -    "type": "null"
        -  }
        -]
      • addedOutput schema / properties / wraparound_risk / properties / databases / items / properties / pct_of_freeze_max_age / type
        Added value: +[
        +  "number",
        +  "null"
        +]
      • removedOutput schema / properties / wraparound_risk / properties / databases / items / properties / pct_of_multixact_freeze_max_age / anyOf
        Removed value: -[
        -  {
        -    "type": "number"
        -  },
        -  {
        -    "type": "null"
        -  }
        -]
      • addedOutput schema / properties / wraparound_risk / properties / databases / items / properties / pct_of_multixact_freeze_max_age / type
        Added value: +[
        +  "number",
        +  "null"
        +]
      • removedOutput schema / properties / wraparound_risk / properties / tables / items / properties / frozen_page_fraction / anyOf
        Removed value: -[
        -  {
        -    "type": "number"
        -  },
        -  {
        -    "type": "null"
        -  }
        -]
      • addedOutput schema / properties / wraparound_risk / properties / tables / items / properties / frozen_page_fraction / type
        Added value: +[
        +  "number",
        +  "null"
        +]
      • removedOutput schema / properties / wraparound_risk / properties / tables / items / properties / mxid_age / anyOf
        Removed value: -[
        -  {
        -    "type": "number"
        -  },
        -  {
        -    "type": "null"
        -  }
        -]
      • addedOutput schema / properties / wraparound_risk / properties / tables / items / properties / mxid_age / type
        Added value: +[
        +  "number",
        +  "null"
        +]
      • removedOutput schema / properties / wraparound_risk / properties / tables / items / properties / pct_of_freeze_max_age / anyOf
        Removed value: -[
        -  {
        -    "type": "number"
        -  },
        -  {
        -    "type": "null"
        -  }
        -]
      • addedOutput schema / properties / wraparound_risk / properties / tables / items / properties / pct_of_freeze_max_age / type
        Added value: +[
        +  "number",
        +  "null"
        +]
      • removedOutput schema / properties / wraparound_risk / properties / tables / items / properties / pct_of_multixact_freeze_max_age / anyOf
        Removed value: -[
        -  {
        -    "type": "number"
        -  },
        -  {
        -    "type": "null"
        -  }
        -]
      • addedOutput schema / properties / wraparound_risk / properties / tables / items / properties / pct_of_multixact_freeze_max_age / type
        Added value: +[
        +  "number",
        +  "null"
        +]
    • Changedpg_describe_table4 fields changed
      • removedOutput schema / properties / columns / items / properties / default_value / anyOf
        Removed value: -[
        -  {
        -    "type": "string"
        -  },
        -  {
        -    "type": "null"
        -  }
        -]
      • addedOutput schema / properties / columns / items / properties / default_value / type
        Added value: +[
        +  "string",
        +  "null"
        +]
      • removedOutput schema / properties / columns / items / properties / generation_expression / anyOf
        Removed value: -[
        -  {
        -    "type": "string"
        -  },
        -  {
        -    "type": "null"
        -  }
        -]
      • addedOutput schema / properties / columns / items / properties / generation_expression / type
        Added value: +[
        +  "string",
        +  "null"
        +]
    • Changedpg_health16 fields changed
      • removedOutput schema / properties / active_queries / items / properties / duration_seconds / anyOf
        Removed value: -[
        -  {
        -    "type": "number"
        -  },
        -  {
        -    "type": "null"
        -  }
        -]
      • addedOutput schema / properties / active_queries / items / properties / duration_seconds / type
        Added value: +[
        +  "number",
        +  "null"
        +]
      • removedOutput schema / properties / active_queries / items / properties / state / anyOf
        Removed value: -[
        -  {
        -    "type": "string"
        -  },
        -  {
        -    "type": "null"
        -  }
        -]
      • addedOutput schema / properties / active_queries / items / properties / state / type
        Added value: +[
        +  "string",
        +  "null"
        +]
      • removedOutput schema / properties / active_queries / items / properties / transaction_age_seconds / anyOf
        Removed value: -[
        -  {
        -    "type": "number"
        -  },
        -  {
        -    "type": "null"
        -  }
        -]
      • addedOutput schema / properties / active_queries / items / properties / transaction_age_seconds / type
        Added value: +[
        +  "number",
        +  "null"
        +]
      • removedOutput schema / properties / active_queries / items / properties / wait_event / anyOf
        Removed value: -[
        -  {
        -    "type": "string"
        -  },
        -  {
        -    "type": "null"
        -  }
        -]
      • addedOutput schema / properties / active_queries / items / properties / wait_event / type
        Added value: +[
        +  "string",
        +  "null"
        +]
      • removedOutput schema / properties / active_queries / items / properties / wait_event_type / anyOf
        Removed value: -[
        -  {
        -    "type": "string"
        -  },
        -  {
        -    "type": "null"
        -  }
        -]
      • addedOutput schema / properties / active_queries / items / properties / wait_event_type / type
        Added value: +[
        +  "string",
        +  "null"
        +]
      • removedOutput schema / properties / active_queries / items / properties / xact_start / anyOf
        Removed value: -[
        -  {
        -    "type": "string"
        -  },
        -  {
        -    "type": "null"
        -  }
        -]
      • addedOutput schema / properties / active_queries / items / properties / xact_start / type
        Added value: +[
        +  "string",
        +  "null"
        +]
      • changedOutput schema / properties / connections / anyOf
        Previous value: -[
        -  {
        -    "additionalProperties": false,
        -    "properties": {
        -      "active": {
        -        "type": "string"
        -      },
        -      "cluster_client_backends": {
        -        "description": "Client backends across ALL databases -- the ones that actually consume connection slots.",
        -        "type": "string"
        -      },
        -      "idle": {
        -        "type": "string"
        -      },
        -      "idle_in_transaction": {
        -        "type": "string"
        -      },
        -      "idle_in_transaction_aborted": {
        -        "description": "Holds locks and blocks vacuum while doing no work, and can never commit.",
        -        "type": "string"
        -      },
        -      "max_connections": {
        -        "type": "number"
        -      },
        -      "other": {
        -        "description": "Catch-all: starting, fastpath function call, disabled, plus any future state.",
        -        "type": "string"
        -      },
        -      "state_unavailable": {
        -        "description": "Sessions whose `state` read NULL for lack of pg_read_all_stats / pg_monitor. Non-zero means every other bucket is under-counted -- do NOT read `active: 0` beside it as an idle database.",
        -        "type": "string"
        -      },
        -      "superuser_reserved_connections": {
        -        "type": "number"
        -      },
        -      "total": {
        -        "description": "Sessions in the CURRENT database. The six state buckets below sum to this.",
        -        "type": "string"
        -      },
        -      "used_fraction": {
        -        "anyOf": [
        -          {
        -            "type": "number"
        -          },
        -          {
        -            "type": "null"
        -          }
        -        ],
        -        "description": "cluster_client_backends / max_connections. Null only if max_connections read as 0."
        -      }
        -    },
        -    "required": [
        -      "total",
        -      "active",
        -      "idle",
        -      "idle_in_transaction",
        -      "idle_in_transaction_aborted",
        -      "other",
        -      "state_unavailable",
        -      "cluster_client_backends",
        -      "max_connections",
        -      "superuser_reserved_connections",
        -      "used_fraction"
        -    ],
        -    "type": "object"
        -  },
        -  {
        -    "type": "null"
        -  }
        -]New value: +[
        +  {
        +    "additionalProperties": false,
        +    "properties": {
        +      "active": {
        +        "type": "string"
        +      },
        +      "cluster_client_backends": {
        +        "description": "Client backends across ALL databases -- the ones that actually consume connection slots.",
        +        "type": "string"
        +      },
        +      "idle": {
        +        "type": "string"
        +      },
        +      "idle_in_transaction": {
        +        "type": "string"
        +      },
        +      "idle_in_transaction_aborted": {
        +        "description": "Holds locks and blocks vacuum while doing no work, and can never commit.",
        +        "type": "string"
        +      },
        +      "max_connections": {
        +        "type": "number"
        +      },
        +      "other": {
        +        "description": "Catch-all: starting, fastpath function call, disabled, plus any future state.",
        +        "type": "string"
        +      },
        +      "state_unavailable": {
        +        "description": "Sessions whose `state` read NULL for lack of pg_read_all_stats / pg_monitor. Non-zero means every other bucket is under-counted -- do NOT read `active: 0` beside it as an idle database.",
        +        "type": "string"
        +      },
        +      "superuser_reserved_connections": {
        +        "type": "number"
        +      },
        +      "total": {
        +        "description": "Sessions in the CURRENT database. The six state buckets below sum to this.",
        +        "type": "string"
        +      },
        +      "used_fraction": {
        +        "description": "cluster_client_backends / max_connections. Null only if max_connections read as 0.",
        +        "type": [
        +          "number",
        +          "null"
        +        ]
        +      }
        +    },
        +    "required": [
        +      "total",
        +      "active",
        +      "idle",
        +      "idle_in_transaction",
        +      "idle_in_transaction_aborted",
        +      "other",
        +      "state_unavailable",
        +      "cluster_client_backends",
        +      "max_connections",
        +      "superuser_reserved_connections",
        +      "used_fraction"
        +    ],
        +    "type": "object"
        +  },
        +  {
        +    "type": "null"
        +  }
        +]
      • changedOutput schema / properties / database_stats / anyOf
        Previous value: -[
        -  {
        -    "additionalProperties": false,
        -    "properties": {
        -      "blks_hit": {
        -        "type": "string"
        -      },
        -      "blks_read": {
        -        "type": "string"
        -      },
        -      "cache_hit_ratio": {
        -        "anyOf": [
        -          {
        -            "type": "number"
        -          },
        -          {
        -            "type": "null"
        -          }
        -        ],
        -        "description": "Null on a freshly reset database (both counters 0)."
        -      },
        -      "conflicts": {
        -        "description": "Recovery conflicts; only ever non-zero on a replica.",
        -        "type": "string"
        -      },
        -      "deadlocks": {
        -        "type": "string"
        -      },
        -      "stats_reset": {
        -        "anyOf": [
        -          {
        -            "type": "string"
        -          },
        -          {
        -            "type": "null"
        -          }
        -        ],
        -        "description": "Every counter here is cumulative SINCE this timestamp. Null = never reset."
        -      },
        -      "temp_bytes": {
        -        "type": "string"
        -      },
        -      "temp_bytes_pretty": {
        -        "type": "string"
        -      },
        -      "temp_files": {
        -        "type": "string"
        -      }
        -    },
        -    "required": [
        -      "deadlocks",
        -      "temp_files",
        -      "temp_bytes",
        -      "temp_bytes_pretty",
        -      "conflicts",
        -      "blks_hit",
        -      "blks_read",
        -      "cache_hit_ratio",
        -      "stats_reset"
        -    ],
        -    "type": "object"
        -  },
        -  {
        -    "type": "null"
        -  }
        -]New value: +[
        +  {
        +    "additionalProperties": false,
        +    "properties": {
        +      "blks_hit": {
        +        "type": "string"
        +      },
        +      "blks_read": {
        +        "type": "string"
        +      },
        +      "cache_hit_ratio": {
        +        "description": "Null on a freshly reset database (both counters 0).",
        +        "type": [
        +          "number",
        +          "null"
        +        ]
        +      },
        +      "conflicts": {
        +        "description": "Recovery conflicts; only ever non-zero on a replica.",
        +        "type": "string"
        +      },
        +      "deadlocks": {
        +        "type": "string"
        +      },
        +      "stats_reset": {
        +        "description": "Every counter here is cumulative SINCE this timestamp. Null = never reset.",
        +        "type": [
        +          "string",
        +          "null"
        +        ]
        +      },
        +      "temp_bytes": {
        +        "type": "string"
        +      },
        +      "temp_bytes_pretty": {
        +        "type": "string"
        +      },
        +      "temp_files": {
        +        "type": "string"
        +      }
        +    },
        +    "required": [
        +      "deadlocks",
        +      "temp_files",
        +      "temp_bytes",
        +      "temp_bytes_pretty",
        +      "conflicts",
        +      "blks_hit",
        +      "blks_read",
        +      "cache_hit_ratio",
        +      "stats_reset"
        +    ],
        +    "type": "object"
        +  },
        +  {
        +    "type": "null"
        +  }
        +]
      • removedOutput schema / properties / table_count / anyOf
        Removed value: -[
        -  {
        -    "type": "string"
        -  },
        -  {
        -    "type": "null"
        -  }
        -]
      • addedOutput schema / properties / table_count / type
        Added value: +[
        +  "string",
        +  "null"
        +]
    • Changedpg_index_advisor4 fields changed
      • removedOutput schema / properties / recommendations / items / properties / estimated_size_bytes / anyOf
        Removed value: -[
        -  {
        -    "type": "string"
        -  },
        -  {
        -    "type": "null"
        -  }
        -]
      • addedOutput schema / properties / recommendations / items / properties / estimated_size_bytes / type
        Added value: +[
        +  "string",
        +  "null"
        +]
      • removedOutput schema / properties / statements / items / properties / baseline_cost / anyOf
        Removed value: -[
        -  {
        -    "type": "number"
        -  },
        -  {
        -    "type": "null"
        -  }
        -]
      • addedOutput schema / properties / statements / items / properties / baseline_cost / type
        Added value: +[
        +  "number",
        +  "null"
        +]
    • Changedpg_inspect_locks17 fields changed
      • removedOutput schema / properties / rows / items / properties / blocked_duration_seconds / anyOf
        Removed value: -[
        -  {
        -    "type": "number"
        -  },
        -  {
        -    "type": "null"
        -  }
        -]
      • addedOutput schema / properties / rows / items / properties / blocked_duration_seconds / type
        Added value: +[
        +  "number",
        +  "null"
        +]
      • removedOutput schema / properties / rows / items / properties / blocked_query / anyOf
        Removed value: -[
        -  {
        -    "type": "string"
        -  },
        -  {
        -    "type": "null"
        -  }
        -]
      • addedOutput schema / properties / rows / items / properties / blocked_query / type
        Added value: +[
        +  "string",
        +  "null"
        +]
      • removedOutput schema / properties / rows / items / properties / blocked_user / anyOf
        Removed value: -[
        -  {
        -    "type": "string"
        -  },
        -  {
        -    "type": "null"
        -  }
        -]
      • addedOutput schema / properties / rows / items / properties / blocked_user / type
        Added value: +[
        +  "string",
        +  "null"
        +]
      • removedOutput schema / properties / rows / items / properties / blocking_duration_seconds / anyOf
        Removed value: -[
        -  {
        -    "type": "number"
        -  },
        -  {
        -    "type": "null"
        -  }
        -]
      • addedOutput schema / properties / rows / items / properties / blocking_duration_seconds / type
        Added value: +[
        +  "number",
        +  "null"
        +]
      • removedOutput schema / properties / rows / items / properties / blocking_query / anyOf
        Removed value: -[
        -  {
        -    "type": "string"
        -  },
        -  {
        -    "type": "null"
        -  }
        -]
      • addedOutput schema / properties / rows / items / properties / blocking_query / type
        Added value: +[
        +  "string",
        +  "null"
        +]
      • removedOutput schema / properties / rows / items / properties / blocking_state / anyOf
        Removed value: -[
        -  {
        -    "type": "string"
        -  },
        -  {
        -    "type": "null"
        -  }
        -]
      • addedOutput schema / properties / rows / items / properties / blocking_state / type
        Added value: +[
        +  "string",
        +  "null"
        +]
      • removedOutput schema / properties / rows / items / properties / blocking_user / anyOf
        Removed value: -[
        -  {
        -    "type": "string"
        -  },
        -  {
        -    "type": "null"
        -  }
        -]
      • addedOutput schema / properties / rows / items / properties / blocking_user / type
        Added value: +[
        +  "string",
        +  "null"
        +]
      • removedOutput schema / properties / rows / items / properties / relation / anyOf
        Removed value: -[
        -  {
        -    "type": "string"
        -  },
        -  {
        -    "type": "null"
        -  }
        -]
      • changedOutput schema / properties / rows / items / properties / relation / description
        Previous value: -"schema.table. For transactionid / virtualxid waits this is a best-effort GUESS among the blocker's held write-intent locks, not authoritative -- disambiguate with the query text."New value: +"schema.table. NULL has two causes, told apart by `lock_type`: on a relation wait (relation / extend / page / tuple) NULL means the lock is held in ANOTHER database and cannot be named from here; on a transactionid / virtualxid / advisory wait this field was only ever a best-effort GUESS among the blocker's held write-intent locks, so NULL means the guess found nothing and the blocker is very likely LOCAL. Never authoritative on the guess path -- disambiguate with the query text."
      • addedOutput schema / properties / rows / items / properties / relation / type
        Added value: +[
        +  "string",
        +  "null"
        +]
    • Changedpg_io_stats4 fields changed
      • changedOutput schema / properties / in_flight / anyOf
        Previous value: -[
        -  {
        -    "items": {
        -      "additionalProperties": false,
        -      "properties": {
        -        "io_id": {
        -          "type": "number"
        -        },
        -        "length": {
        -          "anyOf": [
        -            {
        -              "type": "string"
        -            },
        -            {
        -              "type": "null"
        -            }
        -          ]
        -        },
        -        "off": {
        -          "anyOf": [
        -            {
        -              "type": "string"
        -            },
        -            {
        -              "type": "null"
        -            }
        -          ],
        -          "description": "File offset, cast to text so a widened column stays lossless."
        -        },
        -        "op": {
        -          "anyOf": [
        -            {
        -              "type": "string"
        -            },
        -            {
        -              "type": "null"
        -            }
        -          ]
        -        },
        -        "pid": {
        -          "description": "Line this up against pg_health / pg_inspect_locks output for the same backend.",
        -          "type": "number"
        -        },
        -        "state": {
        -          "anyOf": [
        -            {
        -              "type": "string"
        -            },
        -            {
        -              "type": "null"
        -            }
        -          ]
        -        },
        -        "target_desc": {
        -          "anyOf": [
        -            {
        -              "type": "string"
        -            },
        -            {
        -              "type": "null"
        -            }
        -          ]
        -        }
        -      },
        -      "required": [
        -        "pid",
        -        "io_id",
        -        "op",
        -        "state",
        -        "off",
        -        "length",
        -        "target_desc"
        -      ],
        -      "type": "object"
        -    },
        -    "type": "array"
        -  },
        -  {
        -    "type": "null"
        -  }
        -]New value: +[
        +  {
        +    "items": {
        +      "additionalProperties": false,
        +      "properties": {
        +        "io_id": {
        +          "type": "number"
        +        },
        +        "length": {
        +          "type": [
        +            "string",
        +            "null"
        +          ]
        +        },
        +        "off": {
        +          "description": "File offset, cast to text so a widened column stays lossless.",
        +          "type": [
        +            "string",
        +            "null"
        +          ]
        +        },
        +        "op": {
        +          "type": [
        +            "string",
        +            "null"
        +          ]
        +        },
        +        "pid": {
        +          "description": "Line this up against pg_health / pg_inspect_locks output for the same backend.",
        +          "type": "number"
        +        },
        +        "state": {
        +          "type": [
        +            "string",
        +            "null"
        +          ]
        +        },
        +        "target_desc": {
        +          "type": [
        +            "string",
        +            "null"
        +          ]
        +        }
        +      },
        +      "required": [
        +        "pid",
        +        "io_id",
        +        "op",
        +        "state",
        +        "off",
        +        "length",
        +        "target_desc"
        +      ],
        +      "type": "object"
        +    },
        +    "type": "array"
        +  },
        +  {
        +    "type": "null"
        +  }
        +]
      • changedOutput schema / properties / io / anyOf
        Previous value: -[
        -  {
        -    "items": {
        -      "additionalProperties": false,
        -      "properties": {
        -        "backend_type": {
        -          "type": "string"
        -        },
        -        "evictions": {
        -          "anyOf": [
        -            {
        -              "type": "string"
        -            },
        -            {
        -              "type": "null"
        -            }
        -          ]
        -        },
        -        "extend_bytes": {
        -          "anyOf": [
        -            {
        -              "type": "string"
        -            },
        -            {
        -              "type": "null"
        -            }
        -          ]
        -        },
        -        "extend_time_ms": {
        -          "anyOf": [
        -            {
        -              "type": "number"
        -            },
        -            {
        -              "type": "null"
        -            }
        -          ]
        -        },
        -        "extends": {
        -          "anyOf": [
        -            {
        -              "type": "string"
        -            },
        -            {
        -              "type": "null"
        -            }
        -          ]
        -        },
        -        "fsync_time_ms": {
        -          "anyOf": [
        -            {
        -              "type": "number"
        -            },
        -            {
        -              "type": "null"
        -            }
        -          ]
        -        },
        -        "fsyncs": {
        -          "anyOf": [
        -            {
        -              "type": "string"
        -            },
        -            {
        -              "type": "null"
        -            }
        -          ]
        -        },
        -        "hits": {
        -          "anyOf": [
        -            {
        -              "type": "string"
        -            },
        -            {
        -              "type": "null"
        -            }
        -          ]
        -        },
        -        "io_context": {
        -          "description": "The view's `context` column, renamed for symmetry with io_object.",
        -          "type": "string"
        -        },
        -        "io_object": {
        -          "description": "The view's `object` column, renamed -- it is a postgres keyword.",
        -          "type": "string"
        -        },
        -        "read_bytes": {
        -          "anyOf": [
        -            {
        -              "type": "string"
        -            },
        -            {
        -              "type": "null"
        -            }
        -          ],
        -          "description": "Source named by the top-level `byte_accounting`."
        -        },
        -        "read_time_ms": {
        -          "anyOf": [
        -            {
        -              "type": "number"
        -            },
        -            {
        -              "type": "null"
        -            }
        -          ],
        -          "description": "0 next to a non-zero op count means track_io_timing is OFF, not that the I/O was free."
        -        },
        -        "reads": {
        -          "anyOf": [
        -            {
        -              "type": "string"
        -            },
        -            {
        -              "type": "null"
        -            }
        -          ]
        -        },
        -        "reuses": {
        -          "anyOf": [
        -            {
        -              "type": "string"
        -            },
        -            {
        -              "type": "null"
        -            }
        -          ]
        -        },
        -        "stats_reset": {
        -          "anyOf": [
        -            {
        -              "type": "string"
        -            },
        -            {
        -              "type": "null"
        -            }
        -          ],
        -          "description": "These counters are cumulative SINCE this point. Null = never reset."
        -        },
        -        "write_bytes": {
        -          "anyOf": [
        -            {
        -              "type": "string"
        -            },
        -            {
        -              "type": "null"
        -            }
        -          ]
        -        },
        -        "write_time_ms": {
        -          "anyOf": [
        -            {
        -              "type": "number"
        -            },
        -            {
        -              "type": "null"
        -            }
        -          ]
        -        },
        -        "writeback_time_ms": {
        -          "anyOf": [
        -            {
        -              "type": "number"
        -            },
        -            {
        -              "type": "null"
        -            }
        -          ]
        -        },
        -        "writebacks": {
        -          "anyOf": [
        -            {
        -              "type": "string"
        -            },
        -            {
        -              "type": "null"
        -            }
        -          ]
        -        },
        -        "writes": {
        -          "anyOf": [
        -            {
        -              "type": "string"
        -            },
        -            {
        -              "type": "null"
        -            }
        -          ]
        -        }
        -      },
        -      "required": [
        -        "backend_type",
        -        "io_object",
        -        "io_context",
        -        "reads",
        -        "read_bytes",
        -        "read_time_ms",
        -        "writes",
        -        "write_bytes",
        -        "write_time_ms",
        -        "writebacks",
        -        "writeback_time_ms",
        -        "extends",
        -        "extend_bytes",
        -        "extend_time_ms",
        -        "hits",
        -        "evictions",
        -        "reuses",
        -        "fsyncs",
        -        "fsync_time_ms",
        -        "stats_reset"
        -      ],
        -      "type": "object"
        -    },
        -    "type": "array"
        -  },
        -  {
        -    "type": "null"
        -  }
        -]New value: +[
        +  {
        +    "items": {
        +      "additionalProperties": false,
        +      "properties": {
        +        "backend_type": {
        +          "type": "string"
        +        },
        +        "evictions": {
        +          "type": [
        +            "string",
        +            "null"
        +          ]
        +        },
        +        "extend_bytes": {
        +          "type": [
        +            "string",
        +            "null"
        +          ]
        +        },
        +        "extend_time_ms": {
        +          "type": [
        +            "number",
        +            "null"
        +          ]
        +        },
        +        "extends": {
        +          "type": [
        +            "string",
        +            "null"
        +          ]
        +        },
        +        "fsync_time_ms": {
        +          "type": [
        +            "number",
        +            "null"
        +          ]
        +        },
        +        "fsyncs": {
        +          "type": [
        +            "string",
        +            "null"
        +          ]
        +        },
        +        "hits": {
        +          "type": [
        +            "string",
        +            "null"
        +          ]
        +        },
        +        "io_context": {
        +          "description": "The view's `context` column, renamed for symmetry with io_object.",
        +          "type": "string"
        +        },
        +        "io_object": {
        +          "description": "The view's `object` column, renamed -- it is a postgres keyword.",
        +          "type": "string"
        +        },
        +        "read_bytes": {
        +          "description": "Source named by the top-level `byte_accounting`.",
        +          "type": [
        +            "string",
        +            "null"
        +          ]
        +        },
        +        "read_time_ms": {
        +          "description": "0 next to a non-zero op count means track_io_timing is OFF, not that the I/O was free.",
        +          "type": [
        +            "number",
        +            "null"
        +          ]
        +        },
        +        "reads": {
        +          "type": [
        +            "string",
        +            "null"
        +          ]
        +        },
        +        "reuses": {
        +          "type": [
        +            "string",
        +            "null"
        +          ]
        +        },
        +        "stats_reset": {
        +          "description": "These counters are cumulative SINCE this point. Null = never reset.",
        +          "type": [
        +            "string",
        +            "null"
        +          ]
        +        },
        +        "write_bytes": {
        +          "type": [
        +            "string",
        +            "null"
        +          ]
        +        },
        +        "write_time_ms": {
        +          "type": [
        +            "number",
        +            "null"
        +          ]
        +        },
        +        "writeback_time_ms": {
        +          "type": [
        +            "number",
        +            "null"
        +          ]
        +        },
        +        "writebacks": {
        +          "type": [
        +            "string",
        +            "null"
        +          ]
        +        },
        +        "writes": {
        +          "type": [
        +            "string",
        +            "null"
        +          ]
        +        }
        +      },
        +      "required": [
        +        "backend_type",
        +        "io_object",
        +        "io_context",
        +        "reads",
        +        "read_bytes",
        +        "read_time_ms",
        +        "writes",
        +        "write_bytes",
        +        "write_time_ms",
        +        "writebacks",
        +        "writeback_time_ms",
        +        "extends",
        +        "extend_bytes",
        +        "extend_time_ms",
        +        "hits",
        +        "evictions",
        +        "reuses",
        +        "fsyncs",
        +        "fsync_time_ms",
        +        "stats_reset"
        +      ],
        +      "type": "object"
        +    },
        +    "type": "array"
        +  },
        +  {
        +    "type": "null"
        +  }
        +]
      • removedOutput schema / properties / io_method / anyOf
        Removed value: -[
        -  {
        -    "type": "string"
        -  },
        -  {
        -    "type": "null"
        -  }
        -]
      • addedOutput schema / properties / io_method / type
        Added value: +[
        +  "string",
        +  "null"
        +]
    • Changedpg_list_extensions2 fields changed
      • removedOutput schema / properties / rows / items / properties / description / anyOf
        Removed value: -[
        -  {
        -    "type": "string"
        -  },
        -  {
        -    "type": "null"
        -  }
        -]
      • addedOutput schema / properties / rows / items / properties / description / type
        Added value: +[
        +  "string",
        +  "null"
        +]
    • Changedpg_list_functions2 fields changed
      • removedOutput schema / properties / rows / items / properties / return_type / anyOf
        Removed value: -[
        -  {
        -    "type": "string"
        -  },
        -  {
        -    "type": "null"
        -  }
        -]
      • addedOutput schema / properties / rows / items / properties / return_type / type
        Added value: +[
        +  "string",
        +  "null"
        +]
    • Changedpg_list_tables2 fields changed
      • removedOutput schema / properties / rows / items / properties / estimated_rows / anyOf
        Removed value: -[
        -  {
        -    "type": "number"
        -  },
        -  {
        -    "type": "null"
        -  }
        -]
      • addedOutput schema / properties / rows / items / properties / estimated_rows / type
        Added value: +[
        +  "number",
        +  "null"
        +]
    • Changedpg_query2 fields changed
      • removedOutput schema / properties / rowCount / anyOf
        Removed value: -[
        -  {
        -    "type": "number"
        -  },
        -  {
        -    "type": "null"
        -  }
        -]
      • addedOutput schema / properties / rowCount / type
        Added value: +[
        +  "number",
        +  "null"
        +]
    • Changedpg_readonly2 fields changed
      • removedOutput schema / properties / rowCount / anyOf
        Removed value: -[
        -  {
        -    "type": "number"
        -  },
        -  {
        -    "type": "null"
        -  }
        -]
      • addedOutput schema / properties / rowCount / type
        Added value: +[
        +  "number",
        +  "null"
        +]
    • Changedpg_replication_status22 fields changed
      • removedOutput schema / properties / is_replica / anyOf
        Removed value: -[
        -  {
        -    "type": "boolean"
        -  },
        -  {
        -    "type": "null"
        -  }
        -]
      • addedOutput schema / properties / is_replica / type
        Added value: +[
        +  "boolean",
        +  "null"
        +]
      • removedOutput schema / properties / replicas / items / properties / client_addr / anyOf
        Removed value: -[
        -  {
        -    "type": "string"
        -  },
        -  {
        -    "type": "null"
        -  }
        -]
      • addedOutput schema / properties / replicas / items / properties / client_addr / type
        Added value: +[
        +  "string",
        +  "null"
        +]
      • removedOutput schema / properties / replicas / items / properties / flush_lag_seconds / anyOf
        Removed value: -[
        -  {
        -    "type": "number"
        -  },
        -  {
        -    "type": "null"
        -  }
        -]
      • addedOutput schema / properties / replicas / items / properties / flush_lag_seconds / type
        Added value: +[
        +  "number",
        +  "null"
        +]
      • removedOutput schema / properties / replicas / items / properties / replay_lag_seconds / anyOf
        Removed value: -[
        -  {
        -    "type": "number"
        -  },
        -  {
        -    "type": "null"
        -  }
        -]
      • addedOutput schema / properties / replicas / items / properties / replay_lag_seconds / type
        Added value: +[
        +  "number",
        +  "null"
        +]
      • removedOutput schema / properties / replicas / items / properties / write_lag_seconds / anyOf
        Removed value: -[
        -  {
        -    "type": "number"
        -  },
        -  {
        -    "type": "null"
        -  }
        -]
      • addedOutput schema / properties / replicas / items / properties / write_lag_seconds / type
        Added value: +[
        +  "number",
        +  "null"
        +]
      • removedOutput schema / properties / slots / items / properties / confirmed_flush_lsn / anyOf
        Removed value: -[
        -  {
        -    "type": "string"
        -  },
        -  {
        -    "type": "null"
        -  }
        -]
      • addedOutput schema / properties / slots / items / properties / confirmed_flush_lsn / type
        Added value: +[
        +  "string",
        +  "null"
        +]
      • removedOutput schema / properties / slots / items / properties / database / anyOf
        Removed value: -[
        -  {
        -    "type": "string"
        -  },
        -  {
        -    "type": "null"
        -  }
        -]
      • addedOutput schema / properties / slots / items / properties / database / type
        Added value: +[
        +  "string",
        +  "null"
        +]
      • removedOutput schema / properties / slots / items / properties / plugin / anyOf
        Removed value: -[
        -  {
        -    "type": "string"
        -  },
        -  {
        -    "type": "null"
        -  }
        -]
      • addedOutput schema / properties / slots / items / properties / plugin / type
        Added value: +[
        +  "string",
        +  "null"
        +]
      • removedOutput schema / properties / slots / items / properties / restart_lsn / anyOf
        Removed value: -[
        -  {
        -    "type": "string"
        -  },
        -  {
        -    "type": "null"
        -  }
        -]
      • addedOutput schema / properties / slots / items / properties / restart_lsn / type
        Added value: +[
        +  "string",
        +  "null"
        +]
      • removedOutput schema / properties / slots / items / properties / wal_status / anyOf
        Removed value: -[
        -  {
        -    "type": "string"
        -  },
        -  {
        -    "type": "null"
        -  }
        -]
      • addedOutput schema / properties / slots / items / properties / wal_status / type
        Added value: +[
        +  "string",
        +  "null"
        +]
      • removedOutput schema / properties / wal_position / anyOf
        Removed value: -[
        -  {
        -    "type": "string"
        -  },
        -  {
        -    "type": "null"
        -  }
        -]
      • addedOutput schema / properties / wal_position / type
        Added value: +[
        +  "string",
        +  "null"
        +]
    • Changedpg_seq_scan_tables10 fields changed
      • removedOutput schema / properties / rows / items / properties / last_idx_scan / anyOf
        Removed value: -[
        -  {
        -    "type": "string"
        -  },
        -  {
        -    "type": "null"
        -  }
        -]
      • addedOutput schema / properties / rows / items / properties / last_idx_scan / type
        Added value: +[
        +  "string",
        +  "null"
        +]
      • removedOutput schema / properties / rows / items / properties / last_seq_scan / anyOf
        Removed value: -[
        -  {
        -    "type": "string"
        -  },
        -  {
        -    "type": "null"
        -  }
        -]
      • addedOutput schema / properties / rows / items / properties / last_seq_scan / type
        Added value: +[
        +  "string",
        +  "null"
        +]
      • removedOutput schema / properties / rows / items / properties / ratio / anyOf
        Removed value: -[
        -  {
        -    "type": "number"
        -  },
        -  {
        -    "type": "null"
        -  }
        -]
      • addedOutput schema / properties / rows / items / properties / ratio / type
        Added value: +[
        +  "number",
        +  "null"
        +]
      • removedOutput schema / properties / stats_reset / anyOf
        Removed value: -[
        -  {
        -    "type": "string"
        -  },
        -  {
        -    "type": "null"
        -  }
        -]
      • addedOutput schema / properties / stats_reset / type
        Added value: +[
        +  "string",
        +  "null"
        +]
      • removedOutput schema / properties / stats_reset_age_seconds / anyOf
        Removed value: -[
        -  {
        -    "type": "number"
        -  },
        -  {
        -    "type": "null"
        -  }
        -]
      • addedOutput schema / properties / stats_reset_age_seconds / type
        Added value: +[
        +  "number",
        +  "null"
        +]
    • Changedpg_table_bloat8 fields changed
      • removedOutput schema / properties / rows / items / properties / last_analyze / anyOf
        Removed value: -[
        -  {
        -    "type": "string"
        -  },
        -  {
        -    "type": "null"
        -  }
        -]
      • addedOutput schema / properties / rows / items / properties / last_analyze / type
        Added value: +[
        +  "string",
        +  "null"
        +]
      • removedOutput schema / properties / rows / items / properties / last_autovacuum / anyOf
        Removed value: -[
        -  {
        -    "type": "string"
        -  },
        -  {
        -    "type": "null"
        -  }
        -]
      • addedOutput schema / properties / rows / items / properties / last_autovacuum / type
        Added value: +[
        +  "string",
        +  "null"
        +]
      • removedOutput schema / properties / rows / items / properties / last_vacuum / anyOf
        Removed value: -[
        -  {
        -    "type": "string"
        -  },
        -  {
        -    "type": "null"
        -  }
        -]
      • addedOutput schema / properties / rows / items / properties / last_vacuum / type
        Added value: +[
        +  "string",
        +  "null"
        +]
      • removedOutput schema / properties / rows / items / properties / stats_reset / anyOf
        Removed value: -[
        -  {
        -    "type": "string"
        -  },
        -  {
        -    "type": "null"
        -  }
        -]
      • addedOutput schema / properties / rows / items / properties / stats_reset / type
        Added value: +[
        +  "string",
        +  "null"
        +]
    • Changedpg_top_queries12 fields changed
      • removedOutput schema / properties / dealloc / anyOf
        Removed value: -[
        -  {
        -    "type": "string"
        -  },
        -  {
        -    "type": "null"
        -  }
        -]
      • addedOutput schema / properties / dealloc / type
        Added value: +[
        +  "string",
        +  "null"
        +]
      • removedOutput schema / properties / rows / items / properties / hit_percent / anyOf
        Removed value: -[
        -  {
        -    "type": "number"
        -  },
        -  {
        -    "type": "null"
        -  }
        -]
      • addedOutput schema / properties / rows / items / properties / hit_percent / type
        Added value: +[
        +  "number",
        +  "null"
        +]
      • removedOutput schema / properties / rows / items / properties / io_read_time_ms / anyOf
        Removed value: -[
        -  {
        -    "type": "number"
        -  },
        -  {
        -    "type": "null"
        -  }
        -]
      • addedOutput schema / properties / rows / items / properties / io_read_time_ms / type
        Added value: +[
        +  "number",
        +  "null"
        +]
      • removedOutput schema / properties / rows / items / properties / io_write_time_ms / anyOf
        Removed value: -[
        -  {
        -    "type": "number"
        -  },
        -  {
        -    "type": "null"
        -  }
        -]
      • addedOutput schema / properties / rows / items / properties / io_write_time_ms / type
        Added value: +[
        +  "number",
        +  "null"
        +]
      • removedOutput schema / properties / stats_reset / anyOf
        Removed value: -[
        -  {
        -    "type": "string"
        -  },
        -  {
        -    "type": "null"
        -  }
        -]
      • addedOutput schema / properties / stats_reset / type
        Added value: +[
        +  "string",
        +  "null"
        +]
      • removedOutput schema / properties / stats_reset_age_seconds / anyOf
        Removed value: -[
        -  {
        -    "type": "number"
        -  },
        -  {
        -    "type": "null"
        -  }
        -]
      • addedOutput schema / properties / stats_reset_age_seconds / type
        Added value: +[
        +  "number",
        +  "null"
        +]
    • Changedpg_unused_indexes6 fields changed
      • removedOutput schema / properties / rows / items / properties / last_idx_scan / anyOf
        Removed value: -[
        -  {
        -    "type": "string"
        -  },
        -  {
        -    "type": "null"
        -  }
        -]
      • addedOutput schema / properties / rows / items / properties / last_idx_scan / type
        Added value: +[
        +  "string",
        +  "null"
        +]
      • removedOutput schema / properties / stats_reset / anyOf
        Removed value: -[
        -  {
        -    "type": "string"
        -  },
        -  {
        -    "type": "null"
        -  }
        -]
      • addedOutput schema / properties / stats_reset / type
        Added value: +[
        +  "string",
        +  "null"
        +]
      • removedOutput schema / properties / stats_reset_age_seconds / anyOf
        Removed value: -[
        -  {
        -    "type": "number"
        -  },
        -  {
        -    "type": "null"
        -  }
        -]
      • addedOutput schema / properties / stats_reset_age_seconds / type
        Added value: +[
        +  "number",
        +  "null"
        +]
  2. 23 tool updatesv0.12.0
    • Changedpg_advisor3 fields changed
      • changedInput schema / $schema
        Previous value: -"http://json-schema.org/draft-07/schema#"New value: +"https://json-schema.org/draft/2020-12/schema"
      • addedInput schema / properties / wraparoundThreshold
        Added value: +{
        +  "default": 0.5,
        +  "description": "Minimum used-fraction to flag a database or table for wraparound risk (default 0.5 = 50%). Applied to BOTH ratios -- age(frozenxid) / autovacuum_freeze_max_age and mxid_age(minmxid) / autovacuum_multixact_freeze_max_age -- and a row is flagged if either one clears it. 1.0 is where autovacuum starts forcing anti-wraparound VACUUMs.",
        +  "maximum": 1,
        +  "minimum": 0,
        +  "type": "number"
        +}
      • changedOutput schema / (root)
        Previous value: -nullNew value: +{
        +  "$schema": "https://json-schema.org/draft/2020-12/schema",
        +  "additionalProperties": false,
        +  "properties": {
        +    "_warnings": {
        +      "items": {
        +        "type": "string"
        +      },
        +      "type": "array"
        +    },
        +    "public_tables_without_rls": {
        +      "items": {
        +        "additionalProperties": false,
        +        "properties": {
        +          "schema": {
        +            "type": "string"
        +          },
        +          "table": {
        +            "type": "string"
        +          }
        +        },
        +        "required": [
        +          "schema",
        +          "table"
        +        ],
        +        "type": "object"
        +      },
        +      "type": "array"
        +    },
        +    "sequence_exhaustion": {
        +      "items": {
        +        "additionalProperties": false,
        +        "properties": {
        +          "last_value": {
        +            "description": "Bigint as a decimal string.",
        +            "type": "string"
        +          },
        +          "max_value": {
        +            "description": "Bigint as a decimal string.",
        +            "type": "string"
        +          },
        +          "pct_used": {
        +            "description": "last_value / max_value, rounded for display. The FILTER runs at full precision, so a displayed 0.5000 can sit just above the threshold.",
        +            "type": "number"
        +          },
        +          "schema": {
        +            "type": "string"
        +          },
        +          "sequence": {
        +            "type": "string"
        +          }
        +        },
        +        "required": [
        +          "schema",
        +          "sequence",
        +          "last_value",
        +          "max_value",
        +          "pct_used"
        +        ],
        +        "type": "object"
        +      },
        +      "type": "array"
        +    },
        +    "tables_without_primary_key": {
        +      "description": "Plain and partitioned tables only; foreign tables cannot have a PK and are excluded.",
        +      "items": {
        +        "additionalProperties": false,
        +        "properties": {
        +          "schema": {
        +            "type": "string"
        +          },
        +          "table": {
        +            "type": "string"
        +          }
        +        },
        +        "required": [
        +          "schema",
        +          "table"
        +        ],
        +        "type": "object"
        +      },
        +      "type": "array"
        +    },
        +    "wraparound_risk": {
        +      "additionalProperties": false,
        +      "properties": {
        +        "autovacuum_freeze_max_age": {
        +          "anyOf": [
        +            {
        +              "type": "number"
        +            },
        +            {
        +              "type": "null"
        +            }
        +          ],
        +          "description": "Cluster GUC; the divisor for the xid ratios."
        +        },
        +        "autovacuum_multixact_freeze_max_age": {
        +          "anyOf": [
        +            {
        +              "type": "number"
        +            },
        +            {
        +              "type": "null"
        +            }
        +          ],
        +          "description": "Cluster GUC; the divisor for the multixact ratios."
        +        },
        +        "databases": {
        +          "items": {
        +            "additionalProperties": false,
        +            "properties": {
        +              "database": {
        +                "description": "Template databases included -- template0 ages like any other.",
        +                "type": "string"
        +              },
        +              "mxid_age": {
        +                "anyOf": [
        +                  {
        +                    "type": "number"
        +                  },
        +                  {
        +                    "type": "null"
        +                  }
        +                ],
        +                "description": "mxid_age(datminmxid). Null when no multixact was ever recorded."
        +              },
        +              "pct_of_freeze_max_age": {
        +                "anyOf": [
        +                  {
        +                    "type": "number"
        +                  },
        +                  {
        +                    "type": "null"
        +                  }
        +                ]
        +              },
        +              "pct_of_multixact_freeze_max_age": {
        +                "anyOf": [
        +                  {
        +                    "type": "number"
        +                  },
        +                  {
        +                    "type": "null"
        +                  }
        +                ]
        +              },
        +              "triggered_by": {
        +                "description": "'xid' -> chase freezing/autovacuum; 'multixact' -> chase the lock-heavy workload burning members.",
        +                "enum": [
        +                  "xid",
        +                  "multixact",
        +                  "both"
        +                ],
        +                "type": "string"
        +              },
        +              "xid_age": {
        +                "description": "age(datfrozenxid).",
        +                "type": "number"
        +              }
        +            },
        +            "required": [
        +              "database",
        +              "xid_age",
        +              "mxid_age",
        +              "pct_of_freeze_max_age",
        +              "pct_of_multixact_freeze_max_age",
        +              "triggered_by"
        +            ],
        +            "type": "object"
        +          },
        +          "type": "array"
        +        },
        +        "tables": {
        +          "description": "Deliberately includes pg_catalog and pg_toast -- the culprit is usually one of those.",
        +          "items": {
        +            "additionalProperties": false,
        +            "properties": {
        +              "all_frozen_pages": {
        +                "description": "PostgreSQL 18+ only, absent below that. relallfrozen.",
        +                "type": "number"
        +              },
        +              "freeze_max_age": {
        +                "description": "EFFECTIVE limit: a per-table storage parameter wins over the cluster GUC.",
        +                "type": "number"
        +              },
        +              "frozen_page_fraction": {
        +                "anyOf": [
        +                  {
        +                    "type": "number"
        +                  },
        +                  {
        +                    "type": "null"
        +                  }
        +                ],
        +                "description": "PostgreSQL 18+ only, absent below that. Null when relpages is 0, not when coverage is 0."
        +              },
        +              "multixact_freeze_max_age": {
        +                "description": "EFFECTIVE limit, resolved the same way as freeze_max_age.",
        +                "type": "number"
        +              },
        +              "mxid_age": {
        +                "anyOf": [
        +                  {
        +                    "type": "number"
        +                  },
        +                  {
        +                    "type": "null"
        +                  }
        +                ],
        +                "description": "mxid_age(relminmxid). Null when no multixact was ever recorded."
        +              },
        +              "pages": {
        +                "description": "PostgreSQL 18+ only, absent below that. relpages.",
        +                "type": "number"
        +              },
        +              "pct_of_freeze_max_age": {
        +                "anyOf": [
        +                  {
        +                    "type": "number"
        +                  },
        +                  {
        +                    "type": "null"
        +                  }
        +                ],
        +                "description": "At 1.0 autovacuum forces an anti-wraparound VACUUM."
        +              },
        +              "pct_of_multixact_freeze_max_age": {
        +                "anyOf": [
        +                  {
        +                    "type": "number"
        +                  },
        +                  {
        +                    "type": "null"
        +                  }
        +                ]
        +              },
        +              "relkind": {
        +                "description": "Raw relkind: 'r' heap, 'm' materialized view, 't' TOAST -- the only three checked.",
        +                "type": "string"
        +              },
        +              "schema": {
        +                "type": "string"
        +              },
        +              "table": {
        +                "type": "string"
        +              },
        +              "triggered_by": {
        +                "description": "'xid' -> chase freezing/autovacuum; 'multixact' -> chase the lock-heavy workload burning members.",
        +                "enum": [
        +                  "xid",
        +                  "multixact",
        +                  "both"
        +                ],
        +                "type": "string"
        +              },
        +              "xid_age": {
        +                "description": "age(relfrozenxid).",
        +                "type": "number"
        +              }
        +            },
        +            "required": [
        +              "schema",
        +              "table",
        +              "relkind",
        +              "xid_age",
        +              "freeze_max_age",
        +              "pct_of_freeze_max_age",
        +              "mxid_age",
        +              "multixact_freeze_max_age",
        +              "pct_of_multixact_freeze_max_age",
        +              "triggered_by"
        +            ],
        +            "type": "object"
        +          },
        +          "type": "array"
        +        }
        +      },
        +      "required": [
        +        "autovacuum_freeze_max_age",
        +        "autovacuum_multixact_freeze_max_age",
        +        "databases",
        +        "tables"
        +      ],
        +      "type": "object"
        +    }
        +  },
        +  "required": [
        +    "sequence_exhaustion",
        +    "wraparound_risk",
        +    "tables_without_primary_key",
        +    "public_tables_without_rls"
        +  ],
        +  "type": "object"
        +}
    • Changedpg_describe_table2 fields changed
      • changedInput schema / $schema
        Previous value: -"http://json-schema.org/draft-07/schema#"New value: +"https://json-schema.org/draft/2020-12/schema"
      • changedOutput schema / (root)
        Previous value: -nullNew value: +{
        +  "$schema": "https://json-schema.org/draft/2020-12/schema",
        +  "additionalProperties": false,
        +  "properties": {
        +    "_warnings": {
        +      "items": {
        +        "type": "string"
        +      },
        +      "type": "array"
        +    },
        +    "columns": {
        +      "items": {
        +        "additionalProperties": false,
        +        "properties": {
        +          "default_value": {
        +            "anyOf": [
        +              {
        +                "type": "string"
        +              },
        +              {
        +                "type": "null"
        +              }
        +            ],
        +            "description": "Null for a generated column -- see generation_expression."
        +          },
        +          "generated": {
        +            "anyOf": [
        +              {
        +                "enum": [
        +                  "stored",
        +                  "virtual"
        +                ],
        +                "type": "string"
        +              },
        +              {
        +                "type": "null"
        +              }
        +            ],
        +            "description": "Non-null means the column is NOT writable; omit it from INSERT/UPDATE column lists."
        +          },
        +          "generation_expression": {
        +            "anyOf": [
        +              {
        +                "type": "string"
        +              },
        +              {
        +                "type": "null"
        +              }
        +            ],
        +            "description": "Null unless `generated` is set."
        +          },
        +          "identity": {
        +            "anyOf": [
        +              {
        +                "enum": [
        +                  "always",
        +                  "by_default"
        +                ],
        +                "type": "string"
        +              },
        +              {
        +                "type": "null"
        +              }
        +            ],
        +            "description": "'always' means the column is NOT writable without OVERRIDING SYSTEM VALUE."
        +          },
        +          "name": {
        +            "type": "string"
        +          },
        +          "not_null_validated": {
        +            "description": "PostgreSQL 18+ only, absent below that. False means a NOT VALID not-null constraint, so `nullable: false` can still hide NULLs.",
        +            "type": "boolean"
        +          },
        +          "nullable": {
        +            "description": "NOT attnotnull. On PG18+ read alongside `not_null_validated`.",
        +            "type": "boolean"
        +          },
        +          "ordinal_position": {
        +            "description": "attnum, so dropped columns leave gaps.",
        +            "type": "number"
        +          },
        +          "type": {
        +            "description": "Formatted type, e.g. `character varying(64)`.",
        +            "type": "string"
        +          }
        +        },
        +        "required": [
        +          "name",
        +          "type",
        +          "nullable",
        +          "default_value",
        +          "generation_expression",
        +          "generated",
        +          "identity",
        +          "ordinal_position"
        +        ],
        +        "type": "object"
        +      },
        +      "type": "array"
        +    },
        +    "constraints": {
        +      "description": "CHECK / non-PK UNIQUE / EXCLUDE only; PK and FK have their own lists.",
        +      "items": {
        +        "additionalProperties": false,
        +        "properties": {
        +          "definition": {
        +            "type": "string"
        +          },
        +          "enforced": {
        +            "description": "PostgreSQL 18+ only, absent below that. False means the constraint is recorded but enforces nothing -- do not lean on it as a guarantee.",
        +            "type": "boolean"
        +          },
        +          "has_period": {
        +            "description": "PostgreSQL 18+ only, absent below that. True for a temporal (PERIOD / WITHOUT OVERLAPS) key.",
        +            "type": "boolean"
        +          },
        +          "name": {
        +            "type": "string"
        +          },
        +          "type": {
        +            "description": "check | unique | exclude, or the raw contype.",
        +            "type": "string"
        +          },
        +          "validated": {
        +            "description": "False for a NOT VALID constraint: existing rows were never checked against it.",
        +            "type": "boolean"
        +          }
        +        },
        +        "required": [
        +          "name",
        +          "type",
        +          "definition",
        +          "validated"
        +        ],
        +        "type": "object"
        +      },
        +      "type": "array"
        +    },
        +    "foreign_keys": {
        +      "items": {
        +        "additionalProperties": false,
        +        "properties": {
        +          "columns": {
        +            "items": {
        +              "type": "string"
        +            },
        +            "type": "array"
        +          },
        +          "constraint_name": {
        +            "type": "string"
        +          },
        +          "enforced": {
        +            "description": "PostgreSQL 18+ only, absent below that. False means the constraint is recorded but enforces nothing -- do not lean on it as a guarantee.",
        +            "type": "boolean"
        +          },
        +          "foreign_columns": {
        +            "items": {
        +              "type": "string"
        +            },
        +            "type": "array"
        +          },
        +          "foreign_schema": {
        +            "type": "string"
        +          },
        +          "foreign_table": {
        +            "type": "string"
        +          },
        +          "has_period": {
        +            "description": "PostgreSQL 18+ only, absent below that. True for a temporal (PERIOD / WITHOUT OVERLAPS) key.",
        +            "type": "boolean"
        +          },
        +          "validated": {
        +            "description": "False for a NOT VALID constraint: existing rows were never checked against it.",
        +            "type": "boolean"
        +          }
        +        },
        +        "required": [
        +          "constraint_name",
        +          "columns",
        +          "foreign_table",
        +          "foreign_schema",
        +          "foreign_columns",
        +          "validated"
        +        ],
        +        "type": "object"
        +      },
        +      "type": "array"
        +    },
        +    "indexes": {
        +      "items": {
        +        "additionalProperties": false,
        +        "properties": {
        +          "definition": {
        +            "type": "string"
        +          },
        +          "is_primary": {
        +            "type": "boolean"
        +          },
        +          "is_unique": {
        +            "type": "boolean"
        +          },
        +          "name": {
        +            "type": "string"
        +          }
        +        },
        +        "required": [
        +          "name",
        +          "definition",
        +          "is_unique",
        +          "is_primary"
        +        ],
        +        "type": "object"
        +      },
        +      "type": "array"
        +    },
        +    "kind": {
        +      "description": "table | partitioned_table | view | materialized_view | foreign_table, or the raw relkind. Defaults to 'table' with a `_warnings` entry when the kind fetch failed.",
        +      "type": "string"
        +    },
        +    "partition_of": {
        +      "additionalProperties": false,
        +      "description": "Present only when this relation is itself a partition.",
        +      "properties": {
        +        "schema": {
        +          "type": "string"
        +        },
        +        "table": {
        +          "type": "string"
        +        }
        +      },
        +      "required": [
        +        "schema",
        +        "table"
        +      ],
        +      "type": "object"
        +    },
        +    "partitions": {
        +      "description": "Present only when this relation is a partitioned parent WITH children.",
        +      "items": {
        +        "additionalProperties": false,
        +        "properties": {
        +          "bound": {
        +            "type": "string"
        +          },
        +          "schema": {
        +            "type": "string"
        +          },
        +          "table": {
        +            "type": "string"
        +          }
        +        },
        +        "required": [
        +          "schema",
        +          "table",
        +          "bound"
        +        ],
        +        "type": "object"
        +      },
        +      "type": "array"
        +    },
        +    "primary_key": {
        +      "description": "Key columns in declared order; INCLUDE columns are excluded.",
        +      "items": {
        +        "type": "string"
        +      },
        +      "type": "array"
        +    },
        +    "referenced_by": {
        +      "description": "Other tables whose foreign keys point AT this one.",
        +      "items": {
        +        "additionalProperties": false,
        +        "properties": {
        +          "columns": {
        +            "items": {
        +              "type": "string"
        +            },
        +            "type": "array"
        +          },
        +          "constraint_name": {
        +            "type": "string"
        +          },
        +          "enforced": {
        +            "description": "PostgreSQL 18+ only, absent below that. False means the constraint is recorded but enforces nothing -- do not lean on it as a guarantee.",
        +            "type": "boolean"
        +          },
        +          "has_period": {
        +            "description": "PostgreSQL 18+ only, absent below that. True for a temporal (PERIOD / WITHOUT OVERLAPS) key.",
        +            "type": "boolean"
        +          },
        +          "referenced_columns": {
        +            "items": {
        +              "type": "string"
        +            },
        +            "type": "array"
        +          },
        +          "schema": {
        +            "type": "string"
        +          },
        +          "table": {
        +            "type": "string"
        +          },
        +          "validated": {
        +            "description": "False for a NOT VALID constraint: existing rows were never checked against it.",
        +            "type": "boolean"
        +          }
        +        },
        +        "required": [
        +          "constraint_name",
        +          "schema",
        +          "table",
        +          "columns",
        +          "referenced_columns",
        +          "validated"
        +        ],
        +        "type": "object"
        +      },
        +      "type": "array"
        +    },
        +    "schema": {
        +      "type": "string"
        +    },
        +    "table": {
        +      "type": "string"
        +    }
        +  },
        +  "required": [
        +    "schema",
        +    "table",
        +    "kind",
        +    "columns",
        +    "primary_key",
        +    "foreign_keys",
        +    "referenced_by",
        +    "constraints",
        +    "indexes"
        +  ],
        +  "type": "object"
        +}
    • Changedpg_explain14 fields changed
      • 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"
        +}
    • Changedpg_health2 fields changed
      • changedInput schema / $schema
        Previous value: -"http://json-schema.org/draft-07/schema#"New value: +"https://json-schema.org/draft/2020-12/schema"
      • changedOutput schema / (root)
        Previous value: -nullNew value: +{
        +  "$schema": "https://json-schema.org/draft/2020-12/schema",
        +  "additionalProperties": false,
        +  "properties": {
        +    "_warnings": {
        +      "items": {
        +        "type": "string"
        +      },
        +      "type": "array"
        +    },
        +    "active_queries": {
        +      "description": "Empty array both when nothing is running and when the fetch failed -- check `_warnings`.",
        +      "items": {
        +        "additionalProperties": false,
        +        "properties": {
        +          "application_name": {
        +            "type": "string"
        +          },
        +          "backend_type": {
        +            "type": "string"
        +          },
        +          "duration_seconds": {
        +            "anyOf": [
        +              {
        +                "type": "number"
        +              },
        +              {
        +                "type": "null"
        +              }
        +            ],
        +            "description": "Since query_start."
        +          },
        +          "pid": {
        +            "type": "number"
        +          },
        +          "query": {
        +            "type": "string"
        +          },
        +          "state": {
        +            "anyOf": [
        +              {
        +                "type": "string"
        +              },
        +              {
        +                "type": "null"
        +              }
        +            ]
        +          },
        +          "transaction_age_seconds": {
        +            "anyOf": [
        +              {
        +                "type": "number"
        +              },
        +              {
        +                "type": "null"
        +              }
        +            ],
        +            "description": "Since xact_start; null outside a transaction block. Large here beside a small duration_seconds is a long-open transaction."
        +          },
        +          "wait_event": {
        +            "anyOf": [
        +              {
        +                "type": "string"
        +              },
        +              {
        +                "type": "null"
        +              }
        +            ]
        +          },
        +          "wait_event_type": {
        +            "anyOf": [
        +              {
        +                "type": "string"
        +              },
        +              {
        +                "type": "null"
        +              }
        +            ],
        +            "description": "Null when the backend is RUNNING rather than waiting. Spelling changes between majors ('BufferPin' through PG18, 'Buffer' from 19) -- read it against `version`."
        +          },
        +          "xact_start": {
        +            "anyOf": [
        +              {
        +                "type": "string"
        +              },
        +              {
        +                "type": "null"
        +              }
        +            ]
        +          }
        +        },
        +        "required": [
        +          "pid",
        +          "state",
        +          "duration_seconds",
        +          "transaction_age_seconds",
        +          "xact_start",
        +          "wait_event_type",
        +          "wait_event",
        +          "backend_type",
        +          "query",
        +          "application_name"
        +        ],
        +        "type": "object"
        +      },
        +      "type": "array"
        +    },
        +    "connected": {
        +      "description": "Always true on a success response -- the version probe answered.",
        +      "type": "boolean"
        +    },
        +    "connections": {
        +      "anyOf": [
        +        {
        +          "additionalProperties": false,
        +          "properties": {
        +            "active": {
        +              "type": "string"
        +            },
        +            "cluster_client_backends": {
        +              "description": "Client backends across ALL databases -- the ones that actually consume connection slots.",
        +              "type": "string"
        +            },
        +            "idle": {
        +              "type": "string"
        +            },
        +            "idle_in_transaction": {
        +              "type": "string"
        +            },
        +            "idle_in_transaction_aborted": {
        +              "description": "Holds locks and blocks vacuum while doing no work, and can never commit.",
        +              "type": "string"
        +            },
        +            "max_connections": {
        +              "type": "number"
        +            },
        +            "other": {
        +              "description": "Catch-all: starting, fastpath function call, disabled, plus any future state.",
        +              "type": "string"
        +            },
        +            "state_unavailable": {
        +              "description": "Sessions whose `state` read NULL for lack of pg_read_all_stats / pg_monitor. Non-zero means every other bucket is under-counted -- do NOT read `active: 0` beside it as an idle database.",
        +              "type": "string"
        +            },
        +            "superuser_reserved_connections": {
        +              "type": "number"
        +            },
        +            "total": {
        +              "description": "Sessions in the CURRENT database. The six state buckets below sum to this.",
        +              "type": "string"
        +            },
        +            "used_fraction": {
        +              "anyOf": [
        +                {
        +                  "type": "number"
        +                },
        +                {
        +                  "type": "null"
        +                }
        +              ],
        +              "description": "cluster_client_backends / max_connections. Null only if max_connections read as 0."
        +            }
        +          },
        +          "required": [
        +            "total",
        +            "active",
        +            "idle",
        +            "idle_in_transaction",
        +            "idle_in_transaction_aborted",
        +            "other",
        +            "state_unavailable",
        +            "cluster_client_backends",
        +            "max_connections",
        +            "superuser_reserved_connections",
        +            "used_fraction"
        +          ],
        +          "type": "object"
        +        },
        +        {
        +          "type": "null"
        +        }
        +      ]
        +    },
        +    "database": {
        +      "anyOf": [
        +        {
        +          "additionalProperties": false,
        +          "properties": {
        +            "database": {
        +              "type": "string"
        +            },
        +            "size_bytes": {
        +              "description": "Bigint as a decimal string -- past 2^53 a JS number would lose digits.",
        +              "type": "string"
        +            },
        +            "size_pretty": {
        +              "type": "string"
        +            }
        +          },
        +          "required": [
        +            "database",
        +            "size_pretty",
        +            "size_bytes"
        +          ],
        +          "type": "object"
        +        },
        +        {
        +          "type": "null"
        +        }
        +      ]
        +    },
        +    "database_stats": {
        +      "anyOf": [
        +        {
        +          "additionalProperties": false,
        +          "properties": {
        +            "blks_hit": {
        +              "type": "string"
        +            },
        +            "blks_read": {
        +              "type": "string"
        +            },
        +            "cache_hit_ratio": {
        +              "anyOf": [
        +                {
        +                  "type": "number"
        +                },
        +                {
        +                  "type": "null"
        +                }
        +              ],
        +              "description": "Null on a freshly reset database (both counters 0)."
        +            },
        +            "conflicts": {
        +              "description": "Recovery conflicts; only ever non-zero on a replica.",
        +              "type": "string"
        +            },
        +            "deadlocks": {
        +              "type": "string"
        +            },
        +            "stats_reset": {
        +              "anyOf": [
        +                {
        +                  "type": "string"
        +                },
        +                {
        +                  "type": "null"
        +                }
        +              ],
        +              "description": "Every counter here is cumulative SINCE this timestamp. Null = never reset."
        +            },
        +            "temp_bytes": {
        +              "type": "string"
        +            },
        +            "temp_bytes_pretty": {
        +              "type": "string"
        +            },
        +            "temp_files": {
        +              "type": "string"
        +            }
        +          },
        +          "required": [
        +            "deadlocks",
        +            "temp_files",
        +            "temp_bytes",
        +            "temp_bytes_pretty",
        +            "conflicts",
        +            "blks_hit",
        +            "blks_read",
        +            "cache_hit_ratio",
        +            "stats_reset"
        +          ],
        +          "type": "object"
        +        },
        +        {
        +          "type": "null"
        +        }
        +      ],
        +      "description": "Null when pg_stat_database is unreadable OR has no row for this database."
        +    },
        +    "table_count": {
        +      "anyOf": [
        +        {
        +          "type": "string"
        +        },
        +        {
        +          "type": "null"
        +        }
        +      ],
        +      "description": "User tables and partitioned tables, as a decimal string."
        +    },
        +    "version": {
        +      "description": "Full `version()` banner. Absent (with a `_warnings` entry) if the row came back without it.",
        +      "type": "string"
        +    }
        +  },
        +  "required": [
        +    "connected",
        +    "active_queries",
        +    "database_stats"
        +  ],
        +  "type": "object"
        +}
    • Addedpg_index_advisor
    • Changedpg_inspect_locks2 fields changed
      • changedInput schema / $schema
        Previous value: -"http://json-schema.org/draft-07/schema#"New value: +"https://json-schema.org/draft/2020-12/schema"
      • changedOutput schema / (root)
        Previous value: -nullNew value: +{
        +  "$schema": "https://json-schema.org/draft/2020-12/schema",
        +  "additionalProperties": false,
        +  "properties": {
        +    "rows": {
        +      "items": {
        +        "additionalProperties": false,
        +        "properties": {
        +          "blocked_duration_seconds": {
        +            "anyOf": [
        +              {
        +                "type": "number"
        +              },
        +              {
        +                "type": "null"
        +              }
        +            ],
        +            "description": "Since query_start."
        +          },
        +          "blocked_pid": {
        +            "type": "number"
        +          },
        +          "blocked_query": {
        +            "anyOf": [
        +              {
        +                "type": "string"
        +              },
        +              {
        +                "type": "null"
        +              }
        +            ]
        +          },
        +          "blocked_user": {
        +            "anyOf": [
        +              {
        +                "type": "string"
        +              },
        +              {
        +                "type": "null"
        +              }
        +            ]
        +          },
        +          "blocking_duration_seconds": {
        +            "anyOf": [
        +              {
        +                "type": "number"
        +              },
        +              {
        +                "type": "null"
        +              }
        +            ]
        +          },
        +          "blocking_pid": {
        +            "type": "number"
        +          },
        +          "blocking_query": {
        +            "anyOf": [
        +              {
        +                "type": "string"
        +              },
        +              {
        +                "type": "null"
        +              }
        +            ]
        +          },
        +          "blocking_state": {
        +            "anyOf": [
        +              {
        +                "type": "string"
        +              },
        +              {
        +                "type": "null"
        +              }
        +            ]
        +          },
        +          "blocking_user": {
        +            "anyOf": [
        +              {
        +                "type": "string"
        +              },
        +              {
        +                "type": "null"
        +              }
        +            ]
        +          },
        +          "lock_type": {
        +            "description": "pg_locks.locktype: relation, transactionid, virtualxid, tuple, ...",
        +            "type": "string"
        +          },
        +          "relation": {
        +            "anyOf": [
        +              {
        +                "type": "string"
        +              },
        +              {
        +                "type": "null"
        +              }
        +            ],
        +            "description": "schema.table. For transactionid / virtualxid waits this is a best-effort GUESS among the blocker's held write-intent locks, not authoritative -- disambiguate with the query text."
        +          }
        +        },
        +        "required": [
        +          "blocked_pid",
        +          "blocked_user",
        +          "blocked_query",
        +          "blocked_duration_seconds",
        +          "blocking_pid",
        +          "blocking_user",
        +          "blocking_query",
        +          "blocking_state",
        +          "blocking_duration_seconds",
        +          "relation",
        +          "lock_type"
        +        ],
        +        "type": "object"
        +      },
        +      "type": "array"
        +    }
        +  },
        +  "required": [
        +    "rows"
        +  ],
        +  "type": "object"
        +}
    • Addedpg_io_stats
    • Changedpg_kill2 fields changed
      • changedInput schema / $schema
        Previous value: -"http://json-schema.org/draft-07/schema#"New value: +"https://json-schema.org/draft/2020-12/schema"
      • changedOutput schema / (root)
        Previous value: -nullNew value: +{
        +  "$schema": "https://json-schema.org/draft/2020-12/schema",
        +  "additionalProperties": false,
        +  "properties": {
        +    "mode": {
        +      "description": "Echoed back, after the safer 'cancel' default is applied.",
        +      "enum": [
        +        "cancel",
        +        "terminate"
        +      ],
        +      "type": "string"
        +    },
        +    "note": {
        +      "description": "On `signaled: false`, postgres's own NOTICE explaining why -- act on this, not on the boolean.",
        +      "type": "string"
        +    },
        +    "pid": {
        +      "description": "Echoed back from the request.",
        +      "type": "number"
        +    },
        +    "signaled": {
        +      "description": "What pg_cancel_backend / pg_terminate_backend returned.",
        +      "type": "boolean"
        +    }
        +  },
        +  "required": [
        +    "pid",
        +    "mode",
        +    "signaled",
        +    "note"
        +  ],
        +  "type": "object"
        +}
    • Changedpg_list_extensions2 fields changed
      • changedInput schema / $schema
        Previous value: -"http://json-schema.org/draft-07/schema#"New value: +"https://json-schema.org/draft/2020-12/schema"
      • changedOutput schema / (root)
        Previous value: -nullNew value: +{
        +  "$schema": "https://json-schema.org/draft/2020-12/schema",
        +  "additionalProperties": false,
        +  "properties": {
        +    "rows": {
        +      "items": {
        +        "additionalProperties": false,
        +        "properties": {
        +          "description": {
        +            "anyOf": [
        +              {
        +                "type": "string"
        +              },
        +              {
        +                "type": "null"
        +              }
        +            ]
        +          },
        +          "name": {
        +            "type": "string"
        +          },
        +          "schema": {
        +            "type": "string"
        +          },
        +          "version": {
        +            "description": "Installed extversion, e.g. `1.11` -- not the postgres version.",
        +            "type": "string"
        +          }
        +        },
        +        "required": [
        +          "name",
        +          "version",
        +          "schema",
        +          "description"
        +        ],
        +        "type": "object"
        +      },
        +      "type": "array"
        +    }
        +  },
        +  "required": [
        +    "rows"
        +  ],
        +  "type": "object"
        +}
    • Changedpg_list_functions2 fields changed
      • changedInput schema / $schema
        Previous value: -"http://json-schema.org/draft-07/schema#"New value: +"https://json-schema.org/draft/2020-12/schema"
      • changedOutput schema / (root)
        Previous value: -nullNew value: +{
        +  "$schema": "https://json-schema.org/draft/2020-12/schema",
        +  "additionalProperties": false,
        +  "properties": {
        +    "rows": {
        +      "items": {
        +        "additionalProperties": false,
        +        "properties": {
        +          "arguments": {
        +            "description": "Reconstructed argument list; empty string for a zero-argument routine.",
        +            "type": "string"
        +          },
        +          "kind": {
        +            "description": "function | procedure | aggregate | window, or the raw prokind.",
        +            "type": "string"
        +          },
        +          "language": {
        +            "type": "string"
        +          },
        +          "name": {
        +            "type": "string"
        +          },
        +          "return_type": {
        +            "anyOf": [
        +              {
        +                "type": "string"
        +              },
        +              {
        +                "type": "null"
        +              }
        +            ],
        +            "description": "Null for procedures, which have no return type."
        +          }
        +        },
        +        "required": [
        +          "name",
        +          "arguments",
        +          "return_type",
        +          "kind",
        +          "language"
        +        ],
        +        "type": "object"
        +      },
        +      "type": "array"
        +    }
        +  },
        +  "required": [
        +    "rows"
        +  ],
        +  "type": "object"
        +}
    • Changedpg_list_roles2 fields changed
      • changedInput schema / $schema
        Previous value: -"http://json-schema.org/draft-07/schema#"New value: +"https://json-schema.org/draft/2020-12/schema"
      • changedOutput schema / (root)
        Previous value: -nullNew value: +{
        +  "$schema": "https://json-schema.org/draft/2020-12/schema",
        +  "additionalProperties": false,
        +  "properties": {
        +    "rows": {
        +      "items": {
        +        "additionalProperties": false,
        +        "properties": {
        +          "bypass_rls": {
        +            "type": "boolean"
        +          },
        +          "can_login": {
        +            "description": "False for a group role.",
        +            "type": "boolean"
        +          },
        +          "createdb": {
        +            "type": "boolean"
        +          },
        +          "createrole": {
        +            "type": "boolean"
        +          },
        +          "member_of": {
        +            "description": "Roles this one is a direct member of; empty when none.",
        +            "items": {
        +              "type": "string"
        +            },
        +            "type": "array"
        +          },
        +          "name": {
        +            "type": "string"
        +          },
        +          "replication": {
        +            "type": "boolean"
        +          },
        +          "superuser": {
        +            "type": "boolean"
        +          }
        +        },
        +        "required": [
        +          "name",
        +          "can_login",
        +          "superuser",
        +          "createdb",
        +          "createrole",
        +          "replication",
        +          "bypass_rls",
        +          "member_of"
        +        ],
        +        "type": "object"
        +      },
        +      "type": "array"
        +    }
        +  },
        +  "required": [
        +    "rows"
        +  ],
        +  "type": "object"
        +}
    • Changedpg_list_schemas2 fields changed
      • changedInput schema / $schema
        Previous value: -"http://json-schema.org/draft-07/schema#"New value: +"https://json-schema.org/draft/2020-12/schema"
      • changedOutput schema / (root)
        Previous value: -nullNew value: +{
        +  "$schema": "https://json-schema.org/draft/2020-12/schema",
        +  "additionalProperties": false,
        +  "properties": {
        +    "rows": {
        +      "items": {
        +        "additionalProperties": false,
        +        "properties": {
        +          "owner": {
        +            "description": "Role name from pg_get_userbyid; never null, even for a dropped owner oid.",
        +            "type": "string"
        +          },
        +          "schema_name": {
        +            "type": "string"
        +          }
        +        },
        +        "required": [
        +          "schema_name",
        +          "owner"
        +        ],
        +        "type": "object"
        +      },
        +      "type": "array"
        +    }
        +  },
        +  "required": [
        +    "rows"
        +  ],
        +  "type": "object"
        +}
    • Changedpg_list_tables2 fields changed
      • changedInput schema / $schema
        Previous value: -"http://json-schema.org/draft-07/schema#"New value: +"https://json-schema.org/draft/2020-12/schema"
      • changedOutput schema / (root)
        Previous value: -nullNew value: +{
        +  "$schema": "https://json-schema.org/draft/2020-12/schema",
        +  "additionalProperties": false,
        +  "properties": {
        +    "rows": {
        +      "items": {
        +        "additionalProperties": false,
        +        "properties": {
        +          "estimated_rows": {
        +            "anyOf": [
        +              {
        +                "type": "number"
        +              },
        +              {
        +                "type": "null"
        +              }
        +            ],
        +            "description": "Estimate from reltuples. Null = never ANALYZEd (PG14+); on PG<=13 a never-analyzed table reports 0 instead, indistinguishable from empty."
        +          },
        +          "name": {
        +            "type": "string"
        +          },
        +          "type": {
        +            "description": "table | view | materialized_view | foreign_table | partitioned_table, or the raw relkind.",
        +            "type": "string"
        +          }
        +        },
        +        "required": [
        +          "name",
        +          "type",
        +          "estimated_rows"
        +        ],
        +        "type": "object"
        +      },
        +      "type": "array"
        +    }
        +  },
        +  "required": [
        +    "rows"
        +  ],
        +  "type": "object"
        +}
    • Changedpg_list_views2 fields changed
      • changedInput schema / $schema
        Previous value: -"http://json-schema.org/draft-07/schema#"New value: +"https://json-schema.org/draft/2020-12/schema"
      • changedOutput schema / (root)
        Previous value: -nullNew value: +{
        +  "$schema": "https://json-schema.org/draft/2020-12/schema",
        +  "additionalProperties": false,
        +  "properties": {
        +    "rows": {
        +      "items": {
        +        "additionalProperties": false,
        +        "properties": {
        +          "definition": {
        +            "description": "Reconstructed view body from pg_get_viewdef.",
        +            "type": "string"
        +          },
        +          "name": {
        +            "type": "string"
        +          },
        +          "type": {
        +            "enum": [
        +              "view",
        +              "materialized_view"
        +            ],
        +            "type": "string"
        +          }
        +        },
        +        "required": [
        +          "name",
        +          "type",
        +          "definition"
        +        ],
        +        "type": "object"
        +      },
        +      "type": "array"
        +    }
        +  },
        +  "required": [
        +    "rows"
        +  ],
        +  "type": "object"
        +}
    • Changedpg_query5 fields changed
      • 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"
        -      }
        -    ]
        -  }
        -}
      • changedInput schema / properties / params / items / $ref
        Previous value: -"#/definitions/__schema0"New value: +"#/$defs/__schema0"
      • changedOutput schema / (root)
        Previous value: -nullNew value: +{
        +  "$schema": "https://json-schema.org/draft/2020-12/schema",
        +  "additionalProperties": false,
        +  "properties": {
        +    "command": {
        +      "description": "Postgres command tag (`INSERT`, `CREATE TABLE`, ...). Absent on the cursor path -- read absence as 'row-returning statement, command unknown'.",
        +      "type": "string"
        +    },
        +    "fields": {
        +      "description": "Result column descriptors, in select-list order.",
        +      "items": {
        +        "additionalProperties": false,
        +        "properties": {
        +          "dataTypeID": {
        +            "type": "number"
        +          },
        +          "dataTypeName": {
        +            "type": "string"
        +          },
        +          "name": {
        +            "type": "string"
        +          }
        +        },
        +        "required": [
        +          "name",
        +          "dataTypeID"
        +        ],
        +        "type": "object"
        +      },
        +      "type": "array"
        +    },
        +    "rowCount": {
        +      "anyOf": [
        +        {
        +          "type": "number"
        +        },
        +        {
        +          "type": "null"
        +        }
        +      ],
        +      "description": "Rows AFFECTED for DML -- not necessarily rows.length -- and rows returned on the cursor path. Null when pg reported no count."
        +    },
        +    "rows": {
        +      "description": "Result rows, capped at POSTGRES_MAX_ROWS. Values are whatever JSON type pg parsed the column into.",
        +      "items": {
        +        "additionalProperties": {},
        +        "propertyNames": {
        +          "type": "string"
        +        },
        +        "type": "object"
        +      },
        +      "type": "array"
        +    },
        +    "truncated": {
        +      "description": "Present and true only when the result hit POSTGRES_MAX_ROWS and rows were dropped.",
        +      "type": "boolean"
        +    }
        +  },
        +  "required": [
        +    "rows",
        +    "rowCount",
        +    "fields"
        +  ],
        +  "type": "object"
        +}
    • Changedpg_readonly5 fields changed
      • 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"
        -      }
        -    ]
        -  }
        -}
      • changedInput schema / properties / params / items / $ref
        Previous value: -"#/definitions/__schema0"New value: +"#/$defs/__schema0"
      • changedOutput schema / (root)
        Previous value: -nullNew value: +{
        +  "$schema": "https://json-schema.org/draft/2020-12/schema",
        +  "additionalProperties": false,
        +  "properties": {
        +    "command": {
        +      "description": "Postgres command tag (`INSERT`, `CREATE TABLE`, ...). Absent on the cursor path -- read absence as 'row-returning statement, command unknown'.",
        +      "type": "string"
        +    },
        +    "fields": {
        +      "description": "Result column descriptors, in select-list order.",
        +      "items": {
        +        "additionalProperties": false,
        +        "properties": {
        +          "dataTypeID": {
        +            "type": "number"
        +          },
        +          "dataTypeName": {
        +            "type": "string"
        +          },
        +          "name": {
        +            "type": "string"
        +          }
        +        },
        +        "required": [
        +          "name",
        +          "dataTypeID"
        +        ],
        +        "type": "object"
        +      },
        +      "type": "array"
        +    },
        +    "rowCount": {
        +      "anyOf": [
        +        {
        +          "type": "number"
        +        },
        +        {
        +          "type": "null"
        +        }
        +      ],
        +      "description": "Rows AFFECTED for DML -- not necessarily rows.length -- and rows returned on the cursor path. Null when pg reported no count."
        +    },
        +    "rows": {
        +      "description": "Result rows, capped at POSTGRES_MAX_ROWS. Values are whatever JSON type pg parsed the column into.",
        +      "items": {
        +        "additionalProperties": {},
        +        "propertyNames": {
        +          "type": "string"
        +        },
        +        "type": "object"
        +      },
        +      "type": "array"
        +    },
        +    "truncated": {
        +      "description": "Present and true only when the result hit POSTGRES_MAX_ROWS and rows were dropped.",
        +      "type": "boolean"
        +    }
        +  },
        +  "required": [
        +    "rows",
        +    "rowCount",
        +    "fields"
        +  ],
        +  "type": "object"
        +}
    • Changedpg_replication_status2 fields changed
      • changedInput schema / $schema
        Previous value: -"http://json-schema.org/draft-07/schema#"New value: +"https://json-schema.org/draft/2020-12/schema"
      • changedOutput schema / (root)
        Previous value: -nullNew value: +{
        +  "$schema": "https://json-schema.org/draft/2020-12/schema",
        +  "additionalProperties": false,
        +  "properties": {
        +    "_warnings": {
        +      "items": {
        +        "type": "string"
        +      },
        +      "type": "array"
        +    },
        +    "is_replica": {
        +      "anyOf": [
        +        {
        +          "type": "boolean"
        +        },
        +        {
        +          "type": "null"
        +        }
        +      ],
        +      "description": "pg_is_in_recovery(). Null means the probe failed, NOT 'primary'."
        +    },
        +    "replicas": {
        +      "items": {
        +        "additionalProperties": false,
        +        "properties": {
        +          "application_name": {
        +            "type": "string"
        +          },
        +          "client_addr": {
        +            "anyOf": [
        +              {
        +                "type": "string"
        +              },
        +              {
        +                "type": "null"
        +              }
        +            ],
        +            "description": "Null for a replica connected over a unix socket."
        +          },
        +          "flush_lag_seconds": {
        +            "anyOf": [
        +              {
        +                "type": "number"
        +              },
        +              {
        +                "type": "null"
        +              }
        +            ]
        +          },
        +          "replay_lag_seconds": {
        +            "anyOf": [
        +              {
        +                "type": "number"
        +              },
        +              {
        +                "type": "null"
        +              }
        +            ]
        +          },
        +          "state": {
        +            "description": "startup | catchup | streaming | backup | stopping.",
        +            "type": "string"
        +          },
        +          "sync_state": {
        +            "description": "async | potential | sync | quorum.",
        +            "type": "string"
        +          },
        +          "write_lag_seconds": {
        +            "anyOf": [
        +              {
        +                "type": "number"
        +              },
        +              {
        +                "type": "null"
        +              }
        +            ],
        +            "description": "Null until the primary has measured a round trip."
        +          }
        +        },
        +        "required": [
        +          "application_name",
        +          "client_addr",
        +          "state",
        +          "sync_state",
        +          "write_lag_seconds",
        +          "flush_lag_seconds",
        +          "replay_lag_seconds"
        +        ],
        +        "type": "object"
        +      },
        +      "type": "array"
        +    },
        +    "slots": {
        +      "description": "Empty both on a standalone database and when the fetch failed -- check `_warnings`.",
        +      "items": {
        +        "additionalProperties": false,
        +        "properties": {
        +          "active": {
        +            "description": "False means nothing is consuming the slot -- WAL piles up behind it.",
        +            "type": "boolean"
        +          },
        +          "confirmed_flush_lsn": {
        +            "anyOf": [
        +              {
        +                "type": "string"
        +              },
        +              {
        +                "type": "null"
        +              }
        +            ],
        +            "description": "Logical slots only; null on a physical slot."
        +          },
        +          "database": {
        +            "anyOf": [
        +              {
        +                "type": "string"
        +              },
        +              {
        +                "type": "null"
        +              }
        +            ],
        +            "description": "Logical slots only; null on a physical slot."
        +          },
        +          "plugin": {
        +            "anyOf": [
        +              {
        +                "type": "string"
        +              },
        +              {
        +                "type": "null"
        +              }
        +            ],
        +            "description": "Logical slots only; null on a physical slot."
        +          },
        +          "restart_lsn": {
        +            "anyOf": [
        +              {
        +                "type": "string"
        +              },
        +              {
        +                "type": "null"
        +              }
        +            ]
        +          },
        +          "slot_name": {
        +            "type": "string"
        +          },
        +          "slot_type": {
        +            "description": "physical | logical.",
        +            "type": "string"
        +          },
        +          "wal_status": {
        +            "anyOf": [
        +              {
        +                "type": "string"
        +              },
        +              {
        +                "type": "null"
        +              }
        +            ],
        +            "description": "reserved | extended | unreserved | lost."
        +          }
        +        },
        +        "required": [
        +          "slot_name",
        +          "slot_type",
        +          "active",
        +          "restart_lsn",
        +          "confirmed_flush_lsn",
        +          "wal_status",
        +          "database",
        +          "plugin"
        +        ],
        +        "type": "object"
        +      },
        +      "type": "array"
        +    },
        +    "wal_position": {
        +      "anyOf": [
        +        {
        +          "type": "string"
        +        },
        +        {
        +          "type": "null"
        +        }
        +      ],
        +      "description": "Last received LSN on a replica, current LSN on a primary. Null when the probe failed."
        +    }
        +  },
        +  "required": [
        +    "is_replica",
        +    "wal_position",
        +    "slots",
        +    "replicas"
        +  ],
        +  "type": "object"
        +}
    • Changedpg_search_columns2 fields changed
      • changedInput schema / $schema
        Previous value: -"http://json-schema.org/draft-07/schema#"New value: +"https://json-schema.org/draft/2020-12/schema"
      • changedOutput schema / (root)
        Previous value: -nullNew value: +{
        +  "$schema": "https://json-schema.org/draft/2020-12/schema",
        +  "additionalProperties": false,
        +  "properties": {
        +    "rows": {
        +      "items": {
        +        "additionalProperties": false,
        +        "properties": {
        +          "column": {
        +            "type": "string"
        +          },
        +          "nullable": {
        +            "type": "boolean"
        +          },
        +          "schema": {
        +            "type": "string"
        +          },
        +          "table": {
        +            "description": "Relation name; may be a view or materialized view, not only a table.",
        +            "type": "string"
        +          },
        +          "type": {
        +            "description": "Formatted type from format_type.",
        +            "type": "string"
        +          }
        +        },
        +        "required": [
        +          "schema",
        +          "table",
        +          "column",
        +          "type",
        +          "nullable"
        +        ],
        +        "type": "object"
        +      },
        +      "type": "array"
        +    }
        +  },
        +  "required": [
        +    "rows"
        +  ],
        +  "type": "object"
        +}
    • Changedpg_seq_scan_tables2 fields changed
      • changedInput schema / $schema
        Previous value: -"http://json-schema.org/draft-07/schema#"New value: +"https://json-schema.org/draft/2020-12/schema"
      • changedOutput schema / (root)
        Previous value: -nullNew value: +{
        +  "$schema": "https://json-schema.org/draft/2020-12/schema",
        +  "additionalProperties": false,
        +  "properties": {
        +    "_warnings": {
        +      "items": {
        +        "type": "string"
        +      },
        +      "type": "array"
        +    },
        +    "rows": {
        +      "items": {
        +        "additionalProperties": false,
        +        "properties": {
        +          "idx_scans": {
        +            "description": "COALESCEd to '0', so a never-index-scanned table reads as 0, not null.",
        +            "type": "string"
        +          },
        +          "last_idx_scan": {
        +            "anyOf": [
        +              {
        +                "type": "string"
        +              },
        +              {
        +                "type": "null"
        +              }
        +            ],
        +            "description": "PostgreSQL 16+ only, absent below that. Null = no index scan since the reset."
        +          },
        +          "last_seq_scan": {
        +            "anyOf": [
        +              {
        +                "type": "string"
        +              },
        +              {
        +                "type": "null"
        +              }
        +            ],
        +            "description": "PostgreSQL 16+ only, absent below that. Null = no sequential scan since the reset."
        +          },
        +          "live_tuples": {
        +            "type": "string"
        +          },
        +          "ratio": {
        +            "anyOf": [
        +              {
        +                "type": "number"
        +              },
        +              {
        +                "type": "null"
        +              }
        +            ],
        +            "description": "seq_scans / idx_scans; null when idx_scans is 0 (division guard)."
        +          },
        +          "schema": {
        +            "type": "string"
        +          },
        +          "seq_scans": {
        +            "description": "Bigint as a decimal string.",
        +            "type": "string"
        +          },
        +          "seq_tup_read": {
        +            "type": "string"
        +          },
        +          "table": {
        +            "type": "string"
        +          }
        +        },
        +        "required": [
        +          "schema",
        +          "table",
        +          "seq_scans",
        +          "idx_scans",
        +          "live_tuples",
        +          "seq_tup_read",
        +          "ratio"
        +        ],
        +        "type": "object"
        +      },
        +      "type": "array"
        +    },
        +    "stats_reset": {
        +      "anyOf": [
        +        {
        +          "type": "string"
        +        },
        +        {
        +          "type": "null"
        +        }
        +      ],
        +      "description": "Every counter in `rows` is cumulative SINCE this point. Null = start of the window unknown."
        +    },
        +    "stats_reset_age_seconds": {
        +      "anyOf": [
        +        {
        +          "type": "number"
        +        },
        +        {
        +          "type": "null"
        +        }
        +      ],
        +      "description": "Seconds since `stats_reset`; null whenever that is null."
        +    }
        +  },
        +  "required": [
        +    "rows",
        +    "stats_reset",
        +    "stats_reset_age_seconds"
        +  ],
        +  "type": "object"
        +}
    • Changedpg_table_bloat2 fields changed
      • changedInput schema / $schema
        Previous value: -"http://json-schema.org/draft-07/schema#"New value: +"https://json-schema.org/draft/2020-12/schema"
      • changedOutput schema / (root)
        Previous value: -nullNew value: +{
        +  "$schema": "https://json-schema.org/draft/2020-12/schema",
        +  "additionalProperties": false,
        +  "properties": {
        +    "rows": {
        +      "items": {
        +        "additionalProperties": false,
        +        "properties": {
        +          "dead_ratio": {
        +            "description": "dead / (live + dead), bounded [0, 1]. Rows with both counters at 0 are filtered.",
        +            "type": "number"
        +          },
        +          "dead_tuples": {
        +            "description": "Bigint as a decimal string.",
        +            "type": "string"
        +          },
        +          "last_analyze": {
        +            "anyOf": [
        +              {
        +                "type": "string"
        +              },
        +              {
        +                "type": "null"
        +              }
        +            ]
        +          },
        +          "last_autovacuum": {
        +            "anyOf": [
        +              {
        +                "type": "string"
        +              },
        +              {
        +                "type": "null"
        +              }
        +            ]
        +          },
        +          "last_vacuum": {
        +            "anyOf": [
        +              {
        +                "type": "string"
        +              },
        +              {
        +                "type": "null"
        +              }
        +            ]
        +          },
        +          "live_tuples": {
        +            "description": "Bigint as a decimal string.",
        +            "type": "string"
        +          },
        +          "schema": {
        +            "type": "string"
        +          },
        +          "size_bytes": {
        +            "type": "string"
        +          },
        +          "size_pretty": {
        +            "type": "string"
        +          },
        +          "stats_reset": {
        +            "anyOf": [
        +              {
        +                "type": "string"
        +              },
        +              {
        +                "type": "null"
        +              }
        +            ],
        +            "description": "PostgreSQL 19+ only, absent below that. When THIS relation's counters were last reset by pg_stat_reset_single_table_counters() -- which zeroes the tuple counts AND clears every last_* timestamp together. Null on PG19+ means never reset."
        +          },
        +          "table": {
        +            "type": "string"
        +          }
        +        },
        +        "required": [
        +          "schema",
        +          "table",
        +          "live_tuples",
        +          "dead_tuples",
        +          "dead_ratio",
        +          "size_pretty",
        +          "size_bytes",
        +          "last_vacuum",
        +          "last_autovacuum",
        +          "last_analyze"
        +        ],
        +        "type": "object"
        +      },
        +      "type": "array"
        +    }
        +  },
        +  "required": [
        +    "rows"
        +  ],
        +  "type": "object"
        +}
    • Changedpg_table_privileges2 fields changed
      • changedInput schema / $schema
        Previous value: -"http://json-schema.org/draft-07/schema#"New value: +"https://json-schema.org/draft/2020-12/schema"
      • changedOutput schema / (root)
        Previous value: -nullNew value: +{
        +  "$schema": "https://json-schema.org/draft/2020-12/schema",
        +  "additionalProperties": false,
        +  "properties": {
        +    "rows": {
        +      "items": {
        +        "additionalProperties": false,
        +        "properties": {
        +          "grantee": {
        +            "description": "Role name, or PUBLIC.",
        +            "type": "string"
        +          },
        +          "is_grantable": {
        +            "description": "True when the grantee may pass this privilege on (WITH GRANT OPTION).",
        +            "type": "boolean"
        +          },
        +          "privilege_type": {
        +            "description": "SELECT | INSERT | UPDATE | DELETE | TRUNCATE | REFERENCES | TRIGGER.",
        +            "type": "string"
        +          },
        +          "table": {
        +            "type": "string"
        +          }
        +        },
        +        "required": [
        +          "table",
        +          "grantee",
        +          "privilege_type",
        +          "is_grantable"
        +        ],
        +        "type": "object"
        +      },
        +      "type": "array"
        +    }
        +  },
        +  "required": [
        +    "rows"
        +  ],
        +  "type": "object"
        +}
    • Changedpg_top_queries2 fields changed
      • changedInput schema / $schema
        Previous value: -"http://json-schema.org/draft-07/schema#"New value: +"https://json-schema.org/draft/2020-12/schema"
      • changedOutput schema / (root)
        Previous value: -nullNew value: +{
        +  "$schema": "https://json-schema.org/draft/2020-12/schema",
        +  "additionalProperties": false,
        +  "properties": {
        +    "_warnings": {
        +      "items": {
        +        "type": "string"
        +      },
        +      "type": "array"
        +    },
        +    "dealloc": {
        +      "anyOf": [
        +        {
        +          "type": "string"
        +        },
        +        {
        +          "type": "null"
        +        }
        +      ],
        +      "description": "Times least-executed entries were evicted for exceeding pg_stat_statements.max. Non-zero means this ranking is drawn from an INCOMPLETE population. Absent below extension 1.9."
        +    },
        +    "rows": {
        +      "items": {
        +        "additionalProperties": false,
        +        "properties": {
        +          "calls": {
        +            "description": "Bigint as a decimal string.",
        +            "type": "string"
        +          },
        +          "hit_percent": {
        +            "anyOf": [
        +              {
        +                "type": "number"
        +              },
        +              {
        +                "type": "null"
        +              }
        +            ],
        +            "description": "Null when the statement touched no shared blocks at all."
        +          },
        +          "io_read_time_ms": {
        +            "anyOf": [
        +              {
        +                "type": "number"
        +              },
        +              {
        +                "type": "null"
        +              }
        +            ],
        +            "description": "pg_stat_statements >= 1.10 only, absent below that. Null means track_io_timing is off OR the query did no measurable IO."
        +          },
        +          "io_write_time_ms": {
        +            "anyOf": [
        +              {
        +                "type": "number"
        +              },
        +              {
        +                "type": "null"
        +              }
        +            ],
        +            "description": "See io_read_time_ms."
        +          },
        +          "max_time_ms": {
        +            "type": "number"
        +          },
        +          "mean_time_ms": {
        +            "type": "number"
        +          },
        +          "min_time_ms": {
        +            "type": "number"
        +          },
        +          "query": {
        +            "description": "Normalized text: constants replaced with `?`.",
        +            "type": "string"
        +          },
        +          "rows": {
        +            "description": "Rows returned or affected, bigint as a decimal string.",
        +            "type": "string"
        +          },
        +          "total_time_ms": {
        +            "type": "number"
        +          }
        +        },
        +        "required": [
        +          "query",
        +          "calls",
        +          "total_time_ms",
        +          "mean_time_ms",
        +          "min_time_ms",
        +          "max_time_ms",
        +          "rows",
        +          "hit_percent"
        +        ],
        +        "type": "object"
        +      },
        +      "type": "array"
        +    },
        +    "stats_reset": {
        +      "anyOf": [
        +        {
        +          "type": "string"
        +        },
        +        {
        +          "type": "null"
        +        }
        +      ],
        +      "description": "pg_stat_statements' OWN reset clock, independent of the pg_stat_database one the table/index tools report -- never compare the two. Absent below extension 1.9."
        +    },
        +    "stats_reset_age_seconds": {
        +      "anyOf": [
        +        {
        +          "type": "number"
        +        },
        +        {
        +          "type": "null"
        +        }
        +      ]
        +    }
        +  },
        +  "required": [
        +    "rows"
        +  ],
        +  "type": "object"
        +}
    • Changedpg_unused_indexes2 fields changed
      • changedInput schema / $schema
        Previous value: -"http://json-schema.org/draft-07/schema#"New value: +"https://json-schema.org/draft/2020-12/schema"
      • changedOutput schema / (root)
        Previous value: -nullNew value: +{
        +  "$schema": "https://json-schema.org/draft/2020-12/schema",
        +  "additionalProperties": false,
        +  "properties": {
        +    "_warnings": {
        +      "items": {
        +        "type": "string"
        +      },
        +      "type": "array"
        +    },
        +    "rows": {
        +      "items": {
        +        "additionalProperties": false,
        +        "properties": {
        +          "definition": {
        +            "type": "string"
        +          },
        +          "index": {
        +            "type": "string"
        +          },
        +          "last_idx_scan": {
        +            "anyOf": [
        +              {
        +                "type": "string"
        +              },
        +              {
        +                "type": "null"
        +              }
        +            ],
        +            "description": "PostgreSQL 16+ only, absent below that. Null = never scanned since the reset. Far better grounds for a drop decision than the bare count."
        +          },
        +          "scans": {
        +            "description": "Bigint as a decimal string. A COUNTER, not a verdict -- read stats_reset first.",
        +            "type": "string"
        +          },
        +          "schema": {
        +            "type": "string"
        +          },
        +          "size_bytes": {
        +            "type": "string"
        +          },
        +          "size_pretty": {
        +            "type": "string"
        +          },
        +          "table": {
        +            "type": "string"
        +          }
        +        },
        +        "required": [
        +          "schema",
        +          "table",
        +          "index",
        +          "scans",
        +          "size_pretty",
        +          "size_bytes",
        +          "definition"
        +        ],
        +        "type": "object"
        +      },
        +      "type": "array"
        +    },
        +    "stats_reset": {
        +      "anyOf": [
        +        {
        +          "type": "string"
        +        },
        +        {
        +          "type": "null"
        +        }
        +      ],
        +      "description": "Every counter in `rows` is cumulative SINCE this point. Null = start of the window unknown."
        +    },
        +    "stats_reset_age_seconds": {
        +      "anyOf": [
        +        {
        +          "type": "number"
        +        },
        +        {
        +          "type": "null"
        +        }
        +      ],
        +      "description": "Seconds since `stats_reset`; null whenever that is null."
        +    }
        +  },
        +  "required": [
        +    "rows",
        +    "stats_reset",
        +    "stats_reset_age_seconds"
        +  ],
        +  "type": "object"
        +}
  3. 21 tool updatesv0.7.0
    • First observedpg_advisor
    • First observedpg_describe_table
    • First observedpg_explain
    • First observedpg_health
    • First observedpg_inspect_locks
    • First observedpg_kill
    • First observedpg_list_extensions
    • First observedpg_list_functions
    • First observedpg_list_roles
    • First observedpg_list_schemas
    • First observedpg_list_tables
    • First observedpg_list_views
    • First observedpg_query
    • First observedpg_readonly
    • First observedpg_replication_status
    • First observedpg_search_columns
    • First observedpg_seq_scan_tables
    • First observedpg_table_bloat
    • First observedpg_table_privileges
    • First observedpg_top_queries
    • First observedpg_unused_indexes

TDQS

A4.3/5.0
Disambiguation4/5

Almost every tool targets a distinct schema-introspection, performance, or lock-management function, and overlapping areas like pg_query vs pg_readonly or pg_health vs active_queries are carefully differentiated in the descriptions. A few diagnostics could still be confused at a glance, but the descriptions resolve the boundaries well.

Naming Consistency4/5

Names overwhelmingly share a pg_ prefix and snake_case style, with clear verb_noun tools like pg_list_schemas and pg_describe_table. The small unprefixed cluster (connections, active_queries, database_stats) and one-off names like pg_readonly keep it from being perfectly consistent.

Tool Count3/5

At 26 listed tools, the server is on the heavy side, though nearly every tool covers a distinct Postgres observability or admin concern. It exceeds the typical well-scoped 3-15 range, but it is not bloated enough to feel chaotic.

Completeness5/5

The set covers SQL execution, schema/table introspection, query planning, indexing, health, connections, locks, roles/privileges, replication, and several specialized DBA checks. There are no obvious dead ends for the apparent read-mostly observability and administration scope, and writes remain possible through pg_query when explicitly enabled.

Maintenance

ActivityActive
ResponsivenessResponsive

Resources

Unclaimed servers have limited discoverability.

Looking for Admin?

If you are the server author, to access and configure the admin panel.

Related MCP Connectors

Related MCP Servers

  • F
    license
    Not graded
    quality
    D
    maintenance
    Provides authenticated access to PostgreSQL databases for Claude AI, enabling users to browse database tables, discover schemas, and execute custom SQL queries through natural language interaction.
    -
  • F
    license
    Not graded
    quality
    D
    maintenance
    Enables natural language querying of PostgreSQL databases through the Model Context Protocol. It translates user questions into validated SQL, executes read-only queries safely, and returns results to MCP-compatible clients like Claude Desktop.
    -
  • F
    license
    Not graded
    quality
    D
    maintenance
    Enables AI assistants to safely interact with PostgreSQL databases, perform queries, inspect schemas, and analyze query performance.
    2
    -

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

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