@yawlabs/postgres-mcp
@yawlabs/postgres-mcp is a PostgreSQL MCP server providing:
Secure Query Execution:
pg_readonlyfor guaranteed read-only transactions;pg_queryfor writes (whenALLOW_WRITES=1and role permits). Supports parameterized queries and truncation of large results to prevent injection and context overflow.Schema Introspection: List schemas, tables (with row counts), views (with definitions), functions, extensions; describe table details (columns, PKs, FKs, indexes); search columns by name pattern.
Performance Diagnostics:
pg_explainwith ANALYZE and hypothetical indexes;pg_top_queriesfrompg_stat_statements;pg_seq_scan_tablesfor missing-index candidates;pg_unused_indexes;pg_table_bloatestimation.Health Monitoring:
pg_healthfor DB stats;pg_inspect_locksfor lock contention;pg_replication_status;pg_advisorfor DBA lints (sequence exhaustion, missing PKs, RLS disabled).Access Management: List roles (
pg_list_roles), inspect table privileges (pg_table_privileges), cancel queries/terminate connections (pg_kill).Configuration: Adapts to Docker, managed services via
DATABASE_URL,ALLOW_WRITES, statement timeout, connection pooling, and SSL settings; optimized for fast cold starts.
Provides tools for querying PostgreSQL databases, including SQL execution with read-only safety, schema introspection (tables, columns, keys, indexes), performance diagnostics (EXPLAIN, pg_stat_statements, bloat, locks), health checks, and role/privilege awareness.
Click on "Install Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@@yawlabs/postgres-mcpList all tables in the public schema"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
@yawlabs/postgres-mcp
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.
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:
pg_seq_scan_tables,pg_unused_indexesandpg_top_queriesreturn an envelope, not a bare row array. Readdata.rowswhere you used to readdata. The envelope carriesstats_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.pg_explainwithanalyze: truenow emitsBUFFERS, matching what PostgreSQL 18 does server-side. Plans get longer; passbuffers: falsefor the old output.Node 22 is the floor. Node 20 reached end of life.
Worth knowing even if you are not upgrading yet:
pg_describe_tablenow flags generated and identity columns. Previously a generated column's expression surfaced asdefault_valuewith nothing marking it, so an agent read the column as optional-with-a-default and wrote anINSERTthat PostgreSQL rejects.New
pg_io_statsexposespg_stat_io(PG16+) plus in-flight async I/O frompg_aiosand the activeio_method(PG18+).pg_advisorchecks multixact wraparound alongside transaction-ID wraparound. A lock-heavy workload can exhaust multixacts whilerelfrozenxidstill 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_queryruns user SQL in aBEGIN READ ONLYtransaction, so postgres itself (not string parsing) blocks writes; opt in to writes withALLOW_WRITES=1.pg_readonlyis a separate tool that stays read-only regardless ofALLOW_WRITES, so hosts that gate tools individually (Claude Code permissions, mcp.hosting) can auto-allow it -- paired with a least-privileged role, sinceREAD ONLYbounds 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 withGRANT 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_querysends user input withqueryMode: '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'sBEGIN READ ONLYwrapper. Integration test asserts the rejection.Parameterized queries -
pg_querytakes aparamsarray 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 viarelease.sh.Schema introspection built in -
pg_list_schemas,pg_list_tables,pg_describe_tablereturn columns, primary keys, foreign keys, and indexes without the agent having to rememberpg_catalogjoins.EXPLAINas a first-class tool - text or JSON format, with optionalANALYZE. ANALYZE for non-SELECT statements requiresALLOW_WRITES=1and always rolls back, so the plan is real but the write doesn't persist.Perf diagnostics the deprecated server never had -
pg_top_queries(frompg_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_healthreturns version, db size, connection counts, and the 10 longest-running active queries in one call.Role and privilege awareness -
pg_list_rolesandpg_table_privilegesfor the common "who can touch what?" questions.Instant startup - ships as a single bundled file with zero runtime dependencies. No multi-minute
node_modulesinstall on everynpxcold start.Result truncation - large result sets are capped at
POSTGRES_MAX_ROWS(default 1000) with atruncated: trueflag, so a straySELECT * FROM eventsdoesn'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.spawncannot directly execute.cmdfiles (that's whatnpxis on Windows). Wrapping withcmd /cis 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-sideBEGIN READ ONLY, unconditional), plus the introspection tools (pg_list_*,pg_describe_table,pg_search_columns,pg_explainwithout 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 ONLYdoes and does not cover. ABEGIN READ ONLYtransaction 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, andCOPY ... TO PROGRAMall run to completion insidepg_readonly, which means auto-allowingpg_readonlyreaches the same capability thatpg_killputs behindALLOW_WRITES=1. Every one of them still requires a privilege theDATABASE_URLrole 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-allowpg_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_idcolumn?" ->pg_search_columnswith patternuser_id-> one call instead of iterating every table."This query is slow, why?" ->
pg_explainwithanalyze: true-> returns the plan with actual row counts and timing."What's the slowest query we run?" ->
pg_top_queries-> returns the top N frompg_stat_statementswith 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
pgvectorinstalled?" ->pg_list_extensions-> yes/no with version.
The bigger leverage is multi-tool reasoning. A few real workflows:
Unstick a hung app.
pg_inspect_locksreturns blocked PID + blocking PID + the offending query, thenpg_kill(ALLOW_WRITES=1required) 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_queriesranks the worst queries,pg_explainwithanalyze: trueshows the plan for the top hit,pg_seq_scan_tablesandpg_unused_indexessay whether the answer is "add an index here" or "drop a dead one there."Oncall triage.
pg_healthchecks connectivity + active-query count + database size;pg_inspect_locksandpg_replication_statusconfirm whether contention or replication lag is in play before paging the on-call DBA.
Tools
Tool | Description |
| Run a SQL statement with no persistent data changes - always inside |
| Run a SQL query. Writes gated by the role in |
| List non-system schemas. |
| List tables (and optionally views) in a schema with estimated row counts. Paginated via |
| Kind, columns, PK, outgoing FKs, incoming FKs ( |
| List views and materialized views in a schema, including their SQL definitions. |
| List functions, procedures, and aggregates in a schema with signatures and return types. |
| List installed extensions (pgvector, postgis, pg_stat_statements, etc.) with versions. |
| Find columns by name pattern across all user schemas. Case-insensitive, supports SQL LIKE wildcards. |
|
|
| Recommend indexes for a workload and prove each one pays for itself first. Takes |
| Server version, database size, connections against |
| Top N queries by total/mean execution time. Requires the |
| Tables with heavy sequential scans - missing-index candidates. Returns the |
| Non-unique, non-primary indexes with low scan counts - drop candidates. Also returns |
| I/O observability: |
| Who is blocking whom right now (blocked PID, blocker PID, lock type, queries). |
| Database roles with login/superuser/createdb flags and group memberships. |
| Who has SELECT/INSERT/UPDATE/DELETE/etc. on a table or whole schema. |
| Tables with high dead-tuple ratios - VACUUM candidates. |
| Replication slots, connected replicas, and current WAL position. |
| Rolled-up DBA lints in one call: sequence-exhaustion candidates, wraparound risk for both counters (per-database and per-table |
| Cancel a running query or terminate a backend connection. Requires |
Configuration
All env vars are read from the MCP server's environment:
Variable | Default | Purpose |
| (required) | PostgreSQL connection string. |
| unset | Secondary write gate for |
|
| Per-statement timeout. |
|
| TCP connect timeout. Without this, a dead host hangs until the OS gives up (~2 minutes). |
|
| Cap on rows returned by |
|
| Max pool connections. Set to |
| unset | Set to |
|
| Value reported in |
|
| Which JS runtime executes the server: |
| unset | Explicit path to an |
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+ |
|
PG17+ |
|
PG18+ | Generated-column form ( |
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 ( | 298ms |
| 306ms |
| 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 chainunable to get local issuer certificateunable 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=directIt 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:integrationThe 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:integrationWindows: 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.shwsl-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 toolspg_advisorDatabase advisor (DBA lints)ARead-onlyIdempotent
Rolled-up DBA lint pass. One call returns four categories of findings:
sequence_exhaustion: SERIAL / BIGSERIAL / IDENTITY sequences whose
last_valueis aboveseqExhaustionThresholdofmax_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.databasesrows:{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.tablesrows:{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}, wherefreeze_max_age/multixact_freeze_max_ageare the EFFECTIVE limits -- a per-tableautovacuum_freeze_max_age/autovacuum_multixact_freeze_max_agestorage parameter wins over the GUC. A row is returned when EITHER ratio is at or abovewraparoundThreshold, andtriggered_by('xid' | 'multixact' | 'both') says which one did it: 'xid' means chase freezing/autovacuum, 'multixact' means chase the lock-heavy workload burning members.mxid_ageandpct_of_multixact_freeze_max_ageare 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.tablesdeliberately 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 carrypages/all_frozen_pages/frozen_page_fractionfrompg_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 inrlsSchemas) with row-level security disabled. Useful as a security baseline check. Any category whose query fails (permission-gated catalogs on managed providers) appends to_warningsand returns empty; the other categories still return. Use this as the 'what should I be looking at?' starting point, then drill intopg_unused_indexes,pg_table_bloat,pg_seq_scan_tablesfor the perf side.
| Name | Required | Description | Default |
|---|---|---|---|
| limit | No | Max rows per category (default 50). | |
| rlsSchemas | No | Schemas where RLS-missing should be flagged. Defaults to ['public']. | |
| wraparoundThreshold | No | 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. | |
| seqExhaustionThreshold | No | Minimum used-fraction (last_value / max_value) to flag a sequence (default 0.5 = 50%). |
Output Schema
| Name | Required | Description |
|---|---|---|
| _warnings | No | |
| wraparound_risk | Yes | |
| sequence_exhaustion | Yes | |
| public_tables_without_rls | Yes | |
| tables_without_primary_key | Yes | Plain and partitioned tables only; foreign tables cannot have a PK and are excluded. |
TDQS
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.
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.
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.
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.
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.
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 tableARead-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.
| Name | Required | Description | Default |
|---|---|---|---|
| table | Yes | Table name. | |
| schema | No | Schema name (defaults to 'public'). | public |
Output Schema
| Name | Required | Description |
|---|---|---|
| kind | Yes | table | partitioned_table | view | materialized_view | foreign_table, or the raw relkind. Defaults to 'table' with a `_warnings` entry when the kind fetch failed. |
| table | Yes | |
| schema | Yes | |
| columns | Yes | |
| indexes | Yes | |
| _warnings | No | |
| partitions | No | Present only when this relation is a partitioned parent WITH children. |
| constraints | Yes | CHECK / non-PK UNIQUE / EXCLUDE only; PK and FK have their own lists. |
| primary_key | Yes | Key columns in declared order; INCLUDE columns are excluded. |
| foreign_keys | Yes | |
| partition_of | No | Present only when this relation is itself a partition. |
| referenced_by | Yes | Other tables whose foreign keys point AT this one. |
TDQS
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.
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.
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.
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.
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.
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 planADestructive
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.
| Name | Required | Description | Default |
|---|---|---|---|
| sql | Yes | The SQL statement to explain. Do NOT prefix with EXPLAIN. | |
| wal | No | Report WAL generated by the statement. Requires `analyze` (PostgreSQL 13+). | |
| costs | No | Include estimated cost/rows/width. Set false for a terser plan. | |
| format | No | Output format. | text |
| memory | No | Report memory used by the planner (PostgreSQL 17+). Works with or without `analyze`, since planning happens either way. | |
| params | No | Positional parameters referenced as $1, $2, ... in the SQL. | |
| timing | No | Include per-node actual timing. Setting it to false REQUIRES `analyze: true` (it is rejected otherwise, not silently ignored); false lowers measurement overhead. | |
| analyze | No | Run EXPLAIN ANALYZE (actually executes the query). | |
| buffers | No | 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+. | |
| verbose | No | Include output columns, schema-qualified names, and triggers. | |
| settings | No | Report planner GUCs set away from their defaults - explains a weird plan (PostgreSQL 12+). | |
| serialize | No | Charge the cost of serializing result rows (network-bound queries hide it otherwise). Requires `analyze` (PostgreSQL 17+). | |
| generic_plan | No | 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+). | |
| hypothetical_indexes | No | List 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
| Name | Required | Description |
|---|---|---|
| plan | Yes | 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"`. |
TDQS
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.
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.
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.
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.
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.
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 snapshotARead-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:
totalfor the CURRENT database, broken down intoactive/idle/idle_in_transaction/idle_in_transaction_aborted/other(starting, fastpath function call, disabled) /state_unavailable-- those six sum tototal.idle_in_transaction_abortedis called out separately because it holds locks and blocks vacuum while doing no work and will never commit.state_unavailablecounts sessions whosestatereads 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 readactive: 0next to it as an idle database. Pluscluster_client_backends(client backends across ALL databases -- those are what actually consume connection slots),max_connections,superuser_reserved_connections, andused_fraction(cluster_client_backends / max_connections). A raw connection count means nothing without the cap; readused_fractionfirst.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 reportswait_event_type'BufferPin' through PostgreSQL 18 and 'Buffer' from 19 on, with thewait_eventnames beneath it changing to match. Read them against the reportedversionrather than hard-coding a literal.duration_seconds(since query_start) andtransaction_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, andstats_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_warningsand leave their field null; the rest of the snapshot still returns.
| Name | Required | Description | Default |
|---|---|---|---|
| activeQueryLimit | No | Max active queries to return (default 10, max 100). |
Output Schema
| Name | Required | Description |
|---|---|---|
| version | No | Full `version()` banner. Absent (with a `_warnings` entry) if the row came back without it. |
| database | No | |
| _warnings | No | |
| connected | Yes | Always true on a success response -- the version probe answered. |
| connections | No | |
| table_count | No | User tables and partitioned tables, as a decimal string. |
| active_queries | Yes | Empty array both when nothing is running and when the fetch failed -- check `_warnings`. |
| database_stats | Yes | Null when pg_stat_database is unreadable OR has no row for this database. |
TDQS
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.
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.
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.
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.
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.
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 workloadARead-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).
| Name | Required | Description | Default |
|---|---|---|---|
| limit | No | How many statements to pull from pg_stat_statements. Ignored when `statements` is given. | |
| schema | No | Only recommend indexes on tables in this schema. Candidates elsewhere are dropped. | |
| statements | No | The workload to optimize. When omitted, the top `limit` statements from pg_stat_statements are used instead (and weighted by their call counts). | |
| max_explains | No | Cap 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_candidates | No | Cap on candidate indexes considered. Candidates are ranked by table sequential-scan count first. | |
| min_improvement | No | Fraction 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_columns | No | Widest candidate index to consider. Every narrower prefix is considered too. | |
| max_recommendations | No | Stop after this many accepted indexes, even if more would still help. |
Output Schema
| Name | Required | Description |
|---|---|---|
| _warnings | No | |
| statements | Yes | The workload as analyzed, in the order `helps_statements.statement` indexes into. |
| explains_used | Yes | EXPLAIN round trips spent searching, excluding baseline plans. |
| explain_budget | Yes | |
| recommendations | Yes | |
| budget_exhausted | Yes | True when `max_explains` stopped the search before it converged. |
| final_workload_cost | Yes | Weighted total after applying every recommendation. |
| skip_scan_available | Yes | True on PostgreSQL 18+, where B-tree skip scan exists. |
| candidates_considered | Yes | |
| baseline_workload_cost | Yes | Weighted total estimated cost before any recommendation. |
| candidates_pruned_leading_column | Yes | Multi-column candidates dropped by the pre-PG18 leading-column rule. Always 0 on PG18+. |
TDQS
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.
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.
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.
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.
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.
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 locksARead-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.
| Name | Required | Description | Default |
|---|---|---|---|
| limit | No | Max blocked/blocker pairs (default 50). |
Output Schema
| Name | Required | Description |
|---|---|---|
| rows | Yes |
TDQS
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.
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.
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.
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.
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.
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/OARead-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. Countersreads/writes/extends/writebacks/hits/evictions/reuses/fsyncsare bigints returned as decimal strings;read_time_ms/write_time_ms/writeback_time_ms/extend_time_ms/fsync_time_msare float8 milliseconds. A timing of 0 next to a non-zero op count meanstrack_io_timingis 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 PG18op_byteswas removed and the server reports bytes directly. The top-levelbyte_accountingfield 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_iois mostly zeros on a quiet system, and the noise buries the handful of rows that matter). PassincludeZeroRows: truefor 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_flightarray would read as 'nothing is stalled' when the truth is 'this server cannot tell you'.in_flightis 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 whatin_flightcan contain: withio_method = syncthere 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_ioandpg_aiosare permission-gated on some managed providers) append to_warningsand set their field to null; the rest of the response still returns.
| Name | Required | Description | Default |
|---|---|---|---|
| limit | No | Max 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. | |
| includeZeroRows | No | If 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
| Name | Required | Description |
|---|---|---|
| io | Yes | Null (not []) when the fetch was refused -- [] is a real answer on a freshly reset cluster. |
| _warnings | No | |
| in_flight | No | PostgreSQL 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_method | No | PostgreSQL 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_accounting | Yes | Which source produced read_bytes / write_bytes / extend_bytes: native columns, or op_bytes * ops. |
| include_zero_rows | Yes | Echoed because it changes what an empty `io` means: no recorded I/O, vs the view returned nothing. |
| server_version_num | Yes | Echoed so a caller can tell WHY the PG18-only keys are absent without a second round-trip. |
TDQS
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.
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.
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.
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.
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.
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 backendADestructive
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.
| Name | Required | Description | Default |
|---|---|---|---|
| pid | Yes | Backend PID to signal. | |
| mode | No | `cancel` aborts the current query; `terminate` closes the connection entirely. | cancel |
Output Schema
| Name | Required | Description |
|---|---|---|
| pid | Yes | Echoed back from the request. |
| mode | Yes | Echoed back, after the safer 'cancel' default is applied. |
| note | Yes | On `signaled: false`, postgres's own NOTICE explaining why -- act on this, not on the boolean. |
| signaled | Yes | What pg_cancel_backend / pg_terminate_backend returned. |
TDQS
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.
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.
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.
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.
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.
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 extensionsARead-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.
| Name | Required | Description | Default |
|---|---|---|---|
No parameters | |||
Output Schema
| Name | Required | Description |
|---|---|---|
| rows | Yes |
TDQS
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.
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.
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.
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.
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.
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 proceduresARead-onlyIdempotent
List functions, procedures, and aggregates in a schema. Returns name, arguments, return type, kind (function/procedure/aggregate/window), and implementation language.
| Name | Required | Description | Default |
|---|---|---|---|
| schema | No | Schema name (defaults to 'public'). | public |
Output Schema
| Name | Required | Description |
|---|---|---|
| rows | Yes |
TDQS
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.
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.
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.
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.
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.
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 rolesARead-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.
| Name | Required | Description | Default |
|---|---|---|---|
| includeSystem | No | If true, include built-in `pg_*` roles (pg_read_all_data, pg_monitor, etc.). |
Output Schema
| Name | Required | Description |
|---|---|---|
| rows | Yes |
TDQS
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.
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.
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.
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.
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.
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 schemasARead-onlyIdempotent
List non-system schemas in the database. Excludes pg_catalog, information_schema, and other pg_* internals.
| Name | Required | Description | Default |
|---|---|---|---|
No parameters | |||
Output Schema
| Name | Required | Description |
|---|---|---|
| rows | Yes |
TDQS
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.
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.
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.
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.
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.
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 schemaARead-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.
| Name | Required | Description | Default |
|---|---|---|---|
| limit | No | Max rows to return (default 500, max 10000). | |
| offset | No | Rows to skip for pagination (default 0). | |
| schema | No | Schema name (defaults to 'public'). | public |
| includeViews | No | If true, include views and materialized views. |
Output Schema
| Name | Required | Description |
|---|---|---|
| rows | Yes |
TDQS
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.
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.
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.
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.
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.
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 definitionsARead-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.
| Name | Required | Description | Default |
|---|---|---|---|
| schema | No | Schema name (defaults to 'public'). | public |
| includeMaterialized | No | If true, include materialized views. |
Output Schema
| Name | Required | Description |
|---|---|---|
| rows | Yes |
TDQS
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.
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.
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.
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.
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.
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 queryADestructive
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.
| Name | Required | Description | Default |
|---|---|---|---|
| sql | Yes | The SQL statement to execute. Hard cap of 1 MB. | |
| params | No | Positional parameters referenced as $1, $2, ... in the SQL. |
Output Schema
| Name | Required | Description |
|---|---|---|
| rows | Yes | Result rows, capped at POSTGRES_MAX_ROWS. Values are whatever JSON type pg parsed the column into. |
| fields | Yes | Result column descriptors, in select-list order. |
| command | No | Postgres command tag (`INSERT`, `CREATE TABLE`, ...). Absent on the cursor path -- read absence as 'row-returning statement, command unknown'. |
| rowCount | Yes | Rows AFFECTED for DML -- not necessarily rows.length -- and rows returned on the cursor path. Null when pg reported no count. |
| truncated | No | Present and true only when the result hit POSTGRES_MAX_ROWS and rows were dropped. |
TDQS
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.
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.
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.
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.
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.
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 SQLARead-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.
| Name | Required | Description | Default |
|---|---|---|---|
| sql | Yes | The SQL statement to execute. Hard cap of 1 MB. | |
| params | No | Positional parameters referenced as $1, $2, ... in the SQL. |
Output Schema
| Name | Required | Description |
|---|---|---|
| rows | Yes | Result rows, capped at POSTGRES_MAX_ROWS. Values are whatever JSON type pg parsed the column into. |
| fields | Yes | Result column descriptors, in select-list order. |
| command | No | Postgres command tag (`INSERT`, `CREATE TABLE`, ...). Absent on the cursor path -- read absence as 'row-returning statement, command unknown'. |
| rowCount | Yes | Rows AFFECTED for DML -- not necessarily rows.length -- and rows returned on the cursor path. Null when pg reported no count. |
| truncated | No | Present and true only when the result hit POSTGRES_MAX_ROWS and rows were dropped. |
TDQS
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.
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.
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.
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.
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.
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 statusARead-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.
| Name | Required | Description | Default |
|---|---|---|---|
No parameters | |||
Output Schema
| Name | Required | Description |
|---|---|---|
| slots | Yes | Empty both on a standalone database and when the fetch failed -- check `_warnings`. |
| replicas | Yes | |
| _warnings | No | |
| is_replica | Yes | pg_is_in_recovery(). Null means the probe failed, NOT 'primary'. |
| wal_position | Yes | Last received LSN on a replica, current LSN on a primary. Null when the probe failed. |
TDQS
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.
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.
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.
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.
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.
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 nameARead-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'.
| Name | Required | Description | Default |
|---|---|---|---|
| limit | No | Max rows to return (default 100). | |
| schema | No | Limit to this schema. If omitted, searches all user schemas. | |
| pattern | Yes | LIKE pattern. Use '%' for wildcard: 'user_id', '%email%', 'created_%'. |
Output Schema
| Name | Required | Description |
|---|---|---|
| rows | Yes |
TDQS
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.
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.
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.
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.
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.
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 scansARead-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.
| Name | Required | Description | Default |
|---|---|---|---|
| limit | No | Max rows to return (default 20). | |
| schema | No | Limit to one schema. If omitted, all user schemas are included. | |
| minSize | No | Minimum live tuple count to include (default 1000, filters out tiny/empty tables). |
Output Schema
| Name | Required | Description |
|---|---|---|
| rows | Yes | |
| _warnings | No | |
| stats_reset | Yes | Every counter in `rows` is cumulative SINCE this point. Null = start of the window unknown. |
| stats_reset_age_seconds | Yes | Seconds since `stats_reset`; null whenever that is null. |
TDQS
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.
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.
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.
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.
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.
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 bloatARead-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 passschemawith method='exact' -- scanning all user tables in one statement will hit statement_timeout on non-trivial databases. Install pgstattuple withCREATE EXTENSION pgstattuple(requires superuser).
| Name | Required | Description | Default |
|---|---|---|---|
| limit | No | Max rows to return (default 50). | |
| method | No | Bloat 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 |
| schema | No | Limit to one schema. If omitted, all user schemas are included. | |
| minDeadRatio | No | Minimum dead-tuple fraction to include - dead / (live + dead). Default 0.1 = 10%. |
Output Schema
| Name | Required | Description |
|---|---|---|
| rows | Yes |
TDQS
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.
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.
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.
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.
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.
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 privilegesARead-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.
| Name | Required | Description | Default |
|---|---|---|---|
| table | No | Table name. Omit to list privileges for all tables in the schema. | |
| schema | No | Schema name (defaults to 'public'). | public |
Output Schema
| Name | Required | Description |
|---|---|---|
| rows | Yes |
TDQS
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.
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.
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.
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.
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.
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 timeARead-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.
| Name | Required | Description | Default |
|---|---|---|---|
| limit | No | Number of rows to return (default 20). | |
| orderBy | No | Ranking: total_time (cumulative impact), mean_time (worst per-call), or calls (hottest). | total_time |
Output Schema
| Name | Required | Description |
|---|---|---|
| rows | Yes | |
| dealloc | No | 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. |
| _warnings | No | |
| stats_reset | No | 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 | No |
TDQS
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.
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.
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.
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.
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.
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 indexesARead-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.
| Name | Required | Description | Default |
|---|---|---|---|
| limit | No | Max rows to return (default 50). | |
| schema | No | Limit to one schema. If omitted, all user schemas are included. | |
| maxScans | No | Include indexes with scan count <= this (default 10). Use 0 for 'never scanned'. |
Output Schema
| Name | Required | Description |
|---|---|---|
| rows | Yes | |
| _warnings | No | |
| stats_reset | Yes | Every counter in `rows` is cumulative SINCE this point. Null = start of the window unknown. |
| stats_reset_age_seconds | Yes | Seconds since `stats_reset`; null whenever that is null. |
TDQS
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.
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.
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.
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.
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.
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.
16 tool updates
v0.12.1- Changed
pg_advisor18 fields changed- removed
Output schema / properties / wraparound_risk / properties / autovacuum_freeze_max_age / anyOfRemoved value: -[ - { - "type": "number" - }, - { - "type": "null" - } -] - added
Output schema / properties / wraparound_risk / properties / autovacuum_freeze_max_age / typeAdded value: +[ + "number", + "null" +] - removed
Output schema / properties / wraparound_risk / properties / autovacuum_multixact_freeze_max_age / anyOfRemoved value: -[ - { - "type": "number" - }, - { - "type": "null" - } -] - added
Output schema / properties / wraparound_risk / properties / autovacuum_multixact_freeze_max_age / typeAdded value: +[ + "number", + "null" +] - removed
Output schema / properties / wraparound_risk / properties / databases / items / properties / mxid_age / anyOfRemoved value: -[ - { - "type": "number" - }, - { - "type": "null" - } -] - added
Output schema / properties / wraparound_risk / properties / databases / items / properties / mxid_age / typeAdded value: +[ + "number", + "null" +] - removed
Output schema / properties / wraparound_risk / properties / databases / items / properties / pct_of_freeze_max_age / anyOfRemoved value: -[ - { - "type": "number" - }, - { - "type": "null" - } -] - added
Output schema / properties / wraparound_risk / properties / databases / items / properties / pct_of_freeze_max_age / typeAdded value: +[ + "number", + "null" +] - removed
Output schema / properties / wraparound_risk / properties / databases / items / properties / pct_of_multixact_freeze_max_age / anyOfRemoved value: -[ - { - "type": "number" - }, - { - "type": "null" - } -] - added
Output schema / properties / wraparound_risk / properties / databases / items / properties / pct_of_multixact_freeze_max_age / typeAdded value: +[ + "number", + "null" +] - removed
Output schema / properties / wraparound_risk / properties / tables / items / properties / frozen_page_fraction / anyOfRemoved value: -[ - { - "type": "number" - }, - { - "type": "null" - } -] - added
Output schema / properties / wraparound_risk / properties / tables / items / properties / frozen_page_fraction / typeAdded value: +[ + "number", + "null" +] - removed
Output schema / properties / wraparound_risk / properties / tables / items / properties / mxid_age / anyOfRemoved value: -[ - { - "type": "number" - }, - { - "type": "null" - } -] - added
Output schema / properties / wraparound_risk / properties / tables / items / properties / mxid_age / typeAdded value: +[ + "number", + "null" +] - removed
Output schema / properties / wraparound_risk / properties / tables / items / properties / pct_of_freeze_max_age / anyOfRemoved value: -[ - { - "type": "number" - }, - { - "type": "null" - } -] - added
Output schema / properties / wraparound_risk / properties / tables / items / properties / pct_of_freeze_max_age / typeAdded value: +[ + "number", + "null" +] - removed
Output schema / properties / wraparound_risk / properties / tables / items / properties / pct_of_multixact_freeze_max_age / anyOfRemoved value: -[ - { - "type": "number" - }, - { - "type": "null" - } -] - added
Output schema / properties / wraparound_risk / properties / tables / items / properties / pct_of_multixact_freeze_max_age / typeAdded value: +[ + "number", + "null" +]
- Changed
pg_describe_table4 fields changed- removed
Output schema / properties / columns / items / properties / default_value / anyOfRemoved value: -[ - { - "type": "string" - }, - { - "type": "null" - } -] - added
Output schema / properties / columns / items / properties / default_value / typeAdded value: +[ + "string", + "null" +] - removed
Output schema / properties / columns / items / properties / generation_expression / anyOfRemoved value: -[ - { - "type": "string" - }, - { - "type": "null" - } -] - added
Output schema / properties / columns / items / properties / generation_expression / typeAdded value: +[ + "string", + "null" +]
- Changed
pg_health16 fields changed- removed
Output schema / properties / active_queries / items / properties / duration_seconds / anyOfRemoved value: -[ - { - "type": "number" - }, - { - "type": "null" - } -] - added
Output schema / properties / active_queries / items / properties / duration_seconds / typeAdded value: +[ + "number", + "null" +] - removed
Output schema / properties / active_queries / items / properties / state / anyOfRemoved value: -[ - { - "type": "string" - }, - { - "type": "null" - } -] - added
Output schema / properties / active_queries / items / properties / state / typeAdded value: +[ + "string", + "null" +] - removed
Output schema / properties / active_queries / items / properties / transaction_age_seconds / anyOfRemoved value: -[ - { - "type": "number" - }, - { - "type": "null" - } -] - added
Output schema / properties / active_queries / items / properties / transaction_age_seconds / typeAdded value: +[ + "number", + "null" +] - removed
Output schema / properties / active_queries / items / properties / wait_event / anyOfRemoved value: -[ - { - "type": "string" - }, - { - "type": "null" - } -] - added
Output schema / properties / active_queries / items / properties / wait_event / typeAdded value: +[ + "string", + "null" +] - removed
Output schema / properties / active_queries / items / properties / wait_event_type / anyOfRemoved value: -[ - { - "type": "string" - }, - { - "type": "null" - } -] - added
Output schema / properties / active_queries / items / properties / wait_event_type / typeAdded value: +[ + "string", + "null" +] - removed
Output schema / properties / active_queries / items / properties / xact_start / anyOfRemoved value: -[ - { - "type": "string" - }, - { - "type": "null" - } -] - added
Output schema / properties / active_queries / items / properties / xact_start / typeAdded value: +[ + "string", + "null" +] - changed
Output schema / properties / connections / anyOfPrevious 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" + } +] - changed
Output schema / properties / database_stats / anyOfPrevious 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" + } +] - removed
Output schema / properties / table_count / anyOfRemoved value: -[ - { - "type": "string" - }, - { - "type": "null" - } -] - added
Output schema / properties / table_count / typeAdded value: +[ + "string", + "null" +]
- Changed
pg_index_advisor4 fields changed- removed
Output schema / properties / recommendations / items / properties / estimated_size_bytes / anyOfRemoved value: -[ - { - "type": "string" - }, - { - "type": "null" - } -] - added
Output schema / properties / recommendations / items / properties / estimated_size_bytes / typeAdded value: +[ + "string", + "null" +] - removed
Output schema / properties / statements / items / properties / baseline_cost / anyOfRemoved value: -[ - { - "type": "number" - }, - { - "type": "null" - } -] - added
Output schema / properties / statements / items / properties / baseline_cost / typeAdded value: +[ + "number", + "null" +]
- Changed
pg_inspect_locks17 fields changed- removed
Output schema / properties / rows / items / properties / blocked_duration_seconds / anyOfRemoved value: -[ - { - "type": "number" - }, - { - "type": "null" - } -] - added
Output schema / properties / rows / items / properties / blocked_duration_seconds / typeAdded value: +[ + "number", + "null" +] - removed
Output schema / properties / rows / items / properties / blocked_query / anyOfRemoved value: -[ - { - "type": "string" - }, - { - "type": "null" - } -] - added
Output schema / properties / rows / items / properties / blocked_query / typeAdded value: +[ + "string", + "null" +] - removed
Output schema / properties / rows / items / properties / blocked_user / anyOfRemoved value: -[ - { - "type": "string" - }, - { - "type": "null" - } -] - added
Output schema / properties / rows / items / properties / blocked_user / typeAdded value: +[ + "string", + "null" +] - removed
Output schema / properties / rows / items / properties / blocking_duration_seconds / anyOfRemoved value: -[ - { - "type": "number" - }, - { - "type": "null" - } -] - added
Output schema / properties / rows / items / properties / blocking_duration_seconds / typeAdded value: +[ + "number", + "null" +] - removed
Output schema / properties / rows / items / properties / blocking_query / anyOfRemoved value: -[ - { - "type": "string" - }, - { - "type": "null" - } -] - added
Output schema / properties / rows / items / properties / blocking_query / typeAdded value: +[ + "string", + "null" +] - removed
Output schema / properties / rows / items / properties / blocking_state / anyOfRemoved value: -[ - { - "type": "string" - }, - { - "type": "null" - } -] - added
Output schema / properties / rows / items / properties / blocking_state / typeAdded value: +[ + "string", + "null" +] - removed
Output schema / properties / rows / items / properties / blocking_user / anyOfRemoved value: -[ - { - "type": "string" - }, - { - "type": "null" - } -] - added
Output schema / properties / rows / items / properties / blocking_user / typeAdded value: +[ + "string", + "null" +] - removed
Output schema / properties / rows / items / properties / relation / anyOfRemoved value: -[ - { - "type": "string" - }, - { - "type": "null" - } -] - changed
Output schema / properties / rows / items / properties / relation / descriptionPrevious 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." - added
Output schema / properties / rows / items / properties / relation / typeAdded value: +[ + "string", + "null" +]
- Changed
pg_io_stats4 fields changed- changed
Output schema / properties / in_flight / anyOfPrevious 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" + } +] - changed
Output schema / properties / io / anyOfPrevious 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" + } +] - removed
Output schema / properties / io_method / anyOfRemoved value: -[ - { - "type": "string" - }, - { - "type": "null" - } -] - added
Output schema / properties / io_method / typeAdded value: +[ + "string", + "null" +]
- Changed
pg_list_extensions2 fields changed- removed
Output schema / properties / rows / items / properties / description / anyOfRemoved value: -[ - { - "type": "string" - }, - { - "type": "null" - } -] - added
Output schema / properties / rows / items / properties / description / typeAdded value: +[ + "string", + "null" +]
- Changed
pg_list_functions2 fields changed- removed
Output schema / properties / rows / items / properties / return_type / anyOfRemoved value: -[ - { - "type": "string" - }, - { - "type": "null" - } -] - added
Output schema / properties / rows / items / properties / return_type / typeAdded value: +[ + "string", + "null" +]
- Changed
pg_list_tables2 fields changed- removed
Output schema / properties / rows / items / properties / estimated_rows / anyOfRemoved value: -[ - { - "type": "number" - }, - { - "type": "null" - } -] - added
Output schema / properties / rows / items / properties / estimated_rows / typeAdded value: +[ + "number", + "null" +]
- Changed
pg_query2 fields changed- removed
Output schema / properties / rowCount / anyOfRemoved value: -[ - { - "type": "number" - }, - { - "type": "null" - } -] - added
Output schema / properties / rowCount / typeAdded value: +[ + "number", + "null" +]
- Changed
pg_readonly2 fields changed- removed
Output schema / properties / rowCount / anyOfRemoved value: -[ - { - "type": "number" - }, - { - "type": "null" - } -] - added
Output schema / properties / rowCount / typeAdded value: +[ + "number", + "null" +]
- Changed
pg_replication_status22 fields changed- removed
Output schema / properties / is_replica / anyOfRemoved value: -[ - { - "type": "boolean" - }, - { - "type": "null" - } -] - added
Output schema / properties / is_replica / typeAdded value: +[ + "boolean", + "null" +] - removed
Output schema / properties / replicas / items / properties / client_addr / anyOfRemoved value: -[ - { - "type": "string" - }, - { - "type": "null" - } -] - added
Output schema / properties / replicas / items / properties / client_addr / typeAdded value: +[ + "string", + "null" +] - removed
Output schema / properties / replicas / items / properties / flush_lag_seconds / anyOfRemoved value: -[ - { - "type": "number" - }, - { - "type": "null" - } -] - added
Output schema / properties / replicas / items / properties / flush_lag_seconds / typeAdded value: +[ + "number", + "null" +] - removed
Output schema / properties / replicas / items / properties / replay_lag_seconds / anyOfRemoved value: -[ - { - "type": "number" - }, - { - "type": "null" - } -] - added
Output schema / properties / replicas / items / properties / replay_lag_seconds / typeAdded value: +[ + "number", + "null" +] - removed
Output schema / properties / replicas / items / properties / write_lag_seconds / anyOfRemoved value: -[ - { - "type": "number" - }, - { - "type": "null" - } -] - added
Output schema / properties / replicas / items / properties / write_lag_seconds / typeAdded value: +[ + "number", + "null" +] - removed
Output schema / properties / slots / items / properties / confirmed_flush_lsn / anyOfRemoved value: -[ - { - "type": "string" - }, - { - "type": "null" - } -] - added
Output schema / properties / slots / items / properties / confirmed_flush_lsn / typeAdded value: +[ + "string", + "null" +] - removed
Output schema / properties / slots / items / properties / database / anyOfRemoved value: -[ - { - "type": "string" - }, - { - "type": "null" - } -] - added
Output schema / properties / slots / items / properties / database / typeAdded value: +[ + "string", + "null" +] - removed
Output schema / properties / slots / items / properties / plugin / anyOfRemoved value: -[ - { - "type": "string" - }, - { - "type": "null" - } -] - added
Output schema / properties / slots / items / properties / plugin / typeAdded value: +[ + "string", + "null" +] - removed
Output schema / properties / slots / items / properties / restart_lsn / anyOfRemoved value: -[ - { - "type": "string" - }, - { - "type": "null" - } -] - added
Output schema / properties / slots / items / properties / restart_lsn / typeAdded value: +[ + "string", + "null" +] - removed
Output schema / properties / slots / items / properties / wal_status / anyOfRemoved value: -[ - { - "type": "string" - }, - { - "type": "null" - } -] - added
Output schema / properties / slots / items / properties / wal_status / typeAdded value: +[ + "string", + "null" +] - removed
Output schema / properties / wal_position / anyOfRemoved value: -[ - { - "type": "string" - }, - { - "type": "null" - } -] - added
Output schema / properties / wal_position / typeAdded value: +[ + "string", + "null" +]
- Changed
pg_seq_scan_tables10 fields changed- removed
Output schema / properties / rows / items / properties / last_idx_scan / anyOfRemoved value: -[ - { - "type": "string" - }, - { - "type": "null" - } -] - added
Output schema / properties / rows / items / properties / last_idx_scan / typeAdded value: +[ + "string", + "null" +] - removed
Output schema / properties / rows / items / properties / last_seq_scan / anyOfRemoved value: -[ - { - "type": "string" - }, - { - "type": "null" - } -] - added
Output schema / properties / rows / items / properties / last_seq_scan / typeAdded value: +[ + "string", + "null" +] - removed
Output schema / properties / rows / items / properties / ratio / anyOfRemoved value: -[ - { - "type": "number" - }, - { - "type": "null" - } -] - added
Output schema / properties / rows / items / properties / ratio / typeAdded value: +[ + "number", + "null" +] - removed
Output schema / properties / stats_reset / anyOfRemoved value: -[ - { - "type": "string" - }, - { - "type": "null" - } -] - added
Output schema / properties / stats_reset / typeAdded value: +[ + "string", + "null" +] - removed
Output schema / properties / stats_reset_age_seconds / anyOfRemoved value: -[ - { - "type": "number" - }, - { - "type": "null" - } -] - added
Output schema / properties / stats_reset_age_seconds / typeAdded value: +[ + "number", + "null" +]
- Changed
pg_table_bloat8 fields changed- removed
Output schema / properties / rows / items / properties / last_analyze / anyOfRemoved value: -[ - { - "type": "string" - }, - { - "type": "null" - } -] - added
Output schema / properties / rows / items / properties / last_analyze / typeAdded value: +[ + "string", + "null" +] - removed
Output schema / properties / rows / items / properties / last_autovacuum / anyOfRemoved value: -[ - { - "type": "string" - }, - { - "type": "null" - } -] - added
Output schema / properties / rows / items / properties / last_autovacuum / typeAdded value: +[ + "string", + "null" +] - removed
Output schema / properties / rows / items / properties / last_vacuum / anyOfRemoved value: -[ - { - "type": "string" - }, - { - "type": "null" - } -] - added
Output schema / properties / rows / items / properties / last_vacuum / typeAdded value: +[ + "string", + "null" +] - removed
Output schema / properties / rows / items / properties / stats_reset / anyOfRemoved value: -[ - { - "type": "string" - }, - { - "type": "null" - } -] - added
Output schema / properties / rows / items / properties / stats_reset / typeAdded value: +[ + "string", + "null" +]
- Changed
pg_top_queries12 fields changed- removed
Output schema / properties / dealloc / anyOfRemoved value: -[ - { - "type": "string" - }, - { - "type": "null" - } -] - added
Output schema / properties / dealloc / typeAdded value: +[ + "string", + "null" +] - removed
Output schema / properties / rows / items / properties / hit_percent / anyOfRemoved value: -[ - { - "type": "number" - }, - { - "type": "null" - } -] - added
Output schema / properties / rows / items / properties / hit_percent / typeAdded value: +[ + "number", + "null" +] - removed
Output schema / properties / rows / items / properties / io_read_time_ms / anyOfRemoved value: -[ - { - "type": "number" - }, - { - "type": "null" - } -] - added
Output schema / properties / rows / items / properties / io_read_time_ms / typeAdded value: +[ + "number", + "null" +] - removed
Output schema / properties / rows / items / properties / io_write_time_ms / anyOfRemoved value: -[ - { - "type": "number" - }, - { - "type": "null" - } -] - added
Output schema / properties / rows / items / properties / io_write_time_ms / typeAdded value: +[ + "number", + "null" +] - removed
Output schema / properties / stats_reset / anyOfRemoved value: -[ - { - "type": "string" - }, - { - "type": "null" - } -] - added
Output schema / properties / stats_reset / typeAdded value: +[ + "string", + "null" +] - removed
Output schema / properties / stats_reset_age_seconds / anyOfRemoved value: -[ - { - "type": "number" - }, - { - "type": "null" - } -] - added
Output schema / properties / stats_reset_age_seconds / typeAdded value: +[ + "number", + "null" +]
- Changed
pg_unused_indexes6 fields changed- removed
Output schema / properties / rows / items / properties / last_idx_scan / anyOfRemoved value: -[ - { - "type": "string" - }, - { - "type": "null" - } -] - added
Output schema / properties / rows / items / properties / last_idx_scan / typeAdded value: +[ + "string", + "null" +] - removed
Output schema / properties / stats_reset / anyOfRemoved value: -[ - { - "type": "string" - }, - { - "type": "null" - } -] - added
Output schema / properties / stats_reset / typeAdded value: +[ + "string", + "null" +] - removed
Output schema / properties / stats_reset_age_seconds / anyOfRemoved value: -[ - { - "type": "number" - }, - { - "type": "null" - } -] - added
Output schema / properties / stats_reset_age_seconds / typeAdded value: +[ + "number", + "null" +]
23 tool updates
v0.12.0- Changed
pg_advisor3 fields changed- changed
Input schema / $schemaPrevious value: -"http://json-schema.org/draft-07/schema#"New value: +"https://json-schema.org/draft/2020-12/schema" - added
Input schema / properties / wraparoundThresholdAdded 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" +} - changed
Output 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" +}
- Changed
pg_describe_table2 fields changed- changed
Input schema / $schemaPrevious value: -"http://json-schema.org/draft-07/schema#"New value: +"https://json-schema.org/draft/2020-12/schema" - changed
Output 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" +}
- Changed
pg_explain14 fields changed- added
Input schema / $defsAdded 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" + } + ] + } +} - changed
Input schema / $schemaPrevious value: -"http://json-schema.org/draft-07/schema#"New value: +"https://json-schema.org/draft/2020-12/schema" - removed
Input schema / definitionsRemoved 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" - } - ] - } -} - added
Input schema / properties / buffersAdded 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" +} - added
Input schema / properties / costsAdded value: +{ + "default": true, + "description": "Include estimated cost/rows/width. Set false for a terser plan.", + "type": "boolean" +} - added
Input schema / properties / generic_planAdded 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" +} - added
Input schema / properties / memoryAdded value: +{ + "default": false, + "description": "Report memory used by the planner (PostgreSQL 17+). Works with or without `analyze`, since planning happens either way.", + "type": "boolean" +} - changed
Input schema / properties / params / items / $refPrevious value: -"#/definitions/__schema0"New value: +"#/$defs/__schema0" - added
Input schema / properties / serializeAdded 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" +} - added
Input schema / properties / settingsAdded value: +{ + "default": false, + "description": "Report planner GUCs set away from their defaults - explains a weird plan (PostgreSQL 12+).", + "type": "boolean" +} - added
Input schema / properties / timingAdded 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" +} - added
Input schema / properties / verboseAdded value: +{ + "default": false, + "description": "Include output columns, schema-qualified names, and triggers.", + "type": "boolean" +} - added
Input schema / properties / walAdded value: +{ + "default": false, + "description": "Report WAL generated by the statement. Requires `analyze` (PostgreSQL 13+).", + "type": "boolean" +} - changed
Output 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" +}
- Changed
pg_health2 fields changed- changed
Input schema / $schemaPrevious value: -"http://json-schema.org/draft-07/schema#"New value: +"https://json-schema.org/draft/2020-12/schema" - changed
Output 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" +}
- Added
pg_index_advisor - Changed
pg_inspect_locks2 fields changed- changed
Input schema / $schemaPrevious value: -"http://json-schema.org/draft-07/schema#"New value: +"https://json-schema.org/draft/2020-12/schema" - changed
Output 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" +}
- Added
pg_io_stats - Changed
pg_kill2 fields changed- changed
Input schema / $schemaPrevious value: -"http://json-schema.org/draft-07/schema#"New value: +"https://json-schema.org/draft/2020-12/schema" - changed
Output 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" +}
- Changed
pg_list_extensions2 fields changed- changed
Input schema / $schemaPrevious value: -"http://json-schema.org/draft-07/schema#"New value: +"https://json-schema.org/draft/2020-12/schema" - changed
Output 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" +}
- Changed
pg_list_functions2 fields changed- changed
Input schema / $schemaPrevious value: -"http://json-schema.org/draft-07/schema#"New value: +"https://json-schema.org/draft/2020-12/schema" - changed
Output 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" +}
- Changed
pg_list_roles2 fields changed- changed
Input schema / $schemaPrevious value: -"http://json-schema.org/draft-07/schema#"New value: +"https://json-schema.org/draft/2020-12/schema" - changed
Output 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" +}
- Changed
pg_list_schemas2 fields changed- changed
Input schema / $schemaPrevious value: -"http://json-schema.org/draft-07/schema#"New value: +"https://json-schema.org/draft/2020-12/schema" - changed
Output 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" +}
- Changed
pg_list_tables2 fields changed- changed
Input schema / $schemaPrevious value: -"http://json-schema.org/draft-07/schema#"New value: +"https://json-schema.org/draft/2020-12/schema" - changed
Output 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" +}
- Changed
pg_list_views2 fields changed- changed
Input schema / $schemaPrevious value: -"http://json-schema.org/draft-07/schema#"New value: +"https://json-schema.org/draft/2020-12/schema" - changed
Output 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" +}
- Changed
pg_query5 fields changed- added
Input schema / $defsAdded 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" + } + ] + } +} - changed
Input schema / $schemaPrevious value: -"http://json-schema.org/draft-07/schema#"New value: +"https://json-schema.org/draft/2020-12/schema" - removed
Input schema / definitionsRemoved 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" - } - ] - } -} - changed
Input schema / properties / params / items / $refPrevious value: -"#/definitions/__schema0"New value: +"#/$defs/__schema0" - changed
Output 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" +}
- Changed
pg_readonly5 fields changed- added
Input schema / $defsAdded 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" + } + ] + } +} - changed
Input schema / $schemaPrevious value: -"http://json-schema.org/draft-07/schema#"New value: +"https://json-schema.org/draft/2020-12/schema" - removed
Input schema / definitionsRemoved 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" - } - ] - } -} - changed
Input schema / properties / params / items / $refPrevious value: -"#/definitions/__schema0"New value: +"#/$defs/__schema0" - changed
Output 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" +}
- Changed
pg_replication_status2 fields changed- changed
Input schema / $schemaPrevious value: -"http://json-schema.org/draft-07/schema#"New value: +"https://json-schema.org/draft/2020-12/schema" - changed
Output 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" +}
- Changed
pg_search_columns2 fields changed- changed
Input schema / $schemaPrevious value: -"http://json-schema.org/draft-07/schema#"New value: +"https://json-schema.org/draft/2020-12/schema" - changed
Output 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" +}
- Changed
pg_seq_scan_tables2 fields changed- changed
Input schema / $schemaPrevious value: -"http://json-schema.org/draft-07/schema#"New value: +"https://json-schema.org/draft/2020-12/schema" - changed
Output 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" +}
- Changed
pg_table_bloat2 fields changed- changed
Input schema / $schemaPrevious value: -"http://json-schema.org/draft-07/schema#"New value: +"https://json-schema.org/draft/2020-12/schema" - changed
Output 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" +}
- Changed
pg_table_privileges2 fields changed- changed
Input schema / $schemaPrevious value: -"http://json-schema.org/draft-07/schema#"New value: +"https://json-schema.org/draft/2020-12/schema" - changed
Output 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" +}
- Changed
pg_top_queries2 fields changed- changed
Input schema / $schemaPrevious value: -"http://json-schema.org/draft-07/schema#"New value: +"https://json-schema.org/draft/2020-12/schema" - changed
Output 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" +}
- Changed
pg_unused_indexes2 fields changed- changed
Input schema / $schemaPrevious value: -"http://json-schema.org/draft-07/schema#"New value: +"https://json-schema.org/draft/2020-12/schema" - changed
Output 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" +}
21 tool updates
v0.7.0- First observed
pg_advisor - First observed
pg_describe_table - First observed
pg_explain - First observed
pg_health - First observed
pg_inspect_locks - First observed
pg_kill - First observed
pg_list_extensions - First observed
pg_list_functions - First observed
pg_list_roles - First observed
pg_list_schemas - First observed
pg_list_tables - First observed
pg_list_views - First observed
pg_query - First observed
pg_readonly - First observed
pg_replication_status - First observed
pg_search_columns - First observed
pg_seq_scan_tables - First observed
pg_table_bloat - First observed
pg_table_privileges - First observed
pg_top_queries - First observed
pg_unused_indexes
TDQS
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.
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.
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.
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
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
Query 40 databases from Claude, ChatGPT, or Cursor — on any device. Read-only, encrypted, audited.
Query PostgreSQL databases in plain English — LLM-generated, safety-validated SQL.
Query your warehouse or a CSV with Claude/ChatGPT over MCP, governed by table-level ACL + audit.
Generate, fix, explain and run read-only SQL on PostgreSQL, MySQL and SQL Server
1
Related MCP Servers
- FlicenseNot gradedqualityDmaintenanceProvides 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.-
- FlicenseNot gradedqualityDmaintenanceEnables 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.-
- AlicenseAqualityDmaintenanceConnects Claude to any PostgreSQL database, enabling schema exploration, query execution, and performance analysis through natural conversation.7141MIT
- FlicenseNot gradedqualityDmaintenanceEnables AI assistants to safely interact with PostgreSQL databases, perform queries, inspect schemas, and analyze query performance.2-
Latest Blog Posts
- Who's Calling? MCP Hosts Are an Identity Blind Spot (And the Spec Knows It)By Om-Shree-0709 on .mcpAgent IdentityOAuth 2.1
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
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