BigQuery MCP Server: Google's Remote Server vs. MCP Toolbox, Tested

Andreas Nigg
Andreas Nigg · Co-Founder, Pondhouse Data
Published Updated 13 min read
BigQuery MCP Server: Google's Remote Server vs. MCP Toolbox, Tested
On this page

There are two official ways to give an AI agent access to BigQuery through the Model Context Protocol (MCP). Google runs a managed remote MCP server at https://bigquery.googleapis.com/mcp, and the open-source MCP Toolbox for Databases (MCP Toolbox for short) runs the same kind of tools on your own machine or server. Both let the agent list datasets, read table schemas and run SQL. They differ in control. The Toolbox can restrict queries to an allowlist of datasets and cap the bytes billed per query, while the remote server relies on IAM and has no per-query cost cap.

We set up both on 6 November 2026 (MCP Toolbox 1.16.0, MCP Inspector 2.14.0), connected MCP Inspector, Claude Code and a small Python agent, and let the agent answer seven business questions on the public bigquery-public-data.thelook_ecommerce dataset. With clear instructions the agent answered all seven correctly via the remote server and six via the Toolbox. With a minimal prompt it had answered only four and five correctly. The causes, a parser bug in the Toolbox, an invented number and an ambiguous column, are described under "Pitfalls we hit".

What a BigQuery MCP server does

An MCP server exposes tools that an AI client can call. For BigQuery, the useful set is small:

  • list the datasets and tables the caller may see,
  • read a table's schema and description,
  • run a SQL query and return the rows.

The SQL itself is written by the client. Claude Code, Codex, Gemini CLI or your own agent turns the user's question into SQL, calls the query tool and writes the answer. The server only executes and returns data, which keeps model costs with the client and lets you control access at one point. If MCP is new to you, our post on building an MCP server with FastMCP explains the protocol.

Two ways to connect an agent to BigQuery: MCP Toolbox on your own infrastructure or Google's managed remote MCP server

Every query runs as a normal BigQuery job, billed to a project you choose, under the identity the server uses: the token the client sends, or the Toolbox's own credentials. That makes the identity the most important design decision: whatever that service account or user may read, the agent may read.

Your options compared

OptionAuthRead-only and dataset scopePer-query cost capSetup
Remote BigQuery MCP server (Google)OAuth token of a user or service account, no API keysexecute_sql_readonly tool, IAM deny policy for write tools, dataset access only via IAMnone, only daily quotasnothing to install
MCP Toolbox, prebuilt BigQuery toolsADC, impersonation or the client's OAuth tokenBIGQUERY_READONLY, no allowlist variable in the prebuilt configBIGQUERY_MAXIMUM_BYTES_BILLEDone binary, environment variables
MCP Toolbox, own tools.yamlas abovereadOnly, allowedDatasets, fixed SQL toolsmaximumBytesBilledone binary plus a YAML file
LucasHild/mcp-server-bigquery 0.3.3ADC or key filedataset filter for listing only, queries are not restrictednoneuvx one-liner
ergut/mcp-bigquery-server 1.0.4ADC or key fileSELECT only (checked by dry run), optional column restrictions1 GB default, configurablenpx one-liner

The rows for Google's options come from the official docs and our test; the community servers from their READMEs and source code. LucasHild's execute-query tool runs whatever SQL it receives, so read-only access depends entirely on the permissions of the identity.

Our recommendation:

  • For a developer who wants to explore data from Claude Code, Cursor or Gemini CLI: the remote server. Nothing to run, and IAM already knows who may see what.
  • For an agent that runs unattended or serves many users: the MCP Toolbox with your own tools.yaml, a dataset allowlist and a bytes cap, or fixed SQL tools only.
  • Community servers are fine for a quick local test, but they add a third party between your agent and your warehouse without adding controls that Google's options lack.

Step 1: a dedicated service account

Do not let an agent run with your own account. Create a service account that can run query jobs in a billing project and read only the datasets it needs. These are the commands we ran (replace PROJECT_ID and the e-mail address):

1gcloud services enable iamcredentials.googleapis.com
2gcloud iam service-accounts create bq-mcp-agent --display-name="BigQuery MCP agent"
3
4SA=bq-mcp-agent@PROJECT_ID.iam.gserviceaccount.com
5gcloud projects add-iam-policy-binding PROJECT_ID \
6 --member=serviceAccount:$SA --role=roles/bigquery.jobUser
7# only needed for Google's remote MCP server
8gcloud projects add-iam-policy-binding PROJECT_ID \
9 --member=serviceAccount:$SA --role=roles/mcp.toolUser
10# lets you mint short-lived tokens for the service account
11gcloud iam service-accounts add-iam-policy-binding $SA \
12 --member=user:you@example.com --role=roles/iam.serviceAccountTokenCreator

Our test used a public dataset, which every account can read. For your own data, grant BigQuery Data Viewer on each dataset instead of the whole project. The BigQuery DCL reference shows the SQL form:

1GRANT `roles/bigquery.dataViewer` ON SCHEMA `myProject`.myDataset
2TO "user:raha@example-pet-store.com", "user:sasha@example-pet-store.com"

For a service account the grantee is "serviceAccount:<email>". Dataset-level grants are the only dataset allowlist the remote server has, so they matter.

Instead of downloading a key file, mint a token that is valid for one hour:

1gcloud auth print-access-token --impersonate-service-account=$SA

Both servers accept this token as Authorization: Bearer ....

Option A: Google's remote BigQuery MCP server

The remote server needs no installation. According to the docs it is enabled together with the BigQuery API, uses Streamable HTTP, and requires roles/mcp.toolUser plus the usual BigQuery roles. It offers nine tools: list_dataset_ids, get_dataset_info, list_table_ids, get_table_info, execute_sql_readonly, execute_sql, get_query_results, cancel_job and get_job. All but execute_sql and cancel_job are marked read-only.

Google's client guide for Claude Code uses an OAuth client ID and secret (the remote servers don't support dynamic client registration). For a terminal setup, Claude Code's headersHelper is simpler: it runs a command at connect time and sends its output as headers. This .mcp.json worked for us:

1{
2 "mcpServers": {
3 "bigquery": {
4 "type": "http",
5 "url": "https://bigquery.googleapis.com/mcp",
6 "headersHelper": "echo '{\"Authorization\": \"Bearer '\"$(gcloud auth print-access-token --impersonate-service-account=bq-mcp-agent@PROJECT_ID.iam.gserviceaccount.com 2>/dev/null)\"'\"}'"
7 }
8 }
9}

Claude Code reruns the helper on reconnect and after a 401 or 403, so the one-hour token is refreshed without manual steps.

Four things to know before you rely on it:

  • Every query tool takes a projectId, which is the billing project. Agents tend to pass the project that owns the data. With a public dataset that fails with User does not have bigquery.jobs.create permission in project bigquery-public-data. In our first run, "Run every query with projectId PROJECT_ID" was not explicit enough; "Always pass projectId PROJECT_ID to execute_sql_readonly; it is the billing project" was.
  • There is no maximumBytesBilled parameter. A single query can scan as much as the identity can read. The available brake is a custom quota such as QueryUsagePerUserPerDay, which is unlimited by default.
  • execute_sql_readonly rejects anything but SELECT (MCP execute_sql_readonly tool allows only SELECT statements.), but the agent can still call execute_sql. Block it with an IAM deny policy on read-write MCP tools, as shown in Control MCP use with IAM, or with a tool filter in your client.
  • Results are limited to 3,000 rows and queries to three minutes by default. Every job gets the label goog-mcp-server: true, so you can find agent queries in INFORMATION_SCHEMA.JOBS.

Option B: MCP Toolbox with guardrails

The MCP Toolbox is a single Go binary. Version 1.16.0 was released on 5 November 2026:

1curl -O https://storage.googleapis.com/mcp-toolbox-for-databases/v1.16.0/linux/amd64/toolbox
2chmod +x toolbox
3./toolbox --version

For macOS, replace linux/amd64 with darwin/arm64. The quickest start is the prebuilt BigQuery configuration, which the BigQuery docs describe for Claude Code, Cursor, VS Code and others. It exposes nine tools, including execute_sql, forecast, analyze_contribution and ask_data_insights, and is configured with environment variables such as BIGQUERY_PROJECT, BIGQUERY_READONLY and BIGQUERY_MAXIMUM_BYTES_BILLED.

For an agent you want to control, write your own tools.yaml. This is the file we used. The source carries the guardrails; the tools inherit them:

1kind: source
2name: thelook
3type: bigquery
4project: pondhouse-docs # billing project for query jobs
5location: US
6readOnly: true # only SELECT, write tools are not registered
7allowedDatasets:
8 - bigquery-public-data.thelook_ecommerce
9maximumBytesBilled: 104857600 # 100 MB per query, larger queries fail before running
10maxQueryResultRows: 100
11useClientOAuth: true # use the caller's Bearer token instead of ADC
12---
13kind: tool
14name: list_table_ids
15type: bigquery-list-table-ids
16source: thelook
17description: List the tables in the thelook_ecommerce dataset.
18---
19kind: tool
20name: get_table_info
21type: bigquery-get-table-info
22source: thelook
23description: Get the schema and description of a table.
24---
25kind: tool
26name: execute_sql
27type: bigquery-execute-sql
28source: thelook
29description: |
30 Run a GoogleSQL SELECT query. Always use fully qualified table names
31 like `bigquery-public-data.thelook_ecommerce.orders`. Filter dates with
32 comparisons (created_at >= '2025-01-01'), not with EXTRACT(... FROM alias.column).
33---
34kind: tool
35name: revenue_by_month
36type: bigquery-sql
37source: thelook
38annotations:
39 readOnlyHint: true
40description: Revenue and number of orders per month for one year (completed orders only).
41statement: |
42 SELECT FORMAT_DATE('%Y-%m', DATE(o.created_at)) AS month,
43 COUNT(DISTINCT o.order_id) AS orders,
44 ROUND(SUM(oi.sale_price), 2) AS revenue
45 FROM `bigquery-public-data.thelook_ecommerce.orders` o
46 JOIN `bigquery-public-data.thelook_ecommerce.order_items` oi USING (order_id)
47 WHERE o.status = 'Complete' AND EXTRACT(YEAR FROM o.created_at) = @year
48 GROUP BY month ORDER BY month
49parameters:
50 - name: year
51 type: integer
52 description: Calendar year, for example 2025
53---
54kind: group
55name: analyst
56description: Read-only analysis of the thelook_ecommerce sample shop.
57tools:
58 - list_table_ids
59 - get_table_info
60 - execute_sql
61 - revenue_by_month

What the settings do:

  • readOnly: true lets execute_sql run only SELECT. The Toolbox dry-runs every query first and rejects other statement types.
  • allowedDatasets rejects queries that name tables outside the list, as well as statements it cannot analyze, such as CREATE FUNCTION or EXECUTE IMMEDIATE. With a single dataset, it also becomes the default for the listing tools.
  • maximumBytesBilled is passed to BigQuery with every job. A larger query fails before it is billed.
  • useClientOAuth: true makes the Toolbox use the token the client sends. Without it, the Toolbox uses Application Default Credentials (ADC), set up with gcloud auth application-default login; the impersonateServiceAccount field then switches to the service account.
  • revenue_by_month is a fixed, parameterized query. The agent supplies the year, never the SQL. In read-only mode such tools are hidden unless they are annotated with readOnlyHint: true.
  • kind: group replaces the older kind: toolset. The group gets its own endpoint, here /mcp/analyst.

Start the server. By default it listens on 127.0.0.1:5000:

1./toolbox --config tools.yaml

--config replaces the deprecated --tools-file, and clients connect via Streamable HTTP at /mcp (or /mcp/analyst for the group). The old SSE endpoint /mcp/sse is deprecated. At startup the Toolbox warns that --allowed-origins and --allowed-hosts default to *. Set both when the server is reachable from a browser or a network.

Test with MCP Inspector

MCP Inspector is the quickest way to see what an agent will see:

1npx -y @modelcontextprotocol/inspector@2.14.0

Version 2 starts with a server list. Choose "Add Servers", "Add manually", transport streamable-http and the URL http://127.0.0.1:5000/mcp/analyst. The Authorization header goes into the server's "Settings" under "Custom Headers" (Inspector keeps secrets in the OS keychain). After connecting, the Tools tab shows the four tools, each marked read-only. The description of execute_sql already includes the allowlist, which the Toolbox adds itself:

MCP Inspector with the Toolbox's execute_sql tool, marked read-only, and a query against a dataset outside the allowlist

The query above reads a different public dataset, and the Toolbox refuses it before BigQuery runs anything:

MCP Inspector showing the Toolbox error: access to dataset bigquery-public-data.samples is not allowed

The fixed tool revenue_by_month takes only a year and returns one row per month:

MCP Inspector showing monthly orders and revenue for 2025 returned by the revenue_by_month tool

A query over the 384 MB events table hits the 100 MB cap and is not billed:

1Query exceeded limit for bytes billed: 104857600. 112197632 or higher required.

Connect an agent

For Claude Code, the headersHelper configuration from option A works for the Toolbox as well; only the URL changes to http://127.0.0.1:5000/mcp/analyst. We asked Claude Code (Sonnet) for the top five product categories by revenue in 2025, and it returned the correct figures in three turns.

For your own application, a small Python agent with the OpenAI Agents SDK (0.24.0) is enough. This is the version we used for the test:

1import asyncio, os, sys
2from agents import Agent, Runner
3from agents.mcp import MCPServerStreamableHttp
4
5INSTRUCTIONS = """You answer questions about the online shop in the BigQuery dataset
6bigquery-public-data.thelook_ecommerce. Look up the table schemas first,
7then query with GoogleSQL. Answer briefly and name the numbers you found.
8
9Definitions:
10- Revenue is the sum of order_items.sale_price.
11- A user's traffic source is users.traffic_source (how the user was acquired).
12
13If a tool returns an error you cannot fix, say that you could not answer
14and quote the error. Never estimate or make up numbers."""
15
16async def ask(question: str):
17 server = MCPServerStreamableHttp(
18 params={
19 "url": os.environ["MCP_URL"], # e.g. http://127.0.0.1:5000/mcp/analyst
20 "headers": {"Authorization": f"Bearer {os.environ['GCP_TOKEN']}"},
21 "timeout": 60,
22 },
23 client_session_timeout_seconds=120,
24 )
25 async with server:
26 agent = Agent(
27 name="BigQuery analyst",
28 model="gpt-5.4-mini",
29 instructions=INSTRUCTIONS,
30 mcp_servers=[server],
31 )
32 return await Runner.run(agent, question, max_turns=15)
33
34if __name__ == "__main__":
35 result = asyncio.run(ask(sys.argv[1]))
36 print(result.final_output)

Run it with the token from step 1:

1export MCP_URL=http://127.0.0.1:5000/mcp/analyst
2export GCP_TOKEN=$(gcloud auth print-access-token --impersonate-service-account=$SA)
3python ask.py "How many orders were placed in 2025, and what share of them was cancelled or returned?"

For the remote server, set MCP_URL to https://bigquery.googleapis.com/mcp and add the billing project to the instructions ("Always pass projectId PROJECT_ID to execute_sql_readonly").

Test results: seven business questions

We asked the same seven questions through both servers, first with a minimal prompt (the first paragraph of the instructions above plus "Revenue means the sum of order_items.sale_price"; for the remote server also "Run every query with projectId PROJECT_ID"), then with the full instructions, the explicit billing-project sentence and the date hint in the execute_sql description. The reference answers come from our own SQL. The thelook data is regenerated regularly, so the numbers will differ on another day.

QuestionReference answerToolbox, run 1 → 2Remote server, run 1 → 2
Orders in 2025, share cancelled or returned28,384, 24.9 %correct → correctwrong billing project, no answer → correct
Top five categories by revenue, completed orders 2025Jeans, Outerwear & Coats, Sweaters, Suits & Sport Coats, Swimblocked by Toolbox → correctcorrect → correct
Country with the most users, their average ageChina, 33,738 users, 40.93 yearscorrect → correctcorrect → correct
Average order value, completed orders Q4 202583.32correct → correctcorrect → correct
Traffic source of most users with an orderSearch, 56,031correct → correctwrong table (Email) → correct
Department with the higher gross margin, 2025Women, 52.09 % vs. 51.84 %blocked by Toolbox → correctcorrect → correct
Browser used in the most sessionsChrome, 340,268 sessionsinvented 1,398,587 → events instead of sessionscorrect → correct

Run 2 in numbers:

ServerCorrectQuery jobsBytes processedBytes billedInput tokens
MCP Toolbox6 of 78 (1 stopped by the cap)44 MB114 MB27,564
Remote MCP server7 of 77142 MB238 MB89,218

The whole test, both runs plus reference queries, billed about 1 GB in BigQuery, a negligible amount. The remote server processed more bytes because it answered the browser question, which needs 112 MB. It also used three times as many input tokens for the same questions: its tool descriptions are long, and get_table_info returns the full table resource from the BigQuery API, while the Toolbox returns a compact schema.

Pitfalls we hit

The Toolbox allowlist rejects EXTRACT(YEAR FROM o.created_at). With allowedDatasets set, Toolbox 1.16.0 parses table names from the SQL text and reads the FROM inside EXTRACT as a table reference. The query then fails with access to dataset 'pondhouse-docs.o' is not allowed, where o is the table alias. EXTRACT(YEAR FROM created_at) without an alias works, and so do date comparisons. Agents write the aliased form all the time: in the first run, it broke two of seven questions. A hint in the tool description fixed it. Fixed SQL tools (bigquery-sql) are not affected.

After a blocked query, the agent made up a number. The browser question ran into the 100 MB cap twice. The agent then answered "Chrome had the most sessions: 1,398,587", a number that appears in no query result. The correct value is 340,268. With the rule "never estimate or make up numbers", it instead counted events, which stays under the cap, and said so. That is better, but it still answered a slightly different question. Guardrails make queries fail, and your agent needs instructions for what to do then.

Ambiguous columns lead to plausible wrong answers. "Traffic source" exists in users (how the customer was acquired) and in events (the source of each visit). Through the remote server, the agent used events and answered "Email" instead of "Search". The SQL was valid, and the answer looked right. A one-line definition in the instructions fixed it. With dozens of tables, you need such definitions systematically. Our post on NVIDIA Auto Ontology shows a tool that builds a semantic layer for text-to-SQL.

Small queries are billed at 10 MB. A query that read 1.3 MB of the orders table was billed 10 MB (10,485,760 bytes). For many small agent queries, bytes billed is the number that counts.

The Toolbox moves fast. Between version 0.7 (June 2025) and 1.16 the config format, the CLI flags, the transport and toolset changed. Pin the version and read the release notes before you update.

Security and cost checklist

  • Give the agent its own service account: BigQuery Job User on the billing project, BigQuery Data Viewer only on the datasets it needs, and roles/mcp.toolUser only if you use the remote server.
  • Use short-lived tokens through impersonation or Workload Identity instead of key files.
  • Allow only read access: readOnly: true in the Toolbox; with the remote server, an IAM deny policy for read-write tools.
  • Cap costs per query with maximumBytesBilled in the Toolbox, and per day with the custom quotas QueryUsagePerDay and QueryUsagePerUserPerDay.
  • For production agents, prefer fixed parameterized tools over free SQL. The Toolbox docs say execute_sql is meant for developer workflows with a human in the loop.
  • Make queries traceable: both servers label their jobs (goog-mcp-server: true, or mcp-toolbox-tool with the tool type), so you can filter agent queries and their bytes billed in INFORMATION_SCHEMA.JOBS. For the remote server, enable Data Access audit logs for "Google Cloud MCP servers"; they are off by default.
  • Remember that query results go to the model provider. Keep sensitive columns out of reach with authorized views or column-level security.

Which BigQuery MCP server should you use?

Both servers answered business questions correctly once the agent had clear instructions. The remote server is the faster start and needs no infrastructure. You control it entirely through IAM, which is clean, but it cannot limit what a single query costs. The MCP Toolbox needs a process you operate, and in return gives you the controls an unattended agent should have: dataset allowlist, read-only mode, bytes cap and fixed SQL tools. Its allowlist parser still has rough edges, so test it with the SQL your agent actually writes.

In both cases, quality depends less on the server than on the agent's instructions and the clarity of your schema. If you want to go further with BigQuery, our posts on vector search in BigQuery and Vertex AI Agent Builder cover the neighboring building blocks.

We help companies in Austria, Germany and Switzerland give AI agents safe access to their own data, from the identity setup to evaluating the answers. If you are planning such an agent, our AI consulting is a good place to start.

Interested in building high-quality AI agent systems?

We prepared a comprehensive guide based on cutting-edge research for how to build robust, reliable AI agent systems that actually work in production. This guide covers:

  • Understanding the 14 systematic failure modes in multi-agent systems
  • Evidence-based best practices for agent design
  • Structured communication protocols and verification mechanisms
Andreas Nigg

About the author

Andreas Nigg

Co-Founder, Pondhouse Data

Andreas has spent more than eight years building data applications. At Pondhouse Data he designs and ships AI systems and integrates them into complex, real-world environments.