@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?
Even though annotations already declare read-only/idempotent/non-destructive, the description adds substantial behavioral context: per-category query failures append to _warnings and return empty, system catalogs and TOAST tables are intentionally included, threshold semantics are explained, and PG18+ key-absence behavior is disclosed. This is far beyond what annotations alone provide.
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 it earns its length for a roll-up diagnostic tool with four complex finding categories. It is well-structured with bolded category names, nested field descriptions, and a clear closing usage note. A few asides could be trimmed, but the density is justified by the tool's scope.
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 covers all finding categories, their exact row structures, threshold behaviors, edge cases, failure modes, and follow-up tools. Despite the output schema being available, this description makes the tool's behavior fully understandable without needing additional context. Nothing essential for correct invocation or interpretation 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 input schema already documents all four parameters at 100% coverage, so the baseline is 3. The description goes further by explaining how wraparoundThreshold applies to both xid and multixact ratios independently, and how seqExhaustionThreshold relates to last_value/max_value. This adds meaningful semantic detail beyond the schema field 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 'Rolled-up DBA lint pass' and immediately enumerates the four distinct categories of findings with detailed definitions. It clearly identifies this as the broad advisor tool, distinct from more focused siblings like pg_unused_indexes or pg_table_bloat.
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 positions the tool as the 'what should I be looking at?' starting point and names the perf-side drill-down alternatives: 'pg_unused_indexes, pg_table_bloat, pg_seq_scan_tables'. It lacks an explicit 'don't use when...' statement, but the guidance is clear enough for an agent to choose it as the first-pass diagnostic.
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?
The description goes far beyond the readOnlyHint/idempotentHint annotations by explaining exactly what is included in the result, how plain views behave, which column types are not writable, and how PostgreSQL 18+ adds extra fields. It also clarifies that 'generated' columns report 'generation_expression' rather than 'default_value', which prevents misinterpretation of output.
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?
While the description is long, every sentence carries operational value: relation kinds, output fields, view behavior, writability caveats, generated column handling, and PG18 additions. It is front-loaded with the core result shape and then layers important caveats in a logical order.
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 an output schema exists and annotations already establish the read-only and idempotent nature, the description fully covers what an agent needs to correctly invoke and interpret this tool. It explains edge cases (views, generated/identity columns, NOT VALID constraints) that would otherwise be surprising.
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 baseline is 3. The description does not add meaningful parameter-level semantics beyond the schema — it describes output structure and behavioral details rather than clarifying what 'table' or 'schema' mean or how they should be formatted.
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 opens with the precise verb-resource pair 'Describe a relation' and immediately enumerates the full set of returned dimensions (kind, columns, primary key, foreign keys, constraints, indexes, partition info). It distinguishes itself from generic listing siblings by explaining it returns structural metadata, not just table names or rows.
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 on when this tool is appropriate, notably that it works on views and materialized views and that PK/FK/constraint lists will be empty for plain views. It advises using 'kind' to disambiguate before assuming writability, which is practical guidance, though it does not explicitly name sibling tools as alternatives.
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?
Beyond the readOnly/idempotent annotations, the description discloses partial failure behavior (_warnings and null fields), cumulative counter semantics, state_unavailable under-counting implications, NULL wait events for running backends, and version-dependent wait_event spelling. This goes far beyond what the annotations or schema 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 long but every section earns its place: a front-loaded summary, then structured bullets that clarify connections, active_queries, and database_stats with actionable diagnostic guidance. There is no filler or redundancy.
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 this complex, the description covers output semantics, edge cases, permission-gated failures, and interpretation pitfalls such as under-counted active connections and cumulative stats. The existing output schema can handle the return structure, and nothing essential for correct invocation or interpretation 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 schema already documents activeQueryLimit with default, min, max, and a description (100% coverage). The tool description does not add parameter-specific context beyond mentioning active queries generally, so the schema carries the load and the baseline 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 names a specific resource ('Database health snapshot') and lists concrete components: server version, database size, connection counts, active queries with wait events, pg_stat_database rollup, and table count. It clearly identifies what the tool produces, though it does not explicitly distinguish itself from specialized sibling tools like pg_io_stats or 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 states clear use cases: 'connection sanity check' and detecting runaway queries, connection-cap pressure, and lock/IO waits. It gives strong context for when to invoke the tool but does not mention alternatives or exclusion criteria relative to sibling tools.
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?
Even though readOnlyHint/openWorldHint/idempotentHint/destructiveHint already cover safety, the description adds substantial beyond-annotation behavior: no SQL parser, EXPLAIN-only execution inside BEGIN READ ONLY, session-scoped HypoPG reset on success and failure, greedy bounded search with budget_exhausted, PG18 skip-scan handling, and planner-estimate caveats. Nothing contradicts the 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 long but every block earns its place, covering generation, pruning, caps, version-specific behavior, side effects, and cost interpretation. The core purpose and proof requirement are front-loaded, followed by the most decision-critical caveats.
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 and the existence of a rich output schema, the description covers all operational essentials: the honest no-parser limit, HypoPG requirement and cleanup, read-only safety, bounded search semantics, PG18 version reversal, and the correct interpretation of costs. No major decision-relevant context 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 input schema already describes all 8 parameters at 100% coverage, setting a baseline of 3. The description adds meaningful interplay context: statements vs limit choice, max_candidates/max_explains as stopping caps that produce budget_exhausted, and call-count weighting, so it moves above 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?
The opening line names a specific verb and resource — 'Recommend indexes for a workload' — and adds a concrete behavioral promise: each index must prove it pays for itself. This clearly distinguishes pg_index_advisor from siblings like pg_advisor, pg_explain, and 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 explains the two input modes (statements vs pg_stat_statements), states the HypoPG dependency, and tells the agent to validate with pg_explain and create with CONCURRENTLY. It does not explicitly say 'use this when...' or name when not to use it, but the context is clear enough to route a caller correctly.
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. Caveat on relation: for non-relation waits (transactionid/virtualxid, where the wait is on the blocker's xid rather than a table) relation is a best-effort hint -- an alphabetical guess among the blocker's held write-intent locks -- not authoritative. 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 read-only/idempotent annotations, the description discloses non-obvious behavior: one row per blocked/blocking pair, duplicate rows for multi-blocker sessions, and a caveat that `relation` is only a best-effort guess for transactionid/virtualxid waits. This is exactly the kind of behavioral nuance annotations do not 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 dense but every sentence earns its place: purpose, use case, row-shape semantics, and a critical caveat are all covered without redundancy. The most important information is front-loaded.
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?
With an output schema present and read-only/idempotent annotations, the description covers the remaining essentials: what columns are returned, how to interpret duplicate rows, and how to disambiguate the unreliable `relation` field. An agent has enough to invoke and interpret 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 only parameter, `limit`, is fully described in the input schema (max blocked/blocker pairs, default 50, range). Since schema description coverage is 100%, the description's lack of parameter-specific detail is acceptable and matches 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?
The description opens with a specific verb and resource: 'Show current lock contention.' It clearly enumerates outputs (blocked PID, blocking PID, lock types, relation, queries) and uniquely positions the tool among the sibling diagnostics, none of which target lock contention.
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 an explicit usage trigger: 'Use this first when a tool call hangs or the app feels stuck.' It does not name alternatives or when-not-to-use conditions, but the context is clear enough for an agent to know when to choose this tool.
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 readOnly/idempotent hints: it explains NULL vs 0 semantics, the meaning of 0 timing values with non-zero counts, cumulative counters and stats_reset, absent keys vs empty arrays on PG18, omitted zero rows, per-query sub-failures appending to _warnings, and permission gating. No contradiction 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?
The description is long, but nearly every clause carries a non-obvious behavioral detail that is essential for correct interpretation (e.g., absent vs empty keys, timing=0 caveat, zero-row omission). It is structured with bullets between focused sections and front-loads the overall purpose before the details.
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-complexity PostgreSQL observability tool with an output schema available, the description covers version requirements, permission-gated failures, counter semantics, reset interpretation, zero-row filtering, and in-flight I/O caveats. An agent has enough information to call it correctly and interpret the result.
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 both parameters are already documented with defaults and meaning. The description reinforces includeZeroRows and limit, but it adds little that is not already in the input schema; the baseline of 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 'I/O observability' and precisely defines the resource: cumulative per-backend-type I/O from pg_stat_io plus in-flight async I/O from pg_aios. It also differentiates itself from siblings by calling itself 'the layer underneath pg_top_queries and pg_health' and noting that per-query or per-table views cannot show the subsystem/path involved.
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 tells the agent when this tool is appropriate: when the question is which subsystem (client backends vs autovacuum vs checkpointer) and which I/O path is responsible, as opposed to per-query or per-table views. It also states required PostgreSQL versions and the includeZeroRows behavior for quiet systems, though it does not give explicit 'when not to use' alternatives beyond the sibling references.
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 well covered. The description adds the return column names (name, version, schema, description), which is useful, but it does not add meaningful behavioral context beyond what the annotations and output schema already imply.
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 compact and front-loaded with the core action, followed by relevant return fields and a concrete use case. Each sentence earns its place, and the concrete extension examples add practical value without bloat.
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, parameterless, read-only introspection tool with rich annotations and an output schema, the description is complete. It explains what the tool lists, what it returns, and why an agent would use it, leaving no obvious gap 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 tool takes zero parameters and the schema has no properties, so there is no parameter semantics risk for the agent. Per the rubric, a zero-parameter tool receives a baseline of 4; no additional parameter documentation is needed.
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 clear verb and resource: 'List installed PostgreSQL extensions.' It also mentions the returned fields, making the tool's purpose unmistakable. However, it does not explicitly contrast itself against the many sibling list_* tools, so sibling differentiation relies on the object type rather than direct comparison.
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: checking for extensions like pgvector, postgis, pg_stat_statements, and uuid-ossp before writing dependent queries. This provides clear context for when to invoke the tool, though it does not state when not to use it or mention any alternative tool.
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 establish that the tool is read-only, idempotent, open-world, and non-destructive. The description adds useful behavioral detail by specifying exactly what is returned: name, arguments, return type, kind, and implementation language. It does not mention ordering or error behavior, but that is not essential for a simple listing tool with an output schema.
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 a single sentence that front-loads the action and resource before enumerating return fields. There is no filler, redundant restatement, or unnecessary detail. Every part of the 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 simple listing tool, the combination of a complete parameter schema, a full output schema, and safety-relevant annotations means nothing critical is missing. The description adds the remaining contextual detail by naming the output fields. An agent can invoke this tool correctly without further information.
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 fully documents the single optional 'schema' parameter, including its default and meaning. The description adds little beyond reaffirming the schema scope, so it does not substantially improve on what the schema already provides. This matches the baseline for high schema description coverage.
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'), identifies a concrete resource ('functions, procedures, and aggregates'), and scopes that resource to a schema. This clearly distinguishes it from sibling list tools such as pg_list_tables and pg_list_views. An agent can immediately understand what this tool operates on.
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?
There are no explicit 'when to use' or 'when not to use' instructions, and no alternative tools are named. However, the description makes the usage context clear: it is for listing function-like objects within a schema, which is distinct from the other list tools. The context is clear enough even without explicit exclusions.
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?
The description adds valuable behavioral detail beyond the annotations: it specifies the returned fields, explains the meaning of null and 0 row counts across PostgreSQL versions, and notes the reltuples source. This is rich context that helps an agent interpret results correctly.
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 compact and front-loaded: purpose first, return semantics second, pagination last. Every sentence adds distinct value, with no fluff or repetition of schema fields.
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 listing tool with rich annotations and an output schema, this description is complete. It covers the tool's scope, return fields, important version-dependent caveats, and pagination. No critical information needed 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 coverage is 100%, so parameters are already well documented. The description adds marginal semantic value by tying 'optionally views' to includeViews and mentioning pagination, but it does not substantially expand on the parameter meanings provided 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?
The description clearly states the tool 'List tables (and optionally views) in a schema' with a specific verb and resource. It does not explicitly differentiate this from sibling tools like pg_list_views or pg_list_schemas, but the name and description make the core 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?
There is no guidance on when to choose this tool over siblings such as pg_list_views or pg_describe_table. The only usage-related hint is pagination for large schemas, which addresses how to page results, not when to select this tool versus alternatives.
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 signal destructive capability, and the description adds meaningful behavioral context: Postgres role privileges are the primary safety gate, ALLOW_WRITES only lifts the read-only wrapper, and writes are server-rejected without the role's permission. It also discloses result truncation behavior and the truncated flag, going beyond the structured metadata.
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 the core purpose, then efficiently adds safety, alternatives, parameter usage, and truncation behavior. Each sentence provides actionable details without redundancy or 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?
Given the output schema exists and the tool is a general SQL executor, the description covers what an agent needs: what the tool does, safety constraints, parameterized usage, supported types, and result limits. No critical invocation detail 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 schema already covers both parameters fully, and the description adds substantial extra meaning: supported parameter types (strings, numbers, booleans, null, arrays, objects), ISO-format handling for dates/UUIDs, and positional $1/$2 binding. It also advises using params to avoid SQL injection.
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: 'Run a SQL query against the configured PostgreSQL database.' It also distinguishes this tool from the read-only sibling 'pg_readonly', so an agent can tell them apart without inspecting 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 explicitly says to prefer pg_readonly for read-only access, explains the role-based safety posture, and describes when this tool is useful (managed databases where creating a second role is awkward). It gives clear guidance on using parameters for safety.
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?
Goes well beyond the readOnlyHint annotation by explaining the BEGIN READ ONLY transaction, unconditional rollback, and the caveat that READ ONLY does not block every side effect. It also discloses privilege constraints, parameterized-query advice, and result truncation behavior, giving the agent a thorough behavioral model.
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 sentence carries essential operational information: purpose, transaction semantics, side-effect caveat, privilege model, injection safety, and truncation. It is front-loaded with the primary purpose and then layers in the caveats a caller must know.
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 flexible SQL execution tool with an output schema and rich annotations, the description covers all major calling concerns: what can run, transaction guarantees, exceptions to read-only enforcement, parameter encoding, and row limits. Nothing essential for selecting or invoking 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%, so the baseline is 3, but the description adds real value beyond the schema by explaining how params map to Postgres types: strings, numbers, booleans, null, arrays for ANY, and objects for json/jsonb. This is genuinely useful for constructing valid calls.
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 clear, specific verb and resource: 'Run a SQL statement with no persistent data changes.' It names the exact statement types accepted (SELECT, EXPLAIN, SHOW, VALUES, WITH ... SELECT), which distinguishes it from sibling read tools like pg_explain or pg_list_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?
Explicitly says 'Use this whenever the goal is to read' and lists the applicable SQL forms. It also warns about side-effect functions like pg_cancel_backend that are not blocked, and contrasts with pg_kill, giving the agent concrete when-to-use and when-not-to-use guidance.
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 readOnlyHint, idempotentHint, and destructiveHint=false. The description adds valuable behavior beyond these hints: it sources data from pg_stat_replication, distinguishes primary vs replica behavior, and promises empty arrays on standalone databases instead of errors. This is useful context 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?
Three sentences, each earning its place: first defines scope, second gives usage guidance, third covers the standalone edge case. Information is front-loaded and 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?
For a parameterless read-only status tool with annotations covering safety and an output schema available, the description is complete. It covers what data is returned, where to run it, and the edge-case behavior on standalone databases.
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 schema coverage is already complete at 100%. With no params to document, the baseline of 4 applies; the description need not add parameter-level detail.
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 replication overview covering replication slots, connected replicas, and WAL position. It distinguishes itself from sibling tools by being specifically about replication status rather than general database health or performance.
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 use it: on a primary to spot lagging/disconnected replicas, and on replicas to see upstream status. It does not name specific alternatives or exclusions, but the guidance is clear enough for this specialized status tool.
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 establish read-only/idempotent behavior, and the description goes well beyond them by explaining cumulative counters, the meaning of stats_reset and null, and the PostgreSQL 16+ last-scan timestamps. These caveats prevent a common misreading of the ratio.
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?
Longer than average, but every sentence carries a distinct piece of information: purpose, return shape, interpretation, reset caveat, and version-specific fields. The purpose is front-loaded and the structure progresses naturally from decision to caveats.
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 covers what the tool does, how to interpret its output, the critical stats-reset caveat, and version differences, while annotations cover safety. An agent has enough context to invoke it correctly and use the results.
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?
All three parameters already have descriptions in the schema, including defaults and bounds, so the description need not repeat them. It adds useful context that the ratio matters on large tables and uses live tuples, but no parameter-specific syntax beyond 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?
The opening sentence states a specific diagnostic goal — identifying tables with high sequential-scan counts relative to index scans — and names the tool as the first place to look for missing-index candidates. This is clearly differentiated from sibling diagnostics 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 explicit when-to-use guidance ('the first place to look') and explains the follow-up action: pair with pg_top_queries to find the responsible query. It does not explicitly say when to prefer pg_unused_indexes or pg_index_advisor, 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_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?
Beyond the readOnlyHint and idempotentHint annotations, the description discloses non-obvious behavior: stats_reset semantics on PostgreSQL 19+, the distinction between absent and null keys, the fact that reset clears several counters together, and the statement_timeout risk when calling method='exact' without a schema. This is rich, actionable behavioral context.
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 average, but each paragraph earns its place: purpose/outputs, a critical stats_reset caveat, and method selection with a concrete timeout warning. The structure is logical and front-loaded with the core action, though a slightly tighter version could avoid some repetition with schema method descriptions.
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 moderate complexity, output schema availability, and 100% parameter schema coverage, the description covers everything needed to invoke it correctly: returned columns, interpretation cues, method tradeoffs, extension installation, superuser requirement, and a specific call-safety warning for exact scans. No obvious operational gap remains.
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 adds substantial meaning beyond the schema for the method parameter, explaining tradeoffs, extension prerequisites, and a timeout warning tied to schema usage. It does not add comparable detail for limit or minDeadRatio, but those are already well described 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?
The description states a specific verb ('Estimate'), a specific resource ('table bloat ... for tables in a schema'), and enumerates the returned diagnostic fields. This clearly distinguishes it from sibling tools like pg_unused_indexes or pg_health while matching the tool name and title.
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 when-to-use guidance: 'estimate' is marked as the default and 'Use this first', while 'approx' and 'exact' are contrasted by accuracy, speed, extension requirements, and timeout risk. It also provides a practical interpretation rule for high dead_ratio alongside stale last_autovacuum.
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?
The description goes well beyond the readOnly/idempotent annotations by disclosing cumulative counter semantics, the meaning of stats_reset: null, dealloc-based eviction risks, version-dependent behavior, and database scoping. It explains the exact conditions under which the ranking can be misleading and what remediation looks like. This is exceptionally transparent and contradicts no 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 purpose and primary use case are front-loaded in the first two sentences, and the remaining paragraphs each address a distinct operational risk: reset clocks, dealloc churn, version differences, IO timing fields, and cross-database leakage. It is long, but every sentence earns its place because it prevents a real misinterpretation of the results.
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 covers all the contextual knowledge an agent needs: extension prerequisite, exact return shape, reset-window semantics, dealloc caveats with a remedy, version-specific field availability, and scoping behavior. Combined with the rich input and output schemas, there are no meaningful gaps left for the agent to infer.
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?
Both parameters already have full schema descriptions and the enum is well documented, so the baseline is 3. However, the description adds interpretive depth beyond the schema: the ranking values are measured over a reset-defined window, and a nonzero dealloc can invalidate conclusions drawn from any ordering. That context materially helps an agent choose and interpret orderBy.
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 operation: returning top N queries by total or mean execution time, and names the underlying resource (pg_stat_statements). It also states the intended use ('find slow queries worth optimizing'), which distinguishes it from sibling tools like pg_explain or pg_index_advisor. Even without explicit sibling names, an agent can tell what this tool is for.
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 clear prerequisite (pg_stat_statements must be installed/enabled) and states its intended use case. It also warns against conflating its stats_reset timestamp with those from pg_seq_scan_tables/pg_unused_indexes. It stops short of explicitly saying 'when not to use this and use alternative X instead', so it earns a 4 rather than a 5.
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 operation as read-only and non-destructive, but the description adds substantial behavioral context: scan counters only reflect the window since stats_reset, PK/UNIQUE indexes are excluded, PostgreSQL 16+ includes last_idx_scan, and PostgreSQL 18+ changes skip-scan reasoning. This goes far beyond what annotations provide.
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?
Front-loaded with a clear definition and return shape, then each paragraph covers a distinct and necessary caveat. Though long, every sentence earns its place given the dangerous 'recommend a drop' use case, and there is no repetition of annotation data.
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 complete for safe invocation and interpretation: it explains the output envelope, reset-window caveats, excluded constraint indexes, version-specific fields, and Postgres 18 behavior. The output schema exists, so the description need not exhaustively specify every return field.
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 fully, so the baseline is 3. The description enriches maxScans by explaining that the scan counter is only meaningful relative to stats_reset, which is critical for interpreting that parameter. It does not add much for limit or schema, but their schema descriptions are already clear.
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?
First line defines the output precisely: indexes never scanned or with very low usage, largest first, with the title supplying the 'find' verb. It is clearly distinct from the sibling pg_index_advisor, which is oriented toward recommending indexes to add rather than identifying dead ones.
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 a concrete trigger: before adding a new index, check whether dropping a dead one is the real fix. It also warns when the data is not trustworthy after a stats reset, but it does not explicitly compare against sibling tools or state when not to use this tool.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
TDQS
Each tool has a clearly distinct purpose. pg_readonly and pg_query are differentiated by write ability; other tools target specific schema inspection, performance analysis, or admin functions with no overlap.
Most tools follow a 'pg_verb_noun' pattern (e.g., pg_list_tables, pg_describe_table). A few deviate (pg_readonly, pg_query, pg_health) but the naming remains clear and predictable.
21 tools is well-scoped for a PostgreSQL database server, covering querying, schema exploration, performance diagnostics, and administration without being overwhelming.
The tool surface is comprehensive, covering read/write queries, schema inspection, performance tuning, and health checks. Minor omissions like explicit VACUUM or index creation tools are offset by the advisor and general query tool.
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.
Safe, read-only Postgres and MySQL access for AI agents. Audit log + column-level controls.
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