# io.github.YawLabs/postgres-mcp (npm · @yawlabs/postgres-mcp)

PostgreSQL MCP server - query, schema introspection, explain, and health checks for AI assistants

- Trust score: 68/100 (medium)
- Change this week: +13
- Registry status: active
- Liveness: live
- Owner verified: no
- Last scored: 2026-08-06

## Components

- npm · `@yawlabs/postgres-mcp`: 68/100 (this document), [markdown](https://verifymcp.io/servers/yawlabs-postgres-mcp/yawlabs-postgres-mcp.md), [page](https://verifymcp.io/servers/yawlabs-postgres-mcp/yawlabs-postgres-mcp)

## Channel facts

- Registry: `npm`
- Package: `@yawlabs/postgres-mcp`
- Version: `0.7.0`
- Transport: `stdio`

## Trust breakdown

How this component scores in each security and reliability category. Every signal is checked automatically from public evidence about the published package, including repeated runs of it in an isolated sandbox, and we only credit what we can confirm. Scores are 0–100 per category. Scoring method: https://verifymcp.io/docs/scoring (what has changed: https://verifymcp.io/docs/scoring/changelog)

Scored 2026-08-06.

- **Supply Chain Security**: 100/100
  - No malware found by supply-chain analysis.
  - No known CVEs affecting this package version or its production dependencies.
  - No install/post-install scripts declared.
  - No production dependencies, so there is no dependency health to assess.
- **Provenance & Transparency**: 19/100
  - Repository check failed: no source repository is declared.
  - Provenance check failed: no build-provenance attestation is published.
  - Clear OSI-approved license (MIT).
  - Actively maintained (last published 15 days ago).
  - Disclosure check failed: no security disclosure policy was found in the source repository.
- **Schema Quality & AI Usability**: 71/100
  - AI-judged instruction clarity (excellent).
  - Context-footprint check failed: tool/resource definitions use about 3398 tokens (~161/item across 21 items; 21 tools + 0 resources), over budget; trim descriptions and params.
  - Usage-examples check failed: none of the tools include examples.
- **Stability & Change Management**: 33/100
  - Stability observed for 10 of 30 days with no destabilising changes; credit accrues until the full window elapses.
- **Tool Coverage**: 100/100
  - 100% of tools have a non-trivial description (not blank, and not just the tool's name).
  - 100% of tool parameters carry a description.
- **Capabilities**: 100/100
  - Implements a supported MCP spec version (2025-11-25); the latest is 2026-07-28.

## Install

### Claude

```bash
claude mcp add yawlabs-postgres-mcp -- npx -y @yawlabs/postgres-mcp
```

### Codex

```bash
codex mcp add yawlabs-postgres-mcp -- npx -y @yawlabs/postgres-mcp
```

### opencode

```json
{
  "$schema": "https://opencode.ai/config.json",
  "mcp": {
    "yawlabs-postgres-mcp": {
      "type": "local",
      "command": [
        "npx",
        "-y",
        "@yawlabs/postgres-mcp"
      ],
      "enabled": true
    }
  }
}
```

### OpenClaw

```bash
openclaw mcp add yawlabs-postgres-mcp --command npx --arg -y --arg @yawlabs/postgres-mcp
```

### Hermes

```yaml
mcp_servers:
  yawlabs-postgres-mcp:
    command: "npx"
    args: ["-y", "@yawlabs/postgres-mcp"]
```

### Other

```json
{
  "mcpServers": {
    "yawlabs-postgres-mcp": {
      "command": "npx",
      "args": [
        "-y",
        "@yawlabs/postgres-mcp"
      ]
    }
  }
}
```

## Changelog

Every change recorded for this component, newest first. Days that predate change tracking, or that we cannot explain, say so: "we were watching and nothing happened" and "we were not watching" are different claims.

### 2026-08-05 (score 68, +1)

No change was recorded against any check on this day. Stability & Change Management went from 27 to 30. That category is still filling its 30-day observation window: 8 days of observed history at the previous scan, 9 at this one. The score rises as the window fills, whether or not the server changes.

### 2026-08-04 (score 67, +4)

- [functional improvement] Stability: unverified → 0.27

### 2026-08-02 (score 63, +15)

- [security improvement] Malware scan: unverified → pass

### 2026-07-31 (score 48, −7)

- [functional] We updated how we score, so this day's move reflects our rubric, not a change to the server

### 2026-07-30 (score 55, −18)

- [security regression] Malware scan: pass → unverified

### 2026-07-27 (score 73, +32)

- [functional improvement] Tool coverage: unverified → 100
- [functional] First check of Schema quality: fail
- [functional] First check of Tool coverage: 100
- [functional] First check of Schema quality: fail
- [functional] First check of Schema quality: excellent

### 2026-07-26 (score 41)

First indexed and scored.

- [functional] We updated how we score, so this day's move reflects our rubric, not a change to the server

## MCP tools (21)

### `pg_readonly` (~200 tokens)

Run a SQL statement guaranteed read-only. Always executes inside a `BEGIN READ ONLY` transaction regardless of `ALLOW_WRITES`, so postgres itself rejects any write attempt. Use this whenever the goal is to read - SELECT, EXPLAIN, SHOW, VALUES, WITH ... SELECT, etc. Hosts that gate tools individually (Claude Code permissions, mcp.hosting) can safely auto-allow this one. Use `params` for parameterized queries to avoid SQL injection. Params can be strings, numbers, booleans, null, arrays (for postgres arrays / ANY), or objects (for json/jsonb columns). Large result sets are truncated to POSTGRES_MAX_ROWS (default 1000) with a `truncated: true` flag.

Input parameters:

- `params` (array): Positional parameters referenced as $1, $2, ... in the SQL.
- `sql` (string, required): The SQL statement to execute. Hard cap of 1 MB.

### `pg_query` (~267 tokens)

Run a SQL query against the configured PostgreSQL database. Postgres itself is the primary safety gate: the role in `DATABASE_URL` enforces what queries can succeed. The recommended posture is a least-privileged role (e.g. one granted `pg_read_all_data`), which makes writes server-rejected regardless of any env var. `ALLOW_WRITES=1` is a secondary belt-and-braces gate - it lifts the in-server `BEGIN READ ONLY` wrapper, but it cannot grant privileges the role lacks. Useful for managed databases where creating a second role is awkward. For read-only access where you want the guarantee in the tool name, prefer `pg_readonly`. Use `params` for parameterized queries to avoid SQL injection. Params can be strings, numbers, booleans, null, arrays (for postgres arrays / ANY), or objects (for json/jsonb columns). Dates and UUIDs can be passed as ISO strings. Large result sets are truncated to POSTGRES_MAX_ROWS (default 1000) with a `truncated: true` flag.

Input parameters:

- `params` (array): Positional parameters referenced as $1, $2, ... in the SQL.
- `sql` (string, required): The SQL statement to execute. Hard cap of 1 MB.

### `pg_list_schemas` (~37 tokens)

List non-system schemas in the database. Excludes `pg_catalog`, `information_schema`, and other `pg_*` internals.

### `pg_list_tables` (~153 tokens)

List tables (and optionally views) in a schema. Returns name, type (table/view/materialized view/foreign), and estimated row count (from `reltuples`; null = no ANALYZE yet on PG 14+; 0 may mean empty or unanalyzed on PG <= 13). Paginate via `limit`/`offset` on very large schemas.

Input parameters:

- `includeViews` (boolean): If true, include views and materialized views.
- `limit` (integer): Max rows to return (default 500, max 10000).
- `offset` (integer): Rows to skip for pagination (default 0).
- `schema` (string): Schema name (defaults to 'public').

### `pg_describe_table` (~162 tokens)

Describe a relation: kind (table / view / materialized_view / partitioned_table / foreign_table), columns (name, type, nullable, default), primary key, foreign keys (outgoing), `referenced_by` (other tables whose FKs point at this one), `constraints` (CHECK / UNIQUE non-PK / EXCLUDE), indexes, and partition info (`partition_of` parent, `partitions` children). Works on views and materialized views too -- PK/FK/constraint/index lists will simply be empty for a plain view. Use `kind` to disambiguate before assuming you can write to the relation.

Input parameters:

- `schema` (string): Schema name (defaults to 'public').
- `table` (string, required): Table name.

### `pg_list_views` (~79 tokens)

List views and materialized views in a schema with their SQL definitions. Use this over `pg_list_tables` with `includeViews: true` when you want the view body, not just names.

Input parameters:

- `includeMaterialized` (boolean): If true, include materialized views.
- `schema` (string): Schema name (defaults to 'public').

### `pg_list_functions` (~55 tokens)

List functions, procedures, and aggregates in a schema. Returns name, arguments, return type, kind (function/procedure/aggregate/window), and implementation language.

Input parameters:

- `schema` (string): Schema name (defaults to 'public').

### `pg_list_extensions` (~54 tokens)

List installed PostgreSQL extensions. Returns name, version, schema, and description. Useful to check for pgvector, postgis, pg_stat_statements, uuid-ossp, etc. before writing queries that rely on them.

### `pg_search_columns` (~121 tokens)

Search for columns by name across all user schemas. Supports SQL LIKE patterns (`%` matches any substring, `_` matches one character). Case-insensitive. Use this instead of iterating `pg_describe_table` when the user asks 'which tables have X'.

Input parameters:

- `limit` (integer): Max rows to return (default 100).
- `pattern` (string, required): LIKE pattern. Use '%' for wildcard: 'user_id', '%email%', 'created_%'.
- `schema` (string): Limit to this schema. If omitted, searches all user schemas.

### `pg_explain` (~313 tokens)

Get the query plan for a SQL statement. By default, this uses plain EXPLAIN (no execution). Set `analyze: true` to run the query with EXPLAIN ANALYZE - for non-SELECT statements, ALLOW_WRITES=1 is required (since ANALYZE actually executes the statement). Writes executed during EXPLAIN ANALYZE are always rolled back, so you can inspect a plan for an INSERT/UPDATE/DELETE without persisting the mutation. Format is `text` (default) or `json`. Pass the raw SQL (not an EXPLAIN-prefixed statement). Set `hypothetical_indexes` to a list of `{table, columns, using?}` to ask the planner 'what would the plan be if these indexes existed?' -- requires the HypoPG extension (`CREATE EXTENSION hypopg`). The hypothetical indexes are torn down at the end of the call, never touching real disk.

Input parameters:

- `analyze` (boolean): Run EXPLAIN ANALYZE (actually executes the query).
- `format` (string): Output format.
- `hypothetical_indexes` (array): List of indexes the planner should pretend exist for this EXPLAIN. Requires the HypoPG extension. Indexes are session-scoped and reset at the end of the call.
- `params` (array): Positional parameters referenced as $1, $2, ... in the SQL.
- `sql` (string, required): The SQL statement to explain. Do NOT prefix with EXPLAIN.

### `pg_health` (~62 tokens)

Quick health snapshot: server version, database size, connection count, active queries, and table count. Useful as a connection sanity check and to spot runaway queries.

Input parameters:

- `activeQueryLimit` (integer): Max active queries to return (default 10, max 100).

### `pg_top_queries` (~204 tokens)

Top N queries by total or mean execution time. Requires the `pg_stat_statements` extension to be installed and enabled (most managed Postgres providers have it on by default). Returns normalized query text (constants replaced with `?`), call count, total/mean/min/max time in ms, rows returned, and cache hit ratio. Use this to find slow queries worth optimizing. On pg_stat_statements >= 1.10 (Postgres 15+), also returns `io_read_time_ms` and `io_write_time_ms` to separate IO-bound from CPU-bound queries (null when track_io_timing = off or the query did no measurable IO -- enable track_io_timing in postgresql.conf to get non-null values).

Input parameters:

- `limit` (integer): Number of rows to return (default 20).
- `orderBy` (string): Ranking: total_time (cumulative impact), mean_time (worst per-call), or calls (hottest).

### `pg_seq_scan_tables` (~146 tokens)

Tables with high sequential-scan counts relative to index scans - the first place to look for missing-index candidates. Returns seq_scans, idx_scans, live tuples, and the ratio. A high ratio on a large table usually means a query is reading the whole table where an index would suffice. Pair with `pg_top_queries` to find which query is doing it.

Input parameters:

- `limit` (integer): Max rows to return (default 20).
- `minSize` (integer): Minimum live tuple count to include (default 1000, filters out tiny/empty tables).
- `schema` (string): Limit to one schema. If omitted, all user schemas are included.

### `pg_unused_indexes` (~137 tokens)

Indexes that have never been scanned or have very low usage. Each unused index costs write amplification (every INSERT/UPDATE maintains it) and disk space. Excludes primary keys and unique constraints (which are load-bearing even with zero scans). Use this before adding new indexes - sometimes the fix is to drop a dead one.

Input parameters:

- `limit` (integer): Max rows to return (default 50).
- `maxScans` (integer): Include indexes with scan count <= this (default 10). Use 0 for 'never scanned'.
- `schema` (string): Limit to one schema. If omitted, all user schemas are included.

### `pg_inspect_locks` (~210 tokens)

Show current lock contention: which sessions are blocked and who is blocking them. Returns blocked PID, blocking PID, lock types, relation being contested, and the queries involved. Use this first when a tool call hangs or the app feels stuck - it's the fastest way to identify a long-held transaction holding a lock. Row shape: one row per (blocked_pid, blocking_pid) pair. A session waiting on multiple blockers appears on multiple rows -- group/deduplicate by `blocked_pid` if you want a per-blocked-session count. Caveat on `relation`: for non-relation waits (transactionid/virtualxid, where the wait is on the blocker's xid rather than a table) `relation` is a best-effort hint -- an alphabetical guess among the blocker's held write-intent locks -- not authoritative. Use the blocked/blocking query text to disambiguate which table is actually contested.

Input parameters:

- `limit` (integer): Max blocked/blocker pairs (default 50).

### `pg_list_roles` (~88 tokens)

List database roles (users and groups) with their login/superuser/createdb/createrole attributes and inherited role memberships. Use this to answer 'who has access to this database?' without needing to read `pg_authid` directly.

Input parameters:

- `includeSystem` (boolean): If true, include built-in `pg_*` roles (pg_read_all_data, pg_monitor, etc.).

### `pg_table_privileges` (~185 tokens)

Show which roles have which privileges (SELECT, INSERT, UPDATE, DELETE, TRUNCATE, REFERENCES, TRIGGER) on a table or on every table in a schema. If `table` is omitted, the result spans every table in `schema`, ordered by table then grantee. Use this to answer 'who can write to this table?' or to audit schema-wide access before a migration. Visibility caveat: backed by `information_schema.table_privileges`, which postgres filters by what the calling role can see. A least-privileged role may not see grants involving unrelated third-party roles. For a complete picture, run as a superuser or a member of `pg_read_all_data`.

Input parameters:

- `schema` (string): Schema name (defaults to 'public').
- `table` (string): Table name. Omit to list privileges for all tables in the schema.

### `pg_kill` (~226 tokens)

Cancel a running query (SIGINT-equivalent) or terminate a backend connection (SIGTERM-equivalent) by PID. Find the PID via `pg_health` active_queries or `pg_inspect_locks`. Requires ALLOW_WRITES=1 since this changes database session state. The role in DATABASE_URL must have permission - cancelling another user's query needs the `pg_signal_backend` role or superuser. Note: `pg_signal_backend` does NOT cover superuser-owned backends - only a superuser can signal another superuser's session. Cancel is graceful; terminate is forceful. When `signaled=false`, the `note` field surfaces postgres's NOTICE explaining why (e.g. 'not a PostgreSQL backend process' for a non-pg PID, 'must be a member of...' for permission denial) so an agent can act on the specific cause rather than guess from a three-way list.

Input parameters:

- `mode` (string): `cancel` aborts the current query; `terminate` closes the connection entirely.
- `pid` (integer, required): Backend PID to signal.

### `pg_replication_status` (~70 tokens)

Replication overview: configured replication slots, connected replicas (from `pg_stat_replication`), and current WAL position. Use on primary to spot lagging or disconnected replicas, on replicas to see upstream status. Returns empty arrays on a standalone (non-replicated) database rather than erroring.

### `pg_advisor` (~267 tokens)

Rolled-up DBA lint pass. One call returns three categories of findings:
\- sequence_exhaustion: SERIAL / BIGSERIAL / IDENTITY sequences whose `last_value` is above `seqExhaustionThreshold` of `max_value`. The classic incident class.
\- tables_without_primary_key: user tables (plain and partitioned) with no PK defined. Bloat candidates and a sign of design drift; some replication setups also need PKs. Foreign tables are excluded -- PostgreSQL forbids declaring PKs on foreign tables.
\- public_tables_without_rls: tables in `public` (or any schema in `rlsSchemas`) with row-level security disabled. Useful as a security baseline check.
Use this as the 'what should I be looking at?' starting point, then drill into `pg_unused_indexes`, `pg_table_bloat`, `pg_seq_scan_tables` for the perf side.

Input parameters:

- `limit` (integer): Max rows per category (default 50).
- `rlsSchemas` (array): Schemas where RLS-missing should be flagged. Defaults to ['public'].
- `seqExhaustionThreshold` (number): Minimum used-fraction (last_value / max_value) to flag a sequence (default 0.5 = 50%).

### `pg_table_bloat` (~362 tokens)

Estimate table bloat (dead tuples + free space) for tables in a schema. Returns live tuples, dead tuples, dead-tuple ratio, last_vacuum / last_autovacuum timestamps, and total relation size. A high dead_ratio with a stale last_autovacuum is a sign a table needs VACUUM.

Three methods are available via the `method` parameter:
\- `estimate` (default): reads pg_stat_user_tables -- fast, no extensions, ANALYZE-driven approximations. Use this first.
\- `approx`: uses pgstattuple_approx() -- fast sampling pass, more accurate than estimates, requires the pgstattuple extension.
\- `exact`: uses pgstattuple() -- full table scan, exact counts, slow on large tables, requires the pgstattuple extension. Always pass `schema` with method='exact' -- scanning all user tables in one statement will hit statement_timeout on non-trivial databases.
Install pgstattuple with `CREATE EXTENSION pgstattuple` (requires superuser).

Input parameters:

- `limit` (integer): Max rows to return (default 50).
- `method` (string): Bloat measurement method. 'estimate' (default) uses pg_stat_user_tables (fast, no extensions). 'approx' uses pgstattuple_approx() (fast sampling, more accurate). 'exact' uses pgstattuple() (full scan…
- `minDeadRatio` (number): Minimum dead-tuple fraction to include - dead / (live + dead). Default 0.1 = 10%.
- `schema` (string): Limit to one schema. If omitted, all user schemas are included.

## Diagnostics

Captured diagnostic sections: Provenance, Dependencies. The full working is on the page: https://verifymcp.io/servers/yawlabs-postgres-mcp/yawlabs-postgres-mcp#diagnostics

## Score history

- 2026-08-06: 68
- 2026-08-05: 68
- 2026-08-04: 67
- 2026-08-03: 63
- 2026-08-02: 63
- 2026-08-01: 48
- 2026-07-31: 48
- 2026-07-30: 55
- 2026-07-29: 73
- 2026-07-28: 73
- 2026-07-27: 73
- 2026-07-26: 41

## Links

- npm package: https://www.npmjs.com/package/@yawlabs/postgres-mcp
- Socket report: https://socket.dev/npm/package/@yawlabs/postgres-mcp
- Changelog RSS feed: https://verifymcp.io/servers/yawlabs-postgres-mcp/yawlabs-postgres-mcp.xml
- Changelog JSON feed: https://verifymcp.io/servers/yawlabs-postgres-mcp/yawlabs-postgres-mcp.json
- HTML version of this page: https://verifymcp.io/servers/yawlabs-postgres-mcp/yawlabs-postgres-mcp
