postgres mcp
A blazing fast MCP server that enables AI agents to interact with multiple PostgreSQL databases, providing functionality to list tables, inspect schemas, execute queries, and run transactions.
A blazing fast MCP server that enables AI agents to interact with multiple PostgreSQL databases, providing functionality to list tables, inspect schemas, execute queries, and run transactions.
This project implements a blazing fast, type-safe, and full-featured Model Context Protocol (MCP) Server designed for AI Agents (like Cursor, Claude Desktop) to interact with multiple PostgreSQL databases, including listing tables and inspecting schemas.
It is built with Bun, TypeScript, postgres
, and leverages advanced features of the fastmcp
framework for building robust MCP servers.
This is not a library to be imported into your code. It is a standalone server application. You run it as a process, and MCP clients (like AI agents) communicate with it using the JSON-based Model Context Protocol (v2.0), typically over a stdio
connection managed by the client application (e.g., Cursor).
The package includes a built-in CLI command for testing the MCP server directly:
# From the project repository:
bun run cli
# This will start an interactive MCP CLI session where you can:
# - Call any of the PostgreSQL tools (query_tool, execute_tool, etc.)
# - View server capabilities
# - Test queries against your configured databases
You can also use the MCP Inspector to visually test and debug:
# From the project repository:
bun run inspect
If you see this error when running bunx postgres-mcp
:
FastPostgresMCP started
[warning] FastMCP could not infer client capabilities
followed by ping messages, it means:
This usually indicates you need to use a proper MCP client. Try:
- Using bun run cli
to test with the MCP CLI
- Configuring the MCP server in Cursor or Claude Desktop as described in the Installation section
If you're developing a custom MCP client, make sure it properly implements the MCP protocol including capabilities negotiation.
fastmcp
..env
.postgres
prevent SQL injection.fastmcp
's authenticate
hook).db://{dbAlias}/schema/tables
.db://{dbAlias}/schema/{tableName}
.log
context).reportProgress
context).session
context).server.on
and session.on
for connection/session event handling.fastmcp
tools.FastMCP
Server Coreserver.addTool
(for query_tool
, execute_tool
, schema_tool
, and transaction_tool
)server.addResourceTemplate
(for listing tables and inspecting table schemas)server.start
(with stdio
focus, adaptable for sse
/http
)authenticate
Hook (for API Key validation)context
(log
, reportProgress
, session
)server.on
(for connection logging)session.on
for session-specific logicinformation_schema
.# Install globally
npm install -g postgres-mcp
# Or install locally in your project
npm install postgres-mcp
The npm package is available at https://www.npmjs.com/package/postgres-mcp
Clone the repository:
# Replace with your actual repository URL
git clone https://github.com/llm-graph/postgres-mcp.git
cd postgres-mcp
Install dependencies:
bun install
Configure via environment variables, loaded from appropriate .env
files.
Create environment files:
cp .env.example .env
cp .env.development.example .env.development
Environment file loading order: The server loads environment variables from files in the following order of priority:
.env.<NODE_ENV>
(e.g., .env.development
, .env.production
, .env.staging
).env.local
(for local overrides, not version controlled).env
(default fallback)This allows different configurations for different environments.
Edit the environment files to define database connections and authentication:
DB_ALIASES
- Comma-separated list of unique DB aliasesDEFAULT_DB_ALIAS
- Default alias if 'dbAlias' is omitted in tool callsDB_MAIN_HOST
, DB_REPORTING_HOST
)ENABLE_AUTH
, MCP_API_KEY
)# Example .env file - Key Variables
# REQUIRED: Comma-separated list of unique DB aliases
DB_ALIASES=main,reporting
# REQUIRED: Default alias if 'dbAlias' is omitted in tool calls
DEFAULT_DB_ALIAS=main
# OPTIONAL: Enable API Key auth (primarily for network transports)
ENABLE_AUTH=false
MCP_API_KEY=your_super_secret_api_key_here # CHANGE THIS
# Define DB connection details for each alias (DB_MAIN_*, DB_REPORTING_*, etc.)
DB_MAIN_HOST=localhost
DB_MAIN_PORT=5432
DB_MAIN_NAME=app_prod_db
DB_MAIN_USER=app_user
DB_MAIN_PASSWORD=app_secret_password
DB_MAIN_SSL=disable
# Alternative: Use connection URLs
# DB_MAIN_URL=postgres://user:password@localhost:5432/database?sslmode=require
# --- Optional: Server Logging Level ---
# LOG_LEVEL=info # debug, info, warn, error (defaults to info)
Run this server directly using Bun. The AI Client (like Cursor) will typically start and manage this command for you.
postgres-mcp
npx postgres-mcp
// server.js
import { startServer } from 'postgres-mcp';
// Start the MCP server
startServer();
bun run src/index.ts
bun run --watch src/index.ts
fastmcp
CLI Toolsbunx fastmcp dev src/index.ts
bunx fastmcp inspect src/index.ts
In addition to running as a standalone MCP server, postgres-mcp can also be used programmatically as a library in your Node.js/TypeScript applications.
import { createPostgresMcp } from 'postgres-mcp';
// Create the PostgresMcp instance
const postgresMcp = createPostgresMcp();
// Start the server
postgresMcp.start();
// Direct database operations
const results = await postgresMcp.executeQuery(
'SELECT * FROM users WHERE role = $1',
['admin'],
'main' // optional database alias
);
// When done, stop the server and close connections
await postgresMcp.stop();
For simpler use cases, you can import specific functions directly:
import {
initConnections,
closeConnections,
executeQuery,
executeCommand,
executeTransaction,
getTableSchema,
getAllTableSchemas
} from 'postgres-mcp';
// Configure database connections
const dbConfigs = {
main: {
host: 'localhost',
port: 5432,
database: 'my_db',
user: 'db_user',
password: 'db_password'
}
};
// Initialize connections
initConnections(dbConfigs);
// Execute a query
const results = await executeQuery(
'SELECT * FROM users WHERE role = $1',
['admin'],
'main'
);
// Get schema for a single table
const schema = await getTableSchema('users', 'main');
// Get schema for all tables in the database
const allSchemas = await getAllTableSchemas('main');
// Close connections when done
await closeConnections();
const postgresMcp = createPostgresMcp({
// Custom database configurations (override .env)
databaseConfigs: {
main: {
host: 'localhost',
port: 5432,
database: 'app_db',
user: 'app_user',
password: 'password',
ssl: 'disable'
}
},
// Server configuration
serverConfig: {
name: 'Custom PostgresMCP',
defaultDbAlias: 'main'
},
// Transport options: 'stdio', 'sse', or 'http'
transport: 'http',
port: 3456
});
For complete documentation on the programmatic API, see docs/programmatic-api.md.
Configure your AI Agent (MCP Client) to execute this server script via its command/args mechanism.
settings.json
.Add the following JSON configuration:
// In Cursor's settings.json or MCP configuration UI
{
"mcpServers": {
"postgres-mcp": { // Unique name for Cursor
"description": "MCP Server for PostgreSQL DBs (Main, Reporting)",
"command": "bunx", // Use 'bun' or provide absolute path: "/Users/your_username/.bun/bin/bun"
"args": [
"postgres-mcp"
// or
// *** ABSOLUTE PATH to your server's entry point ***
// "/Users/your_username/projects/postgres-mcp/src/index.ts" /
],
"env": {
// .env file in project dir is loaded automatically by Bun.
// Add overrides or Cursor-specific vars here if needed.
},
"enabled": true
}
}
}
Save and Restart Cursor or "Reload MCP Servers".
config.json
(see previous README for paths).mcpServers
, using the absolute path in args
.X-API-Key
header matching MCP_API_KEY
if ENABLE_AUTH=true
.stdio
connections (default for Cursor/Claude) generally bypass this check.db://{dbAlias}/schema/tables
addResourceTemplate
):uriTemplate
: "db://{dbAlias}/schema/tables"
arguments
:dbAlias
: (string, required) - Alias of the database (from .env
).load({ dbAlias })
: Connects to the database, queries information_schema.tables
(filtered for base tables in the public schema, customizable in implementation), formats the result as a JSON string array ["table1", "table2", ...]
, and returns { text: "..." }
.Example Usage (AI Prompt): "Get the resource db://main/schema/tables
to list tables in the main database."
db://{dbAlias}/schema/{tableName}
addResourceTemplate
):uriTemplate
: "db://{dbAlias}/schema/{tableName}"
arguments
:dbAlias
: (string, required) - Database alias.tableName
: (string, required) - Name of the table.load({ dbAlias, tableName })
: Connects, queries information_schema.columns
for the specific table, formats as JSON string array of column objects, returns { text: "..." }
.Example Usage (AI Prompt): "Describe the resource db://reporting/schema/daily_sales
."
Example Response Content (JSON String):
"[{"column_name":"session_id","data_type":"uuid","is_nullable":"NO","column_default":"gen_random_uuid()"},{"column_name":"user_id","data_type":"integer","is_nullable":"NO","column_default":null},{"column_name":"created_at","data_type":"timestamp with time zone","is_nullable":"YES","column_default":"now()"},{"column_name":"expires_at","data_type":"timestamp with time zone","is_nullable":"YES","column_default":null}]"
Tools receive context
object (log
, reportProgress
, session
).
query_tool
Executes read-only SQL queries.
statement
(string), params
(array, opt), dbAlias
(string, opt).log.info/debug
, optional reportProgress
, access session
.Example Request:
{
"tool_name": "query_tool",
"arguments": {
"statement": "SELECT product_id, name, price FROM products WHERE category = $1 AND price < $2 ORDER BY name LIMIT 10",
"params": ["electronics", 500],
"dbAlias": "main"
}
}
Example Response Content (JSON String):
"[{"product_id":123,"name":"Example Gadget","price":499.99},{"product_id":456,"name":"Another Device","price":350.00}]"
execute_tool
Executes data-modifying SQL statements.
statement
(string), params
(array, opt), dbAlias
(string, opt).log.info/debug
, access session
.Example Request:
{
"tool_name": "execute_tool",
"arguments": {
"statement": "UPDATE users SET last_login = NOW() WHERE user_id = $1",
"params": [54321]
// dbAlias omitted, uses DEFAULT_DB_ALIAS
}
}
Example Response Content (String):
"Rows affected: 1"
schema_tool
Retrieves detailed schema information for a specific table.
tableName
(string), dbAlias
(string, opt).log.info
, access session
.Example Request:
{
"tool_name": "schema_tool",
"arguments": {
"tableName": "user_sessions",
"dbAlias": "main"
}
}
Example Response Content (JSON String):
"[{"column_name":"session_id","data_type":"uuid","is_nullable":"NO","column_default":"gen_random_uuid()"},{"column_name":"user_id","data_type":"integer","is_nullable":"NO","column_default":null},{"column_name":"created_at","data_type":"timestamp with time zone","is_nullable":"YES","column_default":"now()"},{"column_name":"expires_at","data_type":"timestamp with time zone","is_nullable":"YES","column_default":null}]"
transaction_tool
Executes multiple SQL statements atomically.
operations
(array of {statement, params}), dbAlias
(string, opt).log.info/debug/error
, reportProgress
, access session
.{"success": true, "results": [...]}
or {"success": false, "error": ..., "failedOperationIndex": ...}
.Example Request:
{
"tool_name": "transaction_tool",
"arguments": {
"operations": [
{
"statement": "INSERT INTO orders (customer_id, order_date, status) VALUES ($1, NOW(), 'pending') RETURNING order_id",
"params": [101]
},
{
"statement": "INSERT INTO order_items (order_id, product_sku, quantity, price) VALUES ($1, $2, $3, $4)",
"params": [9999, "GADGET-X", 2, 49.99]
},
{
"statement": "UPDATE inventory SET stock_count = stock_count - $1 WHERE product_sku = $2 AND stock_count >= $1",
"params": [2, "GADGET-X"]
}
],
"dbAlias": "main"
}
}
Example Success Response Content (JSON String):
"{"success":true,"results":[{"operation":0,"rowsAffected":1},{"operation":1,"rowsAffected":1},{"operation":2,"rowsAffected":1}]}"
Example Error Response Content (JSON String):
"{"success":false,"error":"Error executing operation 2: new row for relation "inventory" violates check constraint "stock_count_non_negative"","failedOperationIndex":2}"
server.on('connect'/'disconnect')
for logging client connections.session.on(...)
for more granular session event handling if needed.DB_<ALIAS>_USER
, including read access to information_schema
for schema/table listing resources.DB_<ALIAS>_SSL=require
or stricter)..env
file (add to .gitignore
). Use secure secret management for production environments (Vault, Doppler, cloud secrets).authenticate
hook primarily secures network transports. stdio
security relies on the execution environment.information_schema.tables
) and schemas (information_schema.columns
) are generally safe but rely on database permissions. Ensure the configured users have appropriate read access. Customize the table listing query (e.g., schema filtering) if needed for security or clarity.This project is licensed under the MIT License. See the LICENSE file for details.