guide
guide8 min read

How Do You Stop AI Agent MCP Queries From Running Up Your BigQuery or Snowflake Bill?

Byte caps, resource monitors, timeouts and query tags for AI-written SQL

To stop an MCP-connected agent from running up a warehouse bill, set the limit in the warehouse, not in the prompt. On BigQuery, set a maximum bytes billed on every job: per BigQuery's cost documentation, a query that would exceed the limit "fails without incurring a charge." On Snowflake, put the agent's warehouse under a resource monitor and lower its statement timeout. Then tag every query so you can see who spent what.

Key Takeaways

  • •BigQuery bills on-demand queries by bytes scanned, so the guardrail is a per-job byte cap (maximum bytes billed) plus a dry run before execution.
  • •Snowflake bills compute, so the guardrail is a dedicated warehouse, a resource monitor with a credit quota, and a short statement timeout.
  • •A resource monitor covers warehouses only. Serverless features are not controlled by it, so they need a budget instead.
  • •Put the limits in the credential the MCP server uses. A limit the model can talk its way around is a suggestion, not a control.
  • •Tag every agent query with an agent and session identifier so the spend can be attributed after the fact.

Why AI-written SQL is a cost problem, not just a correctness problem

A human analyst who writes a bad query usually notices the spinner. An agent does not. It generates SQL, reads the error or the result, and tries again, sometimes dozens of times in one session. Each attempt can scan a full table because the model has no intuition for partition pruning or table size. A 2026 comparison of MCP servers for Snowflake, BigQuery and Postgres makes the same point: BigQuery bills by bytes scanned, Snowflake and Databricks bill compute, and AI-written queries can get expensive without limits.

The failure modes are mundane. An agent runs SELECT * on a wide event table to "look at the shape". It joins two large tables without a filter because the question was vague. It retries a timed-out query in a loop. None of these is malicious, and none requires a prompt-injection attack. They are what happens when a fast, literal-minded caller gets read access to a metered system.

The MCP specification is clear that tools are model-controlled and that there should be a human in the loop with the ability to deny invocations. It also says servers must rate limit tool invocations and clients should implement timeouts. That is the protocol-level floor. Cost control for a specific warehouse is yours to build, and the rest of this guide covers where.

Layer 1: cap bytes on BigQuery with dry runs and maximum bytes billed

BigQuery gives you two complementary tools. A dry run validates the query and reports how many bytes it would process, without running it. The maximum bytes billed setting is the hard stop: if the estimate exceeds the limit, the job fails and you are not charged. The docs show a --maximum_bytes_billed=1000000 example that returns a clear "Query exceeded limit for bytes billed" error, which an agent can read and react to.

Wire both into the MCP server rather than the prompt. The server should run a dry run on every statement, compare the estimate to a per-call budget, and refuse to execute above it. It should also pass a maximum bytes billed value on the real job as a second line of defense. The dry run gives the agent a useful message ("this scans 400 GB, add a partition filter"). The job-level cap catches anything the dry run misjudged.

Know the edges. The same documentation notes that the billed-bytes estimate is an upper bound and can exceed the actual bytes billed, and that for clustered tables a query can fail even when actual billing would have stayed under the limit. Dry runs against tables protected by row-level security always return 0 bytes, so a dry-run check alone will not protect those tables. BigQuery also bills a minimum of 10 MiB per referenced table and per query, so a loop of tiny queries is not free either.

Layer 2: isolate Snowflake spend with a warehouse and a resource monitor

Snowflake bills credits for warehouse compute, so you cap the compute. Give the agent its own warehouse, sized small, with a short auto-suspend. Then attach a resource monitor. Per the Snowflake resource monitor documentation, a monitor carries a credit quota per interval and can run up to five notify actions, plus one Suspend and one Suspend Immediate action. Thresholds are percentages of the quota and can exceed 100.

A sensible pattern for an agent warehouse: notify at 50 and 80 percent, suspend at 100, suspend immediately at 110. Suspend lets running statements finish, while suspend immediately cancels them, so the last threshold is your backstop for a runaway query. Because a warehouse can belong to only one monitor below the account level, the dedicated agent warehouse is also what makes the quota meaningful. If the agent shares a warehouse with the dashboard workload, the monitor cannot tell them apart.

Be honest about the gap. The same page states that resource monitors cover warehouses only, and that serverless features and AI services should be watched with a budget instead. If your agent can trigger something like automatic clustering or a materialized view, the monitor will not stop it. Pair the monitor with a budget and with an access policy that keeps the agent's role away from DDL.

Layer 3: kill slow queries with statement timeouts

Byte caps and credit quotas are coarse. Timeouts are the cheap, fine-grained control. Snowflake's parameters reference lists a STATEMENT_TIMEOUT_IN_SECONDS default of two days. That default is built for batch jobs, not for an agent answering a chat question. Set it to something like 60 to 300 seconds on the agent's role or warehouse, so a bad join dies in minutes rather than burning a warehouse for two days.

The same logic applies on the MCP client side. The specification recommends that clients implement timeouts for tool calls, so set one that is shorter than the warehouse timeout. The agent then gets a clean failure it can reason about, and the warehouse gets to stop the work instead of finishing a result nobody is waiting for.

Return the failure in a form the model can use. A bare "query failed" invites a blind retry. A message such as "estimated 380 GB, budget 50 GB, add a date filter on the partition column" teaches the agent to narrow the query. This is the difference between a guardrail that wastes tokens and one that actually changes behavior.

Layer 4: tag every query so spend is attributable

Limits stop the worst cases. Attribution tells you about the slow leak. Snowflake supports a QUERY_TAG session parameter, and BigQuery supports job labels. Have the MCP server set both on every statement: the agent name, the user or session it acts for, and the tool call identifier. Weeks later, a question like "what did the support agent spend in October" becomes a GROUP BY instead of a forensic exercise.

Make the tag non-negotiable. If the model supplies it, the model can omit it or get it wrong, so the server should inject it after the SQL is generated. Reject statements on a connection that has no tag set. This also gives security and finance a shared key, since the same tag shows up in the query history and in the audit trail.

Once tags exist, review them on a schedule. Our Cost Agent reads this kind of query history to flag idle warehouses and sizing waste, and the Connectors Agent is where the warehouse credentials and role scoping live. You can build the same loop by hand with a weekly query over account usage views; the point is that someone, or something, looks.

Where a human should still sign off

Not every expensive query is a bad one. A quarterly backfill may legitimately scan terabytes. The right design is a tiered one: small queries run freely under the default budget, queries above the budget are blocked with an explanation, and a human can approve a one-off override for a named query. The override should raise the cap for that single job, not change the role's standing limits.

Keep write and DDL permissions out of the same role. Cost limits assume the agent can only read. An agent that can create tables, schedule tasks or alter warehouse sizes can undo your guardrails from the inside. Read-only by default is also how the open-source edition of Data Workers ships, and it is the simplest cost control there is.

Frequently Asked Questions

Does maximum bytes billed protect me from a retry loop?

It protects each attempt, not the total. A failed job under the cap costs nothing, but a successful job that scans just under the cap and runs fifty times adds up. Combine the per-job cap with a per-session or per-day budget enforced in the MCP server, and use query tags to see loops.

Is a Snowflake resource monitor enough on its own?

No. It acts on credits used by warehouses over an interval, so it reacts after spend has accrued. It does not cover serverless features, and the suspend action lets running statements finish. Use it alongside a short statement timeout, a small dedicated warehouse and query tags.

Can I just tell the agent in the system prompt to keep queries cheap?

You can, and it helps at the margin, but it is not a control. The model does not know table sizes unless you give it metadata, and prompts can be overridden by context. Enforce limits where the model cannot change them: the warehouse role, the job settings and the MCP server.