Skip to content
Conduit

mcp server

spend-brake

Self-hostable egress proxy that enforces hard, real-time dollar caps on paid APIs (Google Maps, Mapbox, HERE, Twilio, and agent search: Tavily, Exa, Brave, SerpApi) for apps, test suites and AI agents

KIvanow/spend-brakecommunitylistedunreviewed

Set up

Runs on your computer. Pick your agent and operating system.

Before you start

  • Node.js 20 or newer
~/Library/Application Support/Claude/claude_desktop_config.json
{
  "mcpServers": {
    "spend-brake": {
      "command": "npx",
      "args": [
        "-y",
        "@spend-brake/mcp"
      ],
      "env": {
        "BRAKE_KEY": "<your BRAKE_KEY>",
        "BRAKE_URL": "<your BRAKE_URL>",
        "BRAKE_API_URL": "<your BRAKE_API_URL>"
      }
    }
  }
}

same format Cursor, GitHub Copilot CLI, Junie, JetBrains AI Assistant, Augment (Auggie CLI), Factory Droid, Kiro, Devin CLI +12

Values in angle brackets are placeholders. Restart Claude Desktop after editing its config.Claude Desktop docs

README

synced Nov 9, 2026

Spend Brake

Hard dollar caps, enforced in real time, for every paid API your apps, test suites and AI agents call. Spend Brake is a self-hosted egress proxy: swap one base URL and one key, and every request is priced before it leaves, checked against your caps in one atomic step, and refused with a clear 402 once a cap would be crossed. Built in: Google Maps Platform (Places, Geocoding, Routes), Twilio Messaging and Verify, Mapbox, HERE, and the agent search APIs Tavily, Exa, Brave and SerpApi; any other API can be added as a custom API, priced per request or at the cost it reports.

You keep your provider account and your own key. The real key never leaves Spend Brake; clients only ever hold revocable virtual keys.

Documentation: docs.spendbrake.dev. Hosted service: spendbrake.dev.

5-minute quickstart

For a production install (HTTPS, published images, backups, upgrades, scaling), see Self-hosting (docs/self-hosting.md in this repo). To use the hosted service instead, see the Quickstart.

Requirements: Docker, Node 22+ (for the CLI), a Google Maps Platform key.

git clone https://github.com/KIvanow/spend-brake && cd spend-brake
cp infra/.env.example infra/.env
# fill in the secrets, e.g.:
sed -i "s|^BRAKE_MASTER_KEY=.*|BRAKE_MASTER_KEY=$(openssl rand -base64 32)|; \
        s|^BRAKE_ACTION_SECRET=.*|BRAKE_ACTION_SECRET=$(openssl rand -base64 32)|; \
        s|^BRAKE_SETUP_TOKEN=.*|BRAKE_SETUP_TOKEN=$(openssl rand -hex 24)|; \
        s|^VALKEY_PASSWORD=.*|VALKEY_PASSWORD=$(openssl rand -hex 24)|; \
        s|^POSTGRES_PASSWORD=.*|POSTGRES_PASSWORD=$(openssl rand -hex 24)|" infra/.env

export BRAKE_VERSION=0.4.0   # the published images to run
docker compose -f infra/docker-compose.yml -f infra/docker-compose.release.yml up -d --wait

That starts the proxy (:8080), admin API (:8081), notifier (:8082), dashboard (:3000), worker, Valkey and Postgres, listening on 127.0.0.1 only. Then set up your org, store the Google key and get a virtual key:

npm install -g @spend-brake/cli   # or run it without installing: npx @spend-brake/cli

# Secrets from the environment (or omit them to be prompted): flags would land in shell history.
export BRAKE_SETUP_TOKEN="$(grep BRAKE_SETUP_TOKEN infra/.env | cut -d= -f2)"
read -rs GOOGLE_MAPS_KEY && export GOOGLE_MAPS_KEY
brake init --api-url http://localhost:8081 --proxy-url http://localhost:8080 \
  --org acme --day-cap 5 --month-cap 50

To build the images and the CLI from source instead, leave out the release file and add --build (docker compose -f infra/docker-compose.yml up -d --build --wait), then pnpm install && pnpm --filter @spend-brake/cli build and use node apps/cli/dist/brake.cjs.

init prints the owner token once (keep it in a password manager) and saves an admin token for the CLI. Valkey and Postgres are only reachable from the other containers.

brake init prints your virtual key once, checks whether the Google key is restricted, and prints the exact Google Cloud steps (console and gcloud) to restrict the real key to the proxy's egress IPs and the Maps APIs, so the proxy is the only path to spend. Then swap the base URL and key in your app:

- https://maps.googleapis.com/maps/api/geocode/json?address=Sofia&key=AIza…
+ http://localhost:8080/google-maps/maps/api/geocode/json?address=Sofia&key=sb_live_…

- https://places.googleapis.com/v1/places:searchText     X-Goog-Api-Key: AIza…
+ http://localhost:8080/google-places/v1/places:searchText    X-Goog-Api-Key: sb_live_…

Every response carries X-Brake-Cost-Micros and X-Brake-Remaining-Micros. Past a cap:

HTTP/1.1 402 Payment Required
{"error":"budget_exceeded","message":"Spend cap reached for org. Stop and ask the user to raise the cap.",
 "scope":"org","window":"d:20261029","cap":5000000,"spent":4990000,"resetsAt":"2026-10-30T00:00:00.000Z"}

Open the dashboard at http://localhost:3000 with the owner token, or the admin token brake init saved in ~/.config/brake/config.json.

Capping an AI agent

Run any agent or script inside a session with its own budget:

export BRAKE_KEY=sb_live_…   BRAKE_URL=http://localhost:8080   BRAKE_API_URL=http://localhost:8081
brake session --budget 5 --day-budget 20 --agent claude -- claude

Claude Code (or anything else) runs with a session key in BRAKE_KEY that can spend at most $5, counts against the agent's $20 per day and the parent key's caps, and is revoked when the agent exits. Inside, the agent calls brake maps geocode "…", brake maps search "…" or brake call <provider> <method> <path>; each prints JSON with the cost. When a cap or the loop breaker stops a call, brake exits 3 or 4 with one line telling the agent to stop and ask you. See Capping an AI agent and the CLI reference.

Run the agent as a different OS user than the one that ran brake init: the agent must not be able to read the saved admin token, or it could lift its own caps (a different XDG_CONFIG_HOME hides the file from brake, not from the agent). brake session warns when it can.

brake doctor checks a workload for these holes from where it runs (a readable admin token, raw provider keys, an uncapped key, unpriced routes that are let through), and --prove shows that an exhausted budget stops forwarding. It lists what it cannot verify rather than calling anything protected. Agents that call paid APIs through MCP can use brake mcp (preview), which sends search, maps and other calls through the proxy. brake mcp --admin is a separate entry for the dashboard.

Google's controls vs Spend Brake

Google Cloud budgetsMaps Platform quotasGoogle spend capsSpend Brake
Stops spendNo, alerts onlyYesYesYes, before the request leaves
UnitDollarsRequests per API per minute/dayDollarsDollars (micro-dollar exact)
Covers Maps PlatformYesYesNo, per Google's docs at the time of writing; check yoursYes
Knows SKU tiers (field masks)After billingNo: a $0.04 request counts like a free oneAfter billingYes, priced per field mask
DelayHours (billing data)Real timeBilling dataReal time
GranularityProject / billing accountProject, per APIBilling accountOrg, key, SKU, environment, agent, session; day and month
Loop / runaway detectionNoNoNoYes, trips a circuit
Kill switchNoSet quota to 0 by handNoOne call, link or button; immediate
Alerts with actionsEmail, Pub/SubNoNoWebhooks (Slack, Discord, Teams, raw) and email with Kill / Raise / Mute links

Keep Google's own budgets and quotas on as a backstop. More in Google's controls vs Spend Brake.

Terms of service

Spend Brake is built to stay inside the Google Maps Platform Terms of Service:

  • Bring your own key, always. Each org uses its own Google key and billing account; Spend Brake never resells access or shares a key.
  • No caching to cut billing. Responses are streamed through, never stored to answer later requests. Only Place IDs may be stored indefinitely and geocode coordinates for at most 30 days; Spend Brake stores neither, only request metadata (SKU, units, cost, status, a hash).
  • Usage stays visible to Google. Every request reaches Google with your key.
  • Client-side Maps is out of scope. The Maps JavaScript API, tiles and client widgets are not proxied.

This is not legal advice: have someone read sections 3.2 and 3.3 of the Maps Platform Terms and the Service Specific Terms before you offer Spend Brake to others as a hosted service.

How it works

app / agent ──▶ proxy ──▶ Google
                 │  1 authenticate the virtual key (cached 5 s)
                 │  2 price: SKU from path + field mask, worst case in micro-dollars
                 │  3 one Lua call: kill switch, loop breaker, reserve against every cap
                 │  4 forward with the real key (decrypted locally, never logged)
                 │  5 settle the actual cost + usage event (one Lua call, background)
                 ▼
               Valkey ──▶ worker ──▶ Postgres ledger, alerts ──▶ notifier ──▶ webhooks / email

Overshoot is bounded by one request's worst case. Valkey down means fail closed (503) unless a key opts into a small fail-open allowance.

PartWhat it does
apps/proxyThe request path
apps/apiAdmin API (keys, caps, kill, credentials, spend, events, webhooks, sessions); OpenAPI at /v1/openapi.json
apps/workerLedger, sweeper, alert jobs, partitions, price drift check
apps/notifierSigned webhooks, email, action-link pages
apps/clibrake: init, agent sessions, capped calls, admin
apps/webDashboard
packages/budgetAtomic budgets on Valkey (Lua)
packages/catalogPrices, SKU rules, provider adapters, migration estimator
packages/dbPostgres schema and migrations
packages/sharedEnvelope encryption, signing, redaction, telemetry

Also: a migration estimate (brake estimate), daily reconciliation with Google's billing export (Business / Enterprise), alerts and signed webhooks, Prometheus and OpenTelemetry, CSV export and the admin API.

Development

Requirements: Node 22+, pnpm 10, Docker.

pnpm install
pnpm dev          # Valkey + Postgres in Docker, then every app in watch mode
pnpm test         # integration tests start their own Valkey/Postgres containers
pnpm lint && pnpm typecheck

Valkey listens on localhost:16379 and Postgres on localhost:15432 (user/password/db brake). Override with BRAKE_VALKEY_PORT / BRAKE_POSTGRES_PORT.

Editions and license

Everything outside ee/ is Apache-2.0 (LICENSE): the proxy, budget engine, caps, kill switch, loop detection, CLI, catalog data, dashboard and signed webhooks. It never needs a license key.

ee/ (LICENSE-EE) adds, with a Business or Enterprise license: single sign-on (OIDC, SAML), roles beyond owner/admin/viewer, two-person approval for raising caps, audit export (JSON Lines to a file, S3 or syslog), more than one org per install, the signed catalog feed, and billing reconciliation. Licenses are Ed25519-signed files checked offline (no phone-home): brake license install acme.license. After a license expires its features keep working for 14 days, then turn read-only; the proxy and caps never stop. See ee/README.md.

The hosted service's plans and license sales run through Stripe (apps/billing, test mode); see apps/billing/README.md.

Contributions use DCO sign-off (git commit -s); see CONTRIBUTING.md.

↑↓ navigate openesc close