list_schemasLABSCO SUMMARY
Install the two extensions or you get a schema browser.
Index tuning needs hypopg to simulate plans and pg_stat_statements to know what your workload actually runs — without them the interesting half of this server is unavailable. On managed RDS, Azure or Cloud SQL both are usually present and a CREATE EXTENSION is enough; self-managed Postgres needs a config change and a restart. Access mode is a launch flag, so read-only is a deployment decision rather than a prompt.
What it is
A Postgres MCP server aimed at the whole development lifecycle, not just query execution. Alongside schema browsing and SQL it does industrial-strength index tuning, simulates plans with indexes that do not exist yet, and runs comprehensive database health checks. Everything is exposed as tools; it does not use MCP resources.
What you get
- Schema exploration: schemas listed, objects listed within one, and a single object's columns, constraints and indexes —
list_schemas,list_objects,get_object_details - SQL executed, with read-only limits applied when the server runs in restricted mode —
execute_sql - Execution plans exposing the planner's cost model, and the same plan simulated with hypothetical indexes —
explain_query - The slowest queries by total execution time from
pg_stat_statements—get_top_queries - Index recommendations two ways: analyse the whole workload to find resource-intensive queries and recommend indexes for them, or hand over up to 10 specific queries —
analyze_workload_indexes,analyze_query_indexes - A comprehensive health report: buffer cache hit rates, connection health, constraint validation, duplicate, unused and invalid indexes, sequence limits and vacuum health —
analyze_db_health - Both stdio and SSE transports, so several clients can share one server
Requirements
Database credentials in DATABASE_URI, and either Docker or Python 3.12 or higher. Run crystaldba/postgres-mcp as a container — it remaps localhost to reach your host automatically — or install with pipx or uv. Access mode is explicit on the command line, with --access-mode=unrestricted opting out of read-only. --transport=sse serves multiple clients on a port. Index tuning and full performance analysis need two extensions loaded on the database: CREATE EXTENSION for pg_stat_statements and hypopg; on self-managed Postgres, pg_stat_statements must also be listed in shared_preload_libraries. Version 0.3.0.
Setup effort
One command plus credentials — docker pull crystaldba/postgres-mcp, then supply credentials
USE CASES
Making a slow application faster without guessing
Pull the slowest queries by total execution time, then have the workload analysed and indexes recommended for the ones that are actually costing you.
Checking whether an index would help before creating it
Explain the query with the hypothetical index in place and compare the plan — no write to the database, no waiting for a build.
Doing a health pass on a database you inherited
Run the health check and read cache hit rates, connection state, unvalidated constraints, duplicate and unused indexes, sequence headroom and vacuum status in one report.
Letting an agent explore production without write access
Run it in restricted mode so execute_sql is read-only, and keep schema browsing and diagnostics fully available.
Tuning a handful of queries you already know are bad
Hand over up to 10 specific statements and get index recommendations for that set, rather than analysing the whole workload.
9 tools
Listed in project docs. Your client may show a different list.
list_objectsget_object_detailsexecute_sqlexplain_queryget_top_queriesanalyze_workload_indexesanalyze_query_indexesanalyze_db_healthBEFORE YOU INSTALL
What to know first
Tools you get
9 tools named in the project's documentation.
Requires
- Docker, or Python 3.12 or higher
- DATABASE_URI in your environment
INSTALLATION
Connect your client
Server setup
docker pull crystaldba/postgres-mcpMCP Clients
Claude Desktop — Docker
{
"mcpServers": {
"postgres": {
"command": "docker",
"args": [
"run",
"-i",
"--rm",
"-e",
"DATABASE_URI",
"crystaldba/postgres-mcp",
"--access-mode=unrestricted"
],
"env": {
"DATABASE_URI": "postgresql://<YOUR_DB_USER>:<YOUR_DB_PASSWORD>@localhost:5432/<YOUR_DB_NAME>"
}
}
}
}OPENAI_API_KEY is needed only for the experimental index tuning by LLM. To use it, set it wherever that route sets DATABASE_URI: in the env block (and, for Docker, add another "-e", "OPENAI_API_KEY" pair to args), or as another -e OPENAI_API_KEY=… flag on the docker run command that starts the SSE server.
Claude Desktop — uvx
{
"mcpServers": {
"postgres": {
"command": "uvx",
"args": [
"postgres-mcp",
"--access-mode=unrestricted"
],
"env": {
"DATABASE_URI": "postgresql://<YOUR_DB_USER>:<YOUR_DB_PASSWORD>@localhost:5432/<YOUR_DB_NAME>"
}
}
}
}OPENAI_API_KEY is needed only for the experimental index tuning by LLM. To use it, set it wherever that route sets DATABASE_URI: in the env block (and, for Docker, add another "-e", "OPENAI_API_KEY" pair to args), or as another -e OPENAI_API_KEY=… flag on the docker run command that starts the SSE server.
Cursor
The DATABASE_URI in the command is a sample; yours is your own connection URI. Use --access-mode=restricted instead for read-only access.
Start the server with the SSE transport
docker run -p 8000:8000 \
-e DATABASE_URI=postgresql://<YOUR_DB_USER>:<YOUR_DB_PASSWORD>@localhost:5432/<YOUR_DB_NAME> \
crystaldba/postgres-mcp --access-mode=unrestricted --transport=sseAdd this to Cursor's mcp.json
{
"mcpServers": {
"postgres": {
"type": "sse",
"url": "http://localhost:8000/sse"
}
}
}OPENAI_API_KEY is needed only for the experimental index tuning by LLM. To use it, set it wherever that route sets DATABASE_URI: in the env block (and, for Docker, add another "-e", "OPENAI_API_KEY" pair to args), or as another -e OPENAI_API_KEY=… flag on the docker run command that starts the SSE server.
Windsurf
The DATABASE_URI in the command is a sample; yours is your own connection URI. Use --access-mode=restricted instead for read-only access.
Start the server with the SSE transport
docker run -p 8000:8000 \
-e DATABASE_URI=postgresql://<YOUR_DB_USER>:<YOUR_DB_PASSWORD>@localhost:5432/<YOUR_DB_NAME> \
crystaldba/postgres-mcp --access-mode=unrestricted --transport=sseAdd this to Windsurf's mcp_config.json
{
"mcpServers": {
"postgres": {
"type": "sse",
"serverUrl": "http://localhost:8000/sse"
}
}
}OPENAI_API_KEY is needed only for the experimental index tuning by LLM. To use it, set it wherever that route sets DATABASE_URI: in the env block (and, for Docker, add another "-e", "OPENAI_API_KEY" pair to args), or as another -e OPENAI_API_KEY=… flag on the docker run command that starts the SSE server.
Cline
The DATABASE_URI in the command is a sample; yours is your own connection URI. Use --access-mode=restricted instead for read-only access.
Start the server with the SSE transport
docker run -p 8000:8000 \
-e DATABASE_URI=postgresql://<YOUR_DB_USER>:<YOUR_DB_PASSWORD>@localhost:5432/<YOUR_DB_NAME> \
crystaldba/postgres-mcp --access-mode=unrestricted --transport=sseAdd this to Cline's cline_mcp_settings.json
{
"mcpServers": {
"postgres": {
"type": "sse",
"url": "http://localhost:8000/sse"
}
}
}OPENAI_API_KEY is needed only for the experimental index tuning by LLM. To use it, set it wherever that route sets DATABASE_URI: in the env block (and, for Docker, add another "-e", "OPENAI_API_KEY" pair to args), or as another -e OPENAI_API_KEY=… flag on the docker run command that starts the SSE server.