pg_licht
A PostgreSQL MCP server that exposes schema exploration tools
Documentation
One read-only PostgreSQL MCP server for every database you run.
pg-licht gives an AI assistant 68 tools and 12 guided investigations for exploring schemas, reading statistics and diagnosing live servers. It can never change your data, it never returns rows from your tables, and it answers for a whole fleet from a single process.
68 read-only tools in 10 groups
12 guided investigations
1 process for any number of connections
0 statements that can write
14–18 PostgreSQL majors tested on every change
One server for the whole fleet
Every connected tool takes a connection argument, so one pg-licht answers for one database or for hundreds. Connections are named once, in one file, and opened only when a call needs them.
One pg-licht
A hundred databases, one entry in your MCP client
- One process, one connections file, credentials kept in libpq's own
.pgpassand service files. - One tool listing in the agent's session. Its size is the same with 1, 150 or 1,000 connections configured, and startup stays under a tenth of a second.
- Connections open when a call needs them; up to 32 idle ones stay warm, and the least recently used is closed first.
- Ask a replication group, an instance or any labelled group in one call: up to 32 members per call, 16 at a time, one result per member, and any beyond 32 listed as skipped rather than silently left out.
One server per database
A hundred entries, a hundred sets of settings
- A hundred processes to start, update and keep logged in.
- A hundred copies of the same tools, each under a different server name, for the agent to tell apart.
- A client that loads tool definitions up front carries every copy in the session.
- No way to ask the same question of a replica and its primary in one call.
Measured with 1, 150 and 1,000 configured connections. One caveat worth knowing: listConnections returns every connection in one answer, roughly 400 bytes each, so on a very large fleet prefer naming the connection you want.
Read-only by construction, not by convention
Safety is enforced by PostgreSQL on every call, not left to how the assistant behaves.
A read-only transaction per call
Every tool runs inside its own READ ONLY transaction. A write, even one reached through a bug, fails in PostgreSQL rather than succeeding.
A timeout on every statement
statement_timeout is set on the same transaction, two minutes by default and set per connection, so no call can hold a backend indefinitely.
No SQL built from arguments
Catalog queries are parameterized. No schema, table or search term is ever concatenated into SQL text.
Plans are proven before anything runs
explainQuery executes a statement only after its plan is shown to contain no write, and only within a memory and CPU budget taken from the host you declared.
Safe behind PgBouncer
The guards are transaction-scoped, so they hold under transaction pooling. The whole suite runs through a real PgBouncer on every change.
Honest about privileges
checkPrivileges says which tools the connecting role can really use: 56 of 68 for a bare login role, 63 with pg_monitor.
It reads the catalog, not your tables
pg-licht describes structure and statistics. It is built so that an assistant can understand a database without seeing what is in it, and it is exact about the few places where values can still appear.
Never
- Writes, alters or deletes anything.
- Returns rows from a table. The two tools that read one return only a yes or no (
checkKey) or a plan with row counts (explainQuery). - Returns a password it holds, a user mapping's options, or a subscription's connection string.
Where values can appear (10 tools, each marked)
- Statement text: what sessions are running now, and what
pg_stat_statementsrecorded. - Column statistics: most common values and histogram bounds.
- Plans and definitions, which repeat the literals written into them.
- Settings, such as a standby's
primary_conninfo.
Each of these is limited further by PostgreSQL's own permissions: a role without SELECT on a column gets none of its statistics, and a role without pg_read_all_stats sees only its own statements. Connect as the narrowest role that answers the question. The full list is under What reaches the caller in the manual.
Built for the questions operators actually ask
Guided investigations
12 prompts walk the assistant through slow queries, lock contention, deadlocks, bloat, disk space, replication slots and schema changes, tool by tool.
Topology aware
Label connections by instance and replication group. Roles are observed on each call, never configured, and verifyTopology checks the labels against the servers.
Answers a client can check
Every tool declares its output schema and is marked read-only, and clients on MCP 2025-06-18 or later receive structured results.
Tested where it matters
PostgreSQL 14 through 18, under AddressSanitizer, ThreadSanitizer and valgrind, through a pooler, with a standby, a cascading standby and a logical subscriber, built with both g++ and clang.
Packages that are installed before they ship
Every .deb and .rpm is installed on the distribution it targets and run before a release is published.
Documentation generated from the binary
The reference and llms.txt are built from the release itself, so they cannot describe a version that does not exist.
68 tools, grouped by what they answer
- Privileges 4
- Schema exploration 19
- Catalog search 3
- Cluster-wide objects 7
- Extensibility and text search 4
- Foreign data and replication 7
- Monitoring and statistics 15
- Diagnostics and query planning 6
- Topology 2
- Connections 1
Install
brew tap sqlambda/pg-licht
brew install pg-licht
claude mcp add --transport stdio pg-licht \
-e DATABASE_URL="postgresql://user@host/db" \
-- pg_licht_mcp
# ~/.config/pg_licht/connections.ini
[billing_prod]
service = billing_ro
instance = pg-prod-01
[billing_replica]
service = billing_replica_ro
replication_group = billing-ha
Debian 13 and Rocky Linux 9 packages and tarballs are on the releases page; see INSTALL for each.