Skip to main content
Glama
Nick-Msk
by Nick-Msk

pg-explain-mcp

Tag Python License MCP SQL lint CI

An MCP (Model Context Protocol) server for analyzing PostgreSQL query execution plans. Built as a bridge between LLM-based coding assistants (Continue.dev, Claude Desktop, Cursor) and a PostgreSQL database.

The server runs EXPLAIN (ANALYZE, BUFFERS, FORMAT JSON) on SELECT queries and returns a structured report highlighting performance bottlenecks — so an LLM can explain why a query is slow and what to do about it, instead of just describing the SQL.

Features

MCP tools

  • explain — runs EXPLAIN (ANALYZE, BUFFERS, FORMAT JSON) on a SELECT / WITH query and returns a structured report.

  • list_tables — returns all user tables and their columns.

  • list_indexes — returns existing indexes for a table (or all tables), so the assistant can avoid recommending an index that already exists.

  • list_parameters — returns runtime parameters relevant to plan analysis: work_mem, hash_mem_multiplier, shared_buffers, effective_cache_size, random_page_cost, seq_page_cost, parallel worker limits, jit.

  • list_relation_info — pg_class metadata: sizes, row/page counts, column/index counts, owner, persistence, tablespace, comment.

  • list_relation_stats — pg_stat_user_tables counters combined with pg_class estimates (reltuples, relpages, relallvisible).

  • list_column_stats — per-column planner statistics from pg_stats: null_frac, avg_width, n_distinct, correlation, most_common_vals, most_common_freqs, histogram_bounds.

  • show_params, set_checker_value, reset_checker_value — runtime check management without editing the SQLite database by hand.

  • ping — health check.

  • history_checker_values — returns the audit log of config changes (config_audit), most recent first. Filters: table_name, column_name, optype ('I' / 'U' / 'D'), count (0 = all), include_seed (default False — seed rows are hidden). Each row carries old/new values and a who tag (system, llm, seed).

Checks

Every explain response includes an issues array. Each entry is produced by an independent, pluggable check — a subclass of ParsedPlanCheckBase:

Check

What it reports

SeqScanCheck

Sequential scan that discards most of what it reads

IndexRegularScanCheck

Index Scan reading too many blocks — poor clustering

IndexOnlyScanCheck

Index Only Scan with a stale visibility map

BitmapHeapScanCheck

Large Bitmap Heap Scan

DiskSpillSortCheck

Sort spilling to disk (external merge)

DiskSpillHashCheck

Hash operation using multiple batches

NestedLoopCheck

Nested Loop with a high number of inner iterations

EstimateMismatchCheck

Planner cardinality misestimate

PartitionPruningCheck

Append over many partitions — pruning may have failed

NonSargableCheck

Predicate wraps an indexed column in a function

JitDecisionCheck

JIT compilation overhead exceeds the plan's useful work

To add a new check, subclass ParsedPlanCheckBase in src/pg_explain_mcp/analyzer.py, implement the three phases (gather_info → validate_rule → generate_msg), register the class in src/pg_explain_mcp/config.py, and add its default parameters to config/seed.sql. Each subclass declares name, type, and PARAMS = {param: type}; values are loaded from the SQLite registry and coerced at construction time.

Issue context

Every Issue carries a context dict alongside the human-readable message — the same values the check used to decide whether to fire. This lets an agent (or an automated tuning tool) build a fix without parsing the message text.

For example, a disk_spill_hash issue includes:

{
  "type": "disk_spill_hash",
  "severity": "warning",
  "message": "Hash operation spilled to disk: 8 batches, peak 25067kB ...",
  "context": {
    "batches": 8,
    "peak_kb": 25067,
    "estimated_mb": 195.8,
    "disk_kb": 0,
    "parallel": false,
    "loops": 1,
    "node_type": "Hash"
  }
}

A consumer reads context.estimated_mb = 195.8 and computes the
required work_mem directly — no regex on the message.

Each check declares its own context shape; the fields are the
same ones documented in the check's gather_info docstring.

### Structured plan output

`explain` returns a compact `plan_nodes` tree alongside `issues`.
Only the fields that matter for reasoning are kept:

- `node_type`, `relation_name`, `index_name`
- `actual_rows`, `plan_rows`, `actual_loops`
- `rows_removed_by_filter`, `heap_fetches`, `shared_read_blocks`
- `sort_method`, `sort_space_type`, `sort_space_used`
- `hash_batches`, `peak_memory_usage`
- `parallel_aware`

The report also carries `root_meta` — top-level EXPLAIN metadata
(`Planning`, `Planning Time`, `JIT`, `Triggers`, `Execution Time`) —
so an LLM can read `JIT.Timing.Total` without a second EXPLAIN callThe report also carries `root_meta` — top-level EXPLAIN metadata
(`Planning`, `Planning Time`, `JIT`, `Triggers`, `Execution Time`) —
so an LLM can read `JIT.Timing.Total` without a second EXPLAIN call..

The list of fields is configurable — see [Configuration](#configuration).

## Installation

Requires Python 3.10+.

```bash
git clone https://github.com/Nick-Msk/pg-explain-mcp.git
cd pg-explain-mcp

python3.12 -m venv .venv
source .venv/bin/activate      # Windows: .venv\Scripts\activate

pip install --upgrade pip
pip install -e .

pip install -e . installs the package in editable mode and registers three console scripts: pg-explain-mcp (the MCP server), pg-explain-parse (plan parser / CLI), and pg-explain-config (SQLite registry management).

Verify

python -c "from pg_explain_mcp import server; print('OK')"
# → OK

pytest -v

Related MCP server: Postgres Scout MCP

Command-line tools

pg-explain-parse

Runs EXPLAIN on a query and prints the plan tree, or parses a saved JSON plan offline. Useful for inspecting plans without an MCP client:

# SQL as argument
pg-explain-parse "select * from t limit 10"

# SQL from a file
pg-explain-parse query.sql

# SQL from stdin
cat query.sql | pg-explain-parse

# Parse a saved plan JSON instead
pg-explain-parse --plan plan.json
cat plan.json | pg-explain-parse --plan -

# Flags
pg-explain-parse --json              # flat node list as JSON
pg-explain-parse --no-analyze        # plan only, don't execute
pg-explain-parse --no-buffers        # skip BUFFERS
pg-explain-parse --all-fields        # keep zero-valued numeric fields
pg-explain-parse --marker-tabs 3     # tab padding in the text tree
pg-explain-parse --with-meta         # wrap JSON as {meta, plan}

python -m pg_explain_mcp.config --init   # rebuild from schema + seed
python -m pg_explain_mcp.config --show   # print current state

--init drops and recreates the seed tables only. The audit log
(config_audit) and the writer tag (_audit_session) survive, so
the history of config changes is preserved across rebuilds. Note
that --init resets every param to its seed default — see
Audit log.

## Configuration

### Connection

The server reads PostgreSQL connection parameters from environment
variables:

| Variable         | Default     | Description                |
|------------------|-------------|----------------------------|
| `PG_HOST`        | `localhost` | PostgreSQL host            |
| `PG_PORT`        | `5432`      | PostgreSQL port            |
| `PG_USER`        | `postgres`  | Database user              |
| `PG_PASSWORD`    | —           | Database password          |
| `PG_DATABASE`    | `postgres`  | Database name              |
| `TARGET_DB_TYPE` | `postgres`  | Database (pg/orcl/mysql..) |

### Check registry

Enabled checks and their thresholds live in a small SQLite database at
`config/checks.db`. The database is created automatically on first
`explain` if missing, and rebuilt from scratch by an explicit `--init`:

```bash
python -m pg_explain_mcp.config --init   # rebuild from schema + seed
python -m pg_explain_mcp.config --show   # print current state

Changes to the database take effect on the next explain call — no MCP server restart required. For example:

# Disable NestedLoopCheck without touching the code.
sqlite3 config/checks.db \
  "update checks set enabled = 0 where name = 'NestedLoopCheck';"

# Raise SeqScanCheck's threshold from 1000 to 5000 rows.
sqlite3 config/checks.db \
  "update check_params set value = '5000'
   where num = 1 and param = 'threshold_rows';"

The schema is multi-database ready (checks and plan_fields are keyed by database), but only postgres is currently populated. The databases table is the FK root — removing a row from it cascades to all of its checks, params, and plan fields.

To extend the registry with a new field for plan_nodes:

sqlite3 config/checks.db \
  "insert into plan_fields (database, raw, key, enabled)
   values ('postgres', 'Total Cost', 'total_cost', 1);"

The new field appears in plan_nodes on the next explain call.


Plan fields

plan_fields controls which EXPLAIN fields appear in plan_nodes and in the text tree. Each row has a raw EXPLAIN name, a compact key, and an enabled mode:

enabled

Meaning

0

Hide the field (Parallel Aware: false, Disabled: false, Async Capable: false).

1

Keep the field even when its value is a numeric zero (Heap Fetches, Rows Removed by Filter, Shared Read Blocks, Temp Read Blocks, Temp Written Blocks).

999

Auto: keep the field, drop numeric zeros (the default).

Fields not listed in plan_fields default to 999 and get an auto-derived snake_case key.

To add a new field:

sqlite3 config/checks.db \
  "insert into plan_fields (database, raw, key, enabled)
   values ('postgres', 'Total Cost', 'total_cost', 1);"

## Usage with Continue.dev

Ready-to-use configuration files are available in
[`config_mcp/`](config_mcp/):

- [`config_mcp/mcpServers/pg-explain.yaml`](config_mcp/mcpServers/pg-explain.yaml)
  — MCP server registration.
- [`config_mcp/postgres-agent.md`](config_mcp/postgres-agent.md)
  — system prompt for an assistant that knows how to use `pg-explain`
  and a generic PostgreSQL MCP server, with per-check guidance.

## Companion to `universal-db-mcp`

`pg-explain-mcp` is designed to be used **alongside** a
general-purpose PostgreSQL MCP server, not to replace it. In the
reference setup we use
[`universal-db-mcp`](https://github.com/Anarkh-Lee/universal-db-mcp)
configured for the same database, and the two servers serve different
purposes:

| Server             | Purpose                                                 |
|--------------------|---------------------------------------------------------|
| `universal-db-mcp` | General SQL execution: `SELECT`, schema exploration, ad-hoc queries. |
| `pg-explain-mcp`   | Plan analysis: `EXPLAIN ANALYZE`, structured bottleneck detection, index/parameter inspection. |

The assistant decides which to call based on the question:

- *"How many rows are in `orders`?"* → `universal-db-mcp`.
- *"Why is this query slow?"* → `pg-explain-mcp`.

All examples in [`usage_examples/`](usage_examples/) were captured with
both servers loaded. The agent configuration in
[`config_mcp/postgres-agent.md`](config_mcp/postgres-agent.md)
describes both and includes guidance on when to prefer one over the
other.

### Audit log

Every change to the config database is recorded in `config_audit`.
The table is populated automatically by triggers — no application
code writes to it directly.

**What is tracked:**

| Table          | Column     | Op recorded on        |
|----------------|------------|-----------------------|
| `checks`       | `enabled`  | insert / update / delete |
| `check_params` | `value`    | insert / update / delete |
| `plan_fields`  | `enabled`  | insert / update / delete |
| `plan_fields`  | `key`      | insert / update / delete |

**Row shape:** `(id, table_name, column_name, optype, ts, old_value,
new_value, who)`. `optype` is `'I'` (insert), `'U'` (update), or
`'D'` (delete) — so a delete and an update-that-sets-NULL are
distinguishable. `ts` is UTC ISO-8601 with milliseconds.

**Writer tags** (`who`):

| Tag        | Set by                                                         |
|------------|----------------------------------------------------------------|
| `system`   | Default. Manual `sqlite3` edits, `--init` overhead.            |
| `llm`      | MCP calls to `set_checker_value` / `reset_checker_value`.      |
| `seed`     | `seed.sql` inserts during `--init`. Hidden by default in `history_checker_values`. |

**Reading the log via MCP:**

history_checker_values() # all non-seed rows
history_checker_values(table_name="check_params") # one table
history_checker_values(optype="U") # updates only
history_checker_values(count=20) # newest 20
history_checker_values(include_seed=True) # include --init noise

**Reading the log via SQL:**

```bash
sqlite3 config/checks.db \
  "select ts, optype, table_name, column_name, old_value, new_value, who
   from config_audit order by id desc limit 20;"
Survives --init. --init drops and rebuilds the seed tables
(checks, check_params, plan_fields, tags, databases) but
leaves config_audit and _audit_session intact. This makes it
possible to answer "who changed this, and when?" across rebuilds.

Caveat: --init resets every param to its seed default. If you
had a non-default value in check_params and you re-run --init,
the value is lost — but the change that produced it is preserved in
config_audit. Before running --init in a shared environment,
check history_checker_values() for recent edits.

### Why not use `pg-explain-mcp` alone?

You can — the server is self-contained and exposes `list_tables` for
schema inspection. Disabling `universal-db-mcp` is perfectly fine if
you only care about plan analysis.

In practice, however, mixing the two is more convenient: `pg-explain`'s
`EXPLAIN ANALYZE` **actually executes** every query, which is expensive
for large tables. For anything that does not need a plan — a row count,
a lookup, a quick sanity check — `universal-db-mcp` runs the same query
in read-only mode with much less overhead.

The split also reduces the blast radius of an LLM mistake: a bad
`SELECT` on `universal-db-mcp` is cheap, while a bad
`EXPLAIN ANALYZE` on `pg-explain-mcp` can be expensive. Keeping the
cheap path as the default makes the setup more robust.

### Setup

1. Copy `config_mcp/mcpServers/pg-explain.yaml` into your
   workspace's `.continue/mcpServers/` directory.
2. Replace the placeholders:
   - `command:` — full path to the Python interpreter inside your
     `.venv`.
   - `PG_USER`, `PG_PASSWORD`, `PG_DATABASE` — your PostgreSQL
     credentials.
3. (Optional) Copy `config_mcp/postgres-agent.md` into
   `.continue/agents/` to use it as a custom agent prompt.
4. In VS Code: `Cmd+Shift+P` → **`Continue: Reload Config`**.
5. Open a new chat in **Agent Mode** (not Chat, not Edit).

### Example

Prompt:

> Use the pg-explain tool to analyze:
> `SELECT val FROM onek1 WHERE hundred BETWEEN 5 AND 55;`

Response:

✓ Continue used the pg-explain explain tool

Execution Time: 0.904 ms Planning Time: 1.228 ms Issues Found: None

Analysis: The plan shows an Index Only Scan on idx_onek1_hundred. Heap Fetches: 0 — the visibility map is fresh, no heap lookups needed.


## Test fixtures

The repository ships a PostgreSQL extension
[`mcp_explain_tool`](fixtures/) that creates empty tables and
`fill_*` / `clear_*` procedures for every check. It is versioned
independently from the Python package.

```bash
cd fixtures
make install
create extension mcp_explain_tool;
call mcp_explain_tool.fill_all(1000000);

See fixtures/README.md for details, including the VACUUM policy and the autovacuum exceptions for tables that need a stale visibility map or fake statistics.

Usage examples

Real-world runs of pg-explain-mcp against PostgreSQL, one set per check, with the raw tool output and analysis:

Check

Examples

IndexOnlyScanCheck

healthy vs. stale visibility map

IndexRegularScanCheck

warm vs. cold cache

SeqScanCheck

with and without an index

DiskSpillSortCheck

in-memory vs. external merge, plus a precise work_mem variant

DiskSpillHashCheck

single-batch vs. multi-batch spill, plus a precise variant

NestedLoopCheck

100 vs. 5000 inner iterations

BitmapHeapScanCheck

narrow vs. wide range on the same table

EstimateMismatchCheck

norm, norm+index, skewed, skewed-other-value

PartitionPruningCheck

range predicate vs. non-sargable predicate on partitioned table

NonSargableCheck

sargable vs. non-sargable predicate on indexed column

JitDecisionCheck

5M-row aggregate (JIT pays off) vs. 100-row query (JIT dominates)

See usage_examples/ for the full index, the test environment, and prompting tips.

Project structure

pg-explain-mcp/
├── src/
│   └── pg_explain_mcp/
│       ├── __init__.py
│       ├── server.py         # MCP entry point — exposes tools
│       ├── parse.py          # pg-explain-parse CLI
│       ├── db.py             # connection + EXPLAIN + schema queries
│       ├── analyzer.py       # PlanNode, checks, new_* pipeline
│       └── config.py         # SQLite-backed check registry + CLI
├── config/                   # SQLite config: schema, seed, checks.db
├── tests/                    # unit tests for checks and helpers
├── fixtures/                 # mcp_explain_tool PostgreSQL extension
├── usage_examples/           # real-world runs, one set per check
├── config_mcp/           # Continue.dev MCP + agent config
├── images/                   # screenshots
├── pyproject.toml
├── CHANGELOG.md
├── DISCLAIMER.md
├── README.md
└── LICENSE

Roadmap

Additional checks

  • RepeatedScanCheck — report the same relation scanned more than once within a single plan (via CTEs, subqueries, or lateral joins, not self-joins). Often signals that CTE materialisation or a temp table would reduce I/O.

Database health checker

A second kind of check, alongside plan checks — health_check — that inspects the database as a whole instead of a single query.

Health checks live in the same SQLite registry as plan checks, distinguished by a new check_type column ('PLAN' or 'HEALTH'). This means they are configured, disabled, and tuned with the same tools: show_params, set_checker_value, reset_checker_value, and the TARGET_DB_TYPE scope all apply without change.

Candidate checks, each independently toggleable:

  1. Tablespace free space. Warn when any tablespace has less than a configurable percentage of free space.

  2. Invalid objects. Report indexes with indisvalid = false and constraints with convalidated = false.

  3. plpgsql_check integration. If the extension is installed, run plpgsql_check_function() over every procedure and function in the target schema. If the extension is missing, skip with an INFO-level note.

  4. Bloat estimation. Compare n_dead_tup to n_live_tup in pg_stat_user_tables and flag tables above a configurable ratio.

  5. Connection and lock pressure. Report long-running transactions and locks held beyond a configurable interval.

Implementation shape:

  • new HealthCheck protocol in analyzer.py — same Issue type, same name / type attributes, but check() takes no arguments and reads from pg_catalog / pg_stat_* directly;

  • CheckRegistry.load_health() filtering by check_type = 'HEALTH';

  • a single new MCP tool health_check returning a report with checks_applied and issues, mirroring the explain output shape.

Multi-database support

The analyzer interface and the SQLite config are database-agnostic in principle. The next step is a MySQL/MariaDB adapter and its own plan_fields / checks rows under TARGET_DB_TYPE=mysql.

Audit log for config changes

Record every set_checker_value and reset_checker_value call into a param_history table with timestamp, old value, and new value. Useful in shared deployments.

Disclaimer

This project is a diagnostic tool provided "as is". Recommendations from the analyzer — or from an LLM assistant using it — are suggestions, not guarantees. Always validate against your own database before applying changes to a production system. EXPLAIN ANALYZE actually executes the query; avoid running it against production databases during peak hours.

See DISCLAIMER.md for the full text.

License

MIT — see LICENSE for details.

Available Tools

3 tools
explainA

Run EXPLAIN ANALYZE on a SELECT query and return a structured report.

Args: sql: A SQL query. Only SELECT and WITH statements are allowed.

ParametersJSON Schema
NameRequiredDescriptionDefault
sqlYes

Output Schema

ParametersJSON Schema
NameRequiredDescription
resultYes

TDQS

A3.9/5.0
Behavior3/5

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

With no annotations, the description carries full responsibility for behavioral disclosure. It usefully notes that only SELECT and WITH statements are allowed, but it does not mention that EXPLAIN ANALYZE actually executes the query and may be resource-intensive, nor does it address error behavior. This is adequate but leaves notable behavioral traits implicit.

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?

The description is compact and well-structured: a single front-loaded sentence states the action and output, followed by a minimal Args block. Every line earns its place with no filler or repetition.

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?

The description covers the core call requirements: the operation, the output, and the parameter constraint. The output schema supplies return structure. However, for a tool with no annotations, it should also mention that EXPLAIN ANALYZE executes the query, which affects cost and safety expectations, and provide clearer usage context.

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?

Schema description coverage is 0%, but the description compensates by defining sql as 'A SQL query' and adding the important SELECT/WITH restriction. For a single self-explanatory parameter, this is sufficient, though it omits minor details like whether semicolons or parameterized queries are accepted.

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 a specific verb-resource pair ('Run EXPLAIN ANALYZE on a SELECT query') and states the deliverable ('structured report'). This makes the tool's purpose immediately clear and distinguishes it from sibling tools ping and list_tables without ambiguity.

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 context is implied: an agent would use this when it needs an EXPLAIN ANALYZE report for a SELECT/WITH query. However, the description does not explicitly state when to prefer this tool over alternatives or provide any exclusionary guidance beyond the allowed statement types.

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

list_tablesA

Return a list of all user tables and their columns.

ParametersJSON Schema
NameRequiredDescriptionDefault

No parameters

Output Schema

ParametersJSON Schema
NameRequiredDescription
resultYes

TDQS

A3.9/5.0
Behavior3/5

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

With no annotations, the description carries the full burden. It discloses that the tool returns a list (read-only behavior) and scopes results to 'user tables' and 'their columns', which is meaningful behavioral context. However, it does not mention potential side effects, permissions, performance characteristics, or ordering, leaving some gaps for a simple read operation.

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 with zero filler. Every word contributes to the purpose, and it is appropriately sized for the tool's simplicity.

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?

Given the tool has no parameters and an output schema exists, the description is fully sufficient. It tells the agent exactly what the tool returns, and no additional usage or behavioral information is required for correct invocation.

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 has zero parameters, so the schema is empty and coverage is trivially 100%. Per the rubric, 0 parameters earns a baseline of 4; the description adds nothing about parameters because there are none, and none are needed.

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 uses a specific verb ('Return') and resource ('list of all user tables and their columns'), clearly distinguishing it from siblings 'ping' and 'explain'. It states exactly the operation and scope without ambiguity.

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 provides no guidance on when to use this tool versus alternatives, no prerequisites, and no exclusions. It only states what the tool does, leaving the agent to infer usage context from the tool name and sibling names.

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

pingA

Health check — returns 'pong' if the server is running.

ParametersJSON Schema
NameRequiredDescriptionDefault

No parameters

Output Schema

ParametersJSON Schema
NameRequiredDescription
resultYes

TDQS

A4/5.0
Behavior3/5

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

With no annotations provided, the description carries the full burden of behavioral disclosure. It discloses the success behavior (returns 'pong') and implicitly signals a non-mutating operation, but it does not state what happens on failure — whether the call errors, times out, or returns a non-pong payload when the server is down.

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 that wastes no words: the purpose ('Health check') appears first, and the expected response is stated immediately. Every word earns its place.

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?

For a zero-parameter tool with an output schema present, the description covers the essential purpose and response behavior. It could add failure semantics, but the output schema handles return-value details and the tool's simplicity means nothing critical is missing.

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 has zero parameters, so the description correctly omits parameter details and the empty schema is fully covered. With no parameters to document, the baseline of 4 applies.

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 states a clear, specific purpose: a liveness health check that returns 'pong' when the server is running. This unambiguously distinguishes it from siblings list_tables and explain — there is no overlap in function, and an agent can tell immediately what this tool does.

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 usage context is implied but not explicit: an agent would infer this is the tool to call when verifying server liveness, and the sibling tools are clearly unrelated. However, there is no explicit statement of when to use it vs. alternatives, no prerequisites, and no named exclusions.

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. 3 tool updatesv0.1.0
    • First observedexplain
    • First observedlist_tables
    • First observedping

TDQS

A4.2/5.0

Scored across 3 tools

Disambiguation5/5

Each tool has a unique role: ping is a health check, list_tables surfaces schema metadata, and explain performs query analysis. There is no overlap or ambiguity in selecting among them.

Naming Consistency4/5

Names are short, lowercase verb forms and are easy to predict. list_tables follows the verb_noun pattern, while ping and explain are bare verbs, so the pattern is not perfectly uniform.

Tool Count5/5

Three tools is minimal but appropriate for the narrow purpose of examining table schemas and running EXPLAIN ANALYZE. Each tool serves a distinct, necessary function without redundancy.

Completeness5/5

Within its stated scope of listing tables and explaining SELECT/WITH queries, the workflow is complete: discover tables, write a query, and get a structured plan. The restriction to read-only queries is intentional and avoids unsafe mutations.

Maintenance

ActivityMaintained
ResponsivenessNo issues

Related MCP Connectors

Related MCP Servers

  • F
    license
    A
    quality
    D
    maintenance
    Enables AI assistants to interact with PostgreSQL databases using natural language queries, providing secure read-only access to database schemas and SQL translation capabilities.
    6
    8 npm
    -
  • A
    license
    Not graded
    quality
    D
    maintenance
    Enables AI assistants to safely explore, analyze, and maintain PostgreSQL databases with read-only mode by default, SQL injection prevention, query performance analysis, and optional write operations.
    52 npm
    Apache 2.0
  • F
    license
    Not graded
    quality
    D
    maintenance
    Enables AI assistants to safely interact with PostgreSQL databases, perform queries, inspect schemas, and analyze query performance.
    2
    -
  • A
    license
    Not graded
    quality
    C
    maintenance
    Securely connect AI assistants to PostgreSQL databases with read-only access, schema discovery, querying, and performance analysis tools.
    10 npm
    MIT