Skip to main content
Glama
pgtriage

pgtriage

Official
by pgtriage

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.

Related MCP server: postgres-mcp-server

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": "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 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 (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

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

Available Tools

5 tools
analyze_slow_queriesA
Read-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.

ParametersJSON Schema
NameRequiredDescriptionDefault
limitNo
min_callsNo
schema_nameNo

TDQS

A3.5/5.0
Behavior4/5

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.

Conciseness5/5

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.

Completeness3/5

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.

Parameters2/5

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.

Purpose4/5

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.

Usage Guidelines3/5

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_configA
Read-onlyIdempotent

Review PostgreSQL configuration settings and flag suboptimal values for shared_buffers, work_mem, max_connections, and autovacuum parameters.

ParametersJSON Schema
NameRequiredDescriptionDefault

No parameters

TDQS

A3.5/5.0
Behavior3/5

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.

Conciseness5/5

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.

Completeness4/5

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.

Parameters4/5

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.

Purpose4/5

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.

Usage Guidelines2/5

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_healthB
Read-onlyIdempotent

Find unused indexes, duplicate indexes, and missing index opportunities based on sequential scan patterns.

ParametersJSON Schema
NameRequiredDescriptionDefault
schema_nameNo

TDQS

B3/5.0
Behavior3/5

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.

Conciseness4/5

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.

Completeness3/5

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.

Parameters2/5

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.

Purpose4/5

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.

Usage Guidelines2/5

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_healthA
Read-onlyIdempotent

Check table health: dead tuples, bloat, vacuum stats, scan ratios. Optionally filter to a specific user schema and/or table by name.

ParametersJSON Schema
NameRequiredDescriptionDefault
table_nameNo
schema_nameNo

TDQS

A3.6/5.0
Behavior3/5

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.

Conciseness5/5

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.

Completeness4/5

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.

Parameters3/5

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.

Purpose4/5

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.

Usage Guidelines3/5

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_auditA
Read-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.

ParametersJSON Schema
NameRequiredDescriptionDefault
schema_nameNo
slow_query_limitNo

TDQS

A4/5.0
Behavior4/5

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.

Conciseness4/5

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.

Completeness3/5

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.

Parameters3/5

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.

Purpose5/5

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.

Usage Guidelines4/5

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.

  1. 4 tool updatesv0.1.4
    • Changedanalyze_slow_queries1 field changed
      • addedInput schema / properties / schema_name
        Added value: +{
        +  "anyOf": [
        +    {
        +      "type": "string"
        +    },
        +    {
        +      "type": "null"
        +    }
        +  ],
        +  "default": null,
        +  "title": "Schema Name"
        +}
    • Changedcheck_index_health3 fields changed
      • addedInput schema / properties / schema_name / anyOf
        Added value: +[
        +  {
        +    "type": "string"
        +  },
        +  {
        +    "type": "null"
        +  }
        +]
      • changedInput schema / properties / schema_name / default
        Previous value: -"public"New value: +null
      • removedInput schema / properties / schema_name / type
        Removed value: -"string"
    • Changedcheck_table_health1 field changed
      • addedInput schema / properties / schema_name
        Added value: +{
        +  "anyOf": [
        +    {
        +      "type": "string"
        +    },
        +    {
        +      "type": "null"
        +    }
        +  ],
        +  "default": null,
        +  "title": "Schema Name"
        +}
    • Changedfull_audit1 field changed
      • addedInput schema / properties / schema_name
        Added value: +{
        +  "anyOf": [
        +    {
        +      "type": "string"
        +    },
        +    {
        +      "type": "null"
        +    }
        +  ],
        +  "default": null,
        +  "title": "Schema Name"
        +}
  2. 5 tool updatesv0.1.2
    • First observedanalyze_slow_queries
    • First observedcheck_config
    • First observedcheck_index_health
    • First observedcheck_table_health
    • First observedfull_audit

TDQS

A3.7/5.0

Scored across 5 tools

Disambiguation4/5

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.

Naming Consistency4/5

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.

Tool Count5/5

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.

Completeness5/5

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

ActivityMaintained
ResponsivenessUnresponsive

Related MCP Connectors

Related MCP Servers

  • A
    license
    Not graded
    quality
    D
    maintenance
    Read-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
  • A
    license
    A
    quality
    C
    maintenance
    Provides PostgreSQL database management and analysis via MCP, enabling schema exploration, query execution, performance monitoring, and database health checks.
    36
    15 npm
    MIT
  • A
    license
    A
    quality
    A
    maintenance
    Governed 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.
    35
    MIT
  • A
    license
    Not graded
    quality
    C
    maintenance
    Provides a read-only PostgreSQL MCP server with schema introspection. Enforces least-privilege database roles to prevent any writes, even from malicious SQL.
    MIT