You know the messages. "Quick one — how many trial users converted last month?" "Can you pull the top 20 accounts by revenue?" "What's our churn for the enterprise plan?" Each one is a two-minute query for you, and a half-day wait for the person asking. Multiply that across a sales team, a support team, and a couple of founders, and you've quietly become a human query API.
There are two obvious ways out of this, and both are traps. You can give people direct database access — hand out a read-only connection string and a SQL editor and tell them to help themselves. Now credentials for your production data are sitting in six laptops, three password managers, and at least one Slack DM, and you have no idea who ran what. Or you can keep the ticket queue and stay the bottleneck. Neither is good. This post is about a third option: putting an AI assistant in front of the database so people can ask questions in plain English, while the actual credentials, the read/write boundary, and the row-level scoping all stay firmly under your control.
The problem with the two easy answers
Self-serve analytics has been promised for a decade and mostly hasn't landed. When teams lock everything down, business users go right back to filing tickets and the data team is the bottleneck again. When teams open everything up, adoption soars but trust collapses — the same metric gets defined three different ways and nobody knows which "active users" number is real. And underneath both failure modes is a security question people gloss over: who actually holds the keys to the database?
Handing raw credentials to non-technical teammates fails on every axis. Credentials leak. They're long-lived and painful to rotate. A well-meaning person can run a monster query that locks a table on your primary during peak traffic. And you get no audit trail worth the name. What you actually want is for people to get answers without ever touching a credential.
The third option: an AI assistant behind a broker
The pattern that makes this safe is a broker (increasingly, a Model Context Protocol server) that sits between the AI tool and your database. The person talks to an AI assistant — Claude, Cursor, ChatGPT, whatever they already use. The assistant doesn't connect to your database. It connects to the broker. The broker holds the real connection and enforces the rules.
Here's the important part: the AI never learns your database credentials. It sends a request like "run this SELECT" to the broker, and the broker — which authenticates the user, not the model — decides whether to run it and with what privileges. Connecting the AI tool looks roughly like this on the client side, with no connection string in sight:
{
"mcpServers": {
"company-db": {
"url": "https://your-broker.example.com/mcp",
"transport": "http",
"auth": "oauth"
}
}
}
The teammate signs in through OAuth. No password gets pasted into a prompt, no secret ends up in a chat log, and access can be revoked centrally the moment someone leaves. That single property — credentials live in one place, not scattered across every laptop — is most of the security win right there.
What "safe" actually means here
A broker in the middle only helps if it enforces real guardrails. There are four worth insisting on.
Read-only by design. The connection the broker uses should be a database role that can SELECT and nothing else. In Postgres:
CREATE ROLE ai_readonly LOGIN PASSWORD '...';
GRANT CONNECT ON DATABASE app TO ai_readonly;
GRANT USAGE ON SCHEMA public TO ai_readonly;
GRANT SELECT ON ALL TABLES IN SCHEMA public TO ai_readonly;
-- new tables should inherit the same restriction
ALTER DEFAULT PRIVILEGES IN SCHEMA public
GRANT SELECT ON TABLES TO ai_readonly;
Now if the assistant ever generates a write — because a prompt was sloppy or someone got clever — the database itself rejects it:
-- assistant attempts this on the read-only connection:
DELETE FROM users WHERE last_login < now() - interval '1 year';
-- ERROR: permission denied for table users
The AI can explore all day and never modify a byte. That's not a promise you're trusting the model to keep; it's enforced one layer below it.
Row-level security scopes what each person sees. Read-only isn't the same as "everyone sees everything." If your support lead should only see EU orders, row-level security enforces that regardless of what SQL the AI writes:
ALTER TABLE orders ENABLE ROW LEVEL SECURITY;
CREATE POLICY team_sees_own_region ON orders
FOR SELECT
USING (region = current_setting('app.current_region'));
The broker sets app.current_region from the authenticated user's identity, so the same natural-language question returns different rows for different people, and nobody can widen their own scope by rephrasing the prompt.
Schema awareness keeps the SQL honest. The broker can share your schema — table names, columns, types — with the assistant. This is the difference between an AI that guesses at a customer_name column that doesn't exist and one that writes SELECT full_name FROM users because it can see the real shape of your data. Sharing schema (not credentials) is what turns text-to-SQL from a party trick into something you'd let a colleague rely on.
Everything is logged. Because every query flows through one gateway, you get a single audit trail: who asked, what SQL ran, when. Compare that to auditing six SQL editors on six laptops — which is to say, not auditing anything.
What it looks like in practice
Once this is wired up, a teammate who has never written SQL just asks. Here are realistic questions and the SQL a schema-aware assistant produces behind the scenes:
| What they type | What runs (they never see this) |
|---|---|
| "How many trials converted last week?" | SELECT count(*) FROM subscriptions WHERE plan <> 'trial' AND status = 'active' AND converted_at >= now() - interval '7 days'; |
| "Top 5 accounts by revenue this quarter" | SELECT a.name, sum(o.amount) AS revenue FROM orders o JOIN accounts a ON a.id = o.account_id WHERE o.created_at >= date_trunc('quarter', now()) GROUP BY a.name ORDER BY revenue DESC LIMIT 5; |
| "Which features did churned users touch least?" | SELECT e.feature, count(*) FROM events e JOIN users u ON u.id = e.user_id WHERE u.churned = true GROUP BY e.feature ORDER BY count(*) ASC; |
A slightly meatier one — weekly signups versus conversions — shows the assistant handling a join and a couple of filtered aggregates the person would never want to write by hand:
SELECT
date_trunc('week', u.created_at) AS signup_week,
count(*) AS signups,
count(*) FILTER (WHERE s.status = 'active'
AND s.plan <> 'trial') AS converted
FROM users u
LEFT JOIN subscriptions s ON s.user_id = u.id
WHERE u.created_at >= now() - interval '8 weeks'
GROUP BY 1
ORDER BY 1;
The teammate gets a table back. You never got pinged. And nobody touched a credential.
Common mistakes to avoid
Pointing the broker at your primary. Even read-only queries consume CPU and can cause lock contention. Point the read-only role at a replica so a curious sales rep's 10-million-row scan can't slow down checkout.
Exposing the entire schema on day one. Start restrictive. Grant SELECT on the ten tables people actually ask about, not all forty including password_resets and internal_audit. Expand based on real questions.
Skipping the semantic layer. If "active user" means three different things in three tables, the AI will pick one and sound confident. Define your core metrics once — in views or a semantic layer — so everyone's "active users" resolves to the same SQL.
Trusting output without spot-checks. AI-generated SQL is usually right and occasionally, fluently wrong. For any number that drives a decision, have someone technical eyeball the generated query the first few times a new question type shows up. The read-only boundary protects your data; it doesn't guarantee the logic.
Assuming read-only means private. Read-only still returns whatever the role can see. If people shouldn't see salaries or other customers' data, that's row-level and column-level security's job — not something "read-only" covers on its own.
If you'd rather not stand up and maintain the broker yourself, managed MCP servers implement these patterns for you — Draxlr, for instance, runs one that connects over OAuth and is read-only (SELECT only), so the AI can list schema, run queries, and build dashboards without ever holding your credentials. The point stands whichever route you take: the credentials, the read/write boundary, and the scoping belong to you, not to the model.
Key takeaways
The goal isn't to teach your whole company SQL, and it definitely isn't to hand out database passwords. It's to let people ask questions in the language they already speak while the guardrails stay yours. Put a broker between the AI and the database so the model never sees credentials. Make the connection read-only at the database level, not by asking nicely. Scope rows per person with RLS. Share schema so the SQL is accurate. Log everything through one gateway. Do that, and the "quick one — can you pull this?" messages mostly stop, without you trading away a single ounce of control.
Your turn
How does your team handle data requests today — ticket queue, shared read-only login, a BI tool, or something AI-driven? If you've let non-technical teammates query your database, what guardrail turned out to matter most, and what surprised you? Drop it in the comments.
Top comments (0)