How to Connect AI Agents to Your Supabase Database Safely

Supabase is Postgres with a nice jacket on. That's a compliment. It means everything you'd do to safely connect an AI agent to plain Postgres applies here, plus a couple of Supabase-specific things worth knowing before you wire anything up.
This is the setup I'd recommend if you want your agent (Claude, Cursor, whatever you use) to answer questions about your Supabase data without you holding your breath every time.
Don't hand it the service role key
Supabase gives you two keys that matter: the anon key and the service_role key. The service role key bypasses row-level security entirely. It is, effectively, god mode for your database.
If you've ever copied the service role key into an agent's config because it was the one that "just worked," go rotate it. The agent doesn't need to bypass RLS. It needs the opposite of that.
Use the pooler connection, not the direct one
Supabase exposes a direct connection and a pooled one (Supavisor). For an agent, you want the pooler. Agents open connections in bursts and don't always clean up after themselves, and the direct connection has a much lower ceiling. You'll hit "too many connections" faster than you'd think.
The pooled connection string lives in your project settings under Database. It looks like a normal Postgres URL pointed at the pooler host on port 6543 (transaction mode) or 5432 (session mode). For read-only agent queries, transaction mode is fine.
Make a read-only role scoped to what the agent needs
This is the part people skip. Create a dedicated database role for the agent and grant it exactly the access it should have:
create role agent_readonly with login password 'use-a-real-secret';
grant connect on database postgres to agent_readonly;
grant usage on schema public to agent_readonly;
-- only the tables you actually want it touching
grant select on public.profiles, public.orders to agent_readonly;
-- protect the database itself, not just the rows
alter role agent_readonly set statement_timeout = '5s';
alter role agent_readonly set default_transaction_read_only = on;Now even if a prompt goes sideways, the worst case is a slow SELECT that gets killed after five seconds. No writes, no access to the tables you didn't list, no way to sit on a lock all afternoon.
Then put a real boundary in front of it
The role above stops writes and runaway queries. It doesn't give you a few things you'll want once this is more than a toy: a log of every query the agent ran, a row cap so a wide SELECT doesn't dump 50,000 records into the model's context, and the ability to add plain-English descriptions of your tables so the agent stops guessing what status = 3 means.
That's the layer QueryBear sits at. You point it at the Supabase pooler connection with the read-only role, and your agent talks to it over MCP instead of talking to Postgres directly. The agent gets the schema, the descriptions, and a safe path to query. You get the audit log. If you'd rather not use a product, you can approximate most of this with a small proxy of your own, but the read-only role is the non-negotiable first step either way.
A quick sanity check
Before you let an agent loose, ask it to do something it shouldn't be able to do. "Delete the test orders." "Add a column to profiles." If your setup is right, it'll come back and tell you it can't, because the role genuinely can't. That's the moment you actually trust it, not when the happy-path query works.
Once that's in place, the day-to-day is genuinely good. "How many signups came from the referral channel last week?" gets answered in the time it takes to read the question, against real Supabase data, with no one filing a ticket.