A simple Model Context Protocol (MCP) server for read-only PostgreSQL database access.
- Read-only DB access over MCP (stdio): list tables, fetch schemas, run
SELECTqueries - Write protection: blocks
INSERT/UPDATE/DELETE/DROP/CREATE/ALTER/TRUNCATE - Safety limits: statement timeouts + hard row caps + streamed fetching
- Python 3.10+
- PostgreSQL database
- An MCP-compatible client (Cursor, Claude Desktop, Codex, etc.)
uvfor project management
uv syncThis server discovers database URLs from environment variables:
- Default:
DATABASE_URL - Per-environment:
DATABASE_URL_<ENV>(e.g.DATABASE_URL_LOCAL,DATABASE_URL_STAGING)
Environment selection:
- Default environment:
DATABASE_TARGET_ENV(also supportsDATABASE_ENVorDB_ENV) - Per-tool override: each MCP tool accepts optional
environment
Cursor reads MCP config from either:
- Project config:
.cursor/mcp.json - Global config:
~/.cursor/mcp.json
Example .cursor/mcp.json:
{
"mcpServers": {
"database-reader": {
"command": "uv",
"args": ["--directory", "${workspaceFolder}", "run", "database_read.py"],
"env": {
"DATABASE_TARGET_ENV": "local",
"DATABASE_URL_LOCAL": "${env:DATABASE_URL_LOCAL}",
"DATABASE_URL_STAGING": "${env:DATABASE_URL_STAGING}",
"DATABASE_URL_PRODUCTION": "${env:DATABASE_URL_PRODUCTION}"
}
}
}
}Notes:
- Keep real credentials out of git; use
${env:...}and set env vars in your shell/secret manager. - Restart Cursor after editing MCP config.
Claude Desktop reads MCP config from:
- macOS:
~/Library/Application Support/Claude/claude_desktop_config.json - Windows (typical):
%AppData%\Claude\claude_desktop_config.json
Example claude_desktop_config.json:
{
"mcpServers": {
"database-reader": {
"command": "uv",
"args": ["--directory", "/ABSOLUTE/PATH/TO/mcp-prototype", "run", "database_read.py"],
"env": {
"DATABASE_TARGET_ENV": "local",
"DATABASE_URL_LOCAL": "postgresql://user:password@localhost:5432/db_name",
"DATABASE_URL_STAGING": "postgresql://user:password@staging-host:5432/db_name",
"DATABASE_URL_PRODUCTION": "postgresql://user:password@prod-host:5432/db_name"
}
}
}
}Notes:
- Claude Desktop commonly requires absolute paths in JSON.
- Restart Claude Desktop after editing the config.
DATABASE_TARGET_ENVsets the default; tools can override per-call withenvironment: "staging"etc.
Codex shares MCP configuration between the CLI and IDE extension via:
~/.codex/config.toml
Option A — configure via CLI:
codex mcp add database-reader \
--env DATABASE_TARGET_ENV=local \
--env DATABASE_URL_LOCAL='postgresql://user:password@localhost:5432/db_name' \
--env DATABASE_URL_STAGING='postgresql://user:password@staging-host:5432/db_name' \
--env DATABASE_URL_PRODUCTION='postgresql://user:password@prod-host:5432/db_name' \
-- uv --directory /ABSOLUTE/PATH/TO/mcp-prototype run database_read.pyOption B — configure via ~/.codex/config.toml:
[mcp_servers.database-reader]
command = "uv"
args = ["--directory", "/ABSOLUTE/PATH/TO/mcp-prototype", "run", "database_read.py"]
[mcp_servers.database-reader.env]
DATABASE_TARGET_ENV = "local"
DATABASE_URL_LOCAL = "postgresql://user:password@localhost:5432/db_name"
DATABASE_URL_STAGING = "postgresql://user:password@staging-host:5432/db_name"
DATABASE_URL_PRODUCTION = "postgresql://user:password@prod-host:5432/db_name"If your client supports STDIO MCP servers, it will typically accept the same fields:
command:uvargs:["--directory", "/path/to/mcp-prototype", "run", "database_read.py"]env: environment variables (at leastDATABASE_URLorDATABASE_URL_<ENV>)
Use the Cursor / Claude Desktop JSON examples above (same mcpServers shape), or the Codex config.toml example if your client uses TOML.
Try these tool calls:
- list_tables: confirm you can see tables
- get_table_schema: pick one table and inspect columns + primary keys
- database_query: run a safe query like
SELECT * FROM some_table LIMIT 5
To test multi-env selection, pass environment: "staging" in the tool call arguments (or change DATABASE_TARGET_ENV).
- health_check: Check database connectivity and server health
- database_query: Run read-only SQL queries
- list_tables: List all tables in the database
- get_table_schema: Get schema for a specific table (includes primary keys)
- get_all_schemas: Get schemas for all tables (also attempts small sample data)
-
Define as many connection strings as you need via
DATABASE_URL_<ENV>(e.g.,DATABASE_URL_LOCAL,DATABASE_URL_STAGING,DATABASE_URL_PRODUCTION).DATABASE_URLstill works as the default when no environment is specified. -
Tell the server which environment to use globally with
DATABASE_TARGET_ENV(aliases such asdev,prod,stage,stg,local,staging,productionare supported). You can also setDATABASE_ENVorDB_ENVif you prefer those names. -
Every MCP tool now accepts an optional
environmentargument, so you can ask the agent to run queries against a different database without editing your config. Example:{ "name": "database_query", "arguments": { "query": "SELECT * FROM users LIMIT 5", "environment": "staging" } } -
The server keeps its own safe connection pool per environment, so switching between local/staging/production reads does not require a restart.
- SQLAlchemy requires
postgresql://notpostgres://in connection strings - Restart your client after adding the configuration
- Do not commit real credentials; use environment variables or a secret manager in production