Skip to main content

pgtriage

CI PyPI Python License

pgtriage: PostgreSQL performance auditing over MCP

Read-only MCP server for PostgreSQL performance auditing. Connect it to Claude Code (or any MCP client) and say "audit my database" to get structured findings with evidence and suggested fixes.

Not related to the pgAudit logging extension. pgtriage does performance triage, not compliance logging.

Why I built this

Built after diagnosing implicit type casts and missing indexes on multi-million-row tables in production fintech systems. The fixes were simple (one CREATE INDEX CONCURRENTLY statement each), but finding them required reading query plans most engineers never look at. A real production audit surfaced 88 findings across 118 tables. pgtriage automates that diagnostic process and lets any AI client explain the results.

How it works

MCP clients call pgtriage, which collects read-only PostgreSQL evidence and returns structured findings

pgtriage connects to your PostgreSQL database and exposes performance auditing tools via the Model Context Protocol. It collects metrics from PostgreSQL system views, runs deterministic pattern detection, and returns structured findings. The MCP client provides the AI layer, interpreting results and explaining fixes in plain English.

No API keys required. No AI costs. No vendor lock-in. The intelligence comes from your MCP client.

Example output

Example audit: 283 tables scanned, 147 findings — 1 critical connection-pressure finding, 19 medium duplicate-index findings, 64 low unused-index findings, and 63 more across seven categories.

{
  "severity": "critical",
  "category": "connection_pressure",
  "detail": "Ordinary client connection utilization at 99% (96/97 usable slots; 100 max, 3 reserved). Ordinary connection capacity is effectively exhausted.",
  "suggested_fix": "Consider using a connection pooler (PgBouncer) or increasing max_connections if RAM allows.",
  "evidence": {
    "total_connections": 96,
    "max_connections": 100,
    "reserved_connections": 3,
    "ordinary_connection_capacity": 97,
    "ordinary_slots_available": 1,
    "utilization_pct": 99.0
  }
}
{
  "severity": "medium",
  "category": "duplicate_index",
  "table": "orders",
  "detail": "Structurally equivalent indexes on 'public.orders': 'public.idx_orders_customer' (2 GB, 18,420 scans), 'public.idx_orders_customer_copy' (2.1 GB, 12 scans). 'public.idx_orders_customer' has the uniquely highest recorded scan count; the remaining eligible indexes are drop candidates subject to workload review.",
  "suggested_fix": "Review every compared definition, current and periodic workload, and replica usage before considering:\nDROP INDEX CONCURRENTLY \"public\".\"idx_orders_customer_copy\";\nDROP INDEX CONCURRENTLY cannot run inside a transaction block.",
  "safe_to_apply": false,
  "evidence": {
    "structurally_equivalent": true,
    "statistics_reset_at": "2026-10-01T00:00:00+00:00",
    "minimum_statistics_age_days": 7,
    "keep_index": {
      "qualified_name": "public.idx_orders_customer",
      "scans": 18420,
      "size": "2 GB",
      "definition": "CREATE INDEX idx_orders_customer ON public.orders USING btree (customer_id)"
    },
    "drop_candidates": [
      {
        "qualified_name": "public.idx_orders_customer_copy",
        "scans": 12,
        "size": "2.1 GB",
        "definition": "CREATE INDEX idx_orders_customer_copy ON public.orders USING btree (customer_id)",
        "sql": "DROP INDEX CONCURRENTLY \"public\".\"idx_orders_customer_copy\";"
      }
    ]
  }
}

What it finds

  • Sequential scans on large tables with missing index suggestions
  • Type casts on indexed columns that suppress index usage, verified against PostgreSQL catalog metadata and the observed query plan
  • Dead tuple buildup and autovacuum health issues
  • Unused and duplicate indexes wasting disk and slowing writes
  • N+1 query patterns from pg_stat_statements analysis
  • Stale table statistics causing bad query plans
  • TOAST table bloat from large JSONB/TEXT columns
  • Configuration issues (shared_buffers, work_mem, autovacuum tuning)
  • Connection pressure approaching max_connections
  • Long-running queries holding locks

See the public roadmap for current priorities and non-goals.

Quick start

Install

pip install pgtriage

Configure Claude Code

Add to your MCP settings (.claude/settings.json or project settings):

{
  "mcpServers": {
    "pgtriage": {
      "command": "python",
      "args": ["-m", "pgtriage"],
      "env": {
        "PGTRIAGE_CONNECTION_STRING": "postgres://user:pass@localhost:5432/dbname"
      }
    }
  }
}

Recommended: Use a dedicated read-only database role:

CREATE ROLE pgtriage_reader LOGIN PASSWORD 'secure_password';
GRANT pg_read_all_stats TO pgtriage_reader;
GRANT USAGE ON SCHEMA public TO pgtriage_reader;
GRANT SELECT ON ALL TABLES IN SCHEMA public TO pgtriage_reader;

Use

> audit my database

> check table health for the users table

> are there any unused indexes?

> review my PostgreSQL configuration

> find slow queries

> audit only the accounts schema

Demo

pgtriage auditing PostgreSQL through an MCP client

Tools

full_audit

Run a comprehensive performance audit covering table health, slow queries, index health, and configuration. Pass the optional schema_name argument to scope table, plan, and index findings to one user schema; omit it to audit every non-system schema. Configuration findings remain database-wide. Returns all findings sorted by severity.

check_table_health

Analyze dead tuples, autovacuum stats, sequential scan ratios, and TOAST bloat. Optionally filter to a specific user schema and/or table.

analyze_slow_queries

Pull the slowest queries from pg_stat_statements, inspect their execution plans, and detect patterns like indexed-column type casts, sequential scans, stale statistics, and N+1 queries. An optional schema_name filter uses verbose execution-plan metadata rather than parsing SQL text. Literal queries use bounded EXPLAIN ANALYZE; normalized queries containing placeholders use a non-executing generic plan.

check_index_health

Find unused indexes, structurally equivalent duplicate-index groups, and tables that likely need indexes based on scan patterns. Duplicate findings preserve the compared definitions, sizes, and scan counts. pgtriage names a review candidate only when statistics are mature, one keeper has the uniquely highest scan count, and the candidate is not protected by a constraint or operational role. Optionally scope the analysis to one user schema.

check_config

Review PostgreSQL settings (shared_buffers, work_mem, autovacuum_vacuum_scale_factor, random_page_cost, etc.) and flag suboptimal values. Checks connection utilization and long-running queries.

Agent runtime integration

pgtriage Agent Lab is a runnable TypeScript reference client that demonstrates authorization, bounded retries, durable workflow state, and fail-closed review around this MCP boundary.

pgtriage publishes MCP safety annotations for every tool. All tools are marked read-only and non-destructive; tools that can run bounded EXPLAIN ANALYZE are deliberately not marked idempotent. Agent runtimes must still allowlist, authorize, and validate every call because MCP annotations are descriptive hints, not permissions. See Agent Runtime Integration for the complete contract, schema-scoped audit behavior, and retry guidance.

Resources

Resource Description
pgtriage://status Connection status, PostgreSQL version, loaded extensions
pgtriage://tables All tables with sizes and approximate row counts

Requirements

  • Python 3.11+
  • PostgreSQL 12+
  • pg_stat_statements extension (recommended for slow query analysis, not required for other tools)
  • Database user with read access to pg_stat_* views

Safety

pgtriage is designed for read-only production use and does not issue write SQL. Three independent layers protect database state:

  1. Session-level read-only: SET default_transaction_read_only = true on every connection. PostgreSQL rejects any write attempt at the server level.
  2. Query validation: EXPLAIN ANALYZE only runs on SELECT statements. INSERT, UPDATE, DELETE, DROP, SELECT INTO, SELECT FOR UPDATE, and stacked queries are all rejected before execution.
  3. Transaction rollback: Every EXPLAIN ANALYZE runs inside an explicit BEGIN/ROLLBACK block with a 10-second statement_timeout. Transactional database changes are rolled back and long-running queries are canceled. Rollback cannot undo external side effects triggered by database extensions or functions, so the dedicated read-only role and session-level read-only enforcement remain essential.

Additionally:

  • Connection strings are never exposed in tool outputs
  • Suggested fixes are advisory and are never executed by pgtriage; findings default to safe_to_apply: false
  • All database access is single-connection, no pooling

Development

git clone https://github.com/pgtriage/pgtriage.git
cd pgtriage
python3 -m venv .venv
source .venv/bin/activate
pip install -e ".[dev]"
pytest

License

MIT

Metadata

Release files for pgtriage 0.2.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for pgtriage 0.2.1
File Size Uploaded Provenance
pgtriage-0.2.1.tar.gz 43.7 kB With provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for pgtriage 0.2.1
File Interpreter ABI Platform
pgtriage-0.2.1-py3-none-any.whl Python 3 none any Details

Total release size: 77.1 kB

Release files / pgtriage-0.2.1.tar.gz

Download URL pgtriage-0.2.1.tar.gz
Size 43.7 kB
Tags Source
SHA-256 checksum
How to use checksums
cc1f48e4352cf56233eb4377feed6eb0e4fe057d17cafcadb84224de0b1a1566
BLAKE2b-256 checksum
How to use checksums
c91680464a7ca9523b7e852309aecdd869412cd8cd9d229cf3dfd4397b82f2e4
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 23, 2026.

Transparency log

Release files / pgtriage-0.2.1-py3-none-any.whl

Download URL pgtriage-0.2.1-py3-none-any.whl
Size 33.4 kB
Tags Python 3
SHA-256 checksum
How to use checksums
945b592726af3f58c70555adcf231f7024dec3e109312bd48f58770eb33a18f7
BLAKE2b-256 checksum
How to use checksums
098433cf98ed2fc24e0d85d7ce499347d0a103fe47a367386caded7581ebd668
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 23, 2026.

Transparency log

Security for pgtriage 0.2.1

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

Full provenance

All 2 files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for pgtriage-0.2.1.tar.gz

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 23, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page