Skip to main content
Glama

bq-cost-guard

An MCP server that estimates the cost of a BigQuery query before you run it. It exposes a single tool, estimate_query_cost, which:

  1. runs a BigQuery dry run (free — no data is read or billed) using your Application Default Credentials,

  2. reports bytes processed and the estimated on-demand cost in USD, and

  3. returns warnings for common sources of wasted spend:

    • SELECT * (and SELECT t.*) — scanning columns you don't need

    • No partition filter — a partitioned table referenced by the query is scanned without a filter on its partition column (or _PARTITIONTIME / _PARTITIONDATE)

Example output

{
  "bytesProcessed": 1099511627776,
  "bytesProcessedHuman": "1.00 TiB",
  "estimatedCostUsd": 6.25,
  "currency": "USD",
  "usdPerTib": 6.25,
  "cacheHit": false,
  "warnings": [
    {
      "type": "SELECT_STAR",
      "message": "Query selects every column (SELECT *). Request only the columns you need to reduce bytes scanned."
    },
    {
      "type": "NO_PARTITION_FILTER",
      "table": "my-proj.sales.orders",
      "partitionColumn": "created_at",
      "message": "`my-proj.sales.orders` is partitioned by the partition column `created_at`, but the query has no filter on it, so the whole table will be scanned. Add a filter on the partition column (or _PARTITIONTIME / _PARTITIONDATE)."
    }
  ]
}

Errors (invalid SQL, missing permissions, ...) are returned as normal tool errors and never crash the server.

Related MCP server: BigQuery FinOps MCP Server

Prerequisites

  • Node.js ≥ 22.18

  • A Google Cloud project with the BigQuery API enabled

  • Permission to run dry runs (bigquery.jobs.create) and to read table metadata (bigquery.tables.get) — the latter is only needed for the partition-filter warnings; if metadata can't be read, the cost estimate still works and the warning is silently skipped

Install

git clone <this repo> bq-cost-guard
cd bq-cost-guard
npm install
npm run build

This produces the executable entry point at dist/src/index.js.

Authentication (Application Default Credentials)

No credentials live in this repository. The server authenticates with Application Default Credentials. Pick one of:

  1. gcloud (recommended for development)

    gcloud auth application-default login
    gcloud config set project your-project-id
  2. Service-account key file (kept outside the repo)

    export GOOGLE_APPLICATION_CREDENTIALS="$HOME/keys/bq-cost-guard-sa.json"

    Point it at a key file stored anywhere outside the checkout — the .gitignore in this repo blocks key files from being committed, but you should still keep keys outside the project tree.

  3. Workload identity — on GCE / GKE / Cloud Run / Cloud Shell, ADC is provided automatically; set GOOGLE_CLOUD_PROJECT if it cannot be inferred.

MCP client configuration

The server communicates over stdio. Replace /absolute/path/to/bq-cost-guard with the real checkout path.

Claude Desktop

Edit claude_desktop_config.json (macOS: ~/Library/Application Support/Claude/claude_desktop_config.json, Windows: %APPDATA%\Claude\claude_desktop_config.json):

{
  "mcpServers": {
    "bq-cost-guard": {
      "command": "node",
      "args": ["/absolute/path/to/bq-cost-guard/dist/src/index.js"],
      "env": {
        "GOOGLE_CLOUD_PROJECT": "your-project-id"
      }
    }
  }
}

Claude Code

claude mcp add bq-cost-guard \
  --env GOOGLE_CLOUD_PROJECT=your-project-id \
  -- node /absolute/path/to/bq-cost-guard/dist/src/index.js

Cursor

Add the same block to ~/.cursor/mcp.json (project-level: .cursor/mcp.json):

{
  "mcpServers": {
    "bq-cost-guard": {
      "command": "node",
      "args": ["/absolute/path/to/bq-cost-guard/dist/src/index.js"],
      "env": {
        "GOOGLE_CLOUD_PROJECT": "your-project-id"
      }
    }
  }
}

Any other stdio MCP client can use the same command/args/env shape. If you rely on a key file, add "GOOGLE_APPLICATION_CREDENTIALS": "/abs/path/to/key.json" to env (or export it in the shell the client inherits).

Environment variables

Variable

Required

Default

Purpose

GOOGLE_CLOUD_PROJECT

no

inferred

Default project for the BigQuery client

GOOGLE_APPLICATION_CREDENTIALS

no

—

Absolute path to a service-account key file (outside the repo)

BQ_COST_GUARD_USD_PER_TIB

no

6.25

On-demand price in USD per TiB scanned

Tool reference

estimate_query_cost

Argument

Type

Description

sql

string (required)

BigQuery standard SQL to estimate

Returns bytesProcessed, bytesProcessedHuman, estimatedCostUsd, currency, usdPerTib, cacheHit, and warnings[] (see example above).

How the warnings work

  • SELECT * — heuristic token scan of the SQL with comments and string literals removed; count(*) and friends do not trigger it.

  • NO_PARTITION_FILTER — the tool extracts FROM/JOIN table references (skipping CTEs, subqueries, UNNEST, wildcard and INFORMATION_SCHEMA references), reads each table's metadata to see whether it is partitioned (time- or range-partitioned), and checks the query's WHERE/ON clauses for the partition column or the _PARTITIONTIME/_PARTITIONDATE pseudo columns. Tables whose metadata can't be read are skipped silently.

Both are best-effort heuristics meant to prompt a second look, not a full SQL parser.

Pricing notes

The cost figure is bytesProcessed / 1024^4 × usdPerTib at the on-demand list price (default $6.25/TiB, override with BQ_COST_GUARD_USD_PER_TIB). It ignores reservations/editions and flat-rate pricing, free-tier monthly credits, storage and other charges, and result-cache effects (a cacheHit flag is reported instead). Treat it as an estimate, not a bill.

Development

npm test          # unit tests (BigQuery client fully mocked, no network/GCP access)
npm run typecheck # tsc --noEmit
npm run build     # emit dist/
npm start         # run the built server on stdio

Tests use Node's built-in test runner with the BigQuery client replaced by a mock of the exact client surface used here (query({ dryRun: true }) and dataset().table().getMetadata()), so the suite runs without credentials or network access. The MCP protocol surface is tested end-to-end over an in-memory transport.

Project layout:

src/
  index.ts      # entry point: wires real BigQuery client + stdio transport
  server.ts     # MCP server + estimate_query_cost tool registration
  estimator.ts  # bytes/cost/warnings orchestration
  bigquery.ts   # thin adapter over @google-cloud/bigquery (dry run + metadata)
  sql.ts        # pure SQL text analysis (SELECT *, table refs, partition filters)
  cost.ts       # pricing math and formatting
test/           # unit tests with the BigQuery client mocked

License

MIT

Available Tools

1 tool
estimate_query_costEstimate BigQuery query costA
Read-onlyIdempotent

Runs a BigQuery dry run (free; no data is read or billed) for the given standard SQL and returns bytes processed, the estimated on-demand cost in USD, and warnings such as SELECT * usage or missing filters on partitioned tables.

ParametersJSON Schema
NameRequiredDescriptionDefault
sqlYesBigQuery standard SQL query to estimate, e.g. SELECT ... FROM ...

TDQS

A4.3/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare readOnly/openWorld/idempotent, so the bar is lower, yet the description adds real value beyond them: the dry run is free and reads/bills no data, and it enumerates what comes back (bytes processed, USD estimate, warnings). It does not mention permission requirements or rate limits, so not a full 5.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

A single sentence with the core action, the cost constraint, and the return payload front-loaded. No filler and no redundancy.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

No output schema exists, so the description must cover returns, and it does (bytes processed, cost in USD, warnings like SELECT * or missing partition filters). Combined with annotations carrying the safety profile, an agent has everything needed to invoke and interpret it.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Only one parameter exists and schema description coverage is 100%, so the schema already documents the `sql` field. The description's restatement ('for the given standard SQL') matches, not exceeds, that documentation. Baseline 3 applies.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb and resource ('Runs a BigQuery dry run ... for the given standard SQL') and immediately scopes the outcome (bytes processed, estimated cost, warnings). No siblings exist, so no differentiation is needed, and the tool's function is unmistakable.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Clearly conveys the context for use: estimate cost before an actual query, and the dry run is free. It stops short of explicit when-not guidance (e.g., non-standard SQL, unsupported statements), but there are no siblings to route between.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

  1. 1 tool updatev0.1.0
    • First observedestimate_query_cost

TDQS

A4.2/5.0

Scored across 1 tool

Disambiguation5/5

There is only one tool, so there is no possibility of confusion or misselection. Its purpose (dry-run cost estimation) is unmistakable.

Naming Consistency5/5

The single name 'estimate_query_cost' follows a clean verb_noun convention and is descriptive. No inconsistent conventions exist to penalize.

Tool Count3/5

A single tool is thin even for a narrow 'cost guard' purpose; there is no way to estimate multiple queries, compare variants, or inspect table metadata. It functions but feels minimal.

Completeness3/5

Core dry-run cost estimation is covered with useful warnings, but the surface is a dead end: no schema/table inspection, partition discovery, or cost comparison to support the guard workflow. Notable gaps remain for a cost-management domain.

Maintenance

ActivityMaintained
ResponsivenessNo issues

Related MCP Connectors

Related MCP Servers