Implementation Guide

    How to Restrict a Postgres MCP Server to Approved Schemas and Tables

    Connect the MCP server with a dedicated least-privilege Postgres role that holds grants only on approved schemas and on views that expose approved columns, and make its sessions read-only. Then enforce policy on each MCP tool call, so requests outside the approved schemas and tables are allowed, redacted or blocked. Retain the role definitions, the policy and the per-call decisions as evidence of what was permitted and what was queried.

    Verify that the restrictions hold

    • Connect as the MCP role and attempt SELECT on a base table outside the approved set. Expect a permission error.
    • Attempt INSERT, UPDATE, DELETE and DDL. Expect permission errors, even after trying to disable read-only mode.
    • Query the system catalogs and confirm what object names are visible. Decide whether that exposure is acceptable.
    • Send an out-of-scope tool call through the MCP server and confirm the policy layer blocks it before it reaches Postgres.
    • Send an in-scope call that returns a protected field and confirm it is redacted.
    • Rerun these tests after each schema change or grant change, and keep the results.

    Why one credential is the core problem

    Many Postgres MCP servers connect with a single database credential. Whatever that credential can reach, the agent can reach, and an agent that can list schemas and run queries will find objects the deployment never intended it to use. Those objects may include tables holding sensitive or regulated data.

    The fix is not one control. Database permissions are the foundation, because they hold even when the agent, the prompt or the MCP server misbehaves. They are configured separately from the agent's tool calls, though, and they know nothing about the request in front of them. A second layer evaluates each tool call before it reaches the database.

    This follows established access-control guidance. NIST's zero trust work calls for access decisions based on least-privilege, per-request, context-based principles tied to identities. NIST also describes role-based access control as a role construct that defines a set of allowable activities. A dedicated database role is the static half of that model. A per-call decision is the runtime half.

    Layer one: confine the credential in Postgres

    Start by defining what the agent is allowed to do, then express that as a role. The steps below apply to any Postgres MCP server, because they act on the credential rather than on the server.

    • Create a dedicated role: Use a login role used only by the MCP server, with no superuser, CREATEROLE or CREATEDB attributes and no membership in broader roles. Keep privileges on a separate group role so they can be reviewed as one unit.
    • Remove inherited access: Revoke default privileges that PUBLIC holds on schemas such as public. Grant USAGE only on the approved schemas, so unapproved schemas are not reachable by name.
    • Expose views, not base tables: Place views containing only approved columns in a dedicated schema and grant SELECT on those views. SQL privileges apply at table or view granularity, and views limit which columns an agent can see.
    • Control future objects: Review ALTER DEFAULT PRIVILEGES so newly created tables do not become readable by the agent role automatically.
    • Make sessions read-only: Withhold INSERT, UPDATE, DELETE and DDL privileges, and set default_transaction_read_only for the role. Add a statement_timeout and a fixed search_path.
    • Apply row-level security where needed: If approved tables mix rows the agent may and may not see, add row-level security policies. Check how view ownership interacts with them.

    Where database controls leave gaps

    Grants are strong at one thing: they refuse access to objects the role cannot use, regardless of how the request was phrased. They have limits that matter when an agent can submit arbitrary SQL.

    First, a read-only default is a setting, not a privilege. A session can often turn it off for itself, so the real protection is the absence of write grants. Treat default_transaction_read_only as a safety net.

    Second, schema discovery is partly outside your grants. System catalogs are readable by ordinary roles, so an agent may still enumerate object names it cannot query. Names alone can reveal what a system holds.

    Third, grants cannot distinguish purpose. If the role may read an approved view, a prompt-injected instruction can read that view and place the results into a response. The database sees a permitted query. It does not know the call was triggered by untrusted content, which NIST lists among agentic AI threats.

    Fourth, the role is only as narrow as its last review. A broad grant added for a one-off task stays in force until someone removes it. These gaps are why a second layer on the tool call is worth the effort.

    Layer two: enforce policy on MCP tool calls

    The MCP tool call is the point where intent is visible: which tool, which arguments, which agent, and which schema or table the request names. Policy at this layer can decide per request, which is the zero trust pattern of evaluating access each time rather than only at connection.

    Define the approved schemas and tables once, as policy, and evaluate every call against it. Calls that stay inside the approved set are allowed. Calls that reach outside it are blocked. Calls that are in scope but return fields that should not leave the boundary can be redacted. The decision belongs to the execution path, not a review after the fact, so an out-of-scope request never reaches Postgres.

    Trussed enforces policy in the execution path across agents, tools and MCP servers, with allow, redact and block outcomes applied to individual tool calls. It works as the second layer described here. It does not replace the database role, which should still hold if the policy layer is misconfigured or bypassed.

    The division of labor is simple. Postgres decides what the credential can ever do. Tool-call policy decides what this agent may do with that credential in this request. Anything you cannot express as a grant, such as limiting an arbitrary SQL tool or schema discovery queries, belongs in policy.

    Evidence to retain for audit

    Auditors and regulators tend to ask two questions: what was the agent allowed to reach, and what did it actually reach. Answer each with separate records.

    For permitted scope, retain the role definition, the grants on schemas, tables and views, the view definitions, and the history of changes to them. Retain the tool-call policy and its version history too. Together these show what was permitted at any point in time.

    For actual activity, retain a record per tool call: the agent identity, timestamp, tool, the schemas and tables referenced, and the policy decision (allowed, redacted or blocked). Attribute each call to an identity rather than to the shared database user, otherwise every query looks like the same actor. Keep the test results from the verification step as proof the restrictions were exercised.

    Comparing the two sets shows whether the agent stayed within scope, and blocked calls show where it tried to leave. Trussed generates audit-ready evidence from enforcement, mapped to HIPAA, NAIC, NYDFS, EU AI Act and NIST, with configurable retention and self-managed deployment in the customer cloud. Whatever tooling you use, set retention and residency to match your own obligations.

    Two enforcement layers for a Postgres MCP server

    Database layer

    Dedicated role, schema and table grants, views, read-only sessions.

    Tool-call layer

    Per-request policy on each MCP call: allow, redact or block.

    Evidence layer

    Permitted scope, actual queries and policy decisions retained for audit.

    Add a second enforcement layer to your Postgres MCP server

    See how policy on MCP tool calls works alongside database permissions, and how the resulting evidence supports audit.

    See How Evidence Is Produced
    Next step

    How does your AI governance program compare?

    See where your program has gaps in less than 2 minutes.