pg_licht
A PostgreSQL MCP server that exposes schema exploration tools
Install
Install and configure the MCP from https://sqlambda.github.io/pg_licht/ now. Follow the documentation's installation instructions, ask me for anything you can't complete yourself, and verify its tools load.One read-only PostgreSQL MCP server for every database you run.
pg-licht gives an AI assistant 75 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.
75 read-only tools in 11 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 is roughly 400 bytes a connection, so on a large fleet ask for the ones you want with pattern. listConnections and listTopology both take one, matched against names and labels.
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 call to a database runs inside its own READ ONLY transaction. A write, even one reached through a bug, fails in PostgreSQL rather than succeeding. The pooler tools send PgBouncer only SHOW, as the stats user you give them, which PgBouncer allows nothing else.
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: 59 of 72 database tools for a bare login role, 67 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.
- Returns a constant
pg_qualstatsrecorded. It keeps the literal of every predicate it samples;predicateStatsandsuggestIndexesreadtable.column = ?, and a test fails the build if a seeded literal ever appears in either.
Where values can appear (13 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.
Beyond what PostgreSQL measures
Where pg_wait_sampling, pg_stat_kcache and pg_qualstats are installed: what the server has been waiting on, the CPU and real disk reads behind each statement, which predicates throw rows away, and the index that would serve them — all joined on the query_id statementStats reports, and each suggestion ready to test with evaluateIndex without building it. A counter the server's platform does not measure is reported as unavailable, with the reason, never as zero.
Answers you can cap
The listings and searches that grow with a database take a pattern or a narrower search, and the largest answers narrow themselves by default. Set a cap in budgets.ini and an answer past it is refused with the arguments that make it smaller, rather than returned.
The pooler too
Point a section at PgBouncer's admin console and ask whether the pools keep up: clients waiting for a server and for how long, servers in use against each pool's size, and the connections and settings behind them — read with SHOW alone, as the stats user you give them.
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, UndefinedBehaviorSanitizer, ThreadSanitizer and valgrind, through a pooler, with a standby, a cascading standby, a logical subscriber and a split brain, built with both g++ and clang, and on FreeBSD 14 and 15. The protocol and both configuration parsers are fuzzed on every change, and clang-tidy fails the build on any finding.
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, its version checked against the binary inside it, and the Homebrew formula is built from source on every change.
Downloads you can verify
Every release asset carries signed build provenance: gh attestation verify proves a file was built by this repository's release workflow from the tagged commit. A SHA256SUMS file is published beside them for a check that needs no GitHub CLI.
Hardened binaries
The Linux binaries are position-independent, with full RELRO, a non-executable stack, stack protection and fortified libc calls — and each release binary is inspected for them before it ships, rather than trusting the flags that were asked for.
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.
75 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 17
- Diagnostics and query planning 8
- Topology 2
- Connections 1
- Connection poolers 3
Two arguments narrow an answer by a string. The listings take pattern, a literal substring of a name: user_ finds user_x, not users. The three search tools take web_search, full-text search over names, comments and source, where order finds orders but ord finds nothing. Which tool takes which.
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.
