Skip to content
Datablare
Guides

PostgreSQL MCP Server, Read-Only: Safe Claude Access

PostgreSQL MCP server read only setup: a SELECT-only role, a read-only session and query checks, so Claude can query Postgres but never change it.

By Kamal Thakur Published 7 min read
On this page
  1. What “read-only” has to mean for an MCP server
  2. Step 1: Create a role that can only read
  3. Step 2: Make the session read-only and bounded
  4. Step 3: Run an MCP server that refuses anything but a read
  5. How Datablare does it
  6. What read-only does not protect you from
  7. Try it

A read-only PostgreSQL MCP server lets Claude run SELECT queries against your Postgres database and refuses everything else. Doing it safely takes three layers, not one: a Postgres role that has SELECT on only the tables Claude needs, a session that Postgres itself keeps read-only, and an MCP server that refuses anything other than a single read statement. Add a statement timeout, point it at a replica if you have one, and keep a log of every query. This guide shows how to build each layer yourself, then how Datablare does the same thing for you.

What “read-only” has to mean for an MCP server

MCP (Model Context Protocol) is the open standard that AI tools such as Claude use to call outside tools. A Postgres MCP server exposes a tool along the lines of “run this SQL”, and the model writes the SQL. That is the part to be careful about: you are letting a language model compose queries against your database.

“Read-only” usually gets implemented as one check. It needs to be several, because each one has gaps the others cover.

LayerWhat it stopsWhat it misses
A role with only SELECTWrites and DDL, whatever connects with itReading everything it was granted; slow queries
A read-only sessionWrites, even from a privileged loginCan be switched off by the session’s own SQL; side-effect functions
A query check in the MCP serverMultiple statements, SET, sleep and file functions, unexposed tablesAnything its parser gets wrong, so it must fail closed
Timeouts and row limitsRunaway scans, huge resultsNothing about what is read

Step 1: Create a role that can only read

Create a dedicated login for the AI. Never reuse your application’s user, and never use a superuser: superusers ignore most permission checks.

CREATE ROLE ai_reader LOGIN PASSWORD 'choose-a-strong-password'
  CONNECTION LIMIT 5;

GRANT CONNECT ON DATABASE shop TO ai_reader;
GRANT USAGE ON SCHEMA sales TO ai_reader;

-- Only the tables the AI should read
GRANT SELECT ON sales.orders, sales.order_items, sales.products TO ai_reader;

-- Keep a sensitive column out: grant columns instead of the whole table
GRANT SELECT (id, city, created_at) ON sales.customers TO ai_reader;

If you would rather grant a whole schema, use GRANT SELECT ON ALL TABLES IN SCHEMA sales plus ALTER DEFAULT PRIVILEGES for tables created later. The details, and the same steps for MySQL, SQL Server and Oracle, are in our guide to creating a read-only database user for AI agents.

Step 2: Make the session read-only and bounded

Set defaults on the role so every connection starts read-only and cannot run forever:

ALTER ROLE ai_reader SET default_transaction_read_only = on;
ALTER ROLE ai_reader SET statement_timeout = '30s';
ALTER ROLE ai_reader SET idle_in_transaction_session_timeout = '60s';

Postgres now rejects writes in that session with “cannot execute INSERT in a read-only transaction”, even if someone later grants the role more than it should have.

Know the limit of this layer. default_transaction_read_only is an ordinary setting, and any session can run SET default_transaction_read_only = off or SET TRANSACTION READ WRITE. A server that passes the model’s SQL straight through, and accepts several statements in one call, can also be sent COMMIT; ... to end a read-only transaction it opened. The session setting is a strong layer only when the MCP server refuses SET, COMMIT and multiple statements.

Step 3: Run an MCP server that refuses anything but a read

There are open-source Postgres MCP servers, and some have a read-only mode. Whichever you choose, check it does these things before you trust it with production:

  1. One statement per call. Anything after a semicolon is refused, not ignored.
  2. Reads only. The query must start with SELECT, WITH or VALUES, and must not contain write or session keywords (INSERT, UPDATE, DELETE, CREATE, SET, COPY, CALL, DO, SELECT ... INTO).
  3. No side-effect functions. pg_sleep, advisory locks and set_config all run inside a read-only transaction. So do dblink and the file functions, for a role that has been given them. A sleep holds a connection for as long as it is asked to; set_config can lift your statement timeout.
  4. Tables you chose. A check against an allow-list, using a real SQL parser rather than string matching, so quoted names, subqueries and CTEs are caught.
  5. Bounded results. A row limit and a byte limit, so a SELECT * on a large table does not flood the model’s context.

Connecting it to Claude

You have two options.

A local server in the Claude desktop app. Open Settings, then Developer, then Edit Config, and add the server to claude_desktop_config.json. The exact command depends on the server you picked; the shape is:

{
  "mcpServers": {
    "postgres": {
      "command": "npx",
      "args": ["-y", "<your-postgres-mcp-server>", "postgresql://ai_reader:[email protected]:5432/shop"]
    }
  }
}

This runs on one person’s computer, with the password in a file on that computer, and nothing records what was asked.

A remote server as a custom connector. In Claude, open Settings, then Connectors, and add a custom connector with your server’s HTTPS address. Custom connectors are reached from Anthropic’s cloud, so the server must be public, must use HTTPS, and should require sign-in. Otherwise anyone who finds the address can query your database. At the time of writing, custom connectors need a paid Claude plan.

How Datablare does it

Datablare is a hosted MCP gateway for your database. It puts the layers above in place for you, so you do not run or maintain a server yourself. The flow:

  1. Sign up and create a project. To try it without your own database, choose Try the e-commerce sample during onboarding: an online shop’s customers, products, orders and stock, already described.
  2. Add your PostgreSQL database as a data source: host, port, user and password, or paste a connection string. Datablare checks whether the login can write. If it can only read, you see “Connection works, and this login can only read.” If it can write, Datablare asks you to confirm. Writes are still blocked, but it tells you a read-only login is safer and gives you the SQL to create one.
  3. Choose tables and hide columns. Only the tables you select exist as far as Claude is concerned. In Modeling, switch off a column such as email or phone, and any query that reads it is refused.
  4. Copy the project’s MCP link from the Connect page.
  5. Add it to Claude as a custom connector, click Connect, sign in to Datablare and choose Allow. No password or key is pasted into Claude.
  6. Ask. For example: “Which products sold most last month?”
  7. Open Audit to see the question, who asked, the SQL that ran, how many rows came back and how long it took.

What happens to each query on PostgreSQL

  • Every connection is opened with default_transaction_read_only=on. When you add the source, Datablare runs SHOW default_transaction_read_only to check that it is in force.
  • Before the query reaches Postgres, a guard refuses anything that is not a single SELECT, WITH or VALUES, including SET, COPY, DO and SELECT ... INTO. That is why the session setting cannot be switched off from inside.
  • Denied functions are refused: pg_sleep, advisory locks, set_config, dblink, pg_read_file, query_to_xml, and functions that reveal the server, such as current_user and inet_server_addr.
  • A parser checks every table against the ones you exposed, and every column against the ones you hid, including columns reached through SELECT *, aliases and CTEs. Result columns are checked again after the query runs.
  • Each query gets a statement timeout (30 seconds by default, 60 at most). If your role already has a stricter statement_timeout, yours is kept. Results stop at 1,000 rows by default, 5,000 at most, and 1 MB.
  • Query results go to Claude and are never stored by Datablare.

See how it works for the full query path, and the PostgreSQL page for Postgres-specific setup, including SSH tunnels for databases behind a firewall.

What read-only does not protect you from

Be clear-eyed about the remaining risks, whichever route you take:

  • Claude reads everything it is allowed to read. Rows from allowed tables and columns go to the AI provider under its terms. Read-only is about changes, not exposure. Grant less, and hide columns you would not paste into a chat.
  • Prompt injection. If a table holds text written by outsiders, such as support tickets or reviews, that text can contain instructions. A read-only connection limits the damage to what the agent can read and send elsewhere through its other tools. It does not remove the risk.
  • Load on your database. A correct but expensive query is still expensive. Use a replica, keep timeouts, and set daily limits per project.
  • Wrong answers. A read-only query can still join the wrong tables. Describing tables, rules and example questions in a context layer helps the model write better SQL; checking the SQL in Audit is how you catch mistakes.

Try it

If you want a read-only Postgres connection to Claude without building and running the server yourself, create a free Datablare account, start with the e-commerce sample, and see your first query in Audit within a few minutes. When you are ready, connect your own database using a read-only role from Step 1. Read more about connecting Claude and how we handle security.

Frequently asked questions

Is a read-only transaction enough to keep an MCP server from changing Postgres?

Not on its own. A read-only transaction stops INSERT, UPDATE, DELETE and DDL, but a session can switch it off with SET, a multi-statement query can end the transaction with COMMIT, and functions such as pg_sleep or advisory locks still run. Combine it with a role that only has SELECT and a server that refuses anything other than one read statement.

Can Claude connect to a PostgreSQL database on my laptop or private network?

Claude's custom connectors are reached from Anthropic's cloud, so they need an HTTPS address that is reachable from the internet. A local MCP server configured in the Claude desktop app can reach a database on your machine. For a private database behind a firewall, use an IP allow-list or an SSH tunnel through a gateway that sits outside it.

Does a read-only MCP server stop Claude from seeing sensitive data?

No. Read-only stops changes, not reading. Claude receives every row it queries from the tables and columns it can access. Limit the role to the tables it needs, and use column-level grants or a gateway that hides columns such as email, phone or PAN.

Should the MCP server point at the primary database or a replica?

A read replica is better where you have one. Queries an AI writes can be slow or scan large tables, and on a replica they cannot compete with your application's writes. Add a statement timeout either way.

Keep reading

Give your team answers, not database logins.

Start free with the e-commerce sample or your own database. Connect Claude in about two minutes.

30 minutes with the founder. Or WhatsApp / [email protected]