Room 42, home

Connect Claude to your database with MCP

Give Claude safe, read-only access to a Postgres database through an MCP server: how to wire it up and what to lock down.

MCPadvanced9 min readUpdated 13 Jun 2026

In short

  1. 01An MCP server like DBHub gives Claude read-only access to Postgres, so you ask questions in plain English and it runs the SELECTs.
  2. 02Safety comes from two layers: a database user that can only read, plus the server's read-only flag that refuses any write.
  3. 03Add the server with claude mcp add, keep the connection string in an environment variable, and never use project scope for a live DSN.
  4. 04Before production, point Claude at a replica, hide sensitive columns behind a view, and set a statement timeout on the role.
Contents · 14 sections
  1. 1The short version
  2. 2Step 1: create a read-only database user
  3. 3Step 2: add the server to Claude Code
  4. 4Step 3: keep the password out of the config
  5. 5Step 4: confirm it connected
  6. 6What can I ask Claude once the database is connected?
  7. 7Why won't the database server connect?
  8. 8Should I use a local stdio server or a remote one?
  9. 9What does read-only block?
  10. 10A note on the deprecated reference server
  11. 11What should I lock down before production?
  12. 12Point at a replica, not the primary
  13. 13Removing the server
  14. 14Where to go next

To let Claude query a Postgres database, run a database MCPAn open standard from Anthropic that lets AI apps connect to tools and data through one common interface instead of a custom integration per service. server pointed at a connection string, add it to Claude Code, and set it to read-only. Then you ask questions in plain English and Claude runs the SELECT queries for you. Safety comes from two layers you control. One is a database user that can only read; the other is a server flag that refuses anything but reads.

Add a read-only server, ask a plain-English question, and watch the SELECT Claude ran. Illustrative session.

If MCP is new to you, read MCP fundamentals first. This guide assumes you know what a host, a client, and a server are.

The short version

You wire one local server between Claude Code and Postgres. Claude never holds your credentials and never opens a raw connection. Instead, it calls a tool on the server. The server runs the query inside a read-only transaction, and the rows come back as context.

The server runs as a local stdioA separate program launched and managed by Claude Code, running as its own child process and talking over standard input and output. subprocess on your machine. No network port is opened. The connection string stays in your local config.

Step 1: create a read-only database user

Do this before you touch Claude. The first and most important lock is at the database, not the AI tool. Create a role that can read and nothing else, so even a bug or a bad query cannot change data.

psql "$ADMIN_DATABASE_URL" <<'SQL'
CREATE ROLE claude_ro LOGIN PASSWORD 'change-me';
GRANT CONNECT ON DATABASE analytics TO claude_ro;
GRANT USAGE ON SCHEMA public TO claude_ro;
GRANT SELECT ON ALL TABLES IN SCHEMA public TO claude_ro;
ALTER DEFAULT PRIVILEGES IN SCHEMA public
  GRANT SELECT ON TABLES TO claude_ro;
SQL

The ALTER DEFAULT PRIVILEGES line matters: it grants SELECT on tables created later, so a new table next month is readable without re-running grants. This role has no INSERT, UPDATE, DELETE, or DROP. The database itself rejects writes, regardless of what any tool sends.

For a database with sensitive columns, grant SELECT on specific tables or views instead of ALL TABLES, and point Claude at a view that omits the columns you do not want it to see.Common gotchaRead-only is not the same as safe to read. A perfectly valid SELECT still pulls real PII (emails, card hints, password hashes) straight into Claude's context, where it may be logged or sent upstream.Grant on a view that drops or masks those columns, so the raw rows never leave the database.

Step 2: add the server to Claude Code

The official Claude Code path uses DBHub. It is a database MCP server that speaks to Postgres, MySQL, SQL Server, MariaDB, and SQLite. Add it with claude mcp add and pass the read-only connection string after the --.

claude mcp add --transport stdio db -- \
  npx -y @bytebase/dbhub \
  --readonly \
  --dsn "postgresql://claude_ro:change-me@db.internal:5432/analytics"

Two flags do the work here:

  • --readonly tells DBHub to refuse anything that is not a read. This is your second lock, on top of the database role.
  • --dsn is the Postgres connection string for the read-only user you just made.

The -- is load-bearing. Everything after it is passed to the server untouched, so --transport, --scope, and --env are Claude's own options and --readonly and --dsn belong to DBHub. Drop the -- and Claude tries to read --readonly as one of its own flags, and fails.

ScopeA flag on `claude mcp add` that controls where the server config is stored, and therefore who can see it. defaults to local, meaning the server is available only to you in the current project. The three choices:

ScopeStored inWho sees it
local (default)your local user configonly you, this project
project.mcp.json in the repoeveryone on the project
useryour global configyou, every project

Never use project scope for a database server with a live connection string. That writes the DSN, password and all, into .mcp.json, which is committed to the repo. Keep it local. Or use user if you query the same database across projects.Common gotchaThe trap is reaching for --scope project so a teammate gets the server too, which silently bakes the plaintext password into .mcp.json and commits it. Once it lands in git history, deleting the line is not enough: the secret is in every clone and you must rotate the database password.Keep the DSN in --env at local or user scope, and share a sanitized .mcp.json that reads the secret from an environment variable instead.

Step 3: keep the password out of the config

A connection string with an inline password sits in plain text in your config file. Pass it through an environment variable instead. The secret then lives in your shell or a secret manager, and the config holds only a reference.

claude mcp add --transport stdio db \
  --env DATABASE_URL="postgresql://claude_ro:change-me@db.internal:5432/analytics" \
  -- npx -y @bytebase/dbhub --readonly

DBHub reads the DSN from the DSN or DATABASE_URL environment variable when --dsn is absent, so the password never appears in the command Claude stores. One detail trips people up. When you use --env, put at least one other option between --env and the server name. The CLI reads the value right after --env as another KEY=value pair, so a bare server name there is rejected. Putting --transport stdio first, as above, avoids that.

Step 4: confirm it connected

List your servers and check the database one is healthy.

claude mcp list

To see the stored config for one server, including its transport and scope:

claude mcp get db

Inside an interactive Claude Code session, run /mcp to view connected servers and their tools. Once db shows as connected, ask a question:

Try this prompt
What are our five highest-revenue customers this quarter? Use the db server and show me the SQL you ran before the results.

Asking Claude to show the SQL is a habit worth keeping. You see exactly what ran, you catch a wrong join early, and you build trust in the answers.

What can I ask Claude once the database is connected?

A read-only database connection is most useful for questions you would otherwise answer by hand-writing SQL or exporting a spreadsheet. Claude reads the schema first, then writes the query.

  • "List the columns and types on the orders table, then count rows per status."
  • "Find the ten users who signed up last week and have not placed an order."
  • "Our revenue dashboard looks off. Compare last month to this month by product category and tell me which one moved."
  • "Write the query for monthly active users, then explain the date math you used."

Because the role is read-only, you can let Claude explore freely. It cannot delete a row or drop a table. The cost of a wrong query is a wrong answer you can spot, not lost data. Treat the first answer to any new question as a draft: read the SQL, sanity-check the row counts, and ask Claude to refine if a join looks wrong.

Why won't the database server connect?

If claude mcp get db shows the server as failed, work through these in order.

SymptomLikely causeFix
Server fails immediatelynpx cannot reach the packageRun the npx -y @bytebase/dbhub command on its own to see the error
Authentication failedWrong password or user in the DSNTest the DSN with psql first, then re-add
Connection refusedHost or port unreachable from your machineConfirm network access to the database, including any VPN
Server starts but every query is rejected--readonly is on, query is a writeExpected behaviour; rephrase as a read, or it is a permissions gap
Timeout on startupServer is slow to launchRaise the limit, for example MCP_TIMEOUT=15000 claude

The fastest way to isolate a problem is to run the server command directly in your shell. If npx -y @bytebase/dbhub --readonly --dsn "..." connects on its own, the issue is in how Claude stored the config, not in the database or the server.

Should I use a local stdio server or a remote one?

This guide uses a local stdio server, which Claude launches as a subprocess on your machine. That keeps the connection string on your laptop and opens no network port. A team that wants one shared, audited database connection has another option. Run a remote server over HTTP and add it with --transport http, where the config holds a URL rather than a DSN.

claude mcp add --transport http team-db https://db-mcp.internal/mcp

In .mcp.json, the type field accepts streamable-http as an alias for http, so a config copied from a server's own docs works without edits. A remote server is the right choice when you want one place to manage credentials, rotate them, and log every query. For a single developer querying a replica, though, local stdio is simpler and just as safe.

What does read-only block?

DBHub's --readonly flag restricts execution to read operations: SELECT, SHOW, DESCRIBE, and the like. A statement that writes is rejected by the server before it ever reaches Postgres. Combine that with a role that has only SELECT and you have defence in depth. The server says no, and if something slips past, the database says no too.

Neither layer alone is enough. The server flag is config you could fat-finger. The database role is the durable guarantee. Set both.

A note on the deprecated reference server

You will find guides that use @modelcontextprotocol/server-postgres, the original reference server. It moved to an archived repository in 2025 and is no longer maintained. True, it ran queries in a read-only transaction, which is a reasonable default. But it is not receiving fixes. Prefer a maintained server such as DBHub, and rely on the database role as your primary control rather than trusting any single package.

What should I lock down before production?

Read-only is the start, not the finish. Run this checklist before you point Claude at anything that holds real data.

ControlWhy it matters
Read-only database roleThe query cannot mutate data even if the server flag is wrong
--readonly on the serverA second gate that rejects writes before they reach the database
DSN in --env, not inlineThe password stays out of stored config and out of the repo
local or user scope, never projectA live DSN must not be committed to .mcp.json
A query view that hides sensitive columnsClaude reads the view, never the raw customers table
A connection on a low-privilege network pathThe read-only user reaches only the database it needs
Statement timeout on the roleA heavy exploratory query cannot stall the database

You can set a statement timeout on the role so one slow scan cannot tie up the database:

psql "$ADMIN_DATABASE_URL" -c \
  "ALTER ROLE claude_ro SET statement_timeout = '15s';"

Point at a replica, not the primary

For a production database, give Claude a read replica or an analytics copy rather than the primary that serves your application. A read replica is a separate copy that receives the same data, so an expensive analytical query Claude runs there cannot compete with live traffic. The read-only role and --readonly flag still apply; the replica is one more boundary between exploration and the system your users depend on.

Removing the server

When you are done, or before handing your machine to someone else, remove it.

claude mcp remove db

That deletes the stored config, including any inline DSN. If you used --env, also clear the variable from your shell or secret manager so the password does not linger.

Where to go next

You now have a pattern that generalises: a read-only role, a maintained server, secrets in environment variables, and the narrowest scope that works. The same shape applies to any data source you connect, such as a folder of notes or docs or your GitHub repos. For the config formats and scopes in depth, read MCP fundamentals, and for deciding what to allow and what to block on any server, MCP security basics.