Connect Codex to PostgreSQL
Published Jul 16, 2026 · Checked against the official docs
The Postgres MCP server most tutorials point you to is archived, and it still has an unpatched SQL injection. The replacement here locks down to restricted, read-only mode, and since the vendor never wrote a word about Codex, every config line gets translated from scratch.
What you get
Postgres MCP Pro is crystaldba’s MCP server for Postgres. It’s MIT licensed, and a completely separate codebase from Anthropic’s old reference server (more on that below, in Gotchas). Over stdio, it hands Codex nine tools, straight from the project’s own docs:
| Tool | What it does |
|---|---|
list_schemas |
Lists every schema in the database |
list_objects |
Lists tables, views, sequences, and extensions in a schema |
get_object_details |
Pulls a table’s columns, constraints, and indexes |
execute_sql |
Runs SQL, read-only limited when the server started in restricted mode |
explain_query |
Gets the execution plan Postgres would actually run |
get_top_queries |
Reports the slowest queries by total execution time, from pg_stat_statements |
analyze_workload_indexes |
Reviews the workload and recommends indexes |
analyze_query_indexes |
Recommends indexes for up to 10 specific queries you hand it |
analyze_db_health |
Checks buffer cache hit rate, connection health, constraint validation, and index health |
The project also ships what its README calls an experimental index tuning feature, which hands your schema and query plans to an LLM instead of using heuristics to search for index configurations. The README says you have to set OPENAI_API_KEY to use it. It doesn’t say which of the index tools route through that path, so if you don’t set the key, don’t expect the LLM step to run.
Setup
Install with one of three methods the project documents: uvx postgres-mcp (uv fetches and runs it, no separate install step), pipx install postgres-mcp, or docker pull crystaldba/postgres-mcp. This page uses uvx.
The vendor’s own Claude Desktop config is JSON:
{
"mcpServers": {
"postgres": {
"command": "uvx",
"args": ["postgres-mcp", "--access-mode=unrestricted"],
"env": { "DATABASE_URI": "postgresql://user:password@host:5432/dbname" }
}
}
}
Translate that with the mapping on Connect an MCP server: command and args carry over as-is, env becomes its own TOML table. Two changes worth making on the way in, before you paste this anywhere:
- Swap
unrestrictedforrestricted. The vendor’s own docs call unrestricted mode “suitable for development environments” and restricted mode “suitable for production environments,” and say switching is just replacing the flag value. A guide called “connect Codex to Postgres” is going to get pointed at real databases, so restricted is the sane starting point. Open it back up per server if you’re working against a scratch database you don’t mind losing. - Keep the connection string out of the committed file. Export
DATABASE_URIin your shell instead of writing it intoconfig.toml, and reference it withenv_varsinstead of theenvtable. Codex’s own docs draw that line:envstores a literal value in the file,env_varswhitelists a name and forwards whatever value is already sitting in your shell (seemcp_servers.<id>.env).
[mcp_servers.postgres]
command = "uvx"
args = ["postgres-mcp", "--access-mode=restricted"]
startup_timeout_sec = 30
default_tools_approval_mode = "writes"
env_vars = ["DATABASE_URI"]
One caveat on that fourth line. Codex’s config reference lists default_tools_approval_mode as taking auto, prompt, writes, or approve, but it doesn’t spell out what each value does, and writes is the one that reads like it gates write tools. Set it if you want, and don’t treat it as your guard rail. The --access-mode=restricted flag is the part the vendor actually documents, and it’s what limits execute_sql to read-only transactions at the database layer.
Add it and confirm it initialized:
codex mcp add postgres -- uvx postgres-mcp --access-mode=restricted
codex mcp list
codex mcp add takes --env VAR=VALUE, which writes the env table, not env_vars. So add the env_vars and default_tools_approval_mode lines to the [mcp_servers.postgres] block by hand after running it. You want postgres listed in codex mcp list with a non-empty tool count.
If you’d rather run this through Docker than install uv, the vendor’s Claude Desktop docker example translates the same way: command = "docker", args = ["run", "-i", "--rm", "-e", "DATABASE_URI", "crystaldba/postgres-mcp", "--access-mode=restricted"], with DATABASE_URI handled through env_vars exactly as above.
Gotchas
Don’t install @modelcontextprotocol/server-postgres. It’s the name most tutorials and model training data still point people at, but Anthropic archived it into modelcontextprotocol/servers-archived on May 29, 2025, and the repo now carries a blunt disclaimer: “NO SECURITY GUARANTEES ARE PROVIDED FOR THESE ARCHIVED SERVERS.” Datadog Security Labs found why that matters: a SQL injection let an agent send COMMIT; DROP SCHEMA public CASCADE; and have it actually execute, because the payload commits the server’s read-only transaction before its automatic rollback runs. Datadog’s timeline says Anthropic fixed the vulnerability in their git repository on May 29, 2025, the same day they archived it, but v0.6.2 “remains unpatched at NPM and Docker Hub.” What you pull from either registry today is still the vulnerable build. It’s still getting installed at real volume: npm’s own download-stats API reports 95,789 downloads of @modelcontextprotocol/server-postgres for the week of July 9 to 15, 2026 alone.
crystaldba’s postgres-mcp was built against exactly that failure mode. Per the project’s own docs, it parses every statement with the pglast library before running it and rejects anything that contains a commit or rollback statement, closing the specific stacked-statement trick that broke the archived server’s read-only guarantee.
No documented default for --access-mode. The vendor’s docs describe what unrestricted and restricted each do but don’t say what happens if the flag is left off entirely. Pass it explicitly every time rather than trusting an unstated default.
Cold uvx can blow the 10-second startup window, the same way a cold npx does. First run has to fetch postgres-mcp from PyPI before it can answer Codex’s handshake. See mcp_servers.<id>.startup_timeout_sec instead of guessing at a number.
There’s no Codex documentation from the vendor at all. The README covers Claude Desktop, Cursor, Windsurf, Goose, and Qodo Gen, entirely in JSON, and never mentions Codex or config.toml. crystaldba/postgres-mcp #125, filed by arjuna-dev on January 5, 2026 and still open and unassigned, is titled exactly what you’d expect: “Documentation for adding the MCP server to codex using their config.toml file is missing.” This page is the TOML translation that issue is asking for.
Related
- Connect an MCP server: the stdio/remote shape and the JSON-to-TOML mapping in full
mcp_servers.<id>.startup_timeout_sec: raising the launch window for a slow cold startmcp_servers.<id>.env:envversusenv_varsfor secrets- How to install Codex: if Codex itself isn’t set up yet