Connects your AI assistant directly to PostgreSQL databases through 17 consolidated tools that handle everything from schema management to real-time monitoring. You can execute parameterized queries, perform CRUD operations, analyze performance bottlenecks, manage users and permissions, and troubleshoot connection issues without switching contexts. The server consolidates what used to be 46 separate tools into intelligent meta-tools, so your AI can discover and use database operations more effectively. Supports connection strings, environment variables, Docker deployment, and includes SQL injection protection with connection pooling for production use.
A Model Context Protocol (MCP) server that provides comprehensive PostgreSQL database management capabilities for AI assistants.
🚀 What's New: This server has been completely redesigned from 46 individual tools to 18 intelligent tools through consolidation (34→8 meta-tools) and enhancement (+4 new tools), providing better AI discovery while adding powerful data manipulation and comment management capabilities.
Version 2.0.0 introduces security boundaries that intentionally change default behavior from the 1.x line:
readonly mode. Mutations, DDL, role administration, filesystem import/export, and arbitrary SQL require --security-mode write, --security-mode admin, or --security-mode unsafe as appropriate.--allow-destructive.connectionString, sourceConnectionString, and targetConnectionString arguments are disabled by default. Use server-level --connection-string or POSTGRES_CONNECTION_STRING, or explicitly opt in with --allow-tool-connection-string.where clauses are rejected for mutation, index, export, and copy filters. Use structured where predicates, or rawWhere only with --security-mode unsafe --allow-destructive.pg_execute_sql calls must use transactional: true, expectRows: false, and no bind parameters.For the non-breaking security patch line, use @henkey/postgres-mcp-server@1.0.7.
# Install globally
npm install -g @henkey/postgres-mcp-server
# Or run directly with npx (no installation)
# Use env var for connection string (optional)
export POSTGRES_CONNECTION_STRING="postgresql://user:pass@localhost:5432/db"
npx @henkey/postgres-mcp-server
# Or pass directly:
npx @henkey/postgres-mcp-server --connection-string "postgresql://user:pass@localhost:5432/db"
npx @henkey/postgres-mcp-server --help
Add to your MCP client configuration:
{
"mcpServers": {
"postgresql-mcp": {
"command": "npx",
"args": [
"@henkey/postgres-mcp-server",
"--connection-string", "postgresql://user:password@host:port/database"
]
}
}
}
npx -y @smithery/cli install @HenkDz/postgresql-mcp-server --client claude
# Build the Docker image
docker build -t postgres-mcp-server .
# Run with environment variable
docker run -i --rm \
-e POSTGRES_CONNECTION_STRING="postgresql://user:password@host:port/database" \
postgres-mcp-server
Add to your MCP client configuration:
{
"mcpServers": {
"postgresql-mcp": {
"command": "docker",
"args": [
"run",
"-i",
"--rm",
"henkey/postgres-mcp:latest",
"-e",
"POSTGRES_CONNECTION_STRING"
],
"env": {
"POSTGRES_CONNECTION_STRING": "postgresql://user:password@host:port/database"
}
}
}
}
git clone <repository-url>
cd postgresql-mcp-server
npm install
npm run build
Add to your MCP client configuration:
{
"mcpServers": {
"postgresql-mcp": {
"command": "node",
"args": [
"/path/to/postgresql-mcp-server/build/index.js",
"--connection-string", "postgresql://user:password@host:port/database"
]
}
}
}
The server now starts in readonly mode by default. Tools may still be listed for MCP discovery, but every call is classified and checked before it reaches the database.
| Mode | Allows | Blocks by default |
|---|---|---|
readonly | schema inspection, analysis, monitoring, SELECT-style query tools | mutations, DDL, role changes, filesystem import/export, arbitrary SQL |
write | readonly operations plus data mutations | DDL, role changes, filesystem import/export, arbitrary SQL |
admin | write operations plus schema, index, function, trigger, RLS, role, and filesystem tools | arbitrary SQL |
unsafe | all tool categories, including arbitrary SQL | destructive operations unless explicitly allowed |
Destructive operations such as drops, resets, and arbitrary SQL also require explicit opt-in:
# Default: readonly, no per-tool connection strings
npx @henkey/postgres-mcp-server --connection-string "postgresql://readonly_user:pass@host:5432/db"
# Enable DML mutations, but still block DDL/admin/arbitrary SQL
npx @henkey/postgres-mcp-server --security-mode write --connection-string "postgresql://app_writer:pass@host:5432/db"
# Enable admin tools and destructive operations
npx @henkey/postgres-mcp-server --security-mode admin --allow-destructive --connection-string "postgresql://admin_user:pass@host:5432/db"
# Enable arbitrary SQL only for trusted local/admin use
npx @henkey/postgres-mcp-server --security-mode unsafe --allow-destructive --connection-string "postgresql://admin_user:pass@host:5432/db"
Per-tool connectionString, sourceConnectionString, and targetConnectionString arguments are disabled by default. Prefer a fixed server-level connection string with a least-privilege PostgreSQL role. For development only, enable per-tool connection strings with --allow-tool-connection-string or POSTGRES_MCP_ALLOW_TOOL_CONNECTION_STRING=true.
Explicit per-tool, CLI, and POSTGRES_CONNECTION_STRING values must be non-empty strings. Blank higher-priority connection strings fail validation instead of falling back to lower-priority sources.
Optionally restrict all server-level and per-tool connection strings to an allowlist with --allowed-connection-target, allowedConnectionTargets, or POSTGRES_MCP_ALLOWED_CONNECTION_TARGETS. Target patterns use [user@]host[:port][/database]; omitted fields are unconstrained and * is allowed only as a full-field wildcard, for example readonly@db.internal:5432/app or *@localhost:*/dev.
For deployment grants, see PostgreSQL Role Templates. The templates split readonly, writer, schema-admin, and role-admin credentials so the PostgreSQL role remains aligned with the selected MCP securityMode.
Security settings can also be placed in the tools config file:
{
"securityMode": "readonly",
"allowDestructive": false,
"allowToolConnectionString": false,
"workspaceDir": "/path/to/mcp-workspace",
"auditFile": "/path/to/postgres-mcp-audit.jsonl",
"maxConnections": 20,
"idleTimeoutMillis": 30000,
"connectionTimeoutMillis": 2000,
"maxFileBytes": 10485760,
"statementTimeoutMs": 30000,
"queryTimeoutMs": 45000,
"lockTimeoutMs": 10000,
"idleInTransactionSessionTimeoutMs": 60000,
"allowedConnectionTargets": [
"readonly@db.internal:5432/app"
],
"enabledTools": [
"pg_analyze_database",
"pg_manage_schema",
"pg_execute_query"
]
}
Runtime configuration precedence is CLI options, then the tools config file, then environment variables. Explicit false values in the tools config override enabling environment variables such as POSTGRES_MCP_ALLOW_DESTRUCTIVE=true.
If a tools config path is provided, the server treats it as required: unreadable, malformed, non-object, incorrectly typed, unknown-key, invalid securityMode, or unknown enabledTools entries stop startup instead of falling back to all available tools.
CLI options:
--version--connection-string--tools-config--security-mode--allow-destructive--allow-tool-connection-string--workspace-dir--audit-file--max-connections--idle-timeout-ms--connection-timeout-ms--max-file-bytes--statement-timeout-ms--query-timeout-ms--lock-timeout-ms--idle-in-transaction-session-timeout-ms--allowed-connection-targetEnvironment variables:
POSTGRES_TOOLS_CONFIG=/path/to/tools.jsonPOSTGRES_MCP_SECURITY_MODE=readonly|write|admin|unsafePOSTGRES_MCP_ALLOW_DESTRUCTIVE=truePOSTGRES_MCP_ALLOW_TOOL_CONNECTION_STRING=truePOSTGRES_MCP_WORKSPACE_DIR=/path/to/mcp-workspacePOSTGRES_MCP_AUDIT_FILE=/path/to/postgres-mcp-audit.jsonlPOSTGRES_MCP_MAX_CONNECTIONS=20POSTGRES_MCP_IDLE_TIMEOUT_MS=30000POSTGRES_MCP_CONNECTION_TIMEOUT_MS=2000POSTGRES_MCP_MAX_FILE_BYTES=10485760POSTGRES_MCP_STATEMENT_TIMEOUT_MS=60000POSTGRES_MCP_QUERY_TIMEOUT_MS=65000POSTGRES_MCP_LOCK_TIMEOUT_MS=10000POSTGRES_MCP_IDLE_IN_TRANSACTION_SESSION_TIMEOUT_MS=60000POSTGRES_MCP_ALLOWED_CONNECTION_TARGETS=readonly@db.internal:5432/app,*@localhost:*/devPOSTGRES_MCP_DEBUG_SQL=true to opt into verbose pg-monitor SQL tracing. This may log raw SQL and bind values, so leave it disabled unless you are debugging a trusted local database.Boolean environment flags must be exactly true or false when set.
Numeric resource settings from CLI, tools config, or environment variables must be positive integers. Runtime defaults use a 20-connection pool, a 30000 ms pool idle timeout, a 2000 ms connection timeout, a 60000 ms PostgreSQL statement_timeout, a 65000 ms node-postgres query timeout, a 10000 ms PostgreSQL lock_timeout, and a 60000 ms PostgreSQL idle_in_transaction_session_timeout. Pool and timeout settings can be raised or lowered with --max-connections, --idle-timeout-ms, --connection-timeout-ms, --statement-timeout-ms, --query-timeout-ms, --lock-timeout-ms, --idle-in-transaction-session-timeout-ms, maxConnections, idleTimeoutMillis, connectionTimeoutMillis, statementTimeoutMs, queryTimeoutMs, lockTimeoutMs, idleInTransactionSessionTimeoutMs, POSTGRES_MCP_MAX_CONNECTIONS, POSTGRES_MCP_IDLE_TIMEOUT_MS, POSTGRES_MCP_CONNECTION_TIMEOUT_MS, POSTGRES_MCP_STATEMENT_TIMEOUT_MS, POSTGRES_MCP_QUERY_TIMEOUT_MS, POSTGRES_MCP_LOCK_TIMEOUT_MS, or POSTGRES_MCP_IDLE_IN_TRANSACTION_SESSION_TIMEOUT_MS.
Explicit connection string, workspaceDir, auditFile, --workspace-dir, and --audit-file values must be non-empty strings.
Connection target allowlists are enforced before tool execution for per-tool connection strings and during connection resolution for server-level sources. When an allowlist is configured, connection strings must be PostgreSQL URL or keyword-style strings with an explicit host or hostaddr.
Mutation, index, export, and copy filters should use structured where predicates. Legacy string where clauses are rejected; the explicit rawWhere escape hatch is treated as arbitrary SQL and requires --security-mode unsafe --allow-destructive.
EXPLAIN tools only accept one read-only statement and run inside a read-only transaction. analyze: true still requires unsafe mode because PostgreSQL executes the supplied query to collect runtime statistics.
Multi-statement pg_execute_sql calls must use transactional: true, expectRows: false, and no bind parameters. Use a single parameterized statement or CTE when bind parameters are needed.
Error messages, diagnostics, and catalog metadata are sanitized by default. SQL text from pg_stat_statements, function definitions, RLS predicates, check constraints, index definitions, and column defaults are redacted unless they are intentionally returned as user data.
Data execution, query/performance, schema, index, constraint, user/permission, trigger, comment, function, RLS, migration, and diagnostic tools reject unknown input fields so misspelled or unintended parameters fail before connection resolution.
Denied security-boundary requests emit one structured stderr line prefixed with [MCP Audit]. Audit events include sanitized fields such as toolName, reason, securityMode, risk, and whether per-tool connection strings were present; they do not log raw SQL, full request payloads, or connection-string passwords. Set POSTGRES_MCP_AUDIT_FILE, --audit-file, or auditFile to append the same sanitized audit events to a JSONL file.
Filesystem tools such as table export/import require a workspace directory and only read or write .json and .csv files inside it:
npx @henkey/postgres-mcp-server \
--security-mode admin \
--allow-destructive \
--workspace-dir /path/to/mcp-workspace \
--connection-string "postgresql://admin_user:pass@host:5432/db"
18 powerful tools organized into three categories:
Brand new capabilities not available in the original 46 tools
// Analyze database performance
{ "analysisType": "performance", "schema": "public" }
// Create a table with constraints
{
"operation": "create_table",
"tableName": "users",
"columns": [
{ "name": "id", "type": "SERIAL PRIMARY KEY" },
{ "name": "email", "type": "VARCHAR(255) UNIQUE NOT NULL" }
]
}
// Query data with parameters
{
"operation": "select",
"query": "SELECT * FROM users WHERE created_at > $1",
"parameters": ["2024-01-01"],
"limit": 100
}
// Select results are always bounded: default limit 100, max 1000.
// Insert new data
{
"operation": "insert",
"table": "users",
"data": {"name": "John Doe", "email": "john@example.com"},
"returning": "*",
"maxReturningRows": 100
}
// Mutation RETURNING output is capped in the response: default 100, max 1000.
// Find slow queries
{
"operation": "get_slow_queries",
"limit": 5,
"minDuration": 100
}
// Execute a parameterized SELECT query
{
"operation": "select",
"query": "SELECT * FROM users WHERE id = $1",
"parameters": [1]
}
// Perform an INSERT mutation
{
"operation": "insert",
"table": "products",
"data": {"name": "New Product", "price": 99.99},
"returning": "id",
"maxReturningRows": 100
}
// Perform an UPDATE mutation with a structured WHERE predicate
{
"operation": "update",
"table": "products",
"data": {"price": 89.99},
"where": {"id": 123},
"returning": ["id", "price"]
}
// Manage database object comments
{
"operation": "set",
"objectType": "table",
"objectName": "users",
"comment": "Main user account information table"
}
📋 Complete Tool Schema Reference - All 18 tool parameters & examples in one place
For additional information, see the docs/ folder:
✅ 34→8 meta-tools - Intelligent consolidation for better AI discovery
✅ Multiple operations per tool - Unified schemas with operation parameters
✅ Smart parameter validation - Clear error messages and type safety
✅ Complete CRUD operations - INSERT/UPDATE/DELETE/UPSERT with parameterized queries
✅ Flexible querying - SELECT with count/exists support and bounded safety limits
✅ Arbitrary SQL execution - Transaction support for complex operations
✅ Controlled connection - CLI args or env vars by default; per-tool connection strings require opt-in ✅ Security focused - Read-only default mode, centralized policy checks, structured mutation predicates ✅ Robust architecture - Connection pooling, comprehensive error handling
The PostgreSQL MCP Server is fully Docker-compatible and can be used in production environments. The image uses a multi-stage build, installs only production dependencies in the runtime stage, and runs as the non-root node user.
# Build locally
docker build -t postgres-mcp-server .
# Or pull from Docker Hub
docker pull henkey/postgres-mcp:latest
# Basic usage (using Docker Hub image)
docker run -i --rm \
-e POSTGRES_CONNECTION_STRING="postgresql://user:password@host:port/database" \
henkey/postgres-mcp:latest
# Or with locally built image
docker run -i --rm \
-e POSTGRES_CONNECTION_STRING="postgresql://user:password@host:port/database" \
postgres-mcp-server
# With tools configuration
docker run -i --rm \
-e POSTGRES_CONNECTION_STRING="postgresql://user:password@host:port/database" \
-e POSTGRES_TOOLS_CONFIG="/app/config/tools.json" \
-v /path/to/config:/app/config \
postgres-mcp-server
version: '3.8'
services:
postgres-mcp:
build: .
environment:
- POSTGRES_CONNECTION_STRING=postgresql://user:password@postgres:5432/database
depends_on:
- postgres
stdin_open: true
tty: true
postgres:
image: postgres:15
environment:
- POSTGRES_DB=database
- POSTGRES_USER=user
- POSTGRES_PASSWORD=password
ports:
- "5432:5432"
For use with MCP clients like Cursor or Claude Desktop:
{
"mcpServers": {
"postgresql-mcp": {
"command": "docker",
"args": [
"run",
"-i",
"--rm",
"-e",
"POSTGRES_CONNECTION_STRING",
"henkey/postgres-mcp:latest"
],
"env": {
"POSTGRES_CONNECTION_STRING": "postgresql://user:password@host:port/database"
}
}
}
}
See Development Guide for detailed setup instructions.
AGPLv3 License - see LICENSE file for details.
hovecapital/read-only-local-postgres-mcp-server
cocaxcode/database-mcp
io.github.infoinlet-marketplace/mcp-mysql
io.github.cybeleri/database-admin
io.github.yash-0620/postgres-mcp-secured