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 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
```
## 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
ActivityNo data
ResponsivenessUnresponsive