See the demo

BigQuery MCP server: what an AI agent can query, and the guardrails it needs

Google's BigQuery MCP server gives an agent 9 tools, SQL on raw tables and up to 3,000 rows. It has no per-query byte cap and no metric definitions.

· 9 min read · Gabriel Sorato

The BigQuery MCP server lets an AI agent list datasets, read table schemas and run SQL in your Google Cloud project, but it has no idea what any column means to your business. Google’s remote server at https://bigquery.googleapis.com/mcp exposes nine tools, cancels queries after three minutes and returns at most 3,000 rows (Google Cloud, page updated October 29, 2026). Its SQL tool accepts no per-query byte cap, and the agent rewrites the definition of “revenue” every time someone asks.

BigQuery MCP server: a server that implements the Model Context Protocol on top of the BigQuery API, giving a language model tools to discover metadata and run GoogleSQL in a Google Cloud project, under the IAM permissions of whatever identity the agent uses.

What does the failure look like?

You connect BigQuery to Claude or Gemini, ask “what was revenue yesterday?”, and get a number in ten seconds. The agent found your GA4 export dataset, read the schema, wrote a SUM over ecommerce.purchase_revenue and answered. As SQL, the query is fine.

The trouble shows up in three places:

  • Tomorrow the number changes. The agent read a daily table GA4 hasn’t finished filling.
  • It doesn’t tie to finance. The sum includes canceled orders, a duplicated purchase and test transactions, and it leaves out Amazon and every refund processed in Shopify after the purchase event fired.
  • The Google Cloud bill climbs. Each question became a scan across every events_* table, and nobody saw the size first.

None of this is a server bug. The server does what it promises: it gives the agent access to the table. What the table means was never part of the deal.

What are Google’s official options?

There are two, and they don’t ship the same guardrails.

1. The remote (managed) server. It runs on Google’s infrastructure, is enabled along with the BigQuery API, and uses OAuth 2.0 with IAM. The docs require three roles: MCP Tool User (mcp.tools.call), BigQuery Job User (bigquery.jobs.create) and BigQuery Data Viewer (bigquery.tables.getData), and recommend creating “a separate identity for agents” (Google Cloud).

2. MCP Toolbox for Databases (local, open source). The repository used to be googleapis/genai-toolbox; it now redirects to googleapis/mcp-toolbox, Apache 2.0, with v1.15.0 released October 23, 2026 (GitHub). Google points to it when you need a custom tool over parameterized SQL or can’t enable the remote server. The prebuilt config starts with --prebuilt bigquery (Google Cloud, updated October 27, 2026).

Tools in each, checked November 3, 2026:

ToolRemote serverToolbox (--prebuilt bigquery)
list_dataset_ids, list_table_idsyesyes
get_dataset_info, get_table_infoyesyes
execute_sql_readonly (SELECT only)yesno
execute_sql (DML and DDL allowed)yesyes
get_query_results, get_job, cancel_jobyesno
search_catalog, forecast, analyze_contribution, ask_data_insightsnoyes

Sources: BigQuery MCP reference (updated October 15, 2026) and the Toolbox bigquery.yaml.

On the remote server, execute_sql is the only tool that isn’t read-only, and Google documents an IAM deny policy to block it. Every query the server runs carries the job label goog-mcp-server: true.

What is left out of the BigQuery MCP server?

The documentation lists the remote server’s limits as of October 29, 2026:

  • execute_sql and execute_sql_readonly can’t query Google Drive external tables.
  • Queries running longer than three minutes are automatically canceled.
  • Results are capped at 3,000 rows (Google Cloud).
  • execute_sql_readonly rejects DML, DDL, Python UDFs and Graph Query Language.

One limit isn’t on that list. The input schema for execute_sql_readonly accepts projectId, query, dryRun, labels, jobTimeoutMs and timeoutMs. There is no maximumBytesBilled. BigQuery’s most direct cost control, the byte cap that fails a query before it bills, doesn’t pass through the tool. What comes back is the cost after the fact: the output includes totalBytesProcessed and totalBytesBilled.

Toolbox has the cap, switched off. Its bigquery source accepts maximumBytesBilled (“queries that exceed this limit fail before executing”), allowedDatasets, and a writeMode of allowed, blocked or protected (source docs). In the prebuilt config, maximumBytesBilled defaults to 0, meaning no cap, writeMode defaults to allowed, and results are trimmed to 50 rows.

Neither option carries the metric definition. get_table_info returns the schema and each column’s description, if someone wrote one. If nobody did, the agent infers meaning from the column name.

Why do raw tables produce wrong numbers?

Because the agent reads data as it was written, and the GA4 export writes events, not sales. Google documents four details that change the answer (Google Analytics):

  1. Yesterday’s table isn’t final. GA4 keeps updating events_YYYYMMDD daily tables with late events “for up to three days”.
  2. Revenue and refunds are separate events. ecommerce.purchase_revenue is populated on purchase events only; ecommerce.refund_value, on refund events only. Summing the first without subtracting the second gives gross revenue.
  3. Currency has two columns. purchase_revenue is local currency, purchase_revenue_in_usd is dollars. For a US store they match; add a Canadian storefront and the agent picks one by name.
  4. Duplicates hinge on ecommerce.transaction_id. If the thank-you page fires purchase twice, the sum counts it twice unless the query dedupes.

Every question makes the agent re-decide all four. Pick differently on Monday and Thursday and you get two queries that are both valid SQL and mutually inconsistent. That is metric hallucination: the model doesn’t invent data, it invents the definition.

Cost follows the same logic. On-demand, BigQuery charges $6.25 per TiB processed in us-central1, with the first TiB each month not billed, a 10 MB minimum per referenced table, and billing on columns read “even if you set an explicit LIMIT” (BigQuery pricing, updated October 15, 2026). The default QueryUsagePerDay quota is 200 TiB per project per day, and QueryUsagePerUserPerDay is unlimited by default (custom quotas). Multiply the two pages and the default ceiling allows up to $1,250 a day in query charges on one us-central1 project.

How do you audit what the agent already ran?

By label. Every MCP query carries goog-mcp-server: true, so you can isolate them in the region’s INFORMATION_SCHEMA.JOBS view:

SELECT
  DATE(creation_time) AS day,
  user_email,
  COUNT(*) AS queries,
  ROUND(SUM(total_bytes_billed) / POW(2, 8 * 5), 3) AS tib_billed  -- 2^40 bytes = 1 TiB
FROM `region-us`.INFORMATION_SCHEMA.JOBS
WHERE EXISTS (
  SELECT 1 FROM UNNEST(labels) l
  WHERE l.key = 'goog-mcp-server' AND l.value = 'true')
  AND creation_time >= TIMESTAMP(DATE_SUB(CURRENT_DATE(), INTERVAL 1 MONTH))
GROUP BY day, user_email
ORDER BY tib_billed DESC;

Then the checklist:

CheckWhere to lookWhat signals a problem
Who the agent isuser_email in the query aboveA person’s account instead of a dedicated identity
Write tool exposedIAM deny policiesexecute_sql available to a read-only agent
Daily cap per userQuotas: “Query usage per day per user”Unlimited (the default)
Scans without a date filterQuery text in INFORMATION_SCHEMA.JOBSevents_* with no _TABLE_SUFFIX
Undocumented columnsget_table_info outputRevenue field with no description
Same question, different answerAsk it on two different daysRefund or currency handling switched

What guardrails should you put in place?

In order of payoff per hour of work:

1. A dedicated, read-only identity. Create a service account for the agent, grant BigQuery Data Viewer only on the datasets it needs, and apply the deny policy that blocks read-write MCP tools. That removes execute_sql and leaves execute_sql_readonly.

2. A daily byte cap. Since the remote server doesn’t accept maximumBytesBilled, the brake is the quota: set QueryUsagePerUserPerDay for the agent’s identity. Past the limit, BigQuery returns quotaExceeded and the agent is done until midnight Pacific. On Toolbox, set maximumBytesBilled and allowedDatasets, and switch writeMode to blocked. The full cost argument is in query cost guardrails.

3. Ask for dryRun before big queries. The parameter exists on the remote server and returns the bytes a query would process without running it. A system instruction like “above 50 GB, show the estimate and ask first” prevents most surprises.

4. Get the agent off raw tables. The first three steps cap the bill; this one fixes the number. Instead of events_*, the agent queries a view or table where revenue is already defined: gross or net, with or without shipping, deduped on transaction_id, with the three-day window settled. That is what a semantic layer does, and what goes into one is a short list. Why letting the model write the whole query is the riskier path is in governed text-to-SQL.

When can’t this be fixed?

In three cases.

Sales that never reached BigQuery don’t exist to the agent. The server reads the project. If Amazon orders, phone orders and Shopify refunds were never loaded there, no tool brings them in. It’s the same limit as the Google Analytics MCP server, one layer down.

A per-query cap on the remote server. There is no parameter for it today. The daily quota limits cumulative damage, not the first big query of the day.

Results above 3,000 rows. The agent can page with get_query_results, but each page lands in the model’s context. A question that needs thousands of rows to answer should be aggregated in the warehouse, not in the chat.

A synthetic example with round numbers, to size it:

Scenario (synthetic)Value
Size of a year of events_* tables2 TiB
Bytes read per question with no date filter (revenue and date columns only)0.2 TiB
Questions per day across the team40
TiB per month (22 business days)176
Cost in us-central1, after the first unbilled TiB$1,093.75

Filter _TABLE_SUFFIX to the last month and the same routine reads roughly a twelfth of that. These numbers are illustrative; yours come out of the audit query above.

For some teams, the BigQuery MCP server is enough. If your team already maintains modeled views in BigQuery, with revenue defined and every source loaded there, the remote server with a read-only identity and a quota does the job, and you don’t need another platform. Precisian fits when sales data is spread across GA4, Shopify, ERP and ad platforms and nobody owns those definitions: it brings the sources into a data lake isolated per customer, on Precisian’s infrastructure, writes the revenue definition once in a semantic layer, and serves it to your agent over MCP, API or BI.

What does this article not cover?

It isn’t an install guide: per-client setup lives in Google’s documentation and changes with the protocol version. It doesn’t cover the Google Cloud CLI MCP server (run_bq_command), which reaches scheduling and reservations. It doesn’t compare on-demand pricing with capacity (slot) pricing. And it doesn’t go into Model Armor, the optional prompt and response filter that, with logging on, records the full payload of each call.


If the agent connected to BigQuery answers “revenue yesterday” and the number moves by Wednesday, the first step is to compare the GA4 export total with booked orders in Shopify, day by day, with refunds and Amazon included. Book a call about your setup — no contract commitment, but no promise of an answer in 30 minutes either.