A surprising amount of debugging boils down to “let me check the database.” Schema lookups, sample row counts, “is this index actually being used,” EXPLAIN plans. None of that is hard to do in psql, but pulling Claude Code into the loop means copy-pasting schemas and query results back and forth, which gets old.
postgresql-mcp is an MCP server that gives Claude Code direct, read-only access to PostgreSQL. Claude does the inspection, you keep the conversation flowing.
Read-only on purpose
The first design choice was the most important: only SELECT and WITH statements are accepted. Not because I don’t trust LLMs to write valid UPDATE statements — they usually do — but because I don’t want to find out the hard way that they sometimes don’t.
If you need to mutate the database, use a tool meant for that. This one is for inspection.
The available tools are exactly the ones I’d reach for in psql:
list_databases,list_schemas,list_tables— discoverydescribe_table— columns, types, constraints, defaultslist_indexes— with usage statisticsexecute_query— read-only SQLexplain_query— query plansget_table_stats— row counts, sizes
That’s the whole surface area. Nine tools, all read-only, all the things you’d type yourself if you were doing this manually.
Install
Homebrew if you’re on macOS or Linux:
brew tap sgaunet/homebrew-tools
brew install sgaunet/tools/postgresql-mcp
Or download a binary from the releases page and drop it on your PATH.
Project-local configuration
Database connections are project-specific, so I usually configure the MCP server in the project’s .mcp.json:
{
"mcpServers": {
"postgres": {
"type": "stdio",
"command": "postgresql-mcp",
"args": [],
"env": {
"POSTGRES_URL": "postgres://postgres:password@localhost:5432/postgres?sslmode=disable"
}
}
}
}
Don’t forget to add .mcp.json to .gitignore if your connection string has credentials in it.
You can also use globally-set environment variables:
export POSTGRES_URL="postgres://user:password@localhost:5432/mydb?sslmode=prefer"
# DATABASE_URL works as a fallback
What it looks like in use
Inside Claude Code, this is the kind of thing that just works now:
List all tables in the public schema
Describe the users table
Execute query: SELECT count(*) FROM orders WHERE created_at > now() - interval '7 days'
Explain this query: SELECT * FROM orders WHERE customer_id = 42
The EXPLAIN tool is the one I underestimated. Asking “why is this query slow?” and having Claude pull the plan, summarize it, and suggest an index — all in one turn — is genuinely useful for performance work.
Tuning
A few environment variables let you tune connection behavior, but the defaults are sensible for everyday work:
| Variable | Default |
|---|---|
POSTGRES_MCP_MAX_OPEN_CONNS | 10 |
POSTGRES_MCP_MAX_IDLE_CONNS | 5 |
POSTGRES_MCP_CONN_MAX_LIFETIME | 3600s |
POSTGRES_MCP_CONN_MAX_IDLE_TIME | 600s |
POSTGRES_MCP_MAX_RESULT_ROWS | 10000 |
The result-row cap matters: it stops Claude from accidentally pulling a million rows into the conversation context if it forgets a LIMIT.
Connection management
Before each tool call, the server pings the database. If the ping fails — say, your database restarted — it tries one automatic reconnection using the original parameters. If that succeeds, the operation proceeds. If it fails, you get an error and can reconnect explicitly via the connect_database tool.
No retry loop, no exponential backoff. Just one shot at recovery, then bail. For environments where connections drop a lot, tune POSTGRES_MCP_CONN_MAX_LIFETIME lower so connections cycle proactively.
Where to find it
- Source: github.com/sgaunet/postgresql-mcp
- License: MIT
If you’re already running PostgreSQL behind a project you’re working on with Claude Code, this is a low-friction add-on. The read-only constraint keeps things safe; the EXPLAIN integration is what’ll make you keep it installed.