Skip to content

Build1 publisher3 min readPublished

A required Pydantic field forces every warehouse cost estimate to name its accuracy tier

The 18-hour-old cost-guard-mcp gets a real byte count out of BigQuery's dry-run planner and only a planner ceiling out of Snowflake's EXPLAIN, so its type system makes an agent see which of the two it got.

The Engineer · Build desk

Illustration accompanying A required Pydantic field forces every warehouse cost estimate to name its accuracy tier

What happened

  • cost-guard-mcp is a Model Context Protocol server in front of BigQuery and Snowflake offering an agent a capability lookup, a pre-flight cost estimate, and a bounded run with byte, row and dollar caps.
  • BigQuery estimates come from a dry-run job that BigQuery's own planner validates and plans without executing or billing it, and the project labels the returned byte count PRECISE.
  • Snowflake estimates come from EXPLAIN USING JSON and the plan's bytesAssigned field, and the code hardcodes AccuracyTier.UPPER_BOUND on line 84 because runtime optimization can scan fewer bytes.

Compiled by The EngineerSomething wrong?How this is made

Why it matters

  • exposure The confidence signal a spend policy depends on comes off a private attribute of the BigQuery client, so a routine library upgrade can quietly break the path that downgrades a PRECISE label.
  • decision Teams already running a warehouse MCP server now have to decide whether to front it with a second server whose entire commit history is eighteen hours old.
  • precedent A required field with no default is a portable pattern for any tool that hands an estimate to a model: the type checker, not the README, makes the caller confront how much the number is worth.

The dry-run call is a real submission, not a local guess. dry_run() in src/cost_guard_mcp/engines/bigquery.py builds a bigquery.QueryJobConfig with dry_run=True and use_query_cache=False, pushes it through client.query(), and reads total_bytes_processed off the API response [10]. BigQuery validates and fully plans the query server-side without executing or billing it, so the byte figure is the one a real run would have produced [11].

Snowflake's number answers a different question. plan['GlobalStats']['bytesAssigned'] is what the planner expects to scan, not what gets scanned [12], and Snowflake's documentation, quoted in a code comment, says runtime plan optimizations "can reduce the number of partitions and bytes scanned" [13]. The BigQuery path has a downgrade table. explain_estimate() has none, because there is no tier better than UPPER_BOUND to fall from [14].

The tier travels with every estimate. accuracy_tier is a required field with no default on the Pydantic CostEstimate model in src/cost_guard_mcp/types.py, so nothing can construct an estimate without choosing PRECISE, UPPER_BOUND or HEURISTIC [7][9]. AGENTS.md in the repo says this is "enforced structurally by the Pydantic model, not by convention" [8]. The third tool, describe_engine_capabilities, is there so an agent can ask up front what an engine can and cannot tell it [3].

run_query_bounded takes max_bytes_billed, max_rows and max_estimated_cost_usd [3]. For one cost cap to mean the same thing on both engines, bytesAssigned would have to track bytes actually scanned, and the vendor documentation says it can come in lower [13]. A cap tuned against BigQuery dry-run figures will refuse Snowflake queries the warehouse would have run for less. The tier field is what lets a policy separate that refusal from a genuine overrun, assuming whatever reads the response branches on it.

The confidence signal sits on a private surface. dry_run() reads BigQuery's own totalBytesProcessedAccuracy through query_job._properties['statistics'] [15]. The underscore is the library saying that dict shape is not part of its contract, and a client release that reshapes it takes the downgrade path with it.

The premise under all of this is one hedged sentence from the author: as far as the author can tell, no warehouse MCP server tells an agent what a query will cost or how much data it will return before execution [2]. The support offered is two scenarios, a forgotten partition filter that scans a multi-terabyte table and bills hundreds of dollars, and a SELECT * that returns four million rows into the agent's context window [1]. The post does not name the servers checked, so anyone already running a BigQuery or Snowflake MCP server has to do that comparison themselves.

The code is small: 715 lines across 15 Python files, about 48 lines each [4][17]. The first commit landed at 22:23 IST on 2026-09-12 and the author called v1 feature-complete four hours and four minutes later [5][16]. Eighteen hours of total history is not much of a soak test [6]. v0.1.1, described as a hardening pass, shipped on 2026-09-13 [6].

What to watch

  • Whether a later release replaces the private _properties read with a supported accessor for totalBytesProcessedAccuracy.
  • Whether any Snowflake user publishes how far bytesAssigned sits above bytes actually scanned on partition-pruned queries.
  • Whether an existing BigQuery or Snowflake MCP server ships a pre-flight estimate tool and settles the author's market claim.
Loading claim ledger
Loading source directory links
Loading share composer
Loading topic controls
Loading related stories