Skip to main content

Give agents SQL, not the keys to every engine

· 6 min read
Amit Gilad
Creator of QueryFlux
Give agents SQL, not the keys to every engine

The fastest way to give an AI agent data is also the worst one: hand it a warehouse connection string and let it write SQL. It works in a demo. In production it means one shared credential for every agent, no record of which agent ran what, and nothing between a generated SELECT * and your largest table.

Agents shouldn't choose engines, and they shouldn't hold warehouse passwords. Something in the query path should decide where each query runs, what it may do, and keep the record.

This post shows how to set that up with QueryFlux using features that ship today.

Flow diagram: agent connects via MCP to QueryFlux, which applies guardrails and impersonation before Trino, with query history in Studio
Agents see one endpoint. Operators keep routing, limits, guardrails, identity, and audit in the proxy.

The setup in one file​

One QueryFlux config gives agents a verified identity, their own entry point, capacity pool, guardrails, and per-user identity on the engine:

QueryFlux YAML with OIDC auth required, Trino impersonation, analysts and agents groups with allowGroups, protocol router for MCP, and agent guardrails
Full example — adjust IdP URLs, endpoints, and limits for your environment. See authentication, configuration, and the MCP quickstart.

The sections below map directly to that file.

1. One endpoint, any agent​

Turn on OIDC (or another verified provider) with auth.required: true so MCP callers cannot connect as anonymous. Agents send a bearer token on every request:

Terminal command: claude mcp add with HTTP transport and Authorization Bearer header to QueryFlux MCP
Cursor and Claude Desktop use the same MCP URL; put the token in an environment variable, not in a committed config. See Connecting a client.

The agent gets tools to run a query, list schemas, describe a table, read the engine's plan, and check or cancel what it started.

Agents that prefer a SQL driver can connect over Trino, PostgreSQL, or MySQL instead — routing, guardrails, impersonation, and audit work the same because they live in the query path, not in the MCP layer.

2. Agents get their own lane​

The protocolBased router sends every MCP query to the agents group, which has its own maxRunningQueries. A burst of exploratory agent queries queues in its own lane instead of starving dashboards in analysts.

MCP's execute_query also accepts an optional engine_hint that can name a cluster group and skip the router. Without a group allow-list, an authenticated agent could hint analysts and pick up that group's capacity without its agent guardrails. The config above sets authorization.allowGroups on each group so only IdP group analysts can reach analysts, and only agents can reach agents.

With Postgres persistence, the limit holds across every QueryFlux replica and worker, and waiting queries are served first come, first served.

3. Guardrails in the query path​

The agents group gets three built-in guards on top of any global ones:

GuardEffect
read_onlyBlocks anything that isn't SELECT, WITH, SHOW, DESCRIBE, or EXPLAIN
row_limitDenies queries whose outer LIMIT exceeds 5,000 rows; warns when there is no LIMIT
require_predicateRejects scans of fact/event tables with no WHERE

A blocked query returns an error naming the rule that failed, so the agent can rewrite and retry. When the built-ins aren't enough, add a Python guard or call your own webhook — both receive the agent's context.

Why guardrails belong in QueryFlux

A rule in an MCP wrapper only holds while the agent uses that wrapper. A rule in the data path holds for MCP, SQL drivers, and anything else that speaks to the proxy.

4. The engine sees the person, not a shared account​

With queryAuth: impersonate, QueryFlux authenticates to Trino with its own service account but runs each query as the verified user from the bearer token. The engine applies that person's permissions and writes that person into its audit log. There is no shared password for an agent to leak or overreach with — and without auth.required, impersonation would only forward anonymous.

Trino and ClickHouse support impersonation. For Snowflake and Trino, QueryFlux can instead exchange the caller's OAuth token for one the engine accepts, or pass the caller's credential through.

For finer control, add row filters and column masks from OPA or Cerbos — they apply to agents exactly as they apply to people.

5. Every query has an owner​

QueryFlux stores agent context when it is present:

  • MCP: agent_id defaults from the authenticated user and conversation_id from the MCP session. step_index and tool_call_id are optional — the client must send them (as tool parameters or X-Step-Index / X-Tool-Call-Id headers) if you want a full step-by-step trail.
  • Trino HTTP (and other SQL drivers): both X-Agent-Id and X-Conversation-Id are required for the request to count as agentic; otherwise it is treated as a normal client query. Optional headers cover step, tool call, and intent.

In Studio, the Agents and Conversations pages group history by agent and session so you can replay what was recorded: every query, where it ran, how long it took, and which guardrail stopped it.

What this adds up to​

An agent gets one endpoint and the SQL it needs. You keep control of:

  • which engine runs each query;
  • how much of the cluster agents can use;
  • what agents may do;
  • whose permissions apply on the engine;
  • a record of agent and conversation — plus step/tool metadata when the client supplies it.

It works the same over MCP and over a SQL driver, on the engines you already run.

Start with the MCP quickstart, then read guardrails, agent context, and authentication.