Scoping AI Agent Data Access to Pre-Aggregated Postgres Views Instead of Live Supabase Tables
Pre-aggregated views let AI agents query safely without degrading production databases.

Governed data access for AI agents on Supabase should use pre-aggregated Postgres materialized views behind a scoped MCP server or REST interface. This separates analytical reads from transactional data and limits what the agent can reach. The agent authenticates to the interface, while the database credential remains behind it with permission to read only approved views.
Why live Supabase tables are the wrong surface for AI agent queries
Live Supabase tables serve application transactions and point lookups. An agent answering a business question asks something different of them. A request for month-over-month revenue by plan tier can require scanning orders and joining users and subscriptions while application writes compete for the same database resources. The query may return the correct answer yet degrade the system serving real users. Correct SQL is not necessarily safe production SQL.
A dashboard normally submits a fixed SQL statement, while an agent can generate a new query on every invocation. A missing WHERE clause or an unintended Cartesian join can create enough load to saturate connections and slow production responses. Supabase row-level security protects rows, but on its own it does not limit which columns can be read or which aggregations can be run. Table privileges are controlled separately through GRANT and REVOKE, so row filtering alone does not provide a suitably narrow analytical interface.
The structural mismatch between OLTP data and analytical questions
The mismatch begins with the data model. OLTP schemas favor write integrity and transactional correctness, often using third normal form to avoid redundant data. This keeps business facts spread across related tables, where they still need to be put together for analysis. An agent must reconstruct those facts at read time, repeating joins and aggregations whenever it answers a question.
For example, identifying active subscribers who upgraded in the last 30 days may touch users, subscriptions, subscription_events, plans, payments, and possibly feature_flags. Indexes designed for single-column transactional lookups do not remove the need to assemble the relevant facts and group them at the requested grain. Adding an index can help a particular query without correcting the underlying mismatch.
An agent sees the tables it can use and writes SQL to query them. It does not necessarily receive a signal that a technically valid query is an operationally poor fit for the database. Better SQL generation cannot, by itself, turn a normalized transactional schema into a pre-built analytical surface. The solution is to change the surface offered to the agent, which can be done inside Postgres without asking the agent to reconstruct the application’s business logic.
What pre-aggregated Postgres views actually are and how they differ from plain views
A plain Postgres view is saved SQL. Reading it executes the underlying query again, including its joins and aggregations. A materialized view instead persists the result as a physical snapshot. The defining work happens at refresh time. A plain view can narrow visible data, but it does not provide this separation of computation.
For pre-aggregated materialized views, the stored rows already contain the calculations defined by the view. An agent can read those rows without running the joins or aggregations again, though a question beyond the stored grain may still need more calculation. The snapshot is also indexable using standard Postgres indexes. Its columns are author-controlled, and its read privileges can be granted independently of access to the source tables. These properties combine a smaller information surface with a more predictable read workload.
Supabase can expose materialized views through its PostgREST API layer, giving clients a familiar interface to the stored results. Refresh timing remains separate from those reads. A schedule or Postgres function can rebuild the snapshot without making every agent request responsible for recomputing the metric. This changes where and when analytical work runs. It does not eliminate the refresh work itself.
Designing the view layer so agents get scoped, intention-revealing data
A materialized view for agent access should encode a business question. The name daily_revenue_by_plan says what the data tells the agent, while mv_orders_subscriptions_join says how it was put together. That difference helps an agent choose an object and explain its answer. Naming is part of the interface. Column names should likewise name the business concepts the agent is expected to use.
For daily_revenue_by_plan, the grain is daily revenue by plan. That grain should match the smallest unit required by the intended questions, because an aggregate cannot reveal detail that was discarded when it was built. A coarser view would leave daily questions unanswered, while an unnecessarily fine-grained view would push more aggregation back onto the agent. Other kinds of questions need other grains, such as daily cohort grain for retention and activation, billing-period grain for revenue and churn, and event-type grain for feature usage. Grain is therefore a scope decision as well as a performance decision.
The lesson for implementation is to include only the columns needed to answer the intended questions. Each extra column gives the agent more information and more room to get confused. A refreshed_at column gives the agent a way to report data age without accessing a separate metadata table. Placing the views in a dedicated analytics schema then makes the boundary explicit. The agent-facing role should have access to that analytical namespace and no access to the production schema.
How Postgres permissions and Supabase roles lock down the agent's reach
Database privileges turn the intended boundary into an enforced one. A dedicated role such as agent_readonly should receive USAGE on analytics and SELECT on specific materialized views, with no access to underlying application tables. The Supabase connection pooler lets that custom role connect with its narrow grants. The role defines reachable data. An application-layer filter cannot enforce this database boundary in its place.
Supabase’s anon role, service-role credentials, and custom roles should not be treated as interchangeable access choices. The service-role key bypasses row-level security and should never be handed to an agent. A custom role allows the required privileges to be expressed specifically for the agent-facing service. When access is limited to approved views, the permission boundary blocks a prompt-injected request to run SELECT * FROM users, keeping it from returning production data.
The practical requirement is that the role’s effective privileges exclude public and the live tables within it. An explicit revocation such as REVOKE ALL ON SCHEMA public FROM agent_readonly belongs in that access-control setup as part of establishing the complete privilege boundary. Column-level grants can narrow access further when a view contains both safe and sensitive columns and its definition cannot be changed. Tenant scope needs equal attention. A shared aggregate should not be assumed to keep the source tables’ row-level protections just because those tables use RLS.
Managing view freshness without blocking agents or disrupting application writes
Freshness should follow the business decision. Revenue trends, cohort retention, and feature adoption can often use data that is minutes or hours old. Their interpretation rarely changes every second. This leaves room to schedule analytical computation apart from the times when agents ask for data. A declared snapshot age is a useful property of the answer.
For scheduled refreshes, pg_cron can run REFRESH MATERIALIZED VIEW CONCURRENTLY at the chosen interval. Concurrent refresh allows reads to continue while the snapshot is rebuilt, but it requires a unique index on the materialized view. That requirement should be accounted for when the view is created. Refresh computation still has a cost even when reads can continue. The plan for refreshes still needs to fit the application’s write workload.
More responsive refreshes can be triggered by a Postgres function or significant write event, such as a recorded billing event. The trade-off is added write-path overhead, which can undermine the separation if freshness is pursued too aggressively. A scheduled Supabase Edge Function can also run refresh logic when it needs context from the application layer. The refreshed_at column defined earlier lets agents say how old the data is and flag refreshes that are late. An answer labeled “revenue as of 2 hours ago” communicates a known limitation instead of implying freshness the interface does not provide.
How to let AI agents query Supabase through a governed MCP or REST interface
An MCP server can give the agent access to approved metrics without giving it a database connection. The server holds the credential for the role with narrow grants and offers each analytical view as a tool with a name and description. The agent authenticates to the server and selects a tool by business intent. This removes the need to generate arbitrary SQL. Database privileges remain the backstop if the interface is used in an unintended way.
For the running example, a tool named get_daily_revenue_by_plan would map to the corresponding view. Its description should state the grain, available time range, and meaning of the returned columns. Parameters should be limited to supported filters, such as date range and plan tier, using the view’s indexed columns. The server then executes a parameterized query.
For teams choosing an MCP server, the relevant criteria are named metric tools, restricted parameters, parameterized execution, and compatibility with narrowly granted database credentials. A connector’s ability to reach Supabase is not enough. Multi-tenant use also requires an enforced tenant boundary rather than an optional tenant filter the agent can omit. The database role limits which objects the server can query, while the governed interface must preserve the scope of the authenticated request. These are separate responsibilities, even when one service implements both.
Supabase’s PostgREST layer offers an alternative when REST endpoints are sufficient. Materialized views in analytics become available through that layer after the schema is added to the exposed schemas list and the relevant roles receive appropriate grants. With either approach, the agent can be kept from getting direct database credentials. MCP adds tool descriptions tied to business questions and limits on the parameters, while REST gives access through the existing Supabase API surface. Repeated metric reads can also be cached at the MCP layer, avoiding another scan of the materialized view.
When this pattern is worth applying and where it has limits
Pre-aggregated views work best when the expected questions are recurring and bounded. Product analytics, business metrics, and reporting commonly fit that condition because their grains and definitions can be specified in advance. Exploratory requests do not fit as neatly. A question such as “find something interesting in our data” may require details and relationships that the approved aggregates omit. For that workload, a separate analytical replica or read-optimized copy queryable through DuckDB or a similar tool is a more suitable surface than live production tables.
The pattern also creates work that needs an owner. Every materialized view adds a refresh job to monitor, a definition to version-control, and a grain decision to revisit as the business changes. Teams without data infrastructure ownership need to include that responsibility in the decision. For very early-stage projects whose schema changes weekly, maintenance may temporarily outweigh the benefit of materialization. A plain view with a tight scope and conservative Postgres grants can set a boundary for now, though reads still have the compute cost described earlier.
The adoption trigger is the moment an agent is given a credential that touches production. At that point, structural separation between agent access and live application tables becomes a deployment requirement.


