Give a data team Claude access to Postgres, without giving anyone the password
Problem
Analysts want to ask questions of the warehouse in natural language. The only
way to do that today is a connection string in claude_desktop_config.json on
every laptop.
That string is unrotatable in practice, unauditable entirely, sitting in
plaintext on a dozen machines — and offboarding one person means rotating the
password and updating everyone else. Meanwhile nothing bounds what those
sessions do: twelve agents deciding at 9am to scan orders is a load pattern
the read replica has never seen.
Fix
1. Create the role your access boundary will actually be
Do this first, in the database, because it is the enforcement nothing downstream can undo. Grant exactly what analysts should reach and nothing else:
CREATE ROLE nthbouncer_ro LOGIN PASSWORD '<generate one>';
GRANT CONNECT ON DATABASE analytics TO nthbouncer_ro;
GRANT USAGE ON SCHEMA public TO nthbouncer_ro;
GRANT SELECT ON ALL TABLES IN SCHEMA public TO nthbouncer_ro;
ALTER ROLE nthbouncer_ro SET default_transaction_read_only = on;
ALTER ROLE nthbouncer_ro SET statement_timeout = '30s';Narrow it further if you have tables analysts should not see — grant per schema
or per table instead of ALL TABLES. There is no allowlist to configure in
nthbouncer, deliberately: your grants are the boundary, and a second list would
only give you two answers to the same question.
The role must use scram-sha-256 password encryption. md5 is refused.
2. Create the pool
Pools → New pool → Postgres database. Backend is the first question and it cannot be changed later.
Then fill in the connection: host, port, database, and the role above. The database must be reachable from the public internet over TLS — a public read replica is the usual answer if your primary is not.
3. Test the connection before you tell anyone about it
Press Test connection. It opens a real connection and tells you which stage failed if one does.
Pay attention to one result in particular: a successful connection that sees
zero tables. That means the role can log in but was never granted USAGE on
the schema, and every query an analyst runs will report that the relation does
not exist. It is the most common mistake in this whole setup, and the preflight
is there so you find it now rather than in a support thread on Monday.
4. Set the concurrency limit to what the replica can take
This is the part a connection string cannot do for you. On Concurrency, set the pool's limit to the number of simultaneous analytical queries the database should ever run. Twelve agents will still queue behind it rather than arriving at once.
Note what a connection pooler would not have solved here: multiplexing twelve connections down to four still runs twelve expensive queries. The limit has to be on the work.
While you are there, lower max rows if analysts mostly want summaries — a smaller cap is cheaper for you and better for the agent, whose context it fills.
5. Connect the team's agents
Each analyst connects once as in Agents and approves a grant
scoped to this pool. From then on their agent has pool_schema,
pool_query, get_query_result, and pool_count_rows.
Nobody is handed a database credential at any point. What an analyst holds is a grant you approved, which you can revoke by itself.
6. Tell them how to ask expensive questions
The one thing worth teaching: for anything analytical, pass wait_ms: 0. The
query starts, the agent gets a handle immediately, and it reads the result on a
later turn.
Every MCP client enforces a tool-call timeout, so a synchronous query caps out around a minute — which is to say, not analytics. This is how you get past that.
Verify
- Logs shows one row per query: the statement, duration, rows returned, and which key ran it. That last column is the answer to "who ran what against production," and it exists because the pool knows who is asking.
- Try a write from an agent (
update orders set total = 0). It comes back refused, and the attempt appears in Logs as a denied statement with the statement text kept. That is the audit trail working, not a bug. - Metrics shows queries queueing behind the concurrency limit when several analysts are busy — the replica staying inside the budget you set.
Offboarding
Revoke that person's grant. That is the whole procedure.
No password rotation, no updating eleven laptops, and no wondering whether an old connection string is still in someone's config file.