postgres-readonly-mcp
v1.0.1
Published
MCP server for read-only PostgreSQL database access
Maintainers
Readme
PostgreSQL Read-Only MCP Server
A Model Context Protocol (MCP) server that provides safe, read-only access to PostgreSQL databases.
This server exposes database tools (list tables, describe schema, preview rows, run read-only queries, show relations, stats) to MCP clients such as Claude Desktop.
What This Project Solves
When AI tools need database context, direct SQL access can be risky. This server adds a controlled layer:
- Read-only query validation
- Tool-based access to schema and data
- Query result limits and timeout protection
- Multi-database support (
dbanddb2) - Sanitized error messages (credentials are redacted)
Features
- Strict read-only access to PostgreSQL (
SELECTonly) - Table and schema inspection
- Data preview with row and text truncation limits
- Foreign key relationship discovery
- Database-level statistics (size, row estimates, largest tables)
- Two logical database targets:
db(primary) anddb2(secondary)
Project Structure
src/index.ts - MCP server entry point and tool registrationsrc/connection-manager.ts - connection pooling, env parsing, query executionsrc/query-validator.ts - read-only validation rulessrc/tools/*.ts - tool implementations
Requirements
- Node.js 18+
- npm
- PostgreSQL network access from your machine
Installation
git clone <repository-url>
cd postgres-readonly-mcp
npm install
npm run buildQuick Start (Step by Step)
1) Configure Environment
Create a .env file in project root.
You can use one of these patterns.
Option A: Single URL for both db and db2
DATABASE_URL=postgresql://USER:PASSWORD@HOST:5432/DB_NAME?schema=publicOption B: Separate host fields (recommended for clarity)
# Primary database (db)
DB_HOST=127.0.0.1
DB_PORT=5432
DB_USER=postgres
DB_PASSWORD=your_password
DB_NAME=your_database
DB_SSL=true
DB_SSL_REJECT_UNAUTHORIZED=true
# Secondary database (db2)
DB2_HOST=127.0.0.1
DB2_PORT=5432
DB2_USER=postgres
DB2_PASSWORD=your_password
DB2_NAME=your_database_2
DB2_SSL=true
DB2_SSL_REJECT_UNAUTHORIZED=trueOption C: Separate URLs
DB_URL=postgresql://USER:PASSWORD@HOST:5432/DB1
DB2_URL=postgresql://USER:PASSWORD@HOST:5432/DB22) Build
npm run build3) Run
npm startFor development mode:
npm run dev4) Connect from Claude Desktop
Add this to claude_desktop_config.json:
{
"mcpServers": {
"postgres-readonly": {
"command": "node",
"args": ["/absolute/path/to/postgres-readonly-mcp/dist/index.js"],
"env": {
"DB_HOST": "127.0.0.1",
"DB_PORT": "5432",
"DB_USER": "postgres",
"DB_PASSWORD": "your_password",
"DB_NAME": "your_database",
"DB_SSL": "true",
"DB_SSL_REJECT_UNAUTHORIZED": "true",
"DB2_HOST": "127.0.0.1",
"DB2_PORT": "5432",
"DB2_USER": "postgres",
"DB2_PASSWORD": "your_password",
"DB2_NAME": "your_database_2",
"DB2_SSL": "true",
"DB2_SSL_REJECT_UNAUTHORIZED": "true"
}
}
}
}Restart Claude Desktop after editing config.
Environment Variable Reference
Core Variables
DB_HOST,DB_PORT,DB_USER,DB_PASSWORD,DB_NAME-> primary connection (db)DB2_HOST,DB2_PORT,DB2_USER,DB2_PASSWORD,DB2_NAME-> secondary connection (db2)DB_SSL,DB_SSL_REJECT_UNAUTHORIZED-> TLS settings fordbDB2_SSL,DB2_SSL_REJECT_UNAUTHORIZED-> TLS settings fordb2DATABASE_URL-> fallback URL for both databasesDB_URL,DB2_URL-> explicit URL per databaseDB_DATABASE_URL,DB2_DATABASE_URL-> alias URL names also supported
DB_SSL defaults to true in strict mode.
Resolution Priority
For db:
DB_*fieldsDB_URLorDB_DATABASE_URLDATABASE_URL- Defaults (
localhost,5432,postgres, empty password,postgres)
For db2:
DB2_*fieldsDB2_URLorDB2_DATABASE_URLDATABASE_URL- Fallback to resolved
dbvalues
URL Encoding Note
If your password includes special characters (/, @, :), URL encode it in connection strings.
Example:
- Real password:
my/pass - In URL:
my%2Fpass
DATABASE_URL=postgresql://postgres:my%[email protected]:5432/your_database?schema=publicTool Reference
All tools accept optional database with values:
db(default)db2
1) list_tables
List tables/views in a schema.
Input:
{
"database": "db",
"schema": "public"
}Returns array of:
nametype(BASE TABLEorVIEW)rowCount(estimate)schema
2) describe_table
Describe columns, primary key, foreign keys, and indexes.
Input:
{
"database": "db",
"schema": "public",
"table": "Mail"
}3) preview_data
Preview rows from a table with optional column selection.
Input:
{
"database": "db",
"schema": "public",
"table": "Mail",
"columns": ["id", "subject", "createdAt"],
"limit": 10
}4) run_query
Run a custom read-only SQL query.
Input:
{
"database": "db",
"query": "SELECT id, subject FROM \"public\".\"Mail\" ORDER BY id DESC",
"limit": 100
}5) show_relations
Show foreign key relations for a table (incoming and outgoing).
Input:
{
"database": "db",
"schema": "public",
"table": "Mail"
}6) db_stats
Get database size and table statistics.
Input:
{
"database": "db"
}Returns:
databasetotalTablestotalRowstotalSizelargestTables
Read-Only and Safety Rules
Allowed statement types
SELECT
Blocked keywords (examples)
INSERT,UPDATE,DELETEDROP,ALTER,TRUNCATE,CREATEGRANT,REVOKE,LOCKCOPY,VACUUM,ANALYZE,REINDEX,CLUSTER
Additional strict-mode checks
- Multiple SQL statements in one request are blocked
- Certain risky function calls are blocked (for example
pg_sleep,dblink, file-read functions) - Final row cap is enforced server-side, even if your SQL includes a larger
LIMIT
Execution limits
preview_datadefault:10, max:100run_querydefault:1000, max:5000- Query timeout:
30s(statement_timeoutandquery_timeout) - Long text truncation:
200chars
Error safety
Connection and runtime errors are sanitized to prevent credential leakage.
Common Usage Examples
Example 1: List all public tables
Use list_tables with:
{ "database": "db", "schema": "public" }Example 2: Preview recent mails
Use run_query with:
{
"database": "db",
"query": "SELECT id, subject, \"createdAt\" FROM \"public\".\"Mail\" ORDER BY \"createdAt\" DESC",
"limit": 20
}Example 3: Compare two databases
- Call
db_statswithdatabase: "db" - Call
db_statswithdatabase: "db2" - Compare
totalTables,totalRows, andlargestTables
Development
# Type-check and compile
npm run build
# Run server in dev mode
npm run dev
# Start compiled server
npm start
# Run tests
npm testRelease to npm
# Patch release (1.0.0 -> 1.0.1)
npm run release
# Minor release (1.0.0 -> 1.1.0)
npm run release:minor
# Major release (1.0.0 -> 2.0.0)
npm run release:major
# Validate flow without changing anything
npm run release:dry-runRelease script behavior:
- Requires a clean git working tree
- Uses
npm versionto bump version (creates commit and tag) - Runs
npm publish --access public(build runs viaprepublishOnly)
Troubleshooting
Connection fails
- Verify host/port is reachable from your machine.
- Verify user/password and database name.
- If using URL, ensure special chars in password are URL encoded.
Server starts but no tools in client
- Confirm client points to correct
dist/index.jspath. - Restart client app after config changes.
- Check stderr logs for startup errors.
Query rejected
- The query likely includes blocked keywords or unsupported statement type.
- Rewrite as strict read-only query (
SELECTonly).
License
MIT
