Skip to main content
Glama
README.md
# spendguard ๐Ÿ’ฐ

[![PyPI version](https://img.shields.io/pypi/v/spendguard-mcp.svg)](https://pypi.org/project/spendguard-mcp/)
![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)
![Python 3.10+](https://img.shields.io/badge/python-3.10%2B-blue.svg)
![Hacktoberfest](https://img.shields.io/badge/Hacktoberfest-opted--in-orange.svg)

**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).