Running the Postgres Model Context Protocol server in read-only mode for AI agents
Enable the read-only setting in the Postgres MCP server, but do not rely on it. Enforce the restriction in PostgreSQL itself with a dedicated least-privilege role that holds only SELECT grants, a read-only transaction default for that role, a statement timeout, and ideally a read replica. Then plan for what read-only mode cannot address: sensitive data exposure through permitted queries, expensive queries, and prompt-driven misuse.
A layered architecture that does not depend on one control
- 1
Layer 1: MCP server access mode
Run the server in its read-only configuration and limit exposed tools to those the agent needs.
- 2
Layer 2: dedicated least-privilege role
The authoritative control. PostgreSQL rejects writes and DDL because the role lacks the privileges.
- 3
Layer 3: session defaults and limits
Read-only transaction default, statement timeout, connection limit, and a replica as the target.
- 4
Layer 4: row and column visibility
Views, row-level security, or column grants restrict which data permitted queries can return.
- 5
Layer 5: runtime policy and evidence
Policy applied to each tool call, with a durable record of what was permitted and what happened.
Verify the restriction from both directions
- Attempt INSERT, UPDATE, DELETE, and TRUNCATE and confirm PostgreSQL rejects each at the role level
- Attempt CREATE, ALTER, and DROP statements and confirm they fail
- Send multi-statement input and transaction-control statements through the tool and confirm no write succeeds
- Run a deliberately slow query and confirm the statement timeout cancels it
- Query a restricted table or column and confirm access is denied or filtered
- Confirm the role cannot connect from an unapproved network location
What read-only mode does and does not guarantee
Read-only mode in an MCP server is usually a statement about how the server behaves: which tools it exposes and how it wraps the SQL it sends. The underlying concept is standard. FIPS 193 describes an SQL read-only server profile in which the transaction access mode is always READ ONLY, set through SET TRANSACTION. That is a useful model, but it describes an interface contract, not a guarantee that every path to the database honors it.
The weakness is the layer where the restriction lives. If the restriction exists only as a transaction wrapper applied by the MCP server, SQL that ends or alters that transaction, or that arrives as several statements in one call, can undermine it. Whether a given server is exposed depends on its implementation, so treat the server setting as a first layer and assume it can fail.
Two further points shape the design. First, read-only access does not mean a stable view of the data. FIPS 193 notes that a read-only profile is still subject to dirty reads and non-repeatable reads when other users can update the database and the isolation level is not Serializable. An agent running several queries may see data change between them. Second, read-only says nothing about which data the agent can see. That is a separate control problem, covered later in this guide.
Enforce read-only access in PostgreSQL
Start in the database, because that is where the restriction survives a misbehaving server. Create a role used only by the agent's MCP connection. Do not reuse an application or administrator role. Grant it the ability to connect, usage on the schemas it needs, and SELECT on specific tables or views. Avoid blanket grants on entire schemas where practical, and avoid default privileges that automatically extend access to future objects. Confirm that the role owns nothing, since owners can alter their own objects, and that it is not a member of any role with broader rights.
Next, set session defaults on the role. Making read-only transactions the default for that role (default_transaction_read_only) adds a second barrier, but understand its limit: it is a default, and a session can generally change it. It is a safety net against accidental writes, not a substitute for withholding write privileges. Set a statement timeout on the role so a single expensive query cannot run indefinitely, and set a connection limit so the agent cannot exhaust connections.
Where the workload allows it, point the agent at a read replica. A standby that is in recovery rejects writes regardless of role privileges, which gives an enforcement point independent of both the MCP server and the role. The tradeoff is replication lag, which compounds the changing-data behavior noted above. Restrict network access to the database host so only the MCP server's address can reach it with that role, and use credentials that can be rotated without redeploying the agent.
- Create a role used only by the agent connection, with no ownership of objects
- Grant CONNECT, schema USAGE, and SELECT on named objects only
- Set a read-only transaction default, a statement timeout, and a connection limit on the role
- Prefer a read replica as the connection target
- Restrict network source addresses for the role
- Expose sensitive tables through views or row-level security rather than direct grants
Configure the MCP server and connection
With the database holding the real restriction, configure the MCP server so it agrees with it. Enable the server's read-only or restricted access mode if it provides one, and connect with the dedicated role's credentials, never with a privileged account. Keep credentials in a secrets store rather than in the agent's configuration or prompt context, so the agent never handles them directly.
Because implementations differ in how they enforce read-only behavior, review the setting against the server's own documentation and source before you depend on it. Check whether it limits tools, wraps transactions, or both, and whether it rejects multi-statement input. Pin the server version so behavior does not change underneath you, and re-run your verification tests when you upgrade.
Keep the exposed tool surface small. If the agent only needs to run queries and inspect table structure, do not expose tools beyond that. Fewer tools mean fewer paths to examine and a smaller set of tool calls to govern later.
Residual risks and where runtime policy adds a layer
A correctly configured read-only role stops modification. It does not stop an agent from reading everything the role can select. If the role can reach customer or personal data, a permitted query can return it, and a prompt-injected instruction embedded in retrieved content can steer an agent to query and relay data it should not. Row-level security and views narrow what the role can see, but they are static and are defined by table, not by what the agent is doing in a given interaction. Expensive queries are a related gap. Timeouts and replicas contain them, but they do not decide whether a particular query should run at all.
There is also an evidence gap. Database privileges show what the role could do. They do not give a reviewer a record of the permitted scope alongside each action the agent took. Teams that must demonstrate this to an auditor need both: a documented least-privilege assignment and a record of actual tool calls.
This is where runtime policy enforcement on tool calls fits. Trussed enforces policy in the execution path on MCP tool calls, with allow, redact, and block outcomes, so a call can be permitted, have sensitive values redacted from the result, or be stopped before it reaches the database. Enforcement is applied at runtime, not reviewed after the fact, and it generates audit-ready evidence of what the agent was permitted to do and what it did. It addresses data visibility and the evidence record, which database-level read-only controls do not. It sits alongside the database controls described above and does not replace them.
Four layers between an agent and production data
MCP server access mode
A read-only setting on the server restricts the tools and transactions it will issue.
Dedicated database role
SELECT-only privileges on named objects, enforced by PostgreSQL regardless of the server.
Session and resource limits
Read-only defaults, statement timeouts, connection limits, and a replica as the target.
Runtime policy on tool calls
Allow, redact, or block individual calls and produce evidence of each decision.
Add runtime policy and evidence to your read-only design
See how policy enforcement on MCP tool calls complements database-level controls and produces audit-ready evidence for a regulated enterprise.
Explore Agent and Tool-Call Enforcement