Skip to main content
Glama
YawLabs

@yawlabs/postgres-mcp

by YawLabs

Database advisor (DBA lints)

pg_advisor
Read-onlyIdempotent

Run a DBA lint check across PostgreSQL to detect sequence exhaustion, transaction wraparound risk, missing primary keys, and disabled row-level security.

Instructions

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

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

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

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

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

Input Schema

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

Output Schema

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

Schema Changelog

Changes observed during successful MCP inspections. Dates show when Glama detected each change.

  1. Changed18 schema fields changedv0.12.1
    • removedOutput schema / properties / wraparound_risk / properties / autovacuum_freeze_max_age / anyOf
      Removed value: -[
      -  {
      -    "type": "number"
      -  },
      -  {
      -    "type": "null"
      -  }
      -]
    • addedOutput schema / properties / wraparound_risk / properties / autovacuum_freeze_max_age / type
      Added value: +[
      +  "number",
      +  "null"
      +]
    • removedOutput schema / properties / wraparound_risk / properties / autovacuum_multixact_freeze_max_age / anyOf
      Removed value: -[
      -  {
      -    "type": "number"
      -  },
      -  {
      -    "type": "null"
      -  }
      -]
    • addedOutput schema / properties / wraparound_risk / properties / autovacuum_multixact_freeze_max_age / type
      Added value: +[
      +  "number",
      +  "null"
      +]
    • removedOutput schema / properties / wraparound_risk / properties / databases / items / properties / mxid_age / anyOf
      Removed value: -[
      -  {
      -    "type": "number"
      -  },
      -  {
      -    "type": "null"
      -  }
      -]
    • addedOutput schema / properties / wraparound_risk / properties / databases / items / properties / mxid_age / type
      Added value: +[
      +  "number",
      +  "null"
      +]
    • removedOutput schema / properties / wraparound_risk / properties / databases / items / properties / pct_of_freeze_max_age / anyOf
      Removed value: -[
      -  {
      -    "type": "number"
      -  },
      -  {
      -    "type": "null"
      -  }
      -]
    • addedOutput schema / properties / wraparound_risk / properties / databases / items / properties / pct_of_freeze_max_age / type
      Added value: +[
      +  "number",
      +  "null"
      +]
    • removedOutput schema / properties / wraparound_risk / properties / databases / items / properties / pct_of_multixact_freeze_max_age / anyOf
      Removed value: -[
      -  {
      -    "type": "number"
      -  },
      -  {
      -    "type": "null"
      -  }
      -]
    • addedOutput schema / properties / wraparound_risk / properties / databases / items / properties / pct_of_multixact_freeze_max_age / type
      Added value: +[
      +  "number",
      +  "null"
      +]
    • removedOutput schema / properties / wraparound_risk / properties / tables / items / properties / frozen_page_fraction / anyOf
      Removed value: -[
      -  {
      -    "type": "number"
      -  },
      -  {
      -    "type": "null"
      -  }
      -]
    • addedOutput schema / properties / wraparound_risk / properties / tables / items / properties / frozen_page_fraction / type
      Added value: +[
      +  "number",
      +  "null"
      +]
    • removedOutput schema / properties / wraparound_risk / properties / tables / items / properties / mxid_age / anyOf
      Removed value: -[
      -  {
      -    "type": "number"
      -  },
      -  {
      -    "type": "null"
      -  }
      -]
    • addedOutput schema / properties / wraparound_risk / properties / tables / items / properties / mxid_age / type
      Added value: +[
      +  "number",
      +  "null"
      +]
    • removedOutput schema / properties / wraparound_risk / properties / tables / items / properties / pct_of_freeze_max_age / anyOf
      Removed value: -[
      -  {
      -    "type": "number"
      -  },
      -  {
      -    "type": "null"
      -  }
      -]
    • addedOutput schema / properties / wraparound_risk / properties / tables / items / properties / pct_of_freeze_max_age / type
      Added value: +[
      +  "number",
      +  "null"
      +]
    • removedOutput schema / properties / wraparound_risk / properties / tables / items / properties / pct_of_multixact_freeze_max_age / anyOf
      Removed value: -[
      -  {
      -    "type": "number"
      -  },
      -  {
      -    "type": "null"
      -  }
      -]
    • addedOutput schema / properties / wraparound_risk / properties / tables / items / properties / pct_of_multixact_freeze_max_age / type
      Added value: +[
      +  "number",
      +  "null"
      +]
  2. Changed3 schema fields changedv0.12.0
    • changedInput schema / $schema
      Previous value: -"http://json-schema.org/draft-07/schema#"New value: +"https://json-schema.org/draft/2020-12/schema"
    • addedInput schema / properties / wraparoundThreshold
      Added value: +{
      +  "default": 0.5,
      +  "description": "Minimum used-fraction to flag a database or table for wraparound risk (default 0.5 = 50%). Applied to BOTH ratios -- age(frozenxid) / autovacuum_freeze_max_age and mxid_age(minmxid) / autovacuum_multixact_freeze_max_age -- and a row is flagged if either one clears it. 1.0 is where autovacuum starts forcing anti-wraparound VACUUMs.",
      +  "maximum": 1,
      +  "minimum": 0,
      +  "type": "number"
      +}
    • changedOutput schema / (root)
      Previous value: -nullNew value: +{
      +  "$schema": "https://json-schema.org/draft/2020-12/schema",
      +  "additionalProperties": false,
      +  "properties": {
      +    "_warnings": {
      +      "items": {
      +        "type": "string"
      +      },
      +      "type": "array"
      +    },
      +    "public_tables_without_rls": {
      +      "items": {
      +        "additionalProperties": false,
      +        "properties": {
      +          "schema": {
      +            "type": "string"
      +          },
      +          "table": {
      +            "type": "string"
      +          }
      +        },
      +        "required": [
      +          "schema",
      +          "table"
      +        ],
      +        "type": "object"
      +      },
      +      "type": "array"
      +    },
      +    "sequence_exhaustion": {
      +      "items": {
      +        "additionalProperties": false,
      +        "properties": {
      +          "last_value": {
      +            "description": "Bigint as a decimal string.",
      +            "type": "string"
      +          },
      +          "max_value": {
      +            "description": "Bigint as a decimal string.",
      +            "type": "string"
      +          },
      +          "pct_used": {
      +            "description": "last_value / max_value, rounded for display. The FILTER runs at full precision, so a displayed 0.5000 can sit just above the threshold.",
      +            "type": "number"
      +          },
      +          "schema": {
      +            "type": "string"
      +          },
      +          "sequence": {
      +            "type": "string"
      +          }
      +        },
      +        "required": [
      +          "schema",
      +          "sequence",
      +          "last_value",
      +          "max_value",
      +          "pct_used"
      +        ],
      +        "type": "object"
      +      },
      +      "type": "array"
      +    },
      +    "tables_without_primary_key": {
      +      "description": "Plain and partitioned tables only; foreign tables cannot have a PK and are excluded.",
      +      "items": {
      +        "additionalProperties": false,
      +        "properties": {
      +          "schema": {
      +            "type": "string"
      +          },
      +          "table": {
      +            "type": "string"
      +          }
      +        },
      +        "required": [
      +          "schema",
      +          "table"
      +        ],
      +        "type": "object"
      +      },
      +      "type": "array"
      +    },
      +    "wraparound_risk": {
      +      "additionalProperties": false,
      +      "properties": {
      +        "autovacuum_freeze_max_age": {
      +          "anyOf": [
      +            {
      +              "type": "number"
      +            },
      +            {
      +              "type": "null"
      +            }
      +          ],
      +          "description": "Cluster GUC; the divisor for the xid ratios."
      +        },
      +        "autovacuum_multixact_freeze_max_age": {
      +          "anyOf": [
      +            {
      +              "type": "number"
      +            },
      +            {
      +              "type": "null"
      +            }
      +          ],
      +          "description": "Cluster GUC; the divisor for the multixact ratios."
      +        },
      +        "databases": {
      +          "items": {
      +            "additionalProperties": false,
      +            "properties": {
      +              "database": {
      +                "description": "Template databases included -- template0 ages like any other.",
      +                "type": "string"
      +              },
      +              "mxid_age": {
      +                "anyOf": [
      +                  {
      +                    "type": "number"
      +                  },
      +                  {
      +                    "type": "null"
      +                  }
      +                ],
      +                "description": "mxid_age(datminmxid). Null when no multixact was ever recorded."
      +              },
      +              "pct_of_freeze_max_age": {
      +                "anyOf": [
      +                  {
      +                    "type": "number"
      +                  },
      +                  {
      +                    "type": "null"
      +                  }
      +                ]
      +              },
      +              "pct_of_multixact_freeze_max_age": {
      +                "anyOf": [
      +                  {
      +                    "type": "number"
      +                  },
      +                  {
      +                    "type": "null"
      +                  }
      +                ]
      +              },
      +              "triggered_by": {
      +                "description": "'xid' -> chase freezing/autovacuum; 'multixact' -> chase the lock-heavy workload burning members.",
      +                "enum": [
      +                  "xid",
      +                  "multixact",
      +                  "both"
      +                ],
      +                "type": "string"
      +              },
      +              "xid_age": {
      +                "description": "age(datfrozenxid).",
      +                "type": "number"
      +              }
      +            },
      +            "required": [
      +              "database",
      +              "xid_age",
      +              "mxid_age",
      +              "pct_of_freeze_max_age",
      +              "pct_of_multixact_freeze_max_age",
      +              "triggered_by"
      +            ],
      +            "type": "object"
      +          },
      +          "type": "array"
      +        },
      +        "tables": {
      +          "description": "Deliberately includes pg_catalog and pg_toast -- the culprit is usually one of those.",
      +          "items": {
      +            "additionalProperties": false,
      +            "properties": {
      +              "all_frozen_pages": {
      +                "description": "PostgreSQL 18+ only, absent below that. relallfrozen.",
      +                "type": "number"
      +              },
      +              "freeze_max_age": {
      +                "description": "EFFECTIVE limit: a per-table storage parameter wins over the cluster GUC.",
      +                "type": "number"
      +              },
      +              "frozen_page_fraction": {
      +                "anyOf": [
      +                  {
      +                    "type": "number"
      +                  },
      +                  {
      +                    "type": "null"
      +                  }
      +                ],
      +                "description": "PostgreSQL 18+ only, absent below that. Null when relpages is 0, not when coverage is 0."
      +              },
      +              "multixact_freeze_max_age": {
      +                "description": "EFFECTIVE limit, resolved the same way as freeze_max_age.",
      +                "type": "number"
      +              },
      +              "mxid_age": {
      +                "anyOf": [
      +                  {
      +                    "type": "number"
      +                  },
      +                  {
      +                    "type": "null"
      +                  }
      +                ],
      +                "description": "mxid_age(relminmxid). Null when no multixact was ever recorded."
      +              },
      +              "pages": {
      +                "description": "PostgreSQL 18+ only, absent below that. relpages.",
      +                "type": "number"
      +              },
      +              "pct_of_freeze_max_age": {
      +                "anyOf": [
      +                  {
      +                    "type": "number"
      +                  },
      +                  {
      +                    "type": "null"
      +                  }
      +                ],
      +                "description": "At 1.0 autovacuum forces an anti-wraparound VACUUM."
      +              },
      +              "pct_of_multixact_freeze_max_age": {
      +                "anyOf": [
      +                  {
      +                    "type": "number"
      +                  },
      +                  {
      +                    "type": "null"
      +                  }
      +                ]
      +              },
      +              "relkind": {
      +                "description": "Raw relkind: 'r' heap, 'm' materialized view, 't' TOAST -- the only three checked.",
      +                "type": "string"
      +              },
      +              "schema": {
      +                "type": "string"
      +              },
      +              "table": {
      +                "type": "string"
      +              },
      +              "triggered_by": {
      +                "description": "'xid' -> chase freezing/autovacuum; 'multixact' -> chase the lock-heavy workload burning members.",
      +                "enum": [
      +                  "xid",
      +                  "multixact",
      +                  "both"
      +                ],
      +                "type": "string"
      +              },
      +              "xid_age": {
      +                "description": "age(relfrozenxid).",
      +                "type": "number"
      +              }
      +            },
      +            "required": [
      +              "schema",
      +              "table",
      +              "relkind",
      +              "xid_age",
      +              "freeze_max_age",
      +              "pct_of_freeze_max_age",
      +              "mxid_age",
      +              "multixact_freeze_max_age",
      +              "pct_of_multixact_freeze_max_age",
      +              "triggered_by"
      +            ],
      +            "type": "object"
      +          },
      +          "type": "array"
      +        }
      +      },
      +      "required": [
      +        "autovacuum_freeze_max_age",
      +        "autovacuum_multixact_freeze_max_age",
      +        "databases",
      +        "tables"
      +      ],
      +      "type": "object"
      +    }
      +  },
      +  "required": [
      +    "sequence_exhaustion",
      +    "wraparound_risk",
      +    "tables_without_primary_key",
      +    "public_tables_without_rls"
      +  ],
      +  "type": "object"
      +}
  3. First observedv0.7.0

TDQS

A4.4/5.0
Behavior5/5

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

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

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

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

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

Completeness5/5

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

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

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

Parameters3/5

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

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

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

Purpose5/5

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

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

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

Usage Guidelines4/5

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

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

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

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/YawLabs/postgres-mcp'

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