PostgreSQL MCP server for schema inspection, data exploration, and query execution.
Project description
pglens
Read-only PostgreSQL introspection for AI agents — 28 MCP tools for schema discovery, data exploration, query execution, and health monitoring. Pure pg_catalog, no extensions required.
Why pglens
Most Postgres MCP servers expose query and list_tables, and little else. Agents end up guessing column names, enum values, and join paths, burning several failed attempts before landing on working SQL.
pglens closes those gaps: it lets the agent check what values actually exist in a column, discover foreign-key relationships, preview sample data, and validate query plans — so it can look before it leaps.
column_valuesin particular: agents frequently writeWHERE status = 'active'when the real value is'Active'or'enabled'.column_valuesreturns the actual distinct values with counts, so the agent picks the right one instead of guessing.
How it works
AI agent (MCP client) ──► pglens ──► your PostgreSQL
Claude, etc. MCP server (read-only)
- Your MCP client (Claude Desktop, Claude Code, Zed, …) launches
pglensand connects over MCP. - pglens connects to PostgreSQL using standard libpq environment variables and opens a connection pool.
- The agent calls pglens tools to introspect the schema, sample data, run read-only queries, and inspect health, instead of guessing.
Every query runs inside a readonly=True transaction, identifiers are escaped with PostgreSQL's quote_ident(), and no DDL tools are exposed. All introspection uses pg_catalog directly, so no PostgreSQL extensions are needed. See Safety.
Tools
Schema and discovery
| Tool | What it does |
|---|---|
list_databases |
List configured database aliases (e.g. default, azure_sys) |
database_info |
Server version, database name, current user, encoding, timezone, uptime, size |
list_schemas |
Schemas with table and view counts |
list_tables |
Tables with row counts and descriptions |
list_views |
Views with their SQL definitions |
list_extensions |
Installed extensions and versions |
describe_table |
Columns, types, PKs, FKs, indexes, check constraints |
find_related_tables |
FK relationships in both directions |
find_join_path |
Multi-hop join paths between two tables via foreign keys |
list_indexes |
All indexes across a schema with types, sizes, and usage stats |
list_functions |
Stored functions/procedures with source code |
list_triggers |
Triggers on a table with definitions and status |
list_policies |
Row-level security policies on a table |
Data exploration
| Tool | What it does |
|---|---|
table_row_counts |
Exact row count via COUNT(*) (vs estimated in list_tables) |
sample_rows |
Random rows from a table |
column_values |
Distinct values with frequency counts |
column_stats |
Min, max, null fraction, distinct count, common values |
search_data |
Case-insensitive search across text columns |
search_columns |
Find columns by name across all tables |
search_enum_values |
Enum types and their allowed values |
Query execution
| Tool | What it does |
|---|---|
explain_query |
Query plan without execution |
query |
Read-only SQL with limit/offset pagination (default 500 rows) |
Performance and health
| Tool | What it does |
|---|---|
table_stats |
Index hit rates, dead tuples, vacuum timestamps |
table_sizes |
Disk usage per table, ranked by size |
unused_indexes |
Indexes that are never scanned |
bloat_stats |
Dead tuples, vacuum status, wraparound risk |
active_queries |
Currently running sessions and their queries |
blocking_locks |
Lock wait chains (who blocks whom) |
sequence_health |
Sequences approaching exhaustion |
matview_status |
Materialized view freshness and refresh eligibility |
Safety before DDL
| Tool | What it does |
|---|---|
object_dependencies |
What depends on a given object (views, functions, constraints) |
There is also a query_guide prompt that describes a reasonable workflow for using these tools together.
Installation
uvx (recommended)
uvx runs pglens straight from PyPI with no install step, always fetching the latest published version:
uvx pglens
There is nothing to upgrade; each launch resolves the newest release. Pin a version when you need: uvx pglens@0.4.0.
pip
pip install pglens # install
pip install --upgrade pglens # upgrade later
Requirements: Python 3.11+ and a reachable PostgreSQL server.
Configuration
pglens needs two things: PostgreSQL connection details (via libpq env vars) and an entry in your MCP client. A minimal Claude Desktop config:
{
"mcpServers": {
"pglens": {
"command": "uvx",
"args": ["pglens"],
"env": {
"PGHOST": "localhost",
"PGPORT": "5432",
"PGUSER": "myuser",
"PGPASSWORD": "mypassword",
"PGDATABASE": "mydb"
}
}
}
}
Connecting to PostgreSQL
pglens reads standard PostgreSQL environment variables (libpq). Connection strings (DSNs) are not supported. Credentials live entirely in PG* env vars, so they never appear in arguments or command lines.
| Variable | Required | Purpose |
|---|---|---|
PGHOST |
yes | Postgres host |
PGPORT |
no (default 5432) | Postgres port |
PGUSER |
yes | Username |
PGPASSWORD |
yes (or PGPASSFILE) |
Password |
PGDATABASE |
recommended | Primary dbname; also the default alias when a tool is called without database= |
PGSSLMODE |
no | disable, prefer, require, verify-ca, verify-full |
PGSERVICE, PGPASSFILE, PGAPPNAME, … |
no | Other libpq env vars, honored by asyncpg automatically |
PGLENS_DATABASES |
no | Comma-separated extra dbnames on the same host (see Multiple databases) |
No connection-string env vars are read. Configuration is libpq env vars only.
To run pglens directly from a shell (e.g. for testing):
export PGHOST=localhost
export PGUSER=myuser
export PGPASSWORD=mypassword
export PGDATABASE=mydb
uvx pglens
MCP clients
If you installed pglens with pip instead of using uvx, replace "command": "uvx", "args": ["pglens"] with "command": "pglens" in any of the configs below.
Claude Desktop
{
"mcpServers": {
"pglens": {
"command": "uvx",
"args": ["pglens"],
"env": {
"PGHOST": "localhost",
"PGPORT": "5432",
"PGUSER": "myuser",
"PGPASSWORD": "mypassword",
"PGDATABASE": "mydb"
}
}
}
}
Claude Code
{
"mcpServers": {
"pglens": {
"command": "uvx",
"args": ["pglens"],
"env": {
"PGHOST": "localhost",
"PGDATABASE": "mydb"
}
}
}
}
Zed
{
"context_servers": {
"pglens": {
"command": {
"path": "uvx",
"args": ["pglens"]
}
}
}
}
Multiple databases
Every tool accepts an optional database argument to target an alternate connection. This is useful for Postgres setups that expose system metrics in a separate database — for example, Azure Database for PostgreSQL Flexible Server keeps server metrics in azure_sys.
PGDATABASE is the primary alias and the default target when a tool is called without database=. List any additional dbnames on the same host in PGLENS_DATABASES; each becomes its own alias with its own pool. Host, user, password, and TLS mode come from the standard libpq env vars and are shared across every pool:
{
"mcpServers": {
"pglens": {
"command": "uvx",
"args": ["pglens"],
"env": {
"PGHOST": "myhost.postgres.database.azure.com",
"PGPORT": "5432",
"PGUSER": "admin",
"PGPASSWORD": "...",
"PGSSLMODE": "require",
"PGDATABASE": "app",
"PGLENS_DATABASES": "azure_sys"
}
}
}
}
- Names are used verbatim as Postgres dbnames, which are case-sensitive (e.g.
PGDATABASE=MyAppconnects toMyApp, notmyapp). - If
PGDATABASEis unset butPGLENS_DATABASESis set, the first listed alias becomes the default. - If both are unset, a single
defaultalias relies on libpq's own default behavior. - If the databases you need live on different hosts or require different credentials, run a separate
pglensserver per host with its ownPG*env block.
Discover what is configured with list_databases, then pass the alias as the database argument:
list_databases() -> ["app", "azure_sys"]
table_sizes(schema="public", database="azure_sys")
query(sql="SELECT * FROM query_store.qs_view LIMIT 10", database="azure_sys")
Omit database (or pass None) to use PGDATABASE (the primary alias).
Transport
By default the server uses stdio transport (what every MCP client config above expects). To run as an HTTP server for remote use:
uvx pglens --transport streamable-http
| Flag | Choices | Default | Description |
|---|---|---|---|
--transport |
stdio, streamable-http |
stdio |
MCP transport type |
Safety
- All user-influenced queries run inside
readonly=Truetransactions. - Table and column identifiers are escaped via PostgreSQL's
quote_ident(); values are passed as parameters ($1,$2). - No DDL tools are exposed.
How it's built
adapters/tools/*.py (MCP tool definitions, organized by category)
│
adapters/mcp_adapter.py (FastMCP server, lifespan, pool management)
│
adapters/asyncpg_adapter.py (SQL queries, asyncpg pool)
│
PostgreSQL
AsyncpgDatabase holds the asyncpg pool and all query methods. Tool modules in adapters/tools/ are thin wrappers that register MCP tools via decorators and delegate to it. All queries use pure pg_catalog introspection — no PostgreSQL extensions required.
Adding a tool:
- Add a method to
AsyncpgDatabaseinadapters/asyncpg_adapter.py. - Add a
@mcp.tool()function in the appropriateadapters/tools/*.pymodule.
License
MIT
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file pglens-1.0.0.tar.gz.
File metadata
- Download URL: pglens-1.0.0.tar.gz
- Upload date:
- Size: 102.0 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
07b368fbaae02e299facfac12d1329d8641cbda2ff825548f57804068c06f592
|
|
| MD5 |
222fca5805b347e6f3b2114f98328f64
|
|
| BLAKE2b-256 |
d9c02112a6c2d7af9a30220bfd69e712de2f0ab231f579c51c581af8fc281c8c
|
Provenance
The following attestation bundles were made for pglens-1.0.0.tar.gz:
Publisher:
release.yml on janbjorge/pglens
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
pglens-1.0.0.tar.gz -
Subject digest:
07b368fbaae02e299facfac12d1329d8641cbda2ff825548f57804068c06f592 - Sigstore transparency entry: 1962753823
- Sigstore integration time:
-
Permalink:
janbjorge/pglens@a24f05fe31ae73480e30a64de67c032b39ebc392 -
Branch / Tag:
refs/tags/v1.0.0 - Owner: https://github.com/janbjorge
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@a24f05fe31ae73480e30a64de67c032b39ebc392 -
Trigger Event:
push
-
Statement type:
File details
Details for the file pglens-1.0.0-py3-none-any.whl.
File metadata
- Download URL: pglens-1.0.0-py3-none-any.whl
- Upload date:
- Size: 25.5 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
b1e7b6fe1267b37d1aa25aca56b2acae048fdf4fc616f6e13f2a8ea9debd3010
|
|
| MD5 |
f7c6f47a0447c27795ec7c97856c5468
|
|
| BLAKE2b-256 |
4643fdbc9e9923f327de0382fb2f90815479f64b5131ec6a5fb31fd593751384
|
Provenance
The following attestation bundles were made for pglens-1.0.0-py3-none-any.whl:
Publisher:
release.yml on janbjorge/pglens
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
pglens-1.0.0-py3-none-any.whl -
Subject digest:
b1e7b6fe1267b37d1aa25aca56b2acae048fdf4fc616f6e13f2a8ea9debd3010 - Sigstore transparency entry: 1962753955
- Sigstore integration time:
-
Permalink:
janbjorge/pglens@a24f05fe31ae73480e30a64de67c032b39ebc392 -
Branch / Tag:
refs/tags/v1.0.0 - Owner: https://github.com/janbjorge
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@a24f05fe31ae73480e30a64de67c032b39ebc392 -
Trigger Event:
push
-
Statement type: