·

Google Cloud MCP With Gemini CLI

Set up Google Cloud MCP in Gemini CLI so your AI agent can manage cloud resources and investigate infrastructure right from your editor.

Installing and Connecting GCP MCP to Gemini CLI

Gemini CLI has a natural advantage for GCP MCP work that's worth naming upfront: it's a Google product, so its auth story integrates more tightly with gcloud's own credential cache than any third-party client does. That doesn't mean setup is zero-config, but it does mean fewer credential-mismatch surprises.

MCP servers go in ~/.gemini/settings.json for user-wide config or .gemini/settings.json at the project root for project-scoped servers — the latter is what you want for a team-shared GCP integration:

{
  "mcpServers": {
    "gcp-bigquery": {
      "command": "./toolbox",
      "args": ["--tools-file", "./tools.yaml", "--stdio"],
      "env": {
        "GOOGLE_APPLICATION_CREDENTIALS": "$HOME/.gcp/mcp-agent-key.json"
      },
      "trust": false
    },
    "gcp-run-logs": {
      "command": "npx",
      "args": ["-y", "gcp-mcp-server", "--services", "run,logging,storage"],
      "env": {
        "GOOGLE_CLOUD_PROJECT": "analytics-project-prod"
      }
    }
  }
}

The trust field is worth calling out specifically — Gemini CLI's MCP config supports a per-server trust flag that, when false (the safer default), prompts for confirmation before that server's tools execute anything with side effects. For a BigQuery server whose execute-sql tool can run real queries against production data, leave trust: false and accept the per-call confirmation friction; it's a legitimate guardrail, not just noise.

Confirm the connection with Gemini CLI's built-in MCP inspection command:

gemini
> /mcp list

Since Gemini CLI is built on Google's own SDKs, it also respects gcloud's active configuration more directly than other clients — running gcloud config configurations list and confirming the active one matches your intended project is a useful pre-flight check specific to this tool, because Gemini CLI's own auth flow can fall back to ADC silently if GOOGLE_APPLICATION_CREDENTIALS isn't set in the MCP server's env block.

gcloud config configurations list

If you maintain multiple gcloud configurations (common when juggling staging and prod), pin the project explicitly inside the MCP server's env rather than relying on whichever configuration happens to be active when Gemini CLI starts — a config switch in one terminal tab doesn't propagate to an already-running Gemini CLI session, which is an easy source of "why did it query the wrong project" confusion.

Tips
- Leave trust: false on any GCP MCP server whose tools can execute (not just read) — the per-call confirmation is cheap insurance against an unreviewed execute-sql call.
- Run gcloud config configurations list before starting a Gemini CLI session if you switch between GCP projects often — stale active-config state is a recurring source of wrong-project queries.
- Use /mcp list at the start of every session touching GCP data, the same discipline as /mcp in Claude Code — a silently failed server produces guesses, not errors.


Exploring BigQuery Schemas and Building Analytical Queries in Gemini CLI

Gemini's strength in this workflow shows up specifically in schema-heavy exploratory analysis — asking it to reason across several related tables and propose a join strategy tends to produce clean first drafts, likely reflecting how much BigQuery-adjacent training data Google's models have direct access to.

Prompt example:
"List the tables in analytics-project-prod.raw_events. I think
orders, order_items, and products are related — show me their
schemas and propose a join to compute revenue by product category
for August 2026."

A solid response inspects all three schemas, correctly infers the join keys (order_id between orders and order_items, product_id between order_items and products), and produces something like:

SELECT
  p.category,
  ROUND(SUM(oi.quantity * oi.unit_price), 2) AS revenue
FROM `analytics-project-prod.raw_events.orders` AS o
JOIN `analytics-project-prod.raw_events.order_items` AS oi
  ON o.order_id = oi.order_id
JOIN `analytics-project-prod.raw_events.products` AS p
  ON oi.product_id = p.product_id
WHERE o.order_date BETWEEN '2026-08-01' AND '2026-08-31'
  AND o.status = 'completed'
GROUP BY p.category
ORDER BY revenue DESC;

Always dry-run before execution — Gemini CLI doesn't enforce this automatically any more than the other clients do, and a three-table join against unpartitioned tables can scan far more than a single-table query would:

bq query --use_legacy_sql=false --dry_run \
  "$(cat ./scratch/revenue-by-category.sql)"

Push further into analytical territory once the base query is validated — window functions, cohort logic, and period-over-period comparisons are where an experienced developer's time is actually saved, since hand-writing correct LAG()/PARTITION BY logic against real column names takes longer than reviewing a generated draft.

Prompt example:
"Extend that query to also show month-over-month revenue growth
by category, using a window function to compare against the
prior month. Keep the dry-run check before running it."
WITH monthly_revenue AS (
  SELECT
    p.category,
    DATE_TRUNC(o.order_date, MONTH) AS month,
    SUM(oi.quantity * oi.unit_price) AS revenue
  FROM `analytics-project-prod.raw_events.orders` AS o
  JOIN `analytics-project-prod.raw_events.order_items` AS oi
    ON o.order_id = oi.order_id
  JOIN `analytics-project-prod.raw_events.products` AS p
    ON oi.product_id = p.product_id
  WHERE o.status = 'completed'
  GROUP BY p.category, month
)
SELECT
  category,
  month,
  revenue,
  ROUND(
    SAFE_DIVIDE(revenue - LAG(revenue) OVER (PARTITION BY category ORDER BY month), 
                LAG(revenue) OVER (PARTITION BY category ORDER BY month)) * 100, 1
  ) AS mom_growth_pct
FROM monthly_revenue
ORDER BY category, month;

The SAFE_DIVIDE here isn't decorative — a naive / would throw a division-by-zero error the first month a category has zero prior revenue, and a well-prompted Gemini CLI session catches that edge case without being told to explicitly, in most of the sessions I've run this pattern through.

Tips
- Let Gemini CLI infer joins across related tables when column naming is reasonably consistent — it's genuinely strong here, but always verify the inferred join keys against the actual schema output, not just the generated SQL.
- Ask explicitly for SAFE_DIVIDE, SAFE_CAST, and similar null-safe BigQuery functions in growth/ratio calculations — most models default to raw operators unless prompted, and a prod query that occasionally divides by zero is a bad surprise for a dashboard consumer.
- Save validated multi-table queries to a .sql file in the repo rather than re-generating them each session — schema drift is easier to catch as a diff against a known-good query than by regenerating from scratch every time.


Practical Example: Turning a Business Question into a BigQuery Report

A stakeholder asks: "Which regions had the biggest drop in repeat purchase rate this quarter compared to last?" That's not a single query — it needs a repeat-purchase definition, two time windows, and a way to present the comparison. Walking this through Gemini CLI end to end:

Prompt (step 1):
"I need to define 'repeat purchase rate' for analytics-project-prod.
raw_events.orders — customers with 2+ completed orders in a quarter,
divided by total customers with at least 1 completed order that
quarter. Show me the schema first, then draft that as a query for
Q2 2026 (Apr-Jun), broken down by region."
WITH q2_customers AS (
  SELECT
    region,
    customer_id,
    COUNT(*) AS order_count
  FROM `analytics-project-prod.raw_events.orders`
  WHERE order_date BETWEEN '2026-04-01' AND '2026-06-30'
    AND status = 'completed'
  GROUP BY region, customer_id
)
SELECT
  region,
  COUNTIF(order_count >= 2) AS repeat_customers,
  COUNT(*) AS total_customers,
  ROUND(SAFE_DIVIDE(COUNTIF(order_count >= 2), COUNT(*)) * 100, 1) AS repeat_rate_pct
FROM q2_customers
GROUP BY region;
Prompt (step 2):
"Dry-run that, then build the same query for Q1 2026 (Jan-Mar),
and combine both into one result showing the percentage point
change per region, sorted by the biggest decline first."
WITH quarterly AS (
  SELECT
    region,
    customer_id,
    CASE
      WHEN order_date BETWEEN '2026-01-01' AND '2026-03-31' THEN 'Q1'
      WHEN order_date BETWEEN '2026-04-01' AND '2026-06-30' THEN 'Q2'
    END AS quarter,
    COUNT(*) AS order_count
  FROM `analytics-project-prod.raw_events.orders`
  WHERE status = 'completed'
    AND order_date BETWEEN '2026-01-01' AND '2026-06-30'
  GROUP BY region, customer_id, quarter
),
rates AS (
  SELECT
    region,
    quarter,
    ROUND(SAFE_DIVIDE(COUNTIF(order_count >= 2), COUNT(*)) * 100, 1) AS repeat_rate_pct
  FROM quarterly
  GROUP BY region, quarter
)
SELECT
  q1.region,
  q1.repeat_rate_pct AS q1_rate,
  q2.repeat_rate_pct AS q2_rate,
  ROUND(q2.repeat_rate_pct - q1.repeat_rate_pct, 1) AS pct_point_change
FROM rates q1
JOIN rates q2 ON q1.region = q2.region
WHERE q1.quarter = 'Q1' AND q2.quarter = 'Q2'
ORDER BY pct_point_change ASC;
Prompt (step 3):
"That query scans the same orders table twice conceptually via
the CASE statement — dry-run it to confirm it's still efficient,
then export the result as a markdown table I can paste into a
Slack update."

The final markdown table export is where Gemini CLI's terminal-native output is genuinely convenient — no copy-paste from a BigQuery console UI, no re-formatting a CSV by hand. The whole path from vague stakeholder question to a shareable, cost-checked report took three prompts and two dry-run confirmations, both logged in the session transcript for anyone who wants to audit exactly what ran.

Tips
- Get the business definition (what counts as "repeat," what date boundaries) nailed down in plain English before the first SQL draft — ambiguity here produces a technically-correct query answering the wrong question.
- Dry-run each incremental version of a growing query, not just the final one — catches a regression in scan cost introduced by an added CTE or join before it compounds.
- Ask for the final result in whatever format you'll actually paste it into (markdown table, CSV, plain summary) — it saves a manual reformatting step and Gemini CLI handles this well natively.


Comparing GCP MCP Output Between Gemini CLI and Claude Code

Running the same GCP MCP server config against both tools on identical prompts surfaces real differences worth knowing before you pick a default for your team.

Schema reasoning depth. On multi-table join inference (the orders/order_items/products example above), Gemini CLI's first-draft joins were correct more consistently across repeated tries in informal side-by-side testing — plausibly reflecting closer training exposure to BigQuery's ecosystem and documentation. Claude Code's first drafts were also generally correct but asked one more clarifying question on ambiguous join keys where Gemini CLI guessed and got it right.

Dry-run discipline. Claude Code more consistently dry-runs unprompted when a query looks non-trivial, in observed sessions — it seems to default toward caution on anything that reads as "this could be an expensive query" even without being told to check cost first. Gemini CLI will dry-run reliably when asked but is less likely to volunteer it unprompted, which argues for building the dry-run instruction into a standing prompt template rather than relying on either tool's judgment alone.

Cloud Run / infra debugging style. Claude Code's longer, more structured tool-call sequences (list revisions → diff configs → pull logs → correlate) tend to produce a more legible investigation trail in the transcript. Gemini CLI's sessions get to the same conclusions but with a flatter, less obviously staged sequence of tool calls — fine for solo debugging, less useful if you want to hand the transcript to a teammate as an incident writeup.

Cost and quota behavior. Neither tool changes the underlying GCP quota or billing behavior — this is entirely a function of the MCP server and your GCP project config, not the client. Don't expect switching clients to change your actual BigQuery spend; it only changes how carefully the drafting process avoids expensive mistakes before they happen.

Comparison prompt used for this evaluation:
"List tables in raw_events, infer the join between orders and
order_items, and write a query for total revenue by month for
2026 so far. Dry-run before running."

Neither tool is categorically better — Gemini CLI's tighter GCP-native integration and stronger schema inference favor exploratory analytical work; Claude Code's more disciplined, more structured tool-call sequencing favors incident investigation and anything you'll need to explain to someone else afterward. Several teams I've talked with run both, picking per task rather than standardizing on one.

Tips
- Use Gemini CLI as the default for exploratory, join-heavy analytical questions where speed to a correct first draft matters most.
- Use Claude Code when the output (a debugging trail, an incident summary) needs to be legible to someone who wasn't in the session.
- Regardless of client, keep the dry-run and bounding-prompt discipline explicit rather than trusting either tool's default behavior — this is the one variable within your control that meaningfully changes outcomes.


Tips

Tips
- Keep trust: false on any GCP MCP server with execute-capable tools in Gemini CLI's config — the per-call confirmation is worth the friction on anything touching production BigQuery.
- Verify the active gcloud configuration before starting a session if you regularly switch between GCP projects — Gemini CLI's tighter gcloud integration is a strength but also a place stale state bites you quietly.
- Save validated analytical queries as .sql files in the repo — Gemini CLI is fast at drafting them, but a saved, reviewed query is more trustworthy than a fresh draft every time the same question comes up.