Spend7

MCP server

The Spend7 MCP server exposes the spend-control layer as tools, so an agent about to spend money can ask “should I?” as a tool call rather than through bespoke HTTP glue.

Two modes

Local, no key

Pass a context object and risk_check scores on the machine, against the ledger in the call. Nothing leaves, nothing is recorded. The other local tools, rail explanations, the scoring policy, the schema, work the same way.

Against your account

Set SPEND7_API_KEY and the account tools work: risk checks use your stored caps and history, decisions are recorded, and on a paid plan the cross-account graph runs.

A tool that needs the account and has no key says so. It never falls back to a local guess dressed up as an answer: the two modes answer different questions, and every response says which one you got.

Run it

bash
git clone https://github.com/maxbeech/spend7
cd spend7 && npm install
npm run mcp        # stdio, ready for a client to connect

Then point a client at it:

json
{
  "mcpServers": {
    "spend7": {
      "command": "npx",
      "args": ["-y", "tsx", "/path/to/spend7/mcp/server.ts"],
      "env": {
        "SPEND7_API_KEY": "sp7_live_…",
        "SPEND7_API_URL": "https://spend7.com"
      }
    }
  }
}

Tools

risk_check
Score a payment and get allow / flag / deny before the money moves.
Local when you pass a context; hosted (stored caps, history, graph) when SPEND7_API_KEY is set.
get_spend_limits
Read the caps and merchant lists in force, so an agent can reason about headroom before planning a purchase.
Needs SPEND7_API_KEY.
list_transactions
Read the decision log. Filter by agent, merchant, decision or rail, and find the paymentId for a payment you want to dispute.
Needs SPEND7_API_KEY.
explain_rail
What a rail lets Spend7 verify and how it fails. Call it when deciding which fields to populate.
Local, no key.
get_scoring_policy
Thresholds, per-signal ceilings, windows, category priors, and the ruleset version and hash.
Local, no key.
describe_payment_intent_schema
The exact JSON schema of a payment intent, so a call is built rather than guessed.
Local, no key.

An example call

json
// risk_check with an inline ledger: no API key, nothing recorded
{
  "intent": {
    "agentId": "shopping-agent",
    "amountMinor": 90000,
    "currency": "USD",
    "merchantId": "giftcards.example",
    "category": "gift_cards",
    "rail": "other"
  },
  "context": {
    "limits": [
      { "scope": "agent", "agentId": "shopping-agent", "window": "daily",
        "capMinor": 50000, "currency": "USD" }
    ],
    "history": []
  }
}

That one comes back deny: $900 against a $500 daily cap is a decisive breach, and gift cards are a category an autonomous agent rarely has a legitimate reason to buy unattended.

Making the check unskippable

An MCP tool is something the model may choose to call. For payments that is often not enough: a model that can decline to check can spend without checking. Where it matters, wrap your payment tool so the check happens in the function body, and use the MCP tools for what they are good at: letting the model reason about budget, headroom and what a rail can verify.