spendguard
by tanveer-arch
README.md
# spendguard ๐ฐ
[](https://pypi.org/project/spendguard-mcp/)



**Your AI agent has a company credit card and no spending limit. spendguard is the bouncer.**
Give an agent a "run SQL" tool on BigQuery or Snowflake and it will happily `SELECT *` a billion-row table and burn $4 before you've finished your coffee. Nobody watches the meter. spendguard is a drop-in MCP server that sits between your agent and the warehouse: it previews the dollar cost *before* every query, enforces budgets, learns how accurate its estimates are, and suggests cheaper rewrites when a query blows the budget.
Not a previewer โ a **spend governor**.
## 30-second start
```bash
uvx spendguard-mcp
# or
pipx install spendguard-mcp
`
*(Note: PyPI release (uvx spendguard-mcp) coming with v0.1.0)*
```
Add to your MCP client (Claude Code, Cursor, Codex, Copilot โ see `examples/.mcp.json.example`):
```json
{ "mcpServers": { "spendguard": { "command": "uvx", "args": ["spendguard-mcp"],
"env": { "BIGQUERY_PROJECT": "my-project",
"GOOGLE_APPLICATION_CREDENTIALS": "/path/to/sa.json" } } } }
```
Then ask your agent:
> *"Using spendguard, how much will this query cost before you run it?"*
```jsonc
// estimate_query_cost("bigquery", "SELECT * FROM proj.ds.events_2025")
{
"accuracy_tier": "PRECISE",
"estimated_bytes": 1409286144,
"estimated_cost_usd": 0.0081,
"caveats": []
}
// run_query_bounded("bigquery", "SELECT * FROM ...", "max_estimated_cost_usd": 5.0)
{ "status": "refused", "reason": "over_call_cap",
"detail": "Estimated $12.40 exceeds your per-call cap $5.00.",
"suggestion": "Call suggest_cheaper_query with this SQL..." }
// spend_report()
{ "bigquery": { "actual_usd": 3.21, "queries": 41 }, ... }
```
## The tools
| Tool | What it does |
|---|---|
| `describe_engine_capabilities` | What each engine can/can't tell you โ the honesty contract, first |
| `estimate_query_cost` | Free pre-flight estimate, calibrated from your ledger history |
| `run_query_bounded` | Estimate โ budget gate โ execute โ reconcile actual billed cost |
| `spend_report` | Reconciled spend per engine + calibration state |
| `set_budget` | Persist a daily/session cap or confirm-above threshold |
| `suggest_cheaper_query` | Concrete rewrites: LIMIT injection, partition filters, SELECT \* guidance |
## Proven live
We tested the full governor loop end-to-end on live BigQuery with the public `bigquery-public-data.samples.shakespeare` dataset. The dry-run returned a **PRECISE** estimate of 1,332,943 bytes (โ $0.000008). The actual billed cost came back ~8ร higher (โ $0.00006) โ entirely because BigQuery enforces a 10 MB minimum per query. After reconciliation, the ledger auto-calibrated the BigQuery engine factor from 1.0 โ 3.06 in a single query.
## How it works
```
agent
โ
โผ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ spendguard โ
โ โ
โ 1. estimate โ free dry-run, $ figure โ
โ 2. budget gate โ refuse / rewrite if over โ
โ 3. execute โ run on warehouse โ
โ 4. reconcile โ actual billed cost โ
โ 5. ledger โ update calibration โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ
โผ
warehouse
(BigQuery ยท Snowflake ยท Databricks)
```
## Accuracy tiers โ we tell you how much to trust the number
| Engine | Tier | How |
|---|---|---|
| BigQuery | **PRECISE** | Free dry run โ exact bytes scanned ร $6.25/TiB |
| Snowflake | **UPPER_BOUND** | `EXPLAIN USING JSON` plan โ largest byte figure as bound; dollars assume one 60s minimum billing window |
| Databricks | **HEURISTIC** | No dry-run API exists โ warehouse size ร plan-shape runtime ร $/DBU, then **calibrated against `system.billing.usage` actuals** over time |
Every estimate carries its tier and caveats. BigQuery enforces a **10 MB minimum billing** per query โ any scan under 10 MB is billed as 10 MB (the estimate will carry a caveat). BigQuery RLS-masked tables report 0 bytes *by design* โ we flag it instead of calling it free. Remote-function / `ML.GENERATE_TEXT` billing is excluded and flagged. Capacity-billed projects get bytes only, no fake dollars.
## What makes it different
- **A ledger with a memory.** Every estimate is stored; actuals are reconciled post-execution (`INFORMATION_SCHEMA.JOBS`, Snowflake query history, `system.billing.usage`). The per-engine calibration factor (EWMA, ฮฑ=0.3) makes heuristic estimates converge on *your* reality.
- **Budgets with teeth.** Daily/session caps, anomaly detection (flags queries >40ร your rolling median), and a human-confirm flow: over-threshold queries return a single-use 5-minute token the agent must hand back.
- **It fixes, not just refuses.** Over-budget queries get concrete rewrites, not error messages.
- **No gateway, no SaaS, no new infrastructure.** One stdio process, SQLite ledger at `~/.spendguard/`. It runs wherever your agent runs.
## GitHub Action: cost-delta on every dbt PR
`action/` is a composite action for dbt/SQL repos: it dry-runs every changed `*.sql` file at head and base SHAs and posts a sticky PR comment with per-file bytes, estimated USD, and the total delta โ optionally failing the check over a budget. v1 is BigQuery-only, and the comment says so.
```yaml
- uses: tanveer-arch/spendguard/action@v1
with:
gcp_project: my-project
gcp_credentials: ${{ secrets.GCP_SA_KEY }}
fail_on_over_cap: true
max_delta_usd: 10
```
## Export the local ledger
Run `spendguard export --csv spend.csv` for the columns `estimate_id`, `engine`, `estimated_usd`, `actual_usd` and `timestamp`. Pending estimates have an empty actual amount; unknown amounts stay empty, while zero remains zero. Multiple actuals for an estimate produce separate rows with their reconciliation timestamps, so consumers should not sum repeated estimate amounts.
The export reads SQLite in read-only mode, writes the CSV atomically, and refuses to overwrite the ledger. An absent ledger produces a header-only CSV without creating state. It never queries a warehouse or exports SQL or query identifiers. Strings that spreadsheet software could interpret as formulas are prefixed with an apostrophe. Running `spendguard` without arguments still starts the stdio MCP server.
## Roadmap
- [ ] Databricks `fetch_actual_cost` wiring against a live workspace (`system.billing.usage` join)
- [ ] Snowflake reconciliation via `ACCOUNT_USAGE.QUERY_HISTORY`
- [ ] Snowflake key-pair auth path (JWT)
- [ ] PR-comment action for Snowflake/Databricks (query-plan based)
- [ ] Per-developer attribution for team spend reports
## Contributing
PRs welcome โ see [CONTRIBUTING.md](CONTRIBUTING.md). We keep a standing queue of `good first issue` / `hacktoberfest` tasks and aim to respond within 24 hours.
## License
MIT โ see [LICENSE](LICENSE).
This server cannot be deployed
Maintenance
ActivityMaintained
ResponsivenessUnresponsive