pgtriage
Officialpgtriage is an MCP server that performs read-only PostgreSQL performance auditing and returns actionable findings for AI clients like Claude Code.
Full database audit: Run
full_auditto get a comprehensive, severity-sorted report covering table health, slow queries, index health, and configuration issues.Table health checks: Inspect dead tuples, autovacuum status, sequential scan ratios, and TOAST bloat for specific tables.
Slow query analysis: Pull top queries from
pg_stat_statements, run safeEXPLAIN ANALYZE, and detect patterns like sequential scans, type casts on indexed columns, stale statistics, and N+1 queries.Index health review: Find unused indexes, duplicate indexes, and missing-index opportunities based on scan patterns.
Configuration review: Flag suboptimal PostgreSQL settings (
shared_buffers,work_mem,max_connections, autovacuum) and check connection pressure.Safety guarantees: All operations are read-only, with session-level read-only enforcement, query validation, and transaction rollback for any
EXPLAIN ANALYZE.
Allows auditing PostgreSQL database performance, providing tools for analyzing table health, slow queries, index usage, and configuration.
pgtriage

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.
Related MCP server: postgres-mcp-server
How it works

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": "Duplicate indexes on 'orders': 'idx_orders_customer' (16 kB) and 'idx_orders_customer_copy' (16 kB). Same column definition. One can be dropped.",
"suggested_fix": "-- Keep the one with more scans, drop the other:\n-- CREATE INDEX idx_orders_customer ON orders (customer_id)\n-- CREATE INDEX idx_orders_customer_copy ON orders (customer_id)",
"safe_to_apply": false,
"evidence": {
"index_1_def": "CREATE INDEX idx_orders_customer ON orders (customer_id)",
"index_2_def": "CREATE INDEX idx_orders_customer_copy ON orders (customer_id)"
}
}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 pgtriageConfigure 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 schemaDemo

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 (zero scans), duplicate indexes (same column definition), and tables that likely need indexes based on scan patterns. 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 |
| Connection status, PostgreSQL version, loaded extensions |
| All tables with sizes and approximate row counts |
Requirements
Python 3.11+
PostgreSQL 12+
pg_stat_statementsextension (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:
Session-level read-only:
SET default_transaction_read_only = trueon every connection. PostgreSQL rejects any write attempt at the server level.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.
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: falseAll 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]"
pytestLicense
MIT
Available Tools
5 toolsanalyze_slow_queriesARead-only
Find and analyze slow queries from pg_stat_statements. Runs EXPLAIN ANALYZE on top slow SELECT queries and detects performance patterns like sequential scans and missing indexes.
| Name | Required | Description | Default |
|---|---|---|---|
| limit | No | ||
| min_calls | No | ||
| schema_name | No |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already establish readOnlyHint=true and destructiveHint=false, so the safety profile is covered. The description adds genuinely useful behavior: it discloses that EXPLAIN ANALYZE is actually executed against top SELECTs (implying real DB load, not just catalog reads) and that results come from pg_stat_statements. It stops short of noting cost/overhead or that it is non-idempotent despite idempotentHint=false.
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 compact sentences with zero filler, and the core action (find/analyze slow queries) plus mechanism are front-loaded. Every clause earns its place by naming the source table and the detection patterns.
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 no output schema, the description should clarify what the analysis returns, but it only gestures at 'performance patterns.' Combined with three fully undocumented parameters and no guidance against siblings, the definition is workable but leaves real gaps for an agent choosing and invoking it.
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 (limit, min_calls, schema_name) have 0% schema description coverage and are completely unexplained in the description. It is unclear whether 'limit' caps rows returned, queries analyzed, or EXPLAIN targets, and min_calls has no stated meaning, so the description fails to compensate for the coverage gap.
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 pair (find/analyze) plus the exact resource and data source ('slow queries from pg_stat_statements'), and names the detection targets (sequential scans, missing indexes). It is clearly distinct from check_config and full_audit, though the 'missing indexes' overlap with the sibling check_index_health is left unresolved.
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?
Usage is only implied: an agent can infer this is for diagnosing query performance, but the description never says when to prefer it over check_index_health or full_audit, nor any prerequisites or exclusions. No explicit when/when-not guidance is present.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
check_configARead-onlyIdempotent
Review PostgreSQL configuration settings and flag suboptimal values for shared_buffers, work_mem, max_connections, and autovacuum parameters.
| Name | Required | Description | Default |
|---|---|---|---|
No parameters | |||
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnlyHint=true, idempotentHint=true, and destructiveHint=false, so the safety profile is covered. The description adds that it compares values against some notion of 'suboptimal,' but does not say whether it only reports or also suggests fixes, nor how thresholds are determined. Adequate but not rich.
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?
A single front-loaded sentence naming the action and the inspected settings with zero filler. Every clause earns its place and the resource is stated before the detail.
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 no parameters and no output schema, the description carries the burden of describing results. Saying it 'flags suboptimal values' implies a findings-oriented return, which is likely sufficient for a trivial read-only check, though the exact shape of the flag output is left implicit.
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 there is nothing for the description to disambiguate; baseline 4 applies. The settings named in the description are internal targets of the check, not inputs.
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 (review/flag) and resource (PostgreSQL configuration settings), and enumerates the concrete settings examined (shared_buffers, work_mem, max_connections, autovacuum). It does not explicitly distinguish itself from the overlapping full_audit sibling, so it stops short of a 5.
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?
No guidance on when to use this versus alternatives. The sibling set includes full_audit, which plausibly subsumes config review, yet the description neither mentions it nor states any trigger condition or exclusion. Usage is only inferable from the tool name and purpose.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
check_index_healthBRead-onlyIdempotent
Find unused indexes, duplicate indexes, and missing index opportunities based on sequential scan patterns.
| Name | Required | Description | Default |
|---|---|---|---|
| schema_name | No |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnlyHint, idempotentHint, and destructiveHint=false, so the safety profile is covered. The description adds the analysis basis ('sequential scan patterns'), which is useful, but says nothing about scope (whole database vs. one schema), whether statistics collection is required, or the cost of the analysis.
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?
A single front-loaded sentence with no filler. It is efficient, though the terseness contributes to the unaddressed parameter and scope questions.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a read-only diagnostic with annotations covering safety, this is close to adequate, but it omits the scope of the scan and the meaning of the one parameter, and there is no output schema to fall back on for expected 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?
The single parameter schema_name has 0% schema description coverage and the description does not mention it at all, so there is no way to know whether it filters the analysis or defaults to all schemas. The description fails to compensate for the coverage gap.
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?
Names a specific verb ('Find') and three concrete resources (unused, duplicate, missing indexes), which is far more informative than the tool name alone. What's missing is any explicit differentiation from siblings like check_table_health or full_audit, which could plausibly overlap.
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 never states when to reach for this tool versus check_table_health, analyze_slow_queries, or full_audit, nor does it give prerequisites. Usage can only be inferred from the index-related subject matter.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
check_table_healthARead-onlyIdempotent
Check table health: dead tuples, bloat, vacuum stats, scan ratios. Optionally filter to a specific user schema and/or table by name.
| Name | Required | Description | Default |
|---|---|---|---|
| table_name | No | ||
| schema_name | No |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnlyHint=true, idempotentHint=true and destructiveHint=false, so the safety profile is covered. The description contributes the useful detail of which health dimensions are measured, but says nothing about thresholds, severity interpretation, or cost of running across a whole database.
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 tight sentences: the purpose and the metrics come first, and the optional filter behavior follows. Every clause carries information and nothing is repeated from the schema or annotations.
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 no output schema, the description does the work of naming the returned metrics, which is the key thing an agent needs. It is a read-only, zero-required-parameter tool, so little else is mandatory, though a note on how to interpret or when to prefer full_audit would close the remaining gap.
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 0%, so the schema supplies only titles and defaults. The description compensates partially by stating both parameters are optional filters and that one narrows to a user schema and the other to a table name, but it adds no format or matching details beyond that.
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 and resource ('Check table health') and enumerates the dimensions inspected (dead tuples, bloat, vacuum stats, scan ratios), so the agent knows exactly what this tool reports. It does not explicitly distinguish itself from siblings like check_index_health or full_audit, but the table scope is clear from the name and text.
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 second sentence explains that the schema/table arguments act as optional narrowing filters, which implies you can run it database-wide or focus on one table. However, there is no explicit when-to-use guidance versus check_index_health or full_audit, and no stated prerequisites, so usage must be inferred.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
full_auditARead-only
Run a comprehensive performance audit: table health, slow queries, index health, and configuration review. Returns a unified report with all findings sorted by severity.
Optionally scope table, slow-query, and index findings to one user schema. Configuration findings remain database-wide. Omitting schema_name audits all non-system schemas.
| Name | Required | Description | Default |
|---|---|---|---|
| schema_name | No | ||
| slow_query_limit | No |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already cover the safety profile (readOnlyHint=true, destructiveHint=false, idempotentHint=false), so the bar is lower. The description earns credit by disclosing what the return looks like (a unified report sorted by severity) and the non-obvious scoping asymmetry where configuration findings are never bounded by schema_name.
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, no filler, and the core capability is front-loaded before the scoping caveats. The return-value sentence could arguably be merged with the first, but nothing is wasted.
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 audit with no output schema, the description says enough about the report shape and scope semantics to call the tool correctly. The gap is slow_query_limit, which is neither described here nor in the schema, so an agent cannot tell whether 10 means 'return 10 queries' or 'queries slower than 10ms'.
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 0%, so the description must carry the burden. It thoroughly documents schema_name (optional, what it scopes, what it excludes, default behavior) but says nothing about slow_query_limit or its default of 10, leaving half the parameters undocumented anywhere.
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 (Run) and a compound resource, then enumerates the four audit domains it covers (table health, slow queries, index health, configuration review). Since the siblings are exactly those four single-domain checks, this description makes clear this is the aggregate entry point rather than any of them.
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?
Explains the scoping behavior in detail: schema_name narrows table/slow-query/index findings, config findings stay database-wide, and omitting it audits all non-system schemas. It gives clear context but never explicitly says 'use the single-domain siblings when you only need one area', leaving the choice between this and check_table_health/analyze_slow_queries to inference.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
4 tool updates
v0.1.4- Changed
analyze_slow_queries1 field changed- added
Input schema / properties / schema_nameAdded value: +{ + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Schema Name" +}
- Changed
check_index_health3 fields changed- added
Input schema / properties / schema_name / anyOfAdded value: +[ + { + "type": "string" + }, + { + "type": "null" + } +] - changed
Input schema / properties / schema_name / defaultPrevious value: -"public"New value: +null - removed
Input schema / properties / schema_name / typeRemoved value: -"string"
- Changed
check_table_health1 field changed- added
Input schema / properties / schema_nameAdded value: +{ + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Schema Name" +}
- Changed
full_audit1 field changed- added
Input schema / properties / schema_nameAdded value: +{ + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Schema Name" +}
5 tool updates
v0.1.2- First observed
analyze_slow_queries - First observed
check_config - First observed
check_index_health - First observed
check_table_health - First observed
full_audit
TDQS
Scored across 5 tools
Each of the four diagnostic tools targets a distinct area (tables, queries, indexes, config), making selection straightforward. full_audit overlaps as an intentional superset aggregator, which is clearly labeled but could still tempt an agent to skip the focused tools.
All names use snake_case and a verb_noun-ish pattern (check_table_health, check_index_health, check_config, analyze_slow_queries). full_audit drops the verb prefix, a minor deviation but still readable and predictable.
Five tools is well-scoped for a read-only PostgreSQL triage/diagnostics server. Each tool earns its place with no redundancy beyond the deliberate full_audit aggregate.
The surface covers the core diagnostic domains for Postgres performance triage: table health, slow queries, index health, configuration, plus a unified audit. As a read-only triage tool there are no obvious lifecycle gaps.
Maintenance
Related MCP Connectors
Hosted MCP server for PostgreSQL diagnostics: slow queries, missing indexes, connection pressure.
- WoWSQLOAuthcom.wowsql
Managed Postgres MCP: projects, SQL, docs search, and storage buckets via OAuth.
Paid remote MCP for governed database query review, SQL simulation, approvals, and audits.
Read-only MCP access to a documented IT fleet: state, changes, posture. 15 tools.
Related MCP Servers
- AlicenseNot gradedqualityDmaintenanceRead-only PostgreSQL analytics MCP server — query plans, slow queries, index usage, table bloat, vacuum status. No DDL/DML/writes. Curated by Archimedes Market with a verified Trust Report.MIT
- AlicenseAqualityCmaintenanceProvides PostgreSQL database management and analysis via MCP, enabling schema exploration, query execution, performance monitoring, and database health checks.3615 npmMIT
- AlicenseAqualityAmaintenanceGoverned PostgreSQL DBA operations — slow-query, bloat, and blocking-lock RCA, index management, vacuum/analyze, and replication lag, with unbypassable audit logging (MCP + CLI), budget/runaway guards, dry-run, and undo/rollback.35MIT
- AlicenseNot gradedqualityCmaintenanceProvides a read-only PostgreSQL MCP server with schema introspection. Enforces least-privilege database roles to prevent any writes, even from malicious SQL.MIT